From 3aaea4c0f9680a471bf9dfb7d7df51d97c12fb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 29 Sep 2026 16:34:38 -0600 Subject: [PATCH] GameInfoCache: Prioritize loads, so launching doesn't wait behind search Type-to-search asks for the title of every game in the list, queueing a load for each, and launching a game whose info wasn't loaded yet then blocked until the whole queue had drained. Search's loads are now LOW, and launching asks at HIGH, which queues its own load rather than wait for a pending one. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/GameBrowser.cpp | 3 ++- UI/GameInfoCache.cpp | 22 ++++++++++++++++------ UI/GameInfoCache.h | 7 ++++++- UI/MainScreen.cpp | 5 +++-- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/UI/GameBrowser.cpp b/UI/GameBrowser.cpp index ef9be81dfb..6aea95b5d3 100644 --- a/UI/GameBrowser.cpp +++ b/UI/GameBrowser.cpp @@ -473,7 +473,8 @@ void GameButton::Draw(UIContext &dc) { } std::string GameButton::DescribeText() const { - std::shared_ptr ginfo = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO); + // LOW: search calls this for every game in the list, which mustn't hold up the visible ones. + std::shared_ptr ginfo = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO, nullptr, GameInfoFlags::EMPTY, TaskPriority::LOW); if (!ginfo->Ready(GameInfoFlags::PARAM_SFO)) return "..."; auto u = GetI18NCategory(I18NCat::UI_ELEMENTS); diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 20fa67ae97..0cde31802f 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -541,8 +541,8 @@ static bool LoadReplacementImage(GameInfo *info, GameInfoTex *tex, const char *f class GameInfoWorkItem : public Task { public: - GameInfoWorkItem(const Path &gamePath, std::shared_ptr &info, GameInfoFlags flags) - : gamePath_(gamePath), info_(info), flags_(flags) {} + GameInfoWorkItem(const Path &gamePath, std::shared_ptr &info, GameInfoFlags flags, TaskPriority priority) + : gamePath_(gamePath), info_(info), flags_(flags), priority_(priority) {} ~GameInfoWorkItem() { info_->DisposeFileLoader(); @@ -553,10 +553,14 @@ public: } TaskPriority Priority() const override { + if (priority_ == TaskPriority::HIGH) { + // Someone's waiting on this, wherever it is. + return TaskPriority::HIGH; + } switch (gamePath_.Type()) { case PathType::NATIVE: case PathType::CONTENT_URI: - return TaskPriority::NORMAL; + return priority_; default: // Remote/network access. @@ -997,6 +1001,7 @@ private: Path gamePath_; std::shared_ptr info_; GameInfoFlags flags_{}; + TaskPriority priority_; DISALLOW_COPY_AND_ASSIGN(GameInfoWorkItem); }; @@ -1110,7 +1115,7 @@ void GameInfoCache::PurgeType(IdentifiedFileType fileType) { // Call on the main thread ONLY - that is from stuff called from NativeFrame. // Can also be called from the audio thread for menu background music, but that cannot request images! -std::shared_ptr GameInfoCache::GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags, GameInfoFlags *outHasFlags, GameInfoFlags refetchFlags) { +std::shared_ptr GameInfoCache::GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags, GameInfoFlags *outHasFlags, GameInfoFlags refetchFlags, TaskPriority priority) { const std::string &pathStr = gamePath.ToString(); // _dbg_assert_(gamePath != GetSysDirectory(DIRECTORY_SAVEDATA)); @@ -1139,6 +1144,11 @@ std::shared_ptr GameInfoCache::GetInfo(Draw::DrawContext *draw, const info->hasFlags &= ~refetchFlags; } GameInfoFlags willHaveFlags = info->hasFlags | info->pendingFlags; // We don't want to re-fetch data that we have, so or in pendingFlags. + if (priority == TaskPriority::HIGH) { + // A pending load may be sitting at the back of the queue (a search over a big list + // queues one per game), and the caller is about to block. Fetch it ourselves. + willHaveFlags = info->hasFlags; + } wanted = (GameInfoFlags)((int)wantFlags & ~(int)willHaveFlags); // & is reserved for testing so we have to cast to int. ugh. // FILE_TYPE is special: every work item switches on info->fileType, so it's not enough that // some *pending* item is going to compute it - that item may not have got there yet, and we'd @@ -1154,7 +1164,7 @@ std::shared_ptr GameInfoCache::GetInfo(Draw::DrawContext *draw, const if (wanted != (GameInfoFlags)0) { // We're missing info that we want. Go get it! - GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wanted); + GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wanted, priority); g_threadManager.EnqueueTask(item); } return info; @@ -1170,7 +1180,7 @@ std::shared_ptr GameInfoCache::GetInfo(Draw::DrawContext *draw, const mapLock_.unlock(); // Just get all the stuff we wanted. - GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wantFlags); + GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wantFlags, priority); g_threadManager.EnqueueTask(item); return info; } diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index 50e4dde834..335dee8ea6 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -25,6 +25,7 @@ #include #include "Common/Thread/Event.h" +#include "Common/Thread/ThreadManager.h" #include "Core/ELF/ParamSFO.h" #include "Core/Util/PSARUnpack.h" #include "Common/File/Path.h" @@ -227,7 +228,11 @@ public: // because they're big. bgTextures and sound may be discarded over time as well. // NOTE: This never returns null, so you don't need to check for that. Do check Ready() flags though. // It's OK to pass in nullptr for draw if you don't need the actual texture right now. - std::shared_ptr GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags, GameInfoFlags *outHasFlags = nullptr, GameInfoFlags refetchFlags = GameInfoFlags::EMPTY); + // priority orders the background loads: NORMAL for what's on screen, LOW for speculative work like + // search. HIGH is for a one-off request you're about to block on: it doesn't wait behind a + // lower-priority load that's already queued for the same data, it queues its own - so don't use it + // every frame. + std::shared_ptr GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags, GameInfoFlags *outHasFlags = nullptr, GameInfoFlags refetchFlags = GameInfoFlags::EMPTY, TaskPriority priority = TaskPriority::NORMAL); void FlushBGs(); // Gets rid of all BG textures. Also gets rid of bg sounds. void CancelAll(); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index cf84b31024..6d32ca3a8a 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -72,9 +72,10 @@ static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, cons // Check if we already know that this game isn't playable. // If coming from the main screen, the info will already be computed here since the icon is displayed etc. // Otherwise (launching from a file association, a shortcut, drag-and-drop...) we have to block until - // it's available - we can't decide what to do below without it. + // it's available - we can't decide what to do below without it. HIGH, so we don't wait behind + // whatever else is queued, like a search's loads for every game in a big list. const GameInfoFlags neededFlags = GameInfoFlags::FILE_TYPE | GameInfoFlags::PARAM_SFO; - std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, path, neededFlags); + std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, path, neededFlags, nullptr, GameInfoFlags::EMPTY, TaskPriority::HIGH); info->WaitUntilReady(neededFlags); switch (info->fileType) {