mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-27 08:51:27 +02:00
51908faabf
bootGame's require("src.core.Game") was resolving to Gen2Compat's Game
facade after a Gold session, so the next Gen 1 boot got an empty proxy
whose rawget(load) is nil (iOS: "src.core.Game missing load after
reload"; Android: Game:load called on a nil value).
Two independent causes, both needed:
* callerIsMod decided "not under src/" meant "a mod", and main.lua is
not under src/, so the engine's own require was gated as a mod's.
Root chunks main.lua and conf.lua are now matched exactly, which a
mod's own mods/<id>/main.lua cannot collide with.
* devShim.generation was only ever set, never cleared, so the
generation Gold declared outlived Gold's session. Loader.endSession
drops it from SessionLifecycle.endMountedSession, and the facade
gate now requires generation == 2 rather than ~= 1.
Only platforms that return to the launcher in-process (Android, and iOS
since #1808) keep the shim alive across sessions; everywhere else the
process restart cleared it.
118 lines
4.5 KiB
Lua
118 lines
4.5 KiB
Lua
-- Central session lifecycle orchestrator. Subsystems register teardown hooks
|
|
-- at module load (Assets release/invalidate bus, process shutdown below); the
|
|
-- host only calls phase entry points.
|
|
--
|
|
-- Three tiers:
|
|
-- mount endMountedSession — GPU release + CacheFs/Data/Runtime/Assets
|
|
-- game endGameSession — audio + game:reset; workers stay alive
|
|
-- process endProcess — worker shutdown on real app exit (love.quit)
|
|
--
|
|
-- Hot reload (Assets.flush / installLoader) stays invalidate-only forever.
|
|
|
|
local SessionLifecycle = {}
|
|
|
|
local processShutdowns = {}
|
|
|
|
function SessionLifecycle.registerProcessShutdown(fn)
|
|
processShutdowns[#processShutdowns + 1] = fn
|
|
end
|
|
|
|
-- Drop CacheFs / Data / mod Runtime / Assets / LegacyCompat for one mounted
|
|
-- version session (save editor or game). GPU release runs before soft
|
|
-- invalidate via installLoader(nil).
|
|
function SessionLifecycle.endMountedSession(version)
|
|
local Assets = require("src.render.Assets")
|
|
if Assets.releaseSession then Assets.releaseSession() end
|
|
if version then
|
|
require("src.import.CacheFs").unmountVersion(version)
|
|
end
|
|
require("src.core.Data"):unloadGenerated()
|
|
local Runtime = require("src.mods.Runtime")
|
|
if Runtime.reset then Runtime.reset() end
|
|
if Assets.installLoader then Assets.installLoader(nil) end
|
|
local Loader = package.loaded["src.mods.Loader"]
|
|
if Loader and Loader.endSession then Loader.endSession() end
|
|
local okCompat, LegacyCompat = pcall(require, "src.mods.LegacyCompat")
|
|
if okCompat and LegacyCompat.reset then LegacyCompat.reset() end
|
|
end
|
|
|
|
-- Evict every save-editor module from package.loaded without a hardcoded
|
|
-- panel whitelist. Flat require names (App, Party, …) resolve under
|
|
-- tools/save-editor/; path-style keys may also appear.
|
|
local function flushEditorPackageLoaded()
|
|
local fs = love and love.filesystem
|
|
local function isEditorFlat(name)
|
|
if not (fs and fs.getInfo) then return false end
|
|
if name:find("[./]") then return false end
|
|
return fs.getInfo("tools/save-editor/" .. name .. ".lua") ~= nil
|
|
or fs.getInfo("tools/save-editor/panels/" .. name .. ".lua") ~= nil
|
|
end
|
|
for k in pairs(package.loaded) do
|
|
if type(k) == "string"
|
|
and (k:find("save%-editor", 1, false) or isEditorFlat(k)) then
|
|
package.loaded[k] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function SessionLifecycle.endEditorSession(opts)
|
|
opts = opts or {}
|
|
if opts.app and opts.app.unload then pcall(opts.app.unload) end
|
|
flushEditorPackageLoaded()
|
|
if opts.version then
|
|
SessionLifecycle.endMountedSession(opts.version)
|
|
end
|
|
end
|
|
|
|
-- EXIT GAME / intent_game before dropping Game. Stops audio and resets the
|
|
-- live game instance so map/GPU holders are gone before endMountedSession.
|
|
--
|
|
-- ChipAudio's worker stays alive across game sessions (see tier comment
|
|
-- above). shutdown() joins the thread and is process-exit only -- calling
|
|
-- it here on every Android EXIT GAME has been observed to leave the Gen1
|
|
-- Game singleton unbootable (Game.load nil) on the next Play of a version
|
|
-- already opened this process, while a debug APK with a different liblove
|
|
-- did not reproduce.
|
|
function SessionLifecycle.endGameSession(game)
|
|
pcall(function() require("src.core.Music").stop() end)
|
|
pcall(function() require("src.core.Sound").stop() end)
|
|
if package.loaded["src.core.ChipAudio"] then
|
|
pcall(package.loaded["src.core.ChipAudio"].stopMusic)
|
|
end
|
|
if package.loaded["src.core.DiscordPresence"] then
|
|
pcall(package.loaded["src.core.DiscordPresence"].shutdown)
|
|
end
|
|
if package.loaded["src.core.gen2.Clock"] then
|
|
pcall(package.loaded["src.core.gen2.Clock"].shutdown)
|
|
end
|
|
if package.loaded["src.net.Gen1Tls"] then
|
|
pcall(package.loaded["src.net.Gen1Tls"].shutdown)
|
|
end
|
|
if love.audio and love.audio.stop then
|
|
pcall(love.audio.stop)
|
|
end
|
|
pcall(function() require("src.render.SecondScreen").setEnabled(false) end)
|
|
|
|
if game and game.reset then
|
|
pcall(function() game:reset() end)
|
|
end
|
|
-- Gen1 Game is the module singleton. Always drop the cached module after
|
|
-- a session that owned it so the next bootGame require rebuilds a clean
|
|
-- table. A type(game.load) check is not enough: a wrong table parked in
|
|
-- package.loaded (e.g. with __index) can still look like it has load.
|
|
if game and package.loaded["src.core.Game"] == game then
|
|
package.loaded["src.core.Game"] = nil
|
|
end
|
|
|
|
local Input = require("src.core.Input")
|
|
local TouchControls = require("src.core.TouchControls")
|
|
Input:reset()
|
|
TouchControls:reset()
|
|
end
|
|
|
|
function SessionLifecycle.endProcess()
|
|
for _, fn in ipairs(processShutdowns) do pcall(fn) end
|
|
end
|
|
|
|
return SessionLifecycle
|