Refactor session management and resource cleanup

- 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.
This commit is contained in:
1jamie
2026-08-23 10:46:19 -05:00
parent e88f2ef060
commit 5807b8d836
16 changed files with 353 additions and 131 deletions
+15 -2
View File
@@ -114,12 +114,25 @@ function MapLoader.invalidateAll()
lru = {}
end
-- Eagerly release every resident map's TileRenderer GPU objects. Only safe
-- when nothing live holds map instances (session end after game:reset).
function MapLoader.releaseAll()
local ids = {}
for id in pairs(cache) do ids[#ids + 1] = id end
for _, id in ipairs(ids) do MapLoader.evict(id) end
end
-- kept as the pre-v2 name
MapLoader.clearCache = MapLoader.invalidateAll
-- the cached Map objects own the per-map TileRenderer instances, so a flush
-- that skipped this one would leave live SpriteBatches built from the old
-- search path (14 cache-invalidation contract, rows 1 and 3)
Assets.register(MapLoader.invalidateAll)
-- search path (14 cache-invalidation contract, rows 1 and 3). invalidateAll
-- deliberately does NOT release GPU (hot reload / live overworld); releaseAll
-- is the session-end path only.
Assets.register({
invalidate = MapLoader.invalidateAll,
release = MapLoader.releaseAll,
})
return MapLoader
+5
View File
@@ -228,6 +228,11 @@ function OverworldState.computeNeighbors(maps, rootId, hops, reachW, reachH)
return out
end
function OverworldState:exit()
self.map = nil
self.neighbors = nil
end
function OverworldState:enter(mapId, x, y, facing, opts)
Game = require("src.core.Game")
Game.overworld = self
+31
View File
@@ -5257,6 +5257,37 @@ function World:dropMapImages(mapId)
if self.connectionMaps then self.connectionMaps[mapId] = nil end
end
local function safeRelease(obj)
if obj and obj ~= false and obj.release then pcall(obj.release, obj) end
end
-- Eagerly free session-owned GPU caches. Assets.image-backed atlases are
-- nilled without release; unique bakes, strips, and roof composites are released.
function World:release()
if self.mapImages then
for _, img in pairs(self.mapImages) do safeRelease(img) end
self.mapImages = {}
end
if self.scrollStrips then
for _, strip in pairs(self.scrollStrips) do safeRelease(strip) end
self.scrollStrips = {}
end
safeRelease(self.tiltCanvas)
self.tiltCanvas = nil
if self.grassAtlases then
for _, atlas in pairs(self.grassAtlases) do safeRelease(atlas) end
self.grassAtlases = {}
end
if self.atlasCache then
for key, atlas in pairs(self.atlasCache) do
if key:find("|", 1, true) then safeRelease(atlas) end
end
self.atlasCache = {}
end
self.animQuads = nil
self.connectionMaps = nil
end
-- LoadMapAttributes' refill, for every map the session has edited. Neighbour
-- strips share the same buffer on the cart, so a connection crossing reloads
-- them too: this runs on any setMap, seamless or not.