From 914851b9116a4cf519d1bae71dbe4c1ca0956aee Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sun, 30 Aug 2026 07:32:02 +1000 Subject: [PATCH] Avoid rescanning all builds when closing a build Reuse the cached recursive build index when returning to the same folder and refresh only the build that was just closed. Full rescans are still performed when the build folder changes. This also avoids scanning a nested folder twice when returning from a build opened through recursive search. --- spec/System/TestBuildListHelpers_spec.lua | 31 +++++++++++++ src/Modules/BuildList.lua | 12 ++++- src/Modules/BuildListHelpers.lua | 54 +++++++++++++++++------ 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/spec/System/TestBuildListHelpers_spec.lua b/spec/System/TestBuildListHelpers_spec.lua index 40f48bbeac5..e7bd91dd09d 100644 --- a/spec/System/TestBuildListHelpers_spec.lua +++ b/spec/System/TestBuildListHelpers_spec.lua @@ -2,6 +2,7 @@ describe("BuildListHelpers", function() local originalBuildPath local originalCloudErrorPopup local originalFileSearch + local originalFilterBuildList local originalOpen local buildListHelpers local fileHeaders @@ -14,6 +15,7 @@ describe("BuildListHelpers", function() originalBuildPath = main.buildPath originalCloudErrorPopup = main.OpenCloudErrorPopup originalFileSearch = _G.NewFileSearch + originalFilterBuildList = main.filterBuildList originalOpen = io.open buildListHelpers = LoadModule("Modules/BuildListHelpers") fileHeaders = { } @@ -22,6 +24,7 @@ describe("BuildListHelpers", function() searchCount = 0 cloudErrorPath = nil main.buildPath = "Builds/" + main.filterBuildList = "" main.OpenCloudErrorPopup = function(_, path) cloudErrorPath = path end @@ -53,6 +56,7 @@ describe("BuildListHelpers", function() main.buildPath = originalBuildPath main.OpenCloudErrorPopup = originalCloudErrorPopup _G.NewFileSearch = originalFileSearch + main.filterBuildList = originalFilterBuildList io.open = originalOpen end) @@ -77,6 +81,33 @@ describe("BuildListHelpers", function() assert.are.same(opensAfterIndex, fileOpenCount) end) + it("refreshes only the reopened build when returning to the build list", function() + searches["files:Builds/*.xml"] = { { name = "Root.xml", modified = 1 } } + searches["folders:Builds/*"] = { { name = "League", modified = 2 } } + searches["files:Builds/League/*.xml"] = { { name = "Nested.xml", modified = 3 } } + fileHeaders["Builds/Root.xml"] = '' + fileHeaders["Builds/League/Nested.xml"] = '' + local listMode = require("Modules.BuildList") + + listMode:Init() + local scansAfterIndex = searchCount + local opensAfterIndex = fileOpenCount + searches["files:Builds/Root.xml"] = { { name = "Root.xml", modified = 4 } } + fileHeaders["Builds/Root.xml"] = '' + listMode:Init("Root", "") + + assert.are.same(scansAfterIndex + 1, searchCount) + assert.are.same(opensAfterIndex + 1, fileOpenCount) + assert.are.same(81, listMode.controls.buildList.selValue.level) + assert.are.same("Assassin", listMode.controls.buildList.selValue.ascendClassName) + assert.are.same(4, listMode.controls.buildList.selValue.modified) + + local scansBeforeFolderChange = searchCount + listMode:Init("Nested", "League/") + assert.are.same(scansBeforeFolderChange + 2, searchCount) + assert.are.same("Builds/League/Nested.xml", listMode.controls.buildList.selValue.fullFileName) + end) + it("reports cloud read failures", function() searches["files:Builds/*.xml"] = { { name = "Cloud.xml" } } fileHeaders["Builds/Cloud.xml"] = false diff --git a/src/Modules/BuildList.lua b/src/Modules/BuildList.lua index 7648110d0ce..053b2f02594 100644 --- a/src/Modules/BuildList.lua +++ b/src/Modules/BuildList.lua @@ -14,6 +14,8 @@ local listMode = new("ControlHost"):ControlHost() function listMode:Init(selBuildName, subPath) if self.initialised then + local subPathChanged = subPath and subPath ~= self.subPath + local reuseBuildIndex = not subPathChanged and self.indexedBuildPath == main.buildPath self.subPath = subPath or self.subPath self.controls.buildList.controls.path:SetSubPath(self.subPath) --if main.showPublicBuilds then @@ -22,7 +24,14 @@ function listMode:Init(selBuildName, subPath) else self.controls.ExtBuildList = nil end - self:BuildList() + if reuseBuildIndex then + if selBuildName then + buildListHelpers.RefreshBuild(self.buildIndex, self.subPath, selBuildName..".xml") + end + self:FilterBuildList() + elseif not subPathChanged then + self:BuildList() + end self.controls.buildList:SelByFullFileName(selBuildName and main.buildPath..self.subPath..selBuildName..".xml") self:SelectControl(self.controls.buildList) return @@ -199,6 +208,7 @@ function listMode:GetDestName(subPath, fileName) end function listMode:BuildList() + self.indexedBuildPath = main.buildPath self.buildIndex = buildListHelpers.ScanFolder(self.subPath) self:FilterBuildList() end diff --git a/src/Modules/BuildListHelpers.lua b/src/Modules/BuildListHelpers.lua index b6fb906d28c..dc1ff177a0d 100644 --- a/src/Modules/BuildListHelpers.lua +++ b/src/Modules/BuildListHelpers.lua @@ -34,6 +34,22 @@ local function ReadBuildHeader(fullFileName) } end +local function ReadBuild(subPath, fileName, modified) + local fullFileName = main.buildPath..subPath..fileName + local header = ReadBuildHeader(fullFileName) + if not header then return end + return { + fileName = fileName, + subPath = subPath, + fullFileName = fullFileName, + modified = modified, + buildName = fileName:gsub("%.xml$",""), + level = header.level, + className = header.className, + ascendClassName = header.ascendClassName, + } +end + local function MatchEntry(entry, terms) for _, term in ipairs(terms) do local termLower = term:lower() @@ -66,19 +82,9 @@ local function ScanFolder(subPath) local handle = NewFileSearch(main.buildPath..currentSubPath.."*.xml") while handle do local fileName = handle:GetFileName() - local fullFileName = main.buildPath..currentSubPath..fileName - local header = ReadBuildHeader(fullFileName) - if header then - t_insert(list, { - fileName = fileName, - subPath = currentSubPath, - fullFileName = fullFileName, - modified = handle:GetFileModifiedTime(), - buildName = fileName:gsub("%.xml$",""), - level = header.level, - className = header.className, - ascendClassName = header.ascendClassName, - }) + local build = ReadBuild(currentSubPath, fileName, handle:GetFileModifiedTime()) + if build then + t_insert(list, build) end if not handle:NextFile() then break end @@ -110,6 +116,27 @@ local function ScanFolder(subPath) return list end +local function RefreshBuild(index, subPath, fileName) + local fullFileName = main.buildPath..subPath..fileName + local handle = NewFileSearch(fullFileName) + local build = handle and ReadBuild(subPath, fileName, handle:GetFileModifiedTime()) + if handle and not build then return end + + for buildIndex, entry in ipairs(index) do + if entry.fullFileName == fullFileName then + if build then + index[buildIndex] = build + else + table.remove(index, buildIndex) + end + return + end + end + if build then + t_insert(index, build) + end +end + -- Filtering the cached index avoids filesystem work on every keystroke. local function FilterList(index, subPath, filterText) local terms = { } @@ -196,6 +223,7 @@ return { buildSortDropList = buildSortDropList, CanMoveToSubPath = CanMoveToSubPath, FilterList = FilterList, + RefreshBuild = RefreshBuild, ScanFolder = ScanFolder, SortList = SortList, }