From aedadc45d3032caf2efe690f6e19ba9f655357c2 Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 6 Feb 2024 14:19:50 +0100 Subject: [PATCH] Perf: Enable CGRF caching for all loaded resources All fetch requests should go through the unified resources API, so at the end of the day this should speed up the one major bottleneck (decoding the table of contents) that exists. --- Core/NativeClient/C_Resources.lua | 23 ++++++++++++- Tests/NativeClient/C_Resources.spec.lua | 45 ++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Core/NativeClient/C_Resources.lua b/Core/NativeClient/C_Resources.lua index 374e4b16..e69ba9f4 100644 --- a/Core/NativeClient/C_Resources.lua +++ b/Core/NativeClient/C_Resources.lua @@ -1,3 +1,4 @@ +local CompiledGRF = require("Core.FileFormats.Optimized.CompiledGRF") local FogParameters = require("Core.FileFormats.FogParameters") local RagnarokGRF = require("Core.FileFormats.RagnarokGRF") @@ -8,13 +9,33 @@ local C_Resources = { ["data/sprite/cursors.spr"] = false, ["data/fogparametertable.txt"] = FogParameters, }, + ENABLE_CGRF_CACHING = true, } local self = C_Resources function C_Resources.PreloadPersistentResources() + local grfFilePath = self.GRF_FILE_PATH + + local hasUpdatedCacheEntry = self.ENABLE_CGRF_CACHING and CompiledGRF:IsCacheUpdated(grfFilePath) or false + local grfName = path.basename(grfFilePath, path.extname(grfFilePath)) + local cgrfFilePath = path.join(CompiledGRF.CGRF_CACHE_DIRECTORY, grfName .. ".cgrf") + + local cgrfFileContents + if hasUpdatedCacheEntry then + printf("CGRF_CACHE_HIT: %s (Restoring table of contents)", cgrfFilePath) + cgrfFileContents = C_FileSystem.ReadFile(cgrfFilePath) + end + local grf = RagnarokGRF() - grf:Open(self.GRF_FILE_PATH) + grf:Open(self.GRF_FILE_PATH, cgrfFileContents) + + local needsCacheWrite = self.ENABLE_CGRF_CACHING and not hasUpdatedCacheEntry + if needsCacheWrite then + cgrfFileContents = CompiledGRF:CompileTableOfContents(grf) + printf("CGRF_CACHE_WRITE: %s (%s)", cgrfFilePath, string.filesize(#cgrfFileContents)) + C_FileSystem.WriteFile(cgrfFilePath, cgrfFileContents) + end printf("Preloading %d persistent resources from %s", table.count(self.PERSISTENT_RESOURCES), self.GRF_FILE_PATH) for filePath, decoder in pairs(self.PERSISTENT_RESOURCES) do diff --git a/Tests/NativeClient/C_Resources.spec.lua b/Tests/NativeClient/C_Resources.spec.lua index 7bfd16cc..eb41b8f5 100644 --- a/Tests/NativeClient/C_Resources.spec.lua +++ b/Tests/NativeClient/C_Resources.spec.lua @@ -1,7 +1,10 @@ +local CompiledGRF = require("Core.FileFormats.Optimized.CompiledGRF") local RagnarokGRF = require("Core.FileFormats.RagnarokGRF") local C_Resources = require("Core.NativeClient.C_Resources") +local TEST_GRF_PATH = path.join("Tests", "Fixtures", "test.grf") + describe("C_Resources", function() describe("PreloadPersistentResources", function() local DEFAULT_GRF_PATH = C_Resources.GRF_FILE_PATH @@ -14,6 +17,7 @@ describe("C_Resources", function() it("should throw if the configured asset container doesn't exist", function() local function preloadFromNonExistingGRF() + C_Resources.ENABLE_CGRF_CACHING = false -- Cache access would throw C_Resources.GRF_FILE_PATH = "invalid.grf" C_Resources.PreloadPersistentResources() end @@ -32,7 +36,7 @@ describe("C_Resources", function() end) it("should load and store all persistent resources from the configured asset container", function() - C_Resources.GRF_FILE_PATH = path.join("Tests", "Fixtures", "test.grf") + C_Resources.GRF_FILE_PATH = TEST_GRF_PATH C_Resources.PERSISTENT_RESOURCES = { ["hello-grf.txt"] = false, ["subdirectory/hello.txt"] = false, @@ -98,5 +102,44 @@ describe("C_Resources", function() local preloadedAssetFiles = C_Resources.PERSISTENT_RESOURCES assertEquals(preloadedAssetFiles["uppercase.png"], expectedFileContents["uppercase.png"]) end) + + it("should generate a CGRF cache entry after loading the configured asset container", function() + C_Resources.ENABLE_CGRF_CACHING = true + C_Resources.GRF_FILE_PATH = TEST_GRF_PATH + C_Resources.PERSISTENT_RESOURCES = {} + C_Resources.PreloadPersistentResources() + + local cgrfFilePath = path.join("Tests", "Fixtures", "test.cgrf") + local expectedFileContents = C_FileSystem.ReadFile(cgrfFilePath) + + -- Creating a new cache entry manually should ensure it's considered up-to-date + local cacheEntryPath = path.join(CompiledGRF.CGRF_CACHE_DIRECTORY, "test.cgrf") + local cgrfFileContents = C_FileSystem.ReadFile(cacheEntryPath) + + C_FileSystem.Delete(cacheEntryPath) + + assertEquals(cgrfFileContents, expectedFileContents) + end) + + it("should use an existing CGRF cache entry if it exists and is still up-to-date", function() + -- Assumes that the fixture's CGRF is always more recent than the GRF itself + -- This should always be the case as it's regenerated from the GRF after each format change + local cgrfFilePath = path.join("Tests", "Fixtures", "test.cgrf") + local cgrfFileContents = C_FileSystem.ReadFile(cgrfFilePath) + + -- Creating a new cache entry manually should ensure it's considered up-to-date + local cacheEntryPath = path.join(CompiledGRF.CGRF_CACHE_DIRECTORY, "test.cgrf") + C_FileSystem.WriteFile(cacheEntryPath, cgrfFileContents) + + C_Resources.ENABLE_CGRF_CACHING = true + C_Resources.GRF_FILE_PATH = TEST_GRF_PATH + C_Resources.PERSISTENT_RESOURCES = {} + C_Resources.PreloadPersistentResources() + + C_FileSystem.Delete(cacheEntryPath) + + assertEquals(C_Resources.grf.fileTable.compressedSizeInBytes, 0) + assertEquals(C_Resources.grf.fileTable.decompressedSizeInBytes, 0) + end) end) end)