mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-26 07:21:22 +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 = require("src.core.Game2").new()
|
||||||
Game:load()
|
Game:load()
|
||||||
else
|
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()
|
Game:load()
|
||||||
if os.getenv("POKEPORT_AUTOPILOT") then
|
if os.getenv("POKEPORT_AUTOPILOT") then
|
||||||
autopilot = require("tests.autopilot")
|
autopilot = require("tests.autopilot")
|
||||||
|
|||||||
+14
-9
@@ -1387,8 +1387,13 @@ end
|
|||||||
|
|
||||||
-- Drop every session-owned field so the next Game:load() starts clean when
|
-- Drop every session-owned field so the next Game:load() starts clean when
|
||||||
-- the process returns to the launcher in-place (Android / intent_game).
|
-- 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()
|
function Game:reset()
|
||||||
if self.stack and self.stack.clear then
|
if self.stack and self.stack.clear then
|
||||||
pcall(function() self.stack:clear() end)
|
pcall(function() self.stack:clear() end)
|
||||||
@@ -1401,23 +1406,23 @@ function Game:reset()
|
|||||||
if canvas and canvas.release then pcall(canvas.release, canvas) end
|
if canvas and canvas.release then pcall(canvas.release, canvas) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if self.renderer and self.renderer.releaseCanvases then
|
if self.renderer then
|
||||||
pcall(function() self.renderer:releaseCanvases() end)
|
local release = self.renderer.releaseCanvases or self.renderer.release
|
||||||
|
if release then pcall(release, self.renderer) end
|
||||||
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 = {}
|
local keys = {}
|
||||||
for key, value in pairs(self) do
|
for key, value in pairs(self) do
|
||||||
if type(value) ~= "function" then
|
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
|
keys[#keys + 1] = key
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _, key in ipairs(keys) do
|
for _, key in ipairs(keys) do
|
||||||
self[key] = nil
|
self[key] = nil
|
||||||
end
|
end
|
||||||
|
self.SKIN_FAST_FORWARD = skinFast or 4
|
||||||
end
|
end
|
||||||
|
|
||||||
return Game
|
return Game
|
||||||
|
|||||||
+5
-7
@@ -2208,6 +2208,8 @@ end
|
|||||||
-- In-process return-to-launcher (Android / intent_game): drop session fields
|
-- 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.
|
-- 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.
|
-- 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()
|
function Game2:reset()
|
||||||
if self.stack and self.stack.clear then
|
if self.stack and self.stack.clear then
|
||||||
pcall(function() self.stack:clear() end)
|
pcall(function() self.stack:clear() end)
|
||||||
@@ -2220,17 +2222,13 @@ function Game2:reset()
|
|||||||
if canvas and canvas.release then pcall(canvas.release, canvas) end
|
if canvas and canvas.release then pcall(canvas.release, canvas) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if self.renderer and self.renderer.releaseCanvases then
|
if self.renderer then
|
||||||
pcall(function() self.renderer:releaseCanvases() end)
|
local release = self.renderer.releaseCanvases or self.renderer.release
|
||||||
|
if release then pcall(release, self.renderer) end
|
||||||
end
|
end
|
||||||
local keys = {}
|
local keys = {}
|
||||||
for key, value in pairs(self) do
|
for key, value in pairs(self) do
|
||||||
if type(value) ~= "function" then
|
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
|
keys[#keys + 1] = key
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -87,6 +87,13 @@ function SessionLifecycle.endGameSession(game)
|
|||||||
if game and game.reset then
|
if game and game.reset then
|
||||||
pcall(function() game:reset() end)
|
pcall(function() game:reset() end)
|
||||||
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 Input = require("src.core.Input")
|
||||||
local TouchControls = require("src.core.TouchControls")
|
local TouchControls = require("src.core.TouchControls")
|
||||||
|
|||||||
@@ -99,4 +99,19 @@ do
|
|||||||
check(type(Game.load) == "function", "Game:reset keeps methods")
|
check(type(Game.load) == "function", "Game:reset keeps methods")
|
||||||
end
|
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")
|
T.finish("android_exit_to_launcher_test")
|
||||||
|
|||||||
@@ -28,8 +28,15 @@ do
|
|||||||
Game.network = { live = true } -- future field: must not need a whitelist
|
Game.network = { live = true } -- future field: must not need a whitelist
|
||||||
Game.stack = StateStack
|
Game.stack = StateStack
|
||||||
Game.renderer = Renderer
|
Game.renderer = Renderer
|
||||||
|
Game.SKIN_FAST_FORWARD = 4
|
||||||
Renderer.canvas = love.graphics.newCanvas(8, 8)
|
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()
|
Game:reset()
|
||||||
|
|
||||||
check(type(Game.load) == "function", "Game:reset keeps methods")
|
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.stack == nil, "Game:reset clears stack reference")
|
||||||
check(Game.renderer == nil, "Game:reset clears renderer reference")
|
check(Game.renderer == nil, "Game:reset clears renderer reference")
|
||||||
check(StateStack:top() == nil, "Game:reset cleared the shared StateStack")
|
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
|
end
|
||||||
|
|
||||||
-- ---- Game2:reset releases world GPU and present canvases ------------------
|
-- ---- Game2:reset releases world GPU and present canvases ------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user