mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +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.
179 lines
6.8 KiB
Lua
179 lines
6.8 KiB
Lua
-- In-process launcher session teardown: Game:reset, Renderer canvas release,
|
|
-- SessionLifecycle mount/game tiers, and editor package.loaded discovery flush.
|
|
-- luajit tests/engine/launcher_session_teardown_test.lua
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
love = love or require("tests.love_stub")
|
|
|
|
local T = require("tests.harness")
|
|
local check, eq = T.check, T.eq
|
|
|
|
local Runtime = require("src.mods.Runtime")
|
|
local Assets = require("src.render.Assets")
|
|
local LegacyCompat = require("src.mods.LegacyCompat")
|
|
local Game = require("src.core.Game")
|
|
local Game2 = require("src.core.Game2")
|
|
local Renderer = require("src.render.Renderer")
|
|
local StateStack = require("src.core.StateStack")
|
|
local SessionLifecycle = require("src.core.SessionLifecycle")
|
|
local MapLoader = require("src.world.MapLoader")
|
|
local World = require("src.world.gen2.World")
|
|
|
|
-- ---- Game:reset drops instance state, keeps methods ----------------------
|
|
do
|
|
StateStack:init()
|
|
StateStack:push({ name = "stale" })
|
|
Game.mods = { id = "orphan" }
|
|
Game.save = { money = 1 }
|
|
Game.network = { live = true } -- future field: must not need a whitelist
|
|
Game.stack = StateStack
|
|
Game.renderer = Renderer
|
|
Renderer.canvas = love.graphics.newCanvas(8, 8)
|
|
|
|
Game:reset()
|
|
|
|
check(type(Game.load) == "function", "Game:reset keeps methods")
|
|
check(type(Game.reset) == "function", "Game:reset keeps itself")
|
|
check(Game.mods == nil, "Game:reset clears mods")
|
|
check(Game.save == nil, "Game:reset clears save")
|
|
check(Game.network == nil, "Game:reset clears arbitrary future fields")
|
|
check(Game.stack == nil, "Game:reset clears stack reference")
|
|
check(Game.renderer == nil, "Game:reset clears renderer reference")
|
|
check(StateStack:top() == nil, "Game:reset cleared the shared StateStack")
|
|
end
|
|
|
|
-- ---- Game2:reset releases world GPU and present canvases ------------------
|
|
do
|
|
local game2 = Game2.new()
|
|
local canvas = love.graphics.newCanvas(4, 4)
|
|
game2.world = World.new({})
|
|
game2.world.mapImages = { ["MAP|d|1"] = canvas }
|
|
game2._canvases = { love.graphics.newCanvas(8, 8) }
|
|
game2:reset()
|
|
check(canvas.released == true, "Game2:reset releases World mapImages")
|
|
check(game2.world == nil, "Game2:reset clears world reference")
|
|
check(game2._canvases == nil, "Game2:reset clears _canvases")
|
|
end
|
|
|
|
-- ---- World:release frees owned GPU caches --------------------------------
|
|
do
|
|
local world = World.new({})
|
|
local bake = love.graphics.newCanvas(16, 16)
|
|
local strip = love.graphics.newCanvas(8, 64)
|
|
local tilt = love.graphics.newCanvas(160, 144)
|
|
world.mapImages = { ["R1|DAY|1"] = bake }
|
|
world.scrollStrips = { ["TS|1|0,0"] = strip }
|
|
world.tiltCanvas = tilt
|
|
world:release()
|
|
check(bake.released == true, "World:release frees map bake canvases")
|
|
check(strip.released == true, "World:release frees scroll strips")
|
|
check(tilt.released == true, "World:release frees tiltCanvas")
|
|
eq(next(world.mapImages), nil, "World:release clears mapImages table")
|
|
end
|
|
|
|
-- ---- Renderer:init releases prior canvases before realloc ----------------
|
|
do
|
|
local first = love.graphics.newCanvas(16, 16)
|
|
Renderer.canvas = first
|
|
Renderer.battleHUDCanvas = love.graphics.newCanvas(16, 16)
|
|
Renderer.worldCanvas = love.graphics.newCanvas(16, 16)
|
|
Renderer.uprightCanvas = love.graphics.newCanvas(16, 16)
|
|
Renderer:init()
|
|
check(first.released == true,
|
|
"Renderer:init Object:release()s the previous primary canvas")
|
|
check(Renderer.canvas ~= nil and Renderer.canvas ~= first,
|
|
"Renderer:init allocates a fresh primary canvas")
|
|
check(Renderer.canvas.released ~= true,
|
|
"the new primary canvas is not released")
|
|
local second = Renderer.canvas
|
|
Renderer:init()
|
|
check(second.released == true,
|
|
"a second Renderer:init releases the canvas from the prior init")
|
|
end
|
|
|
|
-- ---- SessionLifecycle.endMountedSession (closeEditor / returnToLauncher) --
|
|
do
|
|
Runtime.install({ emit = function() end }, { call = function() end }, { "e" })
|
|
Assets.installLoader({
|
|
overrideOrder = function() return {} end,
|
|
derivedPath = function() return nil end,
|
|
})
|
|
LegacyCompat.reports = { some_mod = { order = {} } }
|
|
|
|
SessionLifecycle.endMountedSession(nil)
|
|
|
|
check(Runtime.errors == nil, "endMountedSession clears Runtime.errors")
|
|
check(Assets.loader == nil, "endMountedSession clears Assets.loader")
|
|
eq(next(LegacyCompat.reports), nil, "endMountedSession clears LegacyCompat.reports")
|
|
end
|
|
|
|
-- ---- releaseSession empties MapLoader via releaseAll, not flush -------------
|
|
do
|
|
local data = {
|
|
maps = { T1 = { id = "T1", tileset = "TS", width = 1, height = 1,
|
|
blocks = { 0 }, borderBlock = 0, objects = {}, warps = {}, signs = {} } },
|
|
tilesets = { TS = { id = "TS", image = "assets/generated/t.png",
|
|
walkable = {}, blocks = { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } },
|
|
tilesPerRow = 1 } },
|
|
}
|
|
MapLoader.load(data, "T1")
|
|
check(MapLoader.cached("T1") ~= nil, "MapLoader holds a map before release")
|
|
Assets.releaseSession()
|
|
check(MapLoader.cached("T1") == nil,
|
|
"releaseSession evicts MapLoader via releaseAll")
|
|
end
|
|
|
|
-- ---- Editor package.loaded discovery flush via endEditorSession -----------
|
|
do
|
|
love.filesystem.write("tools/save-editor/App.lua", "return {}")
|
|
love.filesystem.write("tools/save-editor/panels/NewPanel.lua", "return {}")
|
|
package.loaded["App"] = { stale = true }
|
|
package.loaded["NewPanel"] = { stale = true }
|
|
package.loaded["src.core.Data"] = package.loaded["src.core.Data"]
|
|
|
|
SessionLifecycle.endEditorSession({ version = nil, app = nil })
|
|
|
|
check(package.loaded["App"] == nil, "endEditorSession drops flat App")
|
|
check(package.loaded["NewPanel"] == nil,
|
|
"endEditorSession drops a new panel without a hardcoded list")
|
|
check(package.loaded["src.core.Data"] ~= nil,
|
|
"endEditorSession leaves engine modules alone")
|
|
love.filesystem.remove("tools/save-editor/App.lua")
|
|
love.filesystem.remove("tools/save-editor/panels/NewPanel.lua")
|
|
end
|
|
|
|
-- ---- Fetch shutdown clears ready so Play-again can respawn workers ---------
|
|
do
|
|
local Fetch = require("src.net.Fetch")
|
|
local spawnAttempts = 0
|
|
love.thread = love.thread or {}
|
|
local savedNewThread = love.thread.newThread
|
|
local savedGetChannel = love.thread.getChannel
|
|
love.thread.getChannel = function()
|
|
return {
|
|
clear = function() end,
|
|
push = function() end,
|
|
pop = function() return nil end,
|
|
demand = function() end,
|
|
}
|
|
end
|
|
love.thread.newThread = function()
|
|
spawnAttempts = spawnAttempts + 1
|
|
return {
|
|
start = function() end,
|
|
wait = function() end,
|
|
getError = function() return nil end,
|
|
}
|
|
end
|
|
Fetch.available()
|
|
local afterFirst = spawnAttempts
|
|
Fetch.shutdown()
|
|
Fetch.available()
|
|
check(spawnAttempts > afterFirst,
|
|
"Fetch.available retries worker spawn after shutdown (ready=nil)")
|
|
love.thread.newThread = savedNewThread
|
|
love.thread.getChannel = savedGetChannel
|
|
end
|
|
|
|
T.finish("launcher_session_teardown_test")
|