From f1da0cfbcf8baf64322e8043e65a099f21fb3876 Mon Sep 17 00:00:00 2001 From: Maschell Date: Wed, 18 Feb 2026 15:39:14 +0100 Subject: [PATCH] Update ConfigMenu to have a "hidden" sub menu for toggling heap tracking, preserve heap tracking option on wiiloading --- source/PluginManagement.cpp | 4 +- source/ShellCommands.cpp | 39 ++- source/main.cpp | 5 +- source/plugin/PluginContainer.cpp | 9 +- source/plugin/PluginContainer.h | 12 +- source/plugin/PluginDataFactory.cpp | 6 +- source/plugin/PluginLoadWrapper.h | 20 +- source/utils/config/ConfigDisplayItem.cpp | 26 +- source/utils/config/ConfigDisplayItem.h | 10 +- source/utils/config/ConfigRenderer.cpp | 285 ++++++++++--------- source/utils/config/ConfigRenderer.h | 39 ++- source/utils/config/ConfigRendererStates.cpp | 177 ++++++++++++ source/utils/config/ConfigRendererStates.h | 53 ++++ source/utils/config/ConfigUtils.cpp | 5 +- source/utils/exports.cpp | 14 +- source/utils/utils.cpp | 2 +- source/utils/utils.h | 2 +- 17 files changed, 520 insertions(+), 188 deletions(-) create mode 100644 source/utils/config/ConfigRendererStates.cpp create mode 100644 source/utils/config/ConfigRendererStates.h diff --git a/source/PluginManagement.cpp b/source/PluginManagement.cpp index 64fffe3..040b874 100644 --- a/source/PluginManagement.cpp +++ b/source/PluginManagement.cpp @@ -43,10 +43,10 @@ PluginManagement::loadPlugins(const std::vector &pluginDataLi DisplayErrorNotificationMessage(errMsg, 15.0f); continue; } - plugins.emplace_back(std::move(*metaInfo), std::move(*linkInfo), pluginDataWrapper.getPluginData()); + plugins.emplace_back(std::move(*metaInfo), std::move(*linkInfo), pluginDataWrapper.getPluginData(), pluginDataWrapper.getHeapTrackingOptions()); } else { DEBUG_FUNCTION_LINE_INFO("LOAD (INACTIVE) %s", metaInfo->getName().c_str()); - plugins.emplace_back(std::move(*metaInfo), PluginLinkInformation::CreateStub(), pluginDataWrapper.getPluginData()); + plugins.emplace_back(std::move(*metaInfo), PluginLinkInformation::CreateStub(), pluginDataWrapper.getPluginData(), std::nullopt); } } else { auto errMsg = string_format("Failed to load plugin: %s", pluginDataWrapper.getPluginData()->getSource().c_str()); diff --git a/source/ShellCommands.cpp b/source/ShellCommands.cpp index a8910a6..27a27de 100644 --- a/source/ShellCommands.cpp +++ b/source/ShellCommands.cpp @@ -430,9 +430,6 @@ namespace ShellCommands { }; } // namespace - std::unique_ptr sPluginGroup; - std::optional sPluginCmdHandle; - std::string BytesToHumanReadable(uint32_t bytes) { char buffer[32]; snprintf(buffer, sizeof(buffer), "%.2f KiB", bytes / 1024.0f); @@ -658,14 +655,34 @@ namespace ShellCommands { } } + static std::unique_ptr sPluginsGroup; + void InitPluginsCommandGroup() { + // Must be in memory while commands are active + sPluginsGroup = std::make_unique("plugins", "Manage aroma plugins"); + if (const auto res = sPluginsGroup->RegisterGroup(); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to register \"aroma plugins\" command: %s", IOPShellModule::GetErrorString(res)); + } + + if (const auto res = sPluginsGroup->AddCommand("heap_usage", PrintHeapUsage, "Show current heap usage for tracked plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins heap_usage\" command: %s", IOPShellModule::GetErrorString(res)); + } + if (const auto res = sPluginsGroup->AddRawCommand("list", ListPlugins, "Lists active plugins", "Usage: \"list -a\" to list all plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins list\" command: %s", IOPShellModule::GetErrorString(res)); + } + if (auto res = sPluginsGroup->AddCommand("details", PluginDetails, "Shows details for plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins details\" command: %s", IOPShellModule::GetErrorString(res)); + } else { + if (res = sPluginsGroup->AddAlias("details", "show"); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins details\" alias \"aroma plugins show\": %s", IOPShellModule::GetErrorString(res)); + } + } + + if (const auto res = sPluginsGroup->RegisterGroup(); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to register \"aroma plugins\" command: %s", IOPShellModule::GetErrorString(res)); + } + } + void Init() { - sPluginGroup = std::make_unique("plugins", "Manage aroma plugins"); - - sPluginGroup->AddCommand("heap_usage", PrintHeapUsage, "Show current heap usage for tracked plugins"); - - sPluginGroup->AddRawCommand("list", ListPlugins, "Lists active plugins", "Usage: \"list -a\" to list all plugins"); - sPluginGroup->AddCommand("details", PluginDetails, "Shows details for plugins"); - - sPluginCmdHandle = sPluginGroup->Register(); + InitPluginsCommandGroup(); } } // namespace ShellCommands \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp index 38f4236..1d2d68e 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -284,8 +284,11 @@ WUMS_APPLICATION_STARTS() { // Check which plugins are already loaded and which needs to be for (const auto &pluginLoadWrapper : filteredLoadOnNextLaunch) { const auto &pluginNeedsNoReloadFn = [&pluginLoadWrapper](const PluginContainer &container) { + bool willBeLinked = pluginLoadWrapper.isLoadAndLink(); return (container.getPluginDataCopy()->getHandle() == pluginLoadWrapper.getPluginData()->getHandle()) && - (container.isLinkedAndLoaded() == pluginLoadWrapper.isLoadAndLink()); + (container.isLinkedAndLoaded() == pluginLoadWrapper.isLoadAndLink()) && + // Only check if tracking is enabled if the plugin will actually be loaded. + (!willBeLinked || (container.isUsingTrackingPluginHeapMemoryAllocator() == pluginLoadWrapper.isHeapTrackingEnabled())); }; // Check if the plugin data is already loaded if (const auto it = std::ranges::find_if(gLoadedPlugins, pluginNeedsNoReloadFn); diff --git a/source/plugin/PluginContainer.cpp b/source/plugin/PluginContainer.cpp index 4beea4d..3c83dfb 100644 --- a/source/plugin/PluginContainer.cpp +++ b/source/plugin/PluginContainer.cpp @@ -16,14 +16,19 @@ #include -PluginContainer::PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr pluginData) +PluginContainer::PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr pluginData, std::optional heapTrackingOptions) : mMetaInformation(std::move(metaInformation)), mPluginLinkInformation(std::move(pluginLinkInformation)), mPluginData(std::move(pluginData)) { // Abuse this as a stable handle that references itself and survives std::move *mHandle = reinterpret_cast(mHandle.get()); - if (const bool res = useTrackingPluginHeapMemoryAllocator(mMetaInformation.getHeapTrackingOptions()); !res) { + auto trackingOptions = mMetaInformation.getHeapTrackingOptions(); + if (heapTrackingOptions) { + trackingOptions = *heapTrackingOptions; + } + + if (const bool res = useTrackingPluginHeapMemoryAllocator(trackingOptions); !res) { DEBUG_FUNCTION_LINE_WARN("Failed to set heap tracking options for \"%s\"", mMetaInformation.getName().c_str()); } } diff --git a/source/plugin/PluginContainer.h b/source/plugin/PluginContainer.h index 5e917e5..68770c1 100644 --- a/source/plugin/PluginContainer.h +++ b/source/plugin/PluginContainer.h @@ -33,7 +33,7 @@ class PluginData; class PluginContainer { public: - PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr pluginData); + PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr pluginData, std::optional heapTrackingOptions); PluginContainer(const PluginContainer &) = delete; @@ -73,11 +73,6 @@ public: [[nodiscard]] uint32_t getButtonComboManagerHandle() const; - /** - * @return Returns true if setting the value was successful - */ - bool useTrackingPluginHeapMemoryAllocator(PluginMetaInformation::HeapTrackingOptions options); - [[nodiscard]] bool isUsingTrackingPluginHeapMemoryAllocator() const; [[nodiscard]] const IPluginHeapMemoryAllocator &getMemoryAllocator() const; @@ -87,6 +82,11 @@ public: [[nodiscard]] size_t getMemoryFootprint() const; private: + /** + * @return Returns true if setting the value was successful + */ + bool useTrackingPluginHeapMemoryAllocator(PluginMetaInformation::HeapTrackingOptions options); + PluginMetaInformation mMetaInformation; PluginLinkInformation mPluginLinkInformation; std::optional mTrackingHeapAllocatorOpt; diff --git a/source/plugin/PluginDataFactory.cpp b/source/plugin/PluginDataFactory.cpp index 3814f71..b7a1f4b 100644 --- a/source/plugin/PluginDataFactory.cpp +++ b/source/plugin/PluginDataFactory.cpp @@ -42,7 +42,11 @@ std::vector PluginDataFactory::loadDir(const std::string_view if (inactivePluginsFilenames.contains(fileName)) { shouldBeLoadedAndLinked = false; } - result.emplace_back(std::move(pluginData), shouldBeLoadedAndLinked); + bool enableHeapTracking = false; +#ifdef DEBUG + enableHeapTracking = true; +#endif + result.emplace_back(std::move(pluginData), shouldBeLoadedAndLinked, enableHeapTracking); } else { auto errMsg = string_format("Failed to load plugin: %s", full_file_path.c_str()); DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str()); diff --git a/source/plugin/PluginLoadWrapper.h b/source/plugin/PluginLoadWrapper.h index 879a148..6fd8acb 100644 --- a/source/plugin/PluginLoadWrapper.h +++ b/source/plugin/PluginLoadWrapper.h @@ -1,12 +1,16 @@ #pragma once +#include "PluginMetaInformation.h" + + #include class PluginData; class PluginLoadWrapper { public: - PluginLoadWrapper(std::shared_ptr pluginData, const bool linkAndLoad) : mPluginData(std::move(pluginData)), mIsLoadAndLink(linkAndLoad) { + PluginLoadWrapper(std::shared_ptr pluginData, const bool linkAndLoad, const bool heapTrackingEnabled = false) + : mPluginData(std::move(pluginData)), mIsLoadAndLink(linkAndLoad), mIsHeapTrackingEnabled(heapTrackingEnabled) { } [[nodiscard]] const std::shared_ptr &getPluginData() const { @@ -17,7 +21,19 @@ public: return mIsLoadAndLink; } + [[nodiscard]] bool isHeapTrackingEnabled() const { + return mIsHeapTrackingEnabled; + } + + [[nodiscard]] std::optional getHeapTrackingOptions() const { + if (mIsHeapTrackingEnabled) { + return PluginMetaInformation::HeapTrackingOptions::TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES; + } + return std::nullopt; + } + private: std::shared_ptr mPluginData; - bool mIsLoadAndLink = false; + bool mIsLoadAndLink = false; + bool mIsHeapTrackingEnabled = false; }; \ No newline at end of file diff --git a/source/utils/config/ConfigDisplayItem.cpp b/source/utils/config/ConfigDisplayItem.cpp index 126d257..a7bcc3c 100644 --- a/source/utils/config/ConfigDisplayItem.cpp +++ b/source/utils/config/ConfigDisplayItem.cpp @@ -2,16 +2,18 @@ #include "config/WUPSConfig.h" -#include - #include +#include ConfigDisplayItem::ConfigDisplayItem(GeneralConfigInformation &info, std::unique_ptr config, - const bool isActive) : mConfig(std::move(config)), - mInfo(std::move(info)), - mIsActivePlugin(isActive), - mInitialIsActivePlugin(isActive) { + const bool isActive, + const bool isHeapTracking) : mConfig(std::move(config)), + mInfo(std::move(info)), + mIsActivePlugin(isActive), + mInitialIsActivePlugin(isActive), + mIsHeapTrackingEnabled(isHeapTracking), + mInitialIsHeapTrackingEnabled(isHeapTracking) { assert(mConfig); } @@ -33,4 +35,16 @@ void ConfigDisplayItem::toggleIsActivePlugin() { void ConfigDisplayItem::resetIsActivePlugin() { mIsActivePlugin = mInitialIsActivePlugin; +} + +bool ConfigDisplayItem::isHeapTrackingEnabled() const { + return mIsHeapTrackingEnabled; +} + +void ConfigDisplayItem::toggleIsHeapTrackingEnabled() { + mIsHeapTrackingEnabled = !mIsHeapTrackingEnabled; +} + +void ConfigDisplayItem::resetIsHeapTrackingEnabled() { + mIsHeapTrackingEnabled = mInitialIsHeapTrackingEnabled; } \ No newline at end of file diff --git a/source/utils/config/ConfigDisplayItem.h b/source/utils/config/ConfigDisplayItem.h index acc286b..3de036c 100644 --- a/source/utils/config/ConfigDisplayItem.h +++ b/source/utils/config/ConfigDisplayItem.h @@ -15,21 +15,25 @@ struct GeneralConfigInformation { class ConfigDisplayItem { public: - ConfigDisplayItem(GeneralConfigInformation &info, std::unique_ptr config, bool isActive); + ConfigDisplayItem(GeneralConfigInformation &info, std::unique_ptr config, bool isActive, bool isHeapTracking = false); [[nodiscard]] const GeneralConfigInformation &getConfigInformation() const; [[nodiscard]] const WUPSConfigAPIBackend::WUPSConfig &getConfig() const; [[nodiscard]] bool isActivePlugin() const; - void toggleIsActivePlugin(); - void resetIsActivePlugin(); + [[nodiscard]] bool isHeapTrackingEnabled() const; + void toggleIsHeapTrackingEnabled(); + void resetIsHeapTrackingEnabled(); + private: std::unique_ptr mConfig; GeneralConfigInformation mInfo; bool mIsActivePlugin; bool mInitialIsActivePlugin; + bool mIsHeapTrackingEnabled; + bool mInitialIsHeapTrackingEnabled; }; \ No newline at end of file diff --git a/source/utils/config/ConfigRenderer.cpp b/source/utils/config/ConfigRenderer.cpp index bed3d52..df70989 100644 --- a/source/utils/config/ConfigRenderer.cpp +++ b/source/utils/config/ConfigRenderer.cpp @@ -1,4 +1,5 @@ #include "ConfigRenderer.h" +#include "ConfigRendererStates.h" #include "CategoryRenderer.h" #include "ConfigDisplayItem.h" @@ -11,15 +12,11 @@ #include "utils/logger.h" #include +#include +#include ConfigRenderer::ConfigRenderer(std::vector &&vec) : mConfigs(std::move(vec)) { - std::ranges::copy(mConfigs, - std::back_inserter(mAllConfigs)); - std::ranges::copy_if(mConfigs, - std::back_inserter(mActiveConfigs), - [&](const auto &value) { - return value.isActivePlugin(); - }); + SetListState(std::make_unique()); } ConfigRenderer::~ConfigRenderer() = default; @@ -44,6 +41,10 @@ ConfigSubState ConfigRenderer::Update(Input &input, const WUPSConfigSimplePadDat if (complexInputData.vpad.vpadError == VPAD_READ_SUCCESS && complexInputData.vpad.data.hold != 0) { mLastInputWasOnWiimote = false; } + + // Reset transient return state + mNextSubState = SUB_STATE_RUNNING; + switch (mState) { case STATE_MAIN: return UpdateStateMain(input); @@ -51,9 +52,9 @@ ConfigSubState ConfigRenderer::Update(Input &input, const WUPSConfigSimplePadDat if (mCategoryRenderer) { auto subResult = mCategoryRenderer->Update(input, simpleInputData, complexInputData); if (subResult != SUB_STATE_RUNNING) { - mNeedRedraw = true; - mActivePluginsDirty = false; - mState = STATE_MAIN; + mNeedRedraw = true; + mPluginListDirty = false; + mState = STATE_MAIN; return SUB_STATE_RUNNING; } return SUB_STATE_RUNNING; @@ -99,90 +100,50 @@ void ConfigRenderer::ResetNeedsRedraw() { } } +void ConfigRenderer::RequestRedraw() { + mNeedRedraw = true; +} + ConfigSubState ConfigRenderer::UpdateStateMain(const Input &input) { - auto &configs = GetConfigList(); + if (!mListState) return SUB_STATE_ERROR; + auto &configs = GetDisplayedConfigList(); const auto prevSelectedItem = mCursorPos; + auto totalElementSize = (int32_t) configs.size(); - const auto &savePendingConfigFn = [&configs, this]() { - for (const auto &element : configs) { - CallOnCloseCallback(element.get().getConfigInformation(), element.get().getConfig()); - } - }; + // Delegate specific inputs to the State + bool inputHandled = mListState->HandleInput(*this, input); - auto totalElementSize = (int32_t) configs.size(); + if (mNextSubState != SUB_STATE_RUNNING) { + return mNextSubState; + } + + if (inputHandled) { + return SUB_STATE_RUNNING; + } + + // Handle Navigation (Common to all states) if (input.data.buttons_d & Input::eButtons::BUTTON_DOWN) { mCursorPos++; } else if (input.data.buttons_d & Input::eButtons::BUTTON_LEFT) { - // Paging up mCursorPos -= MAX_BUTTONS_ON_SCREEN - 1; - // Don't jump past the top - if (mCursorPos < 0) - mCursorPos = 0; + if (mCursorPos < 0) mCursorPos = 0; } else if (input.data.buttons_d & Input::eButtons::BUTTON_RIGHT) { - // Paging down mCursorPos += MAX_BUTTONS_ON_SCREEN - 1; - // Don't jump past the bottom - if (mCursorPos >= totalElementSize) - mCursorPos = totalElementSize - 1; + if (mCursorPos >= totalElementSize) mCursorPos = totalElementSize - 1; } else if (input.data.buttons_d & Input::eButtons::BUTTON_UP) { mCursorPos--; - } else if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) { - if (mSetActivePluginsMode) { - mNeedRedraw = true; - mCategoryRenderer.reset(); - savePendingConfigFn(); - return SUB_STATE_RETURN_WITH_PLUGIN_RELOAD; - } - } else if (input.data.buttons_d & Input::eButtons::BUTTON_X) { - if (!mSetActivePluginsMode && !mAllConfigs.empty()) { - mSetActivePluginsMode = true; - mNeedRedraw = true; - return SUB_STATE_RUNNING; - } - } else if (input.data.buttons_d & Input::eButtons::BUTTON_A) { - if (mSetActivePluginsMode) { - mActivePluginsDirty = true; - mNeedRedraw = true; - configs[mCursorPos].get().toggleIsActivePlugin(); - return SUB_STATE_RUNNING; - } else if (!configs.empty()) { - if (mCursorPos != mCurrentOpen) { - mCategoryRenderer.reset(); - mCategoryRenderer = make_unique_nothrow(&(configs[mCursorPos].get().getConfigInformation()), &(configs[mCursorPos].get().getConfig()), true); - } - mNeedRedraw = true; - mCurrentOpen = mCursorPos; - mState = STATE_SUB; - return SUB_STATE_RUNNING; - } - } else if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) { - if (mSetActivePluginsMode) { - for (auto &cur : mConfigs) { - cur.resetIsActivePlugin(); - } - mActivePluginsDirty = false; - mNeedRedraw = true; - mSetActivePluginsMode = false; - return SUB_STATE_RUNNING; - } else { - mNeedRedraw = true; - mCategoryRenderer.reset(); - savePendingConfigFn(); - return SUB_STATE_RETURN; - } } - if (mCursorPos < 0) { - mCursorPos = totalElementSize - 1; - } else if (mCursorPos >= totalElementSize) { - mCursorPos = 0; - } - if (mCursorPos < 0) { + if (totalElementSize > 0) { + if (mCursorPos < 0) mCursorPos = totalElementSize - 1; + else if (mCursorPos >= totalElementSize) + mCursorPos = 0; + } else { mCursorPos = 0; } - // Adjust the render offset when reaching the boundaries + // Adjust render offset if (mCursorPos < mRenderOffset) { mRenderOffset = mCursorPos; } else if (mCursorPos >= mRenderOffset + MAX_BUTTONS_ON_SCREEN - 1) { @@ -197,71 +158,70 @@ ConfigSubState ConfigRenderer::UpdateStateMain(const Input &input) { } void ConfigRenderer::RenderStateMain() const { - auto &configs = GetConfigList(); + auto &configs = GetDisplayedConfigList(); DrawUtils::beginDraw(); DrawUtils::clear(COLOR_BACKGROUND); auto totalElementSize = (int32_t) configs.size(); + int start = std::max(0, mRenderOffset); + int end = std::min(start + MAX_BUTTONS_ON_SCREEN, totalElementSize); - // Calculate the range of items to display - int start = std::max(0, mRenderOffset); - int end = std::min(start + MAX_BUTTONS_ON_SCREEN, totalElementSize); - - if (mActiveConfigs.empty() && !mSetActivePluginsMode) { + if (configs.empty()) { DrawUtils::setFontSize(24); - std::string noConfigText = "No active plugins"; - uint32_t szNoConfig = DrawUtils::getTextWidth(noConfigText.data()); + std::string noConfigText = "No plugins available"; - if (!mAllConfigs.empty()) { + if (!mListState->IsMainView()) { + noConfigText = "No active plugins"; + } + + uint32_t szNoConfig = DrawUtils::getTextWidth(noConfigText.data()); + + if (mListState->IsMainView()) { + DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2), noConfigText.data()); + } else { const auto activateHint = string_format("Press %s to activate inactive plugins", mLastInputWasOnWiimote ? "\uE048" : "\uE002"); const auto szHint = DrawUtils::getTextWidth(activateHint.c_str()); - DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2) - 16, noConfigText.data()); DrawUtils::print((SCREEN_WIDTH / 2) - (szHint / 2), (SCREEN_HEIGHT / 2) + 16, activateHint.data()); - } else { - DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2), noConfigText.data()); } } else { uint32_t yOffset = 8 + 24 + 8 + 4; for (int32_t i = start; i < end; i++) { - DrawConfigEntry(yOffset, configs[i].get().getConfigInformation(), i == mCursorPos, configs[i].get().isActivePlugin()); + DrawConfigEntry(yOffset, configs[i].get(), i == mCursorPos); yOffset += 42 + 8; } } DrawUtils::setFontColor(COLOR_TEXT); - // draw top bar + // Top Bar DrawUtils::setFontSize(24); - if (mSetActivePluginsMode) { - DrawUtils::print(16, 6 + 24, "Please select the plugins that should be active"); - } else { - DrawUtils::print(16, 6 + 24, "Wii U Plugin System Config Menu"); + DrawUtils::print(16, 6 + 24, mListState->GetTitle().c_str()); - auto countInactivePlugins = mAllConfigs.size() - mActiveConfigs.size(); + if (mListState->IsMainView()) { + auto countInactivePlugins = mConfigs.size() - mFilteredConfigs.size(); if (countInactivePlugins > 0) { DrawUtils::setFontSize(14); const std::string plugin_unloaded = string_format("Found %d inactive plugins", countInactivePlugins); DrawUtils::print(SCREEN_WIDTH - 16 - DrawUtils::getTextWidth(MODULE_VERSION_FULL) - 32, 8 + 24, plugin_unloaded.c_str(), true); } } + DrawUtils::setFontSize(18); DrawUtils::print(SCREEN_WIDTH - 16, 8 + 24, MODULE_VERSION_FULL, true); DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_BLACK); - // draw bottom bar + // Bottom Bar DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_BLACK); DrawUtils::setFontSize(18); - DrawUtils::print(16, SCREEN_HEIGHT - 10, "\uE07D/\uE07E Navigate "); - if (mSetActivePluginsMode) { - DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, "\uE000 Toggle | \uE045 Apply", true); - } else if (totalElementSize > 0) { - const auto text = string_format("\ue000 Select | %s Manage plugins", mLastInputWasOnWiimote ? "\uE048" : "\uE002"); - DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, text.c_str(), true); - } - // draw scroll indicator + if (totalElementSize > 0) { + DrawUtils::print(16, SCREEN_HEIGHT - 10, "\uE07D/\uE07E Navigate "); + } + DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, mListState->GetBottomBar(mLastInputWasOnWiimote).c_str(), true); + + // Scroll Indicator DrawUtils::setFontSize(24); if (end < totalElementSize) { DrawUtils::print(SCREEN_WIDTH / 2 + 12, SCREEN_HEIGHT - 32, "\ufe3e", true); @@ -270,18 +230,15 @@ void ConfigRenderer::RenderStateMain() const { DrawUtils::print(SCREEN_WIDTH / 2 + 12, 32 + 20, "\ufe3d", true); } - // draw home button + // Home Button DrawUtils::setFontSize(18); - const char *exitHint = "\ue044 Exit"; - if (mSetActivePluginsMode) { - exitHint = "\ue001 Abort"; - } + const char *exitHint = mListState->IsMainView() ? "\ue001 Abort" : "\ue044 Exit"; DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(exitHint) / 2, SCREEN_HEIGHT - 10, exitHint, true); DrawUtils::endDraw(); } -void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInformation &configInformation, bool isHighlighted, bool isActive) const { +void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const ConfigDisplayItem &item, bool isHighlighted) const { DrawUtils::setFontColor(COLOR_TEXT); if (isHighlighted) { @@ -291,18 +248,15 @@ void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInform } int textXOffset = 16 * 2; - if (mSetActivePluginsMode) { - DrawUtils::setFontSize(24); - if (isActive) { - DrawUtils::print(textXOffset, yOffset + 8 + 24, "\u25C9"); - } else { - DrawUtils::print(textXOffset, yOffset + 8 + 24, "\u25CE"); - } + + // Delegate Icon drawing to state, returns true if icon was drawn + if (mListState->RenderItemIcon(item, textXOffset, yOffset + 8 + 24)) { textXOffset += 32; } DrawUtils::setFontSize(24); + const auto &configInformation = item.getConfigInformation(); DrawUtils::print(textXOffset, yOffset + 8 + 24, configInformation.name.c_str()); uint32_t sz = DrawUtils::getTextWidth(configInformation.name.c_str()); DrawUtils::setFontSize(12); @@ -310,6 +264,82 @@ void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInform DrawUtils::print(SCREEN_WIDTH - 16 * 2, yOffset + 8 + 24, configInformation.version.c_str(), true); } +void ConfigRenderer::SetListState(std::unique_ptr state) { + mListState = std::move(state); + mNeedRedraw = true; + mCursorPos = 0; + mRenderOffset = 0; + // Fallback to "show all" + std::function pred = [](const auto &) { return true; }; + if (mListState) { + pred = mListState->GetConfigFilter(); + } + // Copy references into filteredConfigView + mFilteredConfigs.clear(); + std::ranges::copy_if(mConfigs, std::back_inserter(mFilteredConfigs), + std::move(pred)); +} + +const std::vector &ConfigRenderer::GetConfigItems() { + return mConfigs; +} + +std::vector> &ConfigRenderer::GetFilteredConfigItems() { + return mFilteredConfigs; +} + +int32_t ConfigRenderer::GetCursorPos() const { + return mCursorPos; +} + +void ConfigRenderer::EnterSelectedCategory() { + auto &items = GetDisplayedConfigList(); + if (mCursorPos < 0 || static_cast(mCursorPos) >= items.size()) return; + + if (mCursorPos != mCurrentOpen) { + mCategoryRenderer.reset(); + mCategoryRenderer = make_unique_nothrow(&(items[mCursorPos].get().getConfigInformation()), &(items[mCursorPos].get().getConfig()), true); + } + mNeedRedraw = true; + mCurrentOpen = mCursorPos; + mState = STATE_SUB; +} + +void ConfigRenderer::SavePendingConfigs() { + for (const auto &element : mConfigs) { + CallOnCloseCallback(element.getConfigInformation(), element.getConfig()); + } +} + +void ConfigRenderer::Exit() { + mNeedRedraw = true; + mCategoryRenderer.reset(); + SavePendingConfigs(); + mNextSubState = SUB_STATE_RETURN; +} + +void ConfigRenderer::ExitWithReload() { + mNeedRedraw = true; + mCategoryRenderer.reset(); + SavePendingConfigs(); + mNextSubState = SUB_STATE_RETURN_WITH_PLUGIN_RELOAD; +} + +void ConfigRenderer::SetPluginsListDirty(bool dirty) { + mPluginListDirty = dirty; +} + +bool ConfigRenderer::GetPluginsListIfChanged(std::vector &result) { + if (mPluginListDirty) { + result.clear(); + for (const auto &cur : mConfigs) { + result.emplace_back(cur.getConfigInformation().pluginData, cur.isActivePlugin(), cur.isHeapTrackingEnabled()); + } + return true; + } + return false; +} + void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, const std::vector> &categories) { for (const auto &cat : categories) { if (!cat->getCategories().empty()) { @@ -321,18 +351,6 @@ void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, c } } -bool ConfigRenderer::GetActivePluginsIfChanged(std::vector &result) { - if (mActivePluginsDirty) { - std::vector inactive_plugins; - result.clear(); - for (const auto &cur : mConfigs) { - result.emplace_back(cur.getConfigInformation().pluginData, cur.isActivePlugin()); - } - return true; - } - return false; -} - void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, const WUPSConfigAPIBackend::WUPSConfig &config) { CallOnCloseCallback(info, config.getCategories()); for (const auto &item : config.getItems()) { @@ -340,9 +358,6 @@ void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, c } } -const std::vector> &ConfigRenderer::GetConfigList() const { - if (mSetActivePluginsMode) { - return mAllConfigs; - } - return mActiveConfigs; -} +const std::vector> &ConfigRenderer::GetDisplayedConfigList() const { + return mFilteredConfigs; +} \ No newline at end of file diff --git a/source/utils/config/ConfigRenderer.h b/source/utils/config/ConfigRenderer.h index 7b15154..df7f8c7 100644 --- a/source/utils/config/ConfigRenderer.h +++ b/source/utils/config/ConfigRenderer.h @@ -5,11 +5,11 @@ #include +#include +#include #include #include -#include - namespace WUPSConfigAPIBackend { class WUPSConfig; class WUPSConfigCategory; @@ -18,6 +18,8 @@ class PluginLoadWrapper; class Input; class CategoryRenderer; class ConfigDisplayItem; +class ConfigListState; + class ConfigRenderer { public: @@ -30,22 +32,31 @@ public: void Render() const; [[nodiscard]] bool NeedsRedraw() const; - void ResetNeedsRedraw(); + void RequestRedraw(); - bool GetActivePluginsIfChanged(std::vector &result); + bool GetPluginsListIfChanged(std::vector &result); + + void SetListState(std::unique_ptr state); + const std::vector &GetConfigItems(); + std::vector> &GetFilteredConfigItems(); // Mutable access + int32_t GetCursorPos() const; + void EnterSelectedCategory(); + void Exit(); + void ExitWithReload(); + void SetPluginsListDirty(bool dirty); private: ConfigSubState UpdateStateMain(const Input &input); - void RenderStateMain() const; - - void DrawConfigEntry(uint32_t yOffset, const GeneralConfigInformation &configInformation, bool isHighlighted, bool isActive) const; + void DrawConfigEntry(uint32_t yOffset, const ConfigDisplayItem &item, bool isHighlighted) const; void CallOnCloseCallback(const GeneralConfigInformation &info, const std::vector> &categories); void CallOnCloseCallback(const GeneralConfigInformation &info, const WUPSConfigAPIBackend::WUPSConfig &config); + void SavePendingConfigs(); - [[nodiscard]] const std::vector> &GetConfigList() const; + + [[nodiscard]] const std::vector> &GetDisplayedConfigList() const; enum State { STATE_MAIN = 0, @@ -53,18 +64,20 @@ private: }; std::vector mConfigs; - std::vector> mAllConfigs; - std::vector> mActiveConfigs; + mutable std::vector> mFilteredConfigs; + + std::unique_ptr mListState; std::unique_ptr mCategoryRenderer; State mState = STATE_MAIN; + // Used to signal the main loop to return a specific state + ConfigSubState mNextSubState = SUB_STATE_RUNNING; int32_t mCursorPos = 0; int32_t mRenderOffset = 0; int32_t mCurrentOpen = -1; bool mNeedRedraw = true; - bool mSetActivePluginsMode = false; - bool mActivePluginsDirty = false; + bool mPluginListDirty = false; bool mLastInputWasOnWiimote = false; -}; +}; \ No newline at end of file diff --git a/source/utils/config/ConfigRendererStates.cpp b/source/utils/config/ConfigRendererStates.cpp new file mode 100644 index 0000000..6d0b0c4 --- /dev/null +++ b/source/utils/config/ConfigRendererStates.cpp @@ -0,0 +1,177 @@ +#include "ConfigRendererStates.h" +#include "ConfigDisplayItem.h" +#include "ConfigRenderer.h" +#include "utils/DrawUtils.h" +#include "utils/StringTools.h" +#include "utils/input/Input.h" + +std::function DefaultListState::GetConfigFilter() const { + return [](const auto &item) { return item.isActivePlugin(); }; +} + +bool DefaultListState::IsMainView() const { + return true; +} + +bool DefaultListState::HandleInput(ConfigRenderer &renderer, const Input &input) { + if (input.data.buttons_d & Input::eButtons::BUTTON_DOWN) { + // Hidden Combo: L + R + Down + constexpr auto COMBO_HOLD = Input::eButtons::BUTTON_L | Input::eButtons::BUTTON_R; + if ((input.data.buttons_h & COMBO_HOLD) == COMBO_HOLD) { + // Switch to Heap Tracking Mode + if (!renderer.GetConfigItems().empty()) { + renderer.SetListState(std::make_unique()); + return true; + } + } + return false; // Allow scrolling + } + + if (input.data.buttons_d & Input::eButtons::BUTTON_A) { + if (!renderer.GetConfigItems().empty()) { + renderer.EnterSelectedCategory(); + } + return true; + } + + if (input.data.buttons_d & Input::eButtons::BUTTON_X) { + if (!renderer.GetConfigItems().empty()) { + renderer.SetListState(std::make_unique()); + } + return true; + } + + if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) { + renderer.Exit(); + return true; + } + + return false; +} + +std::string DefaultListState::GetTitle() const { + return "Wii U Plugin System Config Menu"; +} + +std::string DefaultListState::GetBottomBar(bool isWiimote) const { + return string_format("\ue000 Select | %s Manage plugins", isWiimote ? "\uE048" : "\uE002"); +} + +bool DefaultListState::RenderItemIcon(const ConfigDisplayItem & /*item*/, int /*x*/, int /*y*/) const { + return false; +} + + +bool ActivePluginsListState::HandleInput(ConfigRenderer &renderer, const Input &input) { + if (input.data.buttons_d & Input::eButtons::BUTTON_A) { + auto &items = renderer.GetFilteredConfigItems(); + int pos = renderer.GetCursorPos(); + if (pos >= 0 && static_cast(pos) < items.size()) { + items[pos].get().toggleIsActivePlugin(); + renderer.SetPluginsListDirty(true); + renderer.RequestRedraw(); + } + return true; + } + + if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) { + // Apply and Reload + renderer.ExitWithReload(); + return true; + } + + if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) { + // Abort / Reset + for (auto &item : renderer.GetFilteredConfigItems()) { + item.get().resetIsActivePlugin(); + } + renderer.SetPluginsListDirty(false); + renderer.SetListState(std::make_unique()); + return true; + } + + return false; +} + +std::string ActivePluginsListState::GetTitle() const { + return "Please select the plugins that should be active"; +} + +std::string ActivePluginsListState::GetBottomBar(bool /*isWiimote*/) const { + return "\uE000 Toggle | \uE001 Abort | \uE045 Apply"; +} + +bool ActivePluginsListState::RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const { + DrawUtils::setFontSize(24); + if (item.isActivePlugin()) { + DrawUtils::print(x, y, "\u25C9"); + } else { + DrawUtils::print(x, y, "\u25CE"); + } + return true; +} + +std::function ActivePluginsListState::GetConfigFilter() const { + return [](const auto &) { return true; }; +} + +bool ActivePluginsListState::IsMainView() const { + return false; +} + +bool HeapTrackingListState::HandleInput(ConfigRenderer &renderer, const Input &input) { + if (input.data.buttons_d & Input::eButtons::BUTTON_A) { + auto &items = renderer.GetFilteredConfigItems(); + int pos = renderer.GetCursorPos(); + if (pos >= 0 && static_cast(pos) < items.size()) { + items[pos].get().toggleIsHeapTrackingEnabled(); + renderer.SetPluginsListDirty(true); + renderer.RequestRedraw(); + } + return true; + } + + if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) { + // Apply and Reload + renderer.ExitWithReload(); + return true; + } + + if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) { + // Abort + for (auto &item : renderer.GetFilteredConfigItems()) { + item.get().resetIsHeapTrackingEnabled(); + } + renderer.SetPluginsListDirty(false); + renderer.SetListState(std::make_unique()); + return true; + } + + return false; +} + +std::string HeapTrackingListState::GetTitle() const { + return "Select plugins to enable Heap Tracking"; +} + +std::string HeapTrackingListState::GetBottomBar(bool /*isWiimote*/) const { + return "\uE000 Toggle | \uE001 Abort | \uE045 Apply"; +} + +bool HeapTrackingListState::RenderItemIcon(const ConfigDisplayItem &item, const int x, const int y) const { + DrawUtils::setFontSize(24); + if (item.isHeapTrackingEnabled()) { + DrawUtils::print(x, y, "\u25C9"); + } else { + DrawUtils::print(x, y, "\u25CE"); + } + return true; +} + +std::function HeapTrackingListState::GetConfigFilter() const { + return [](const auto &item) { return item.isActivePlugin(); }; +} + +bool HeapTrackingListState::IsMainView() const { + return false; +} diff --git a/source/utils/config/ConfigRendererStates.h b/source/utils/config/ConfigRendererStates.h new file mode 100644 index 0000000..6cf5f59 --- /dev/null +++ b/source/utils/config/ConfigRendererStates.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +class ConfigRenderer; +class Input; +class ConfigDisplayItem; + +class ConfigListState { +public: + virtual ~ConfigListState() = default; + + virtual bool HandleInput(ConfigRenderer &renderer, const Input &input) = 0; + + virtual std::string GetTitle() const = 0; + virtual std::string GetBottomBar(bool isWiimote) const = 0; + virtual bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const = 0; + + virtual std::function GetConfigFilter() const = 0; + virtual bool IsMainView() const = 0; +}; + +class DefaultListState : public ConfigListState { +public: + bool HandleInput(ConfigRenderer &renderer, const Input &input) override; + std::string GetTitle() const override; + std::string GetBottomBar(bool isWiimote) const override; + bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override; + std::function GetConfigFilter() const override; + bool IsMainView() const override; +}; + +class ActivePluginsListState : public ConfigListState { +public: + bool HandleInput(ConfigRenderer &renderer, const Input &input) override; + std::string GetTitle() const override; + std::string GetBottomBar(bool isWiimote) const override; + bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override; + std::function GetConfigFilter() const override; + bool IsMainView() const override; +}; + +class HeapTrackingListState : public ConfigListState { +public: + bool HandleInput(ConfigRenderer &renderer, const Input &input) override; + std::string GetTitle() const override; + std::string GetBottomBar(bool isWiimote) const override; + bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override; + std::function GetConfigFilter() const override; + bool IsMainView() const override; +}; \ No newline at end of file diff --git a/source/utils/config/ConfigUtils.cpp b/source/utils/config/ConfigUtils.cpp index 19eb871..c0d6cc9 100644 --- a/source/utils/config/ConfigUtils.cpp +++ b/source/utils/config/ConfigUtils.cpp @@ -133,7 +133,7 @@ void ConfigUtils::displayMenu() { config = make_unique_nothrow(info.name); } - configs.emplace_back(info, std::move(config), plugin.isLinkedAndLoaded()); + configs.emplace_back(info, std::move(config), plugin.isLinkedAndLoaded(), plugin.isUsingTrackingPluginHeapMemoryAllocator()); } // Sort Configs by name @@ -226,7 +226,7 @@ void ConfigUtils::displayMenu() { std::vector newActivePluginsList; - if (subStateReturnValue == SUB_STATE_RETURN_WITH_PLUGIN_RELOAD && renderer.GetActivePluginsIfChanged(newActivePluginsList)) { + if (subStateReturnValue == SUB_STATE_RETURN_WITH_PLUGIN_RELOAD && renderer.GetPluginsListIfChanged(newActivePluginsList)) { startTime = OSGetTime(); renderBasicScreen("Applying changes, app will now restart..."); @@ -243,6 +243,7 @@ void ConfigUtils::displayMenu() { } } } + gLoadOnNextLaunch = newActivePluginsList; WUPSBackendSettings::SetInactivePluginFilenames(newInactivePluginsList); if (!WUPSBackendSettings::SaveSettings()) { diff --git a/source/utils/exports.cpp b/source/utils/exports.cpp index a8ac206..7518bf8 100644 --- a/source/utils/exports.cpp +++ b/source/utils/exports.cpp @@ -38,7 +38,17 @@ extern "C" PluginBackendApiErrorType WUPSLoadAndLinkByDataHandle(const wups_back for (const auto &pluginData : gLoadedData) { if (pluginData->getHandle() == handle) { - gLoadOnNextLaunch.emplace_back(pluginData, true); + bool heapTrackingActive = false; +#ifdef DEBUG + heapTrackingActive = true; +#endif + for (const auto &plugin : gLoadedPlugins) { + if (plugin.getPluginDataCopy()->getHandle() == handle) { + heapTrackingActive = plugin.isUsingTrackingPluginHeapMemoryAllocator(); + break; + } + } + gLoadOnNextLaunch.emplace_back(pluginData, true, heapTrackingActive); found = true; break; } @@ -52,7 +62,7 @@ extern "C" PluginBackendApiErrorType WUPSLoadAndLinkByDataHandle(const wups_back // Keep inactive plugins loaded. Duplicates will be eliminated by comparing name/author for (const auto &plugin : gLoadedPlugins) { if (!plugin.isLinkedAndLoaded()) { - gLoadOnNextLaunch.emplace_back(plugin.getPluginDataCopy(), false); + gLoadOnNextLaunch.emplace_back(plugin.getPluginDataCopy(), false, false); } } diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index 77e5359..32304f5 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -253,7 +253,7 @@ std::string getModuleAndSymbolName(uint32_t addr) { } } -void PrintCapturedStackTrace(std::span trace) { +void PrintCapturedStackTrace(const std::span trace) { if (trace.empty()) { DEBUG_FUNCTION_LINE_INFO("┌────────────────────── CAPTURED TRACE ──────────────────────┐"); DEBUG_FUNCTION_LINE_INFO("│ "); diff --git a/source/utils/utils.h b/source/utils/utils.h index c227179..d86b641 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -169,6 +169,6 @@ std::vector CaptureStackTrace(uint32_t maxDepth); std::string getModuleAndSymbolName(uint32_t addr); -void PrintCapturedStackTrace(std::span trace); +void PrintCapturedStackTrace(std::span trace); std::string hookNameToString(wups_loader_hook_type_t type); \ No newline at end of file