mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
Merge pull request #1786 from 1Jamie/more-android-lc
fix(android): keep Gen1 Game.load after in-process EXIT GAME
This commit is contained in:
@@ -384,7 +384,14 @@ function bootGame(version, cartId)
|
||||
Game = require("src.core.Game2").new()
|
||||
Game:load()
|
||||
else
|
||||
Game = require("src.core.Game")
|
||||
-- Gen1 Game is a module singleton. In-process EXIT GAME resets it in
|
||||
-- place; if a prior teardown left load missing, rebuild from source.
|
||||
local gameMod = require("src.core.Game")
|
||||
if type(gameMod.load) ~= "function" then
|
||||
package.loaded["src.core.Game"] = nil
|
||||
gameMod = require("src.core.Game")
|
||||
end
|
||||
Game = gameMod
|
||||
Game:load()
|
||||
if os.getenv("POKEPORT_AUTOPILOT") then
|
||||
autopilot = require("tests.autopilot")
|
||||
|
||||
+14
-9
@@ -1387,8 +1387,13 @@ end
|
||||
|
||||
-- Drop every session-owned field so the next Game:load() starts clean when
|
||||
-- the process returns to the launcher in-place (Android / intent_game).
|
||||
-- main.lua must not guess field names: new systems (Game.network, …) are
|
||||
-- cleared automatically because only functions (methods) are kept.
|
||||
--
|
||||
-- Gen1 Game is a MODULE SINGLETON (methods live on this table). Never fan
|
||||
-- out arbitrary field:release() here: session fields can hold shared modules
|
||||
-- (Fetch, SyncClient, …) whose :release is a job-handle API, not instance
|
||||
-- teardown -- calling them as value:release() corrupts process state and has
|
||||
-- been observed to leave Game.load nil after EXIT GAME on Android.
|
||||
-- Explicit GPU owners are released below; everything else is just dropped.
|
||||
function Game:reset()
|
||||
if self.stack and self.stack.clear then
|
||||
pcall(function() self.stack:clear() end)
|
||||
@@ -1401,23 +1406,23 @@ function Game:reset()
|
||||
if canvas and canvas.release then pcall(canvas.release, canvas) end
|
||||
end
|
||||
end
|
||||
if self.renderer and self.renderer.releaseCanvases then
|
||||
pcall(function() self.renderer:releaseCanvases() end)
|
||||
if self.renderer then
|
||||
local release = self.renderer.releaseCanvases or self.renderer.release
|
||||
if release then pcall(release, self.renderer) end
|
||||
end
|
||||
-- Keep methods; clear every other field (including session scalars like
|
||||
-- speedOverride). Re-seed module constants afterward.
|
||||
local skinFast = self.SKIN_FAST_FORWARD
|
||||
local keys = {}
|
||||
for key, value in pairs(self) do
|
||||
if type(value) ~= "function" then
|
||||
if key ~= "world" and key ~= "renderer" and key ~= "_canvases" then
|
||||
if type(value) == "table" and value.release then
|
||||
pcall(value.release, value)
|
||||
end
|
||||
end
|
||||
keys[#keys + 1] = key
|
||||
end
|
||||
end
|
||||
for _, key in ipairs(keys) do
|
||||
self[key] = nil
|
||||
end
|
||||
self.SKIN_FAST_FORWARD = skinFast or 4
|
||||
end
|
||||
|
||||
return Game
|
||||
|
||||
+5
-7
@@ -2208,6 +2208,8 @@ end
|
||||
-- In-process return-to-launcher (Android / intent_game): drop session fields
|
||||
-- so a later Game2.new() + load is not sharing a live stack or mod loader.
|
||||
-- Methods live on the class table; pairs(self) only sees instance state.
|
||||
-- Same rule as Gen1: only release known GPU owners -- never fan out
|
||||
-- arbitrary field:release() (shared modules use :release as a handle API).
|
||||
function Game2:reset()
|
||||
if self.stack and self.stack.clear then
|
||||
pcall(function() self.stack:clear() end)
|
||||
@@ -2220,17 +2222,13 @@ function Game2:reset()
|
||||
if canvas and canvas.release then pcall(canvas.release, canvas) end
|
||||
end
|
||||
end
|
||||
if self.renderer and self.renderer.releaseCanvases then
|
||||
pcall(function() self.renderer:releaseCanvases() end)
|
||||
if self.renderer then
|
||||
local release = self.renderer.releaseCanvases or self.renderer.release
|
||||
if release then pcall(release, self.renderer) end
|
||||
end
|
||||
local keys = {}
|
||||
for key, value in pairs(self) do
|
||||
if type(value) ~= "function" then
|
||||
if key ~= "world" and key ~= "renderer" and key ~= "_canvases" then
|
||||
if type(value) == "table" and value.release then
|
||||
pcall(value.release, value)
|
||||
end
|
||||
end
|
||||
keys[#keys + 1] = key
|
||||
end
|
||||
end
|
||||
|
||||
@@ -87,6 +87,13 @@ function SessionLifecycle.endGameSession(game)
|
||||
if game and game.reset then
|
||||
pcall(function() game:reset() end)
|
||||
end
|
||||
-- Gen1 Game is the module singleton. If teardown left it without load,
|
||||
-- drop the cached module so the next require rebuilds a clean table
|
||||
-- (bootGame also guards this; doing it here keeps Play-again reliable).
|
||||
if game and type(game.load) ~= "function"
|
||||
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")
|
||||
|
||||
@@ -99,4 +99,19 @@ do
|
||||
check(type(Game.load) == "function", "Game:reset keeps methods")
|
||||
end
|
||||
|
||||
-- 6. Play-again after endGameSession: Game.load must still be callable
|
||||
-- (Android crash: main.lua bootGame → Game:load with load == nil)
|
||||
do
|
||||
local Game = require("src.core.Game")
|
||||
local SessionLifecycle = require("src.core.SessionLifecycle")
|
||||
Game.save = {}
|
||||
Game.stack = { clear = function() end }
|
||||
-- Mimic a shared net module parked on the singleton (handle-style release).
|
||||
Game.net = { release = function(id) end }
|
||||
SessionLifecycle.endGameSession(Game)
|
||||
local again = require("src.core.Game")
|
||||
check(type(again.load) == "function",
|
||||
"Play-again can call Game:load after endGameSession")
|
||||
end
|
||||
|
||||
T.finish("android_exit_to_launcher_test")
|
||||
|
||||
@@ -28,8 +28,15 @@ do
|
||||
Game.network = { live = true } -- future field: must not need a whitelist
|
||||
Game.stack = StateStack
|
||||
Game.renderer = Renderer
|
||||
Game.SKIN_FAST_FORWARD = 4
|
||||
Renderer.canvas = love.graphics.newCanvas(8, 8)
|
||||
|
||||
-- Handle-style :release (Fetch/SyncClient) must not run as instance teardown.
|
||||
local shared = {
|
||||
release = function(self) self.killed = true end,
|
||||
}
|
||||
Game.sharedNet = shared
|
||||
|
||||
Game:reset()
|
||||
|
||||
check(type(Game.load) == "function", "Game:reset keeps methods")
|
||||
@@ -40,6 +47,29 @@ do
|
||||
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")
|
||||
check(shared.killed ~= true,
|
||||
"Game:reset does not call handle-style :release on session fields")
|
||||
check(Game.SKIN_FAST_FORWARD == 4,
|
||||
"Game:reset preserves module scalars like SKIN_FAST_FORWARD")
|
||||
end
|
||||
|
||||
-- ---- endGameSession must leave Gen1 Game.load callable for Play-again ------
|
||||
do
|
||||
Game.save = { money = 1 }
|
||||
Game.stack = { clear = function() end }
|
||||
-- Poison pattern from the Android crash: a field whose :release is a
|
||||
-- job-handle API. Old reset called it as value:release() and could leave
|
||||
-- the singleton unbootable (Game.load nil → main.lua bootGame crash).
|
||||
local jobs = {}
|
||||
Game.linkFetch = {
|
||||
release = function(id) jobs[id] = nil end,
|
||||
}
|
||||
SessionLifecycle.endGameSession(Game)
|
||||
check(type(Game.load) == "function",
|
||||
"endGameSession leaves Game.load intact for the next bootGame")
|
||||
check(package.loaded["src.core.Game"] == Game
|
||||
or type((package.loaded["src.core.Game"] or {}).load) == "function",
|
||||
"Gen1 Game module remains require-able after endGameSession")
|
||||
end
|
||||
|
||||
-- ---- Game2:reset releases world GPU and present canvases ------------------
|
||||
|
||||
Reference in New Issue
Block a user