mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 15:01:11 +02:00
5807b8d836
- Replaced `teardownMountedSession` and `flushEditorPackageLoaded` with a unified `SessionLifecycle` approach for managing session transitions and resource cleanup. - Implemented `SessionLifecycle.endEditorSession` and `SessionLifecycle.endGameSession` to streamline the teardown process for editor and game sessions. - Introduced `Assets.releaseSession` to handle GPU resource release at session end, ensuring efficient memory management. - Updated `Game` and `Game2` reset methods to include world and canvas resource releases. - Enhanced `MapLoader` with a new `releaseAll` method for eager GPU cache cleanup. - Added tests to verify the new session lifecycle functionality and resource management. This commit improves the stability and performance of in-process transitions, particularly during editor and game session changes. further improves and addresses #1662 specifically around android gc pressure.
138 lines
4.7 KiB
Lua
138 lines
4.7 KiB
Lua
-- Central image cache plus the mod-visible asset search path. Every
|
|
-- renderer that used to call love.graphics.newImage(path) straight goes
|
|
-- through Assets.image, so an enabled mod shadows a generated asset with
|
|
-- its own file without editing a single record, and one flush() drops
|
|
-- every downstream cache for dev-mode hot reload.
|
|
--
|
|
-- No loader installed means resolve() is the identity. The NX Blue/Yellow
|
|
-- versioned-cache fallback lives in src/core/NxAssetOverlay.lua (installed
|
|
-- once at boot on NX only), not here, so this module stays platform-free.
|
|
|
|
local Assets = {}
|
|
|
|
-- resolved path -> love Image
|
|
local cache = {}
|
|
-- downstream caches that must empty when the search path changes
|
|
local invalidators = {}
|
|
-- optional GPU release hooks for session end (never run on hot reload flush)
|
|
local releasers = {}
|
|
|
|
-- The loader bridge: overrideOrder() yields mods highest-priority-first
|
|
-- and derivedPath(rel) yields an existing save/mod-derived/<id>/<rel>.
|
|
-- nil until the loader installs one.
|
|
Assets.loader = nil
|
|
|
|
local GENERATED = "assets/generated/"
|
|
|
|
local function exists(path)
|
|
local fs = love and love.filesystem
|
|
if not (fs and fs.getInfo) then return false end
|
|
return fs.getInfo(path) ~= nil
|
|
end
|
|
Assets.exists = exists
|
|
|
|
-- Mod overrides win; on NX, Blue/Yellow then use the prefixed save-dir file;
|
|
-- otherwise the caller's unprefixed path (Red / mounted overlay).
|
|
function Assets.resolve(path)
|
|
if type(path) ~= "string" then return path end
|
|
if path:sub(1, #GENERATED) ~= GENERATED then return path end
|
|
|
|
local rel = path:sub(#GENERATED + 1)
|
|
local loader = Assets.loader
|
|
if loader then
|
|
for _, mod in ipairs(loader:overrideOrder()) do
|
|
local candidate = mod.path .. "/overrides/" .. rel
|
|
if exists(candidate) then return candidate end
|
|
end
|
|
local derived = loader:derivedPath(rel)
|
|
if derived then return derived end
|
|
end
|
|
|
|
-- NX Blue/Yellow: no rewrite here -- NxAssetOverlay (installed once at
|
|
-- boot on NX only) covers every loader globally, so this module stays
|
|
-- the mod-override choke point it always was.
|
|
return path
|
|
end
|
|
|
|
function Assets.image(path)
|
|
local resolved = Assets.resolve(path)
|
|
local image = cache[resolved]
|
|
if not image then
|
|
image = love.graphics.newImage(resolved)
|
|
cache[resolved] = image
|
|
end
|
|
return image
|
|
end
|
|
|
|
-- pixel-level reads (tile-shift variants, the spinner strip blit) resolve
|
|
-- the same way but stay uncached: the caller keeps the derived product
|
|
function Assets.imageData(path)
|
|
return love.image.newImageData(Assets.resolve(path))
|
|
end
|
|
|
|
-- Register a cache invalidator, or { invalidate = fn, release = fn } when a
|
|
-- module caches LOVE Images/Canvases and can eagerly free them at session end.
|
|
-- release is optional and is NOT run on flush/invalidate (HotReload safe).
|
|
-- MapLoader is the canonical split-hook example: invalidateAll clears tables
|
|
-- without GPU release; releaseAll evicts every resident map renderer.
|
|
function Assets.register(hooks)
|
|
if type(hooks) == "function" then
|
|
invalidators[#invalidators + 1] = hooks
|
|
return
|
|
end
|
|
if hooks.invalidate then invalidators[#invalidators + 1] = hooks.invalidate end
|
|
if hooks.release then releasers[#releasers + 1] = hooks.release end
|
|
end
|
|
|
|
-- hot reload's single entry point (20-developer-tooling): drop the central
|
|
-- cache and fan out to every registered downstream one. A cache whose
|
|
-- invalidator throws must not strand the ones behind it in the list.
|
|
function Assets.invalidate()
|
|
cache = {}
|
|
for _, fn in ipairs(invalidators) do pcall(fn) end
|
|
end
|
|
|
|
Assets.flush = Assets.invalidate
|
|
|
|
-- In-process return-to-launcher / editor close: release central Images and
|
|
-- run release hooks only. Does not call invalidate hooks (MapLoader must
|
|
-- keep invalidateAll separate from releaseAll).
|
|
function Assets.releaseSession()
|
|
for _, img in pairs(cache) do
|
|
if img and img.release then pcall(img.release, img) end
|
|
end
|
|
cache = {}
|
|
for _, fn in ipairs(releasers) do pcall(fn) end
|
|
end
|
|
|
|
-- Loader:load hands over the live mod set once the merge is done. Load
|
|
-- order is priority ascending, so the search walks it backwards: the mod
|
|
-- that wins the record merge wins the asset lookup too.
|
|
function Assets.installLoader(loader)
|
|
if not loader then
|
|
Assets.loader = nil
|
|
Assets.invalidate()
|
|
return
|
|
end
|
|
local bridge = {}
|
|
function bridge:overrideOrder()
|
|
local order = {}
|
|
local loaded = loader.loaded or {}
|
|
for i = #loaded, 1, -1 do
|
|
order[#order + 1] = { id = loaded[i].manifest.id, path = loaded[i].path }
|
|
end
|
|
return order
|
|
end
|
|
function bridge:derivedPath(rel)
|
|
for _, mod in ipairs(self:overrideOrder()) do
|
|
local candidate = "save/mod-derived/" .. mod.id .. "/" .. rel
|
|
if exists(candidate) then return candidate end
|
|
end
|
|
return nil
|
|
end
|
|
Assets.loader = bridge
|
|
Assets.invalidate()
|
|
end
|
|
|
|
return Assets
|