From a1fd0959c14834170b04e8a54217a1fd81fee4e0 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Jun 2021 20:56:41 +0200 Subject: [PATCH] Added an offline miximage generator. --- es-app/CMakeLists.txt | 2 + es-app/src/guis/GuiOfflineGenerator.cpp | 339 ++++++++++++++++++++++++ es-app/src/guis/GuiOfflineGenerator.h | 87 ++++++ es-app/src/guis/GuiScraperMenu.cpp | 49 +++- es-app/src/guis/GuiScraperMenu.h | 2 + es-app/src/guis/GuiScraperSearch.cpp | 2 +- es-app/src/guis/GuiSettings.h | 2 +- 7 files changed, 479 insertions(+), 4 deletions(-) create mode 100644 es-app/src/guis/GuiOfflineGenerator.cpp create mode 100644 es-app/src/guis/GuiOfflineGenerator.h diff --git a/es-app/CMakeLists.txt b/es-app/CMakeLists.txt index d8316fe47..1c75bdffb 100644 --- a/es-app/CMakeLists.txt +++ b/es-app/CMakeLists.txt @@ -24,6 +24,7 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.h @@ -75,6 +76,7 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.cpp diff --git a/es-app/src/guis/GuiOfflineGenerator.cpp b/es-app/src/guis/GuiOfflineGenerator.cpp new file mode 100644 index 000000000..a3ff6748d --- /dev/null +++ b/es-app/src/guis/GuiOfflineGenerator.cpp @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// GuiOfflineGenerator.cpp +// +// User interface for the miximage offline generator. +// Calls MiximageGenerator to do the actual work. +// + +#include "guis/GuiOfflineGenerator.h" + +#include "components/MenuComponent.h" +#include "views/ViewController.h" +#include "SystemData.h" + +GuiOfflineGenerator::GuiOfflineGenerator( + Window* window, + const std::queue& gameQueue) + : GuiComponent(window), + mBackground(window, ":/graphics/frame.svg"), + mGrid(window, Vector2i(5, 13)), + mGameQueue(gameQueue) +{ + addChild(&mBackground); + addChild(&mGrid); + + mProcessing = false; + mPaused = false; + mOverwriting = false; + + mTotalGames = static_cast(mGameQueue.size()); + mGamesProcessed = 0; + mImagesGenerated = 0; + mImagesOverwritten = 0; + mGamesSkipped = 0; + mGamesFailed = 0; + + mGame = nullptr; + + // Header. + mTitle = std::make_shared(mWindow, "MIXIMAGE OFFLINE GENERATOR", + Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); + mGrid.setEntry(mTitle, Vector2i(0, 0), false, true, Vector2i(5, 1)); + + mStatus = std::make_shared(mWindow, "NOT STARTED", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); + mGrid.setEntry(mStatus, Vector2i(0, 1), false, true, Vector2i(5, 1)); + + mGameCounter = std::make_shared(mWindow, + std::to_string(mGamesProcessed) + " OF " + std::to_string(mTotalGames) + + (mTotalGames == 1 ? " GAME " : " GAMES ") + "PROCESSED", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER); + mGrid.setEntry(mGameCounter, Vector2i(0, 2), false, true, Vector2i(5, 1)); + + // Spacer row with top border. + mGrid.setEntry(std::make_shared(mWindow), Vector2i(0, 3), + false, false, Vector2i(5, 1), GridFlags::BORDER_TOP); + + // Left spacer. + mGrid.setEntry(std::make_shared(mWindow), Vector2i(0, 4), + false, false, Vector2i(1, 7)); + + // Generated label. + mGeneratedLbl = std::make_shared(mWindow, "Generated:", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mGeneratedLbl, Vector2i(1, 4), false, true, Vector2i(1, 1)); + + // Generated value/counter. + mGeneratedVal = std::make_shared(mWindow, + std::to_string(mGamesProcessed), + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mGeneratedVal, Vector2i(2, 4), false, true, Vector2i(1, 1)); + + // Overwritten label. + mOverwrittenLbl = std::make_shared(mWindow, "Overwritten:", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mOverwrittenLbl, Vector2i(1, 5), false, true, Vector2i(1, 1)); + + // Overwritten value/counter. + mOverwrittenVal = std::make_shared(mWindow, + std::to_string(mImagesOverwritten), + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mOverwrittenVal, Vector2i(2, 5), false, true, Vector2i(1, 1)); + + // Skipping label. + mSkippedLbl = std::make_shared(mWindow, "Skipped (existing):", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mSkippedLbl, Vector2i(1, 6), false, true, Vector2i(1, 1)); + + // Skipping value/counter. + mSkippedVal= std::make_shared(mWindow, + std::to_string(mGamesSkipped), + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mSkippedVal, Vector2i(2, 6), false, true, Vector2i(1, 1)); + + // Failed label. + mFailedLbl = std::make_shared(mWindow, "Failed:", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mFailedLbl, Vector2i(1, 7), false, true, Vector2i(1, 1)); + + // Failed value/counter. + mFailedVal = std::make_shared(mWindow, + std::to_string(mGamesFailed), + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mFailedVal, Vector2i(2, 7), false, true, Vector2i(1, 1)); + + // Processing label. + mProcessingLbl = std::make_shared(mWindow, "Processing: ", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mProcessingLbl, Vector2i(3, 4), false, true, Vector2i(1, 1)); + + // Processing value. + mProcessingVal = std::make_shared(mWindow, "", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mProcessingVal, Vector2i(4, 4), false, true, Vector2i(1, 1)); + + // Spacer row. + mGrid.setEntry(std::make_shared(mWindow), Vector2i(1, 8), + false, false, Vector2i(5, 1)); + + // Last error message label. + mLastErrorLbl = std::make_shared(mWindow, "Last error message:", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mLastErrorLbl, Vector2i(1, 9), false, true, Vector2i(4, 1)); + + // Last error message value. + mLastErrorVal = std::make_shared(mWindow, "", + Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); + mGrid.setEntry(mLastErrorVal, Vector2i(1, 10), false, true, Vector2i(4, 1)); + + // Spacer row with bottom border. + mGrid.setEntry(std::make_shared(mWindow), Vector2i(0, 11), + false, false, Vector2i(5, 1), GridFlags::BORDER_BOTTOM); + + // Buttons. + std::vector> buttons; + + mStartPauseButton = std::make_shared(mWindow, "START", + "start processing", [this](){ + if (!mProcessing) { + mProcessing = true; + mPaused = false; + mStartPauseButton->setText("PAUSE", "pause processing"); + mCloseButton->setText("CLOSE", "close (abort processing)"); + mStatus->setText("RUNNING..."); + if (mGamesProcessed == 0) { + LOG(LogInfo) << "GuiOfflineGenerator: Processing " << mTotalGames << " games"; + } + } + else { + if (mMiximageGeneratorThread.joinable()) + mMiximageGeneratorThread.join(); + mPaused = true; + update(1); + mProcessing = false; + this->mStartPauseButton->setText("START", "start processing"); + this->mCloseButton->setText("CLOSE", "close (abort processing)"); + mStatus->setText("PAUSED"); + } + }); + + buttons.push_back(mStartPauseButton); + + mCloseButton = std::make_shared(mWindow, "CLOSE", "close", [this](){ + if (mGamesProcessed != 0 && mGamesProcessed != mTotalGames) { + LOG(LogInfo) << "GuiOfflineGenerator: Aborted after processing " << + mGamesProcessed << (mGamesProcessed == 1 ? " game (" : " games (") << + mImagesGenerated << (mImagesGenerated == 1 ? " image " : " images ") << + "generated, " << mGamesSkipped << + (mGamesSkipped == 1 ? " game " : " games ") << "skipped, " << mGamesFailed << + (mGamesFailed == 1 ? " game " : " games ") << "failed)"; + } + delete this; + }); + + buttons.push_back(mCloseButton); + mButtonGrid = makeButtonGrid(mWindow, buttons); + + mGrid.setEntry(mButtonGrid, Vector2i(0, 12), true, false, Vector2i(5, 1)); + + // For narrower displays (e.g. in 4:3 ratio), allow the window to fill 95% of the screen + // width rather than the 85% allowed for wider displays. + float width = Renderer::getScreenWidth() * + ((Renderer::getScreenAspectRatio() < 1.4f) ? 0.95f : 0.85f); + + setSize(width, Renderer::getScreenHeight() * 0.75f); + setPosition((Renderer::getScreenWidth() - mSize.x()) / 2.0f, + (Renderer::getScreenHeight() - mSize.y()) / 2.0f); +} + +GuiOfflineGenerator::~GuiOfflineGenerator() +{ + // Let the miximage generator thread complete. + if (mMiximageGeneratorThread.joinable()) + mMiximageGeneratorThread.join(); + + mMiximageGenerator.reset(); + + if (mImagesGenerated > 0) + ViewController::get()->reloadAll(); +} + +void GuiOfflineGenerator::onSizeChanged() +{ + mBackground.fitTo(mSize, Vector3f::Zero(), Vector2f(-32.0f, -32.0f)); + + // Set row heights. + mGrid.setRowHeightPerc(0, mTitle->getFont()->getLetterHeight() * 1.9725f / mSize.y(), false); + mGrid.setRowHeightPerc(1, (mStatus->getFont()->getLetterHeight() + 2.0f) / mSize.y(), false); + mGrid.setRowHeightPerc(2, mGameCounter->getFont()->getHeight() * 1.75f / mSize.y(), false); + mGrid.setRowHeightPerc(3, (mStatus->getFont()->getLetterHeight() + 3.0f) / mSize.y(), false); + mGrid.setRowHeightPerc(4, 0.07f, false); + mGrid.setRowHeightPerc(5, 0.07f, false); + mGrid.setRowHeightPerc(6, 0.07f, false); + mGrid.setRowHeightPerc(7, 0.07f, false); + mGrid.setRowHeightPerc(8, 0.02f, false); + mGrid.setRowHeightPerc(9, 0.07f, false); + mGrid.setRowHeightPerc(10, 0.07f, false); + mGrid.setRowHeightPerc(12, mButtonGrid->getSize().y() / mSize.y(), false); + + // Set column widths. + mGrid.setColWidthPerc(0, 0.03f); + mGrid.setColWidthPerc(1, 0.20f); + mGrid.setColWidthPerc(2, 0.145f); + + // Adjust the width slightly depending on the aspect ratio of the screen to make sure + // that the label does not get abbreviated. + if (Renderer::getScreenAspectRatio() <= 1.4f) + mGrid.setColWidthPerc(3, 0.13f); + else if (Renderer::getScreenAspectRatio() <= 1.6f) + mGrid.setColWidthPerc(3, 0.12f); + else + mGrid.setColWidthPerc(3, 0.113f); + + mGrid.setSize(mSize); +} + +void GuiOfflineGenerator::update(int deltaTime) +{ + if (!mProcessing) + return; + + // Check if a miximage generator thread was started, and if the processing has been completed. + if (mMiximageGenerator && mGeneratorFuture.valid()) { + // Only wait one millisecond as this update() function runs very frequently. + if (mGeneratorFuture.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready) { + // We always let the miximage generator thread complete. + if (mMiximageGeneratorThread.joinable()) + mMiximageGeneratorThread.join(); + mMiximageGenerator.reset(); + if (!mGeneratorFuture.get()) { + mImagesGenerated++; + TextureResource::manualUnload(mGame->getMiximagePath(), false); + mProcessingVal->setText(""); + if (mOverwriting) { + mImagesOverwritten++; + mOverwriting = false; + } + } + else { + std::string errorMessage = mResultMessage + " (" + mGameName + ")"; + mLastErrorVal->setText(errorMessage); + LOG(LogInfo) << "GuiOfflineGenerator: " << errorMessage; + mGamesFailed++; + } + mGame = nullptr; + mGamesProcessed++; + } + } + + // This is simply to retain the name of the last processed game on-screen while paused. + if (mPaused) + mProcessingVal->setText(mGameName); + + if (!mPaused && !mGameQueue.empty() && !mMiximageGenerator) { + mGame = mGameQueue.front(); + mGameQueue.pop(); + + mGameName = mGame->getName() + " [" + + Utils::String::toUpper(mGame->getSystem()->getName()) + "]"; + mProcessingVal->setText(mGameName); + + if (!Settings::getInstance()->getBool("MiximageOverwrite") && + mGame->getMiximagePath() != "") { + mGamesProcessed++; + mGamesSkipped++; + mSkippedVal->setText(std::to_string(mGamesSkipped)); + } + else { + if (mGame->getMiximagePath() != "") + mOverwriting = true; + + mMiximageGenerator = std::make_unique(mGame, mResultMessage); + + // The promise/future mechanism is used as signaling for the thread to indicate + // that processing has been completed. + std::promise().swap(mGeneratorPromise); + mGeneratorFuture = mGeneratorPromise.get_future(); + + mMiximageGeneratorThread = std::thread(&MiximageGenerator::startThread, + mMiximageGenerator.get(), &mGeneratorPromise); + } + } + + // Update the statistics. + mStatus->setText("RUNNING..."); + mGameCounter->setText(std::to_string(mGamesProcessed) + " OF " + std::to_string(mTotalGames) + + (mTotalGames == 1 ? " GAME " : " GAMES ") + "PROCESSED"); + + mGeneratedVal->setText(std::to_string(mImagesGenerated)); + mFailedVal->setText(std::to_string(mGamesFailed)); + mOverwrittenVal->setText(std::to_string(mImagesOverwritten)); + + if (mGamesProcessed == mTotalGames) { + mStatus->setText("COMPLETED"); + mStartPauseButton->setText("DONE", "done (close)"); + mStartPauseButton->setPressedFunc([this](){ delete this; }); + mCloseButton->setText("CLOSE", "close"); + mProcessingVal->setText(""); + LOG(LogInfo) << "GuiOfflineGenerator: Completed processing (" << mImagesGenerated << + (mImagesGenerated == 1 ? " image " : " images ") << "generated, " << + mGamesSkipped << (mGamesSkipped == 1 ? " game " : " games ") << "skipped, " << + mGamesFailed << (mGamesFailed == 1 ? " game " : " games ") << "failed)"; + mProcessing = false; + } +} + +std::vector GuiOfflineGenerator::getHelpPrompts() +{ + std::vector prompts = mGrid.getHelpPrompts(); + return prompts; +} + +HelpStyle GuiOfflineGenerator::getHelpStyle() +{ + HelpStyle style = HelpStyle(); + style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system"); + return style; +} diff --git a/es-app/src/guis/GuiOfflineGenerator.h b/es-app/src/guis/GuiOfflineGenerator.h new file mode 100644 index 000000000..667d03fdb --- /dev/null +++ b/es-app/src/guis/GuiOfflineGenerator.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// GuiOfflineGenerator.h +// +// User interface for the miximage offline generator. +// Calls MiximageGenerator to do the actual work. +// + +#ifndef ES_APP_GUIS_GUI_OFFLINE_GENERATOR_H +#define ES_APP_GUIS_GUI_OFFLINE_GENERATOR_H + +#include "components/ButtonComponent.h" +#include "components/ComponentGrid.h" +#include "GuiComponent.h" +#include "MiximageGenerator.h" + +#include + +class TextComponent; + +class GuiOfflineGenerator : public GuiComponent +{ +public: + GuiOfflineGenerator(Window* window, const std::queue& gameQueue); + ~GuiOfflineGenerator(); + +private: + void onSizeChanged() override; + void update(int deltaTime) override; + + virtual std::vector getHelpPrompts() override; + HelpStyle getHelpStyle() override; + + std::queue mGameQueue; + + std::unique_ptr mMiximageGenerator; + std::thread mMiximageGeneratorThread; + std::promise mGeneratorPromise; + std::future mGeneratorFuture; + + FileData* mGame; + + bool mProcessing; + bool mPaused; + bool mOverwriting; + std::string mResultMessage; + + unsigned int mTotalGames; + unsigned int mGamesProcessed; + unsigned int mImagesGenerated; + unsigned int mImagesOverwritten; + unsigned int mGamesSkipped; + unsigned int mGamesFailed; + + NinePatchComponent mBackground; + ComponentGrid mGrid; + + std::shared_ptr mTitle; + std::shared_ptr mStatus; + std::shared_ptr mGameCounter; + + std::shared_ptr mGeneratedLbl; + std::shared_ptr mGeneratedVal; + + std::shared_ptr mOverwrittenLbl; + std::shared_ptr mOverwrittenVal; + + std::shared_ptr mSkippedLbl; + std::shared_ptr mSkippedVal; + + std::shared_ptr mFailedLbl; + std::shared_ptr mFailedVal; + + std::shared_ptr mProcessingLbl; + std::shared_ptr mProcessingVal; + std::string mGameName; + + std::shared_ptr mLastErrorLbl; + std::shared_ptr mLastErrorVal; + + std::shared_ptr mButtonGrid; + std::shared_ptr mStartPauseButton; + std::shared_ptr mCloseButton; +}; + +#endif // ES_APP_GUIS_GUI_OFFLINE_GENERATOR_H diff --git a/es-app/src/guis/GuiScraperMenu.cpp b/es-app/src/guis/GuiScraperMenu.cpp index fb2223fb9..87930ae56 100644 --- a/es-app/src/guis/GuiScraperMenu.cpp +++ b/es-app/src/guis/GuiScraperMenu.cpp @@ -13,10 +13,11 @@ #include "components/OptionListComponent.h" #include "components/SwitchComponent.h" #include "guis/GuiMsgBox.h" +#include "guis/GuiOfflineGenerator.h" #include "guis/GuiScraperMulti.h" -#include "guis/GuiSettings.h" #include "views/ViewController.h" #include "FileData.h" +#include "FileSorts.h" #include "SystemData.h" @@ -439,9 +440,53 @@ void GuiScraperMenu::openMiximageOptions() } }); + // Miximage offline generator. + ComponentListRow offline_generator_row; + offline_generator_row.elements.clear(); + offline_generator_row.addElement(std::make_shared + (mWindow, "OFFLINE GENERATOR", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + offline_generator_row.addElement(makeArrow(mWindow), false); + offline_generator_row.makeAcceptInputHandler( + std::bind(&GuiScraperMenu::openOfflineGenerator, this, s)); + s->addRow(offline_generator_row); + mWindow->pushGui(s); } +void GuiScraperMenu::openOfflineGenerator(GuiSettings* settings) +{ + if (mSystems->getSelectedObjects().empty()) { + mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), + "THE MIXIMAGE GENERATOR USES THE SAME SYSTEM\n" + "SELECTIONS AS THE SCRAPER, SO PLEASE SELECT\n" + "AT LEAST ONE SYSTEM TO GENERATE IMAGES FOR")); + return; + } + + // Always save the settings before starting the generator, in case any of the + // miximage settings were modified. + settings->save(); + // Also unset the save flag so that a double saving does not take place when closing + // the miximage options menu later on. + settings->setNeedsSaving(false); + + // Build the queue of games to process. + std::queue gameQueue; + std::vector systems = mSystems->getSelectedObjects(); + + for (auto sys = systems.cbegin(); sys != systems.cend(); sys++) { + std::vector games = (*sys)->getRootFolder()->getChildrenRecursive(); + + // Sort the games by "filename, ascending". + std::stable_sort(games.begin(), games.end(), FileSorts::SortTypes.at(0).comparisonFunction); + + for (FileData* game : games) + gameQueue.push(game); + } + + mWindow->pushGui(new GuiOfflineGenerator(mWindow, gameQueue)); +} + void GuiScraperMenu::openOtherOptions() { auto s = new GuiSettings(mWindow, "OTHER SETTINGS"); @@ -731,7 +776,7 @@ void GuiScraperMenu::start() { if (mSystems->getSelectedObjects().empty()) { mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), - "PLEASE SELECT AT LEAST ONE SYSTEM TO SCRAPE")); + "PLEASE SELECT AT LEAST ONE SYSTEM TO SCRAPE")); return; } diff --git a/es-app/src/guis/GuiScraperMenu.h b/es-app/src/guis/GuiScraperMenu.h index 450e5f79c..08763832a 100644 --- a/es-app/src/guis/GuiScraperMenu.h +++ b/es-app/src/guis/GuiScraperMenu.h @@ -12,6 +12,7 @@ #define ES_APP_GUIS_GUI_SCRAPER_MENU_H #include "components/MenuComponent.h" +#include "guis/GuiSettings.h" #include "scrapers/Scraper.h" class FileData; @@ -42,6 +43,7 @@ private: void openAccountOptions(); void openContentOptions(); void openMiximageOptions(); + void openOfflineGenerator(GuiSettings* settings); void openOtherOptions(); std::queue getSearches( diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 2e8e23ad2..d56238233 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -662,7 +662,7 @@ void GuiScraperSearch::update(int deltaTime) // Check if a miximage generator thread was started, and if the processing has been completed. if (mMiximageGenerator && mGeneratorFuture.valid()) { - // Only wait one millisecond, this update() function runs very frequently. + // Only wait one millisecond as this update() function runs very frequently. if (mGeneratorFuture.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready) { mMDResolveHandle.reset(); // We always let the miximage generator thread complete. diff --git a/es-app/src/guis/GuiSettings.h b/es-app/src/guis/GuiSettings.h index b303789c6..0cc975ec4 100644 --- a/es-app/src/guis/GuiSettings.h +++ b/es-app/src/guis/GuiSettings.h @@ -33,7 +33,7 @@ public: bool isPassword = false); inline void addSaveFunc(const std::function& func) { mSaveFuncs.push_back(func); }; - void setNeedsSaving() { mNeedsSaving = true; }; + void setNeedsSaving(bool state = true) { mNeedsSaving = state; }; void setNeedsReloadHelpPrompts() { mNeedsReloadHelpPrompts = true; }; void setNeedsCollectionsUpdate() { mNeedsCollectionsUpdate = true; }; void setNeedsSorting() { mNeedsSorting = true; };