From 7513d9286269247b9cda466b7377ac5441fc2dd6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 23 Sep 2021 22:00:16 -0700 Subject: [PATCH 1/3] UI: Reduce IO in savedata manager sorting. --- UI/SavedataScreen.cpp | 49 ++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index cfd61045a3..2e698c8774 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -424,53 +424,50 @@ bool SavedataBrowser::ByFilename(const UI::View *v1, const UI::View *v2) { return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; } -static time_t GetTotalSize(const SavedataButton *b) { - auto fileLoader = std::unique_ptr(ConstructFileLoader(b->GamePath())); - std::string errorString; - switch (Identify_File(fileLoader.get(), &errorString)) { - case IdentifiedFileType::PSP_PBP_DIRECTORY: - case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY: - return File::ComputeRecursiveDirectorySize(ResolvePBPDirectory(b->GamePath())); - default: - return fileLoader->FileSize(); +static uint64_t GetTotalSize(const SavedataButton *b) { + File::FileInfo info; + if (File::GetFileInfo(b->GamePath(), &info)) { + if (info.isDirectory) + return File::ComputeRecursiveDirectorySize(b->GamePath()); + return info.size; } + return 0; } bool SavedataBrowser::BySize(const UI::View *v1, const UI::View *v2) { const SavedataButton *b1 = static_cast(v1); const SavedataButton *b2 = static_cast(v2); + const uint64_t size1 = GetTotalSize(b1); + const uint64_t size2 = GetTotalSize(b2); - if (GetTotalSize(b1) > GetTotalSize(b2)) + if (size1 > size2) return true; - else if (GetTotalSize(b1) < GetTotalSize(b2)) + else if (size1 < size2) return false; return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; } -static time_t GetDateSeconds(const SavedataButton *b) { - auto fileLoader = std::unique_ptr(ConstructFileLoader(b->GamePath())); - tm datetm; - bool success; - std::string errorString; - if (Identify_File(fileLoader.get(), &errorString) == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY) { - success = File::GetModifTime(b->GamePath() / "PARAM.SFO", datetm); - } else { - success = File::GetModifTime(b->GamePath(), datetm); +static int64_t GetDateSeconds(const SavedataButton *b) { + File::FileInfo info; + if (File::GetFileInfo(b->GamePath(), &info)) { + if (info.isDirectory && File::GetFileInfo(b->GamePath() / "PARAM.SFO", &info)) { + return info.mtime; + } + return info.mtime; } - if (success) { - return mktime(&datetm); - } - return (time_t)0; + return 0; } bool SavedataBrowser::ByDate(const UI::View *v1, const UI::View *v2) { const SavedataButton *b1 = static_cast(v1); const SavedataButton *b2 = static_cast(v2); + const int64_t time1 = GetDateSeconds(b1); + const int64_t time2 = GetDateSeconds(b2); - if (GetDateSeconds(b1) > GetDateSeconds(b2)) + if (time1 > time2) return true; - if (GetDateSeconds(b1) < GetDateSeconds(b2)) + if (time1 < time2) return false; return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; } From 8c937f45f77149cc5e88d64886546818d8eaf500 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 23 Sep 2021 22:14:25 -0700 Subject: [PATCH 2/3] UI: Cache save size on save buttons. It was calculating the size and date for each permutation, which became monstrously slow on Android with scoped storage. --- UI/SavedataScreen.cpp | 88 +++++++++++++++++++++++++------------------ UI/SavedataScreen.h | 6 +-- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index 2e698c8774..be06271797 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -138,7 +138,7 @@ private: class SortedLinearLayout : public UI::LinearLayoutList { public: - typedef std::function CompareFunc; + typedef std::function CompareFunc; typedef std::function DoneFunc; SortedLinearLayout(UI::Orientation orientation, UI::LayoutParams *layoutParams = nullptr) @@ -184,14 +184,52 @@ public: const Path &GamePath() const { return savePath_; } + uint64_t GetTotalSize(); + int64_t GetDateSeconds(); + private: void UpdateText(const std::shared_ptr &ginfo); Path savePath_; std::string title_; std::string subtitle_; + uint64_t totalSize_ = 0; + int64_t dateSeconds_ = 0; + bool hasTotalSize_ = false; + bool hasDateSeconds_ = false; }; +uint64_t SavedataButton::GetTotalSize() { + if (hasTotalSize_) + return totalSize_; + + File::FileInfo info; + if (File::GetFileInfo(savePath_, &info)) { + totalSize_ = info.size; + if (info.isDirectory) + totalSize_ = File::ComputeRecursiveDirectorySize(savePath_); + } + + hasTotalSize_ = true; + return totalSize_; +} + +int64_t SavedataButton::GetDateSeconds() { + if (hasDateSeconds_) + return dateSeconds_; + + File::FileInfo info; + if (File::GetFileInfo(savePath_, &info)) { + dateSeconds_ = info.mtime; + if (info.isDirectory && File::GetFileInfo(savePath_ / "PARAM.SFO", &info)) { + dateSeconds_ = info.mtime; + } + } + + hasDateSeconds_ = true; + return dateSeconds_; +} + UI::EventReturn SavedataPopupScreen::OnDeleteButtonClick(UI::EventParams &e) { std::shared_ptr ginfo = g_gameInfoCache->GetInfo(nullptr, savePath_, GAMEINFO_WANTSIZE); ginfo->Delete(); @@ -417,28 +455,18 @@ void SavedataBrowser::SetSortOption(SavedataSortOption opt) { } } -bool SavedataBrowser::ByFilename(const UI::View *v1, const UI::View *v2) { - const SavedataButton *b1 = static_cast(v1); - const SavedataButton *b2 = static_cast(v2); +bool SavedataBrowser::ByFilename(UI::View *v1, UI::View *v2) { + SavedataButton *b1 = static_cast(v1); + SavedataButton *b2 = static_cast(v2); return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; } -static uint64_t GetTotalSize(const SavedataButton *b) { - File::FileInfo info; - if (File::GetFileInfo(b->GamePath(), &info)) { - if (info.isDirectory) - return File::ComputeRecursiveDirectorySize(b->GamePath()); - return info.size; - } - return 0; -} - -bool SavedataBrowser::BySize(const UI::View *v1, const UI::View *v2) { - const SavedataButton *b1 = static_cast(v1); - const SavedataButton *b2 = static_cast(v2); - const uint64_t size1 = GetTotalSize(b1); - const uint64_t size2 = GetTotalSize(b2); +bool SavedataBrowser::BySize(UI::View *v1, UI::View *v2) { + SavedataButton *b1 = static_cast(v1); + SavedataButton *b2 = static_cast(v2); + const uint64_t size1 = b1->GetTotalSize(); + const uint64_t size2 = b2->GetTotalSize(); if (size1 > size2) return true; @@ -447,23 +475,11 @@ bool SavedataBrowser::BySize(const UI::View *v1, const UI::View *v2) { return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; } -static int64_t GetDateSeconds(const SavedataButton *b) { - File::FileInfo info; - if (File::GetFileInfo(b->GamePath(), &info)) { - if (info.isDirectory && File::GetFileInfo(b->GamePath() / "PARAM.SFO", &info)) { - return info.mtime; - } - return info.mtime; - } - - return 0; -} - -bool SavedataBrowser::ByDate(const UI::View *v1, const UI::View *v2) { - const SavedataButton *b1 = static_cast(v1); - const SavedataButton *b2 = static_cast(v2); - const int64_t time1 = GetDateSeconds(b1); - const int64_t time2 = GetDateSeconds(b2); +bool SavedataBrowser::ByDate(UI::View *v1, UI::View *v2) { + SavedataButton *b1 = static_cast(v1); + SavedataButton *b2 = static_cast(v2); + const int64_t time1 = b1->GetDateSeconds(); + const int64_t time2 = b2->GetDateSeconds(); if (time1 > time2) return true; diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index b9d057d025..14ea48a960 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -46,9 +46,9 @@ public: UI::Event OnChoice; private: - static bool ByFilename(const UI::View *, const UI::View *); - static bool BySize(const UI::View *, const UI::View *); - static bool ByDate(const UI::View *, const UI::View *); + static bool ByFilename(UI::View *, UI::View *); + static bool BySize(UI::View *, UI::View *); + static bool ByDate(UI::View *, UI::View *); static bool SortDone(); void Refresh(); From 09ca16361cea8db047bf1cd9cbef676390dd1505 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 23 Sep 2021 22:30:25 -0700 Subject: [PATCH 3/3] UI: Asynchronously update size and dates on sort. This means items shift and jump into place, but it also means we don't hang even for large savedata collections. --- UI/SavedataScreen.cpp | 92 +++++++++++++++++++++++++++++-------------- UI/SavedataScreen.h | 10 +++-- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index be06271797..1fbab9ab6a 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -138,32 +138,49 @@ private: class SortedLinearLayout : public UI::LinearLayoutList { public: - typedef std::function CompareFunc; - typedef std::function DoneFunc; + typedef std::function PrepFunc; + typedef std::function CompareFunc; SortedLinearLayout(UI::Orientation orientation, UI::LayoutParams *layoutParams = nullptr) : UI::LinearLayoutList(orientation, layoutParams) { } - void SetCompare(CompareFunc lessFunc, DoneFunc doneFunc) { + void SetCompare(const PrepFunc &prepFunc, const CompareFunc &lessFunc) { + prepIndex_ = 0; + prepFunc_ = prepFunc; lessFunc_ = lessFunc; - doneFunc_ = doneFunc; } void Update() override; private: + size_t prepIndex_ = 0; + PrepFunc prepFunc_; CompareFunc lessFunc_; - DoneFunc doneFunc_; }; void SortedLinearLayout::Update() { + if (prepFunc_) { + // Try to avoid dropping more than a frame, prefer items shift. + constexpr double ALLOWED_TIME = 0.95 / 60.0; + double start_time = time_now_d(); + for (; prepIndex_ < views_.size(); ++prepIndex_) { + prepFunc_(views_[prepIndex_]); + if (time_now_d() > start_time + ALLOWED_TIME) { + break; + } + } + } if (lessFunc_) { + // We may sort several times while calculating. std::stable_sort(views_.begin(), views_.end(), lessFunc_); } - if (doneFunc_ && doneFunc_()) { + // We're done if we got through all items. + if (prepIndex_ >= views_.size()) { + prepFunc_ = PrepFunc(); lessFunc_ = CompareFunc(); } + UI::LinearLayout::Update(); } @@ -184,8 +201,15 @@ public: const Path &GamePath() const { return savePath_; } - uint64_t GetTotalSize(); - int64_t GetDateSeconds(); + uint64_t GetTotalSize() const { + return totalSize_; + } + int64_t GetDateSeconds() const { + return dateSeconds_; + } + + void UpdateTotalSize(); + void UpdateDateSeconds(); private: void UpdateText(const std::shared_ptr &ginfo); @@ -199,9 +223,9 @@ private: bool hasDateSeconds_ = false; }; -uint64_t SavedataButton::GetTotalSize() { +void SavedataButton::UpdateTotalSize() { if (hasTotalSize_) - return totalSize_; + return; File::FileInfo info; if (File::GetFileInfo(savePath_, &info)) { @@ -211,12 +235,11 @@ uint64_t SavedataButton::GetTotalSize() { } hasTotalSize_ = true; - return totalSize_; } -int64_t SavedataButton::GetDateSeconds() { +void SavedataButton::UpdateDateSeconds() { if (hasDateSeconds_) - return dateSeconds_; + return; File::FileInfo info; if (File::GetFileInfo(savePath_, &info)) { @@ -227,7 +250,6 @@ int64_t SavedataButton::GetDateSeconds() { } hasDateSeconds_ = true; - return dateSeconds_; } UI::EventReturn SavedataPopupScreen::OnDeleteButtonClick(UI::EventParams &e) { @@ -446,25 +468,34 @@ void SavedataBrowser::SetSortOption(SavedataSortOption opt) { if (gameList_) { SortedLinearLayout *gl = static_cast(gameList_); if (sortOption_ == SavedataSortOption::FILENAME) { - gl->SetCompare(&ByFilename, &SortDone); + gl->SetCompare(&PrepFilename, &ByFilename); } else if (sortOption_ == SavedataSortOption::SIZE) { - gl->SetCompare(&BySize, &SortDone); + gl->SetCompare(&PrepSize, &BySize); } else if (sortOption_ == SavedataSortOption::DATE) { - gl->SetCompare(&ByDate, &SortDone); + gl->SetCompare(&PrepDate, &ByDate); } } } -bool SavedataBrowser::ByFilename(UI::View *v1, UI::View *v2) { - SavedataButton *b1 = static_cast(v1); - SavedataButton *b2 = static_cast(v2); +void SavedataBrowser::PrepFilename(UI::View *v) { + // Nothing needed. +} + +bool SavedataBrowser::ByFilename(const UI::View *v1, const UI::View *v2) { + const SavedataButton *b1 = static_cast(v1); + const SavedataButton *b2 = static_cast(v2); return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; } -bool SavedataBrowser::BySize(UI::View *v1, UI::View *v2) { - SavedataButton *b1 = static_cast(v1); - SavedataButton *b2 = static_cast(v2); +void SavedataBrowser::PrepSize(UI::View *v) { + SavedataButton *b = static_cast(v); + b->UpdateTotalSize(); +} + +bool SavedataBrowser::BySize(const UI::View *v1, const UI::View *v2) { + const SavedataButton *b1 = static_cast(v1); + const SavedataButton *b2 = static_cast(v2); const uint64_t size1 = b1->GetTotalSize(); const uint64_t size2 = b2->GetTotalSize(); @@ -475,9 +506,14 @@ bool SavedataBrowser::BySize(UI::View *v1, UI::View *v2) { return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; } -bool SavedataBrowser::ByDate(UI::View *v1, UI::View *v2) { - SavedataButton *b1 = static_cast(v1); - SavedataButton *b2 = static_cast(v2); +void SavedataBrowser::PrepDate(UI::View *v) { + SavedataButton *b = static_cast(v); + b->UpdateDateSeconds(); +} + +bool SavedataBrowser::ByDate(const UI::View *v1, const UI::View *v2) { + const SavedataButton *b1 = static_cast(v1); + const SavedataButton *b2 = static_cast(v2); const int64_t time1 = b1->GetDateSeconds(); const int64_t time2 = b2->GetDateSeconds(); @@ -488,10 +524,6 @@ bool SavedataBrowser::ByDate(UI::View *v1, UI::View *v2) { return strcmp(b1->GamePath().c_str(), b2->GamePath().c_str()) < 0; } -bool SavedataBrowser::SortDone() { - return true; -} - void SavedataBrowser::Refresh() { using namespace UI; diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index 14ea48a960..1f9509e687 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -46,10 +46,12 @@ public: UI::Event OnChoice; private: - static bool ByFilename(UI::View *, UI::View *); - static bool BySize(UI::View *, UI::View *); - static bool ByDate(UI::View *, UI::View *); - static bool SortDone(); + static void PrepFilename(UI::View *); + static void PrepSize(UI::View *); + static void PrepDate(UI::View *); + static bool ByFilename(const UI::View *, const UI::View *); + static bool BySize(const UI::View *, const UI::View *); + static bool ByDate(const UI::View *, const UI::View *); void Refresh(); UI::EventReturn SavedataButtonClick(UI::EventParams &e);