mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
feat(host): add optional display lifecycle backend
This commit is contained in:
@@ -14,6 +14,7 @@ local SwitchDiagnostics = require("src.debug.SwitchDiagnostics")
|
|||||||
local LaunchOptions = require("src.core.LaunchOptions")
|
local LaunchOptions = require("src.core.LaunchOptions")
|
||||||
local NxDisplay = require("src.core.NxDisplay")
|
local NxDisplay = require("src.core.NxDisplay")
|
||||||
local PlatformHooks = require("src.core.PlatformHooks")
|
local PlatformHooks = require("src.core.PlatformHooks")
|
||||||
|
local HostDisplay = require("src.core.HostDisplay")
|
||||||
|
|
||||||
-- Lua errors: persist a redacted trace in the save dir and surface a hint.
|
-- Lua errors: persist a redacted trace in the save dir and surface a hint.
|
||||||
do
|
do
|
||||||
@@ -425,6 +426,7 @@ function love.load(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function love.update(dt)
|
function love.update(dt)
|
||||||
|
HostDisplay.update(dt)
|
||||||
SwitchDiagnostics.maybeFlush(false)
|
SwitchDiagnostics.maybeFlush(false)
|
||||||
-- NX only (no-op elsewhere): follow dock/undock without waiting for SDL.
|
-- NX only (no-op elsewhere): follow dock/undock without waiting for SDL.
|
||||||
NxDisplay.sync()
|
NxDisplay.sync()
|
||||||
@@ -472,11 +474,27 @@ function love.update(dt)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function love.draw()
|
function love.draw()
|
||||||
if editorMode then return EditorApp.draw() end
|
if editorMode then
|
||||||
if TouchEditor then return TouchEditor.draw() end
|
HostDisplay.beginFrame("editor", EditorApp)
|
||||||
if Importer then return Importer:draw() end
|
local result = EditorApp.draw()
|
||||||
|
HostDisplay.endFrame("editor", EditorApp)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
if TouchEditor then
|
||||||
|
HostDisplay.beginFrame("touch_editor", TouchEditor)
|
||||||
|
local result = TouchEditor.draw()
|
||||||
|
HostDisplay.endFrame("touch_editor", TouchEditor)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
if Importer then
|
||||||
|
HostDisplay.beginFrame("launcher", Importer)
|
||||||
|
local result = Importer:draw()
|
||||||
|
HostDisplay.endFrame("launcher", Importer)
|
||||||
|
return result
|
||||||
|
end
|
||||||
if not Game then return end
|
if not Game then return end
|
||||||
|
|
||||||
|
HostDisplay.beginFrame("game", Game)
|
||||||
Game:draw()
|
Game:draw()
|
||||||
-- frame capture requested by a driver
|
-- frame capture requested by a driver
|
||||||
if Game.capturePath then
|
if Game.capturePath then
|
||||||
@@ -491,6 +509,7 @@ function love.draw()
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
HostDisplay.endFrame("game", Game)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.keypressed(key, scancode, isrepeat)
|
function love.keypressed(key, scancode, isrepeat)
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
-- Optional native-host display lifecycle.
|
||||||
|
--
|
||||||
|
-- The engine always owns simulation and drawing. A packaged host may install
|
||||||
|
-- one backend to observe per-frame updates and prepare/finalize a render target
|
||||||
|
-- around an otherwise unchanged draw. With no backend installed every method
|
||||||
|
-- is a no-op, which is the normal desktop and mobile path.
|
||||||
|
--
|
||||||
|
-- Backend methods are optional:
|
||||||
|
-- backend:update(dt)
|
||||||
|
-- backend:beginFrame(kind, subject)
|
||||||
|
-- backend:endFrame(kind, subject)
|
||||||
|
--
|
||||||
|
-- `kind` is "editor", "touch_editor", "launcher", or "game". `subject` is
|
||||||
|
-- the object whose existing draw method runs between beginFrame and endFrame.
|
||||||
|
local HostDisplay = {}
|
||||||
|
|
||||||
|
local backend
|
||||||
|
|
||||||
|
function HostDisplay.setBackend(value)
|
||||||
|
if value ~= nil and type(value) ~= "table" then
|
||||||
|
error("host display backend must be a table or nil", 2)
|
||||||
|
end
|
||||||
|
backend = value
|
||||||
|
end
|
||||||
|
|
||||||
|
function HostDisplay.update(dt)
|
||||||
|
local fn = backend and backend.update
|
||||||
|
if fn then return fn(backend, dt) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function HostDisplay.beginFrame(kind, subject)
|
||||||
|
local fn = backend and backend.beginFrame
|
||||||
|
if fn then return fn(backend, kind, subject) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function HostDisplay.endFrame(kind, subject)
|
||||||
|
local fn = backend and backend.endFrame
|
||||||
|
if fn then return fn(backend, kind, subject) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return HostDisplay
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
-- Optional native-host display lifecycle. The default path is inert; a fake
|
||||||
|
-- backend proves callback order and arguments without graphics or a ROM.
|
||||||
|
-- luajit tests/engine/host_display_test.lua
|
||||||
|
|
||||||
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||||
|
|
||||||
|
local T = require("tests.harness")
|
||||||
|
local check, eq = T.check, T.eq
|
||||||
|
local HostDisplay = require("src.core.HostDisplay")
|
||||||
|
|
||||||
|
-- Vanilla: no backend, no state requirement, and no invented return value.
|
||||||
|
HostDisplay.setBackend(nil)
|
||||||
|
eq(HostDisplay.update(1 / 60), nil, "default update is a no-op")
|
||||||
|
eq(HostDisplay.beginFrame("game", {}), nil, "default beginFrame is a no-op")
|
||||||
|
eq(HostDisplay.endFrame("game", {}), nil, "default endFrame is a no-op")
|
||||||
|
|
||||||
|
-- Bad installation fails at the boundary instead of producing a later frame
|
||||||
|
-- error whose source is difficult for a native host to diagnose.
|
||||||
|
local ok, err = pcall(HostDisplay.setBackend, function() end)
|
||||||
|
check(not ok, "non-table backend is rejected")
|
||||||
|
check(tostring(err):find("table or nil", 1, true) ~= nil,
|
||||||
|
"backend type error explains the accepted shape")
|
||||||
|
|
||||||
|
local calls = {}
|
||||||
|
local subject = { tag = "launcher-instance" }
|
||||||
|
local fake = {}
|
||||||
|
function fake:update(dt)
|
||||||
|
calls[#calls + 1] = { "update", self, dt }
|
||||||
|
return "updated"
|
||||||
|
end
|
||||||
|
function fake:beginFrame(kind, gotSubject)
|
||||||
|
calls[#calls + 1] = { "begin", self, kind, gotSubject }
|
||||||
|
return "begun"
|
||||||
|
end
|
||||||
|
function fake:endFrame(kind, gotSubject)
|
||||||
|
calls[#calls + 1] = { "end", self, kind, gotSubject }
|
||||||
|
return "ended"
|
||||||
|
end
|
||||||
|
|
||||||
|
HostDisplay.setBackend(fake)
|
||||||
|
eq(HostDisplay.update(0.25), "updated", "update return is forwarded")
|
||||||
|
eq(HostDisplay.beginFrame("launcher", subject), "begun",
|
||||||
|
"beginFrame return is forwarded")
|
||||||
|
eq(HostDisplay.endFrame("launcher", subject), "ended",
|
||||||
|
"endFrame return is forwarded")
|
||||||
|
eq(#calls, 3, "each lifecycle callback fires exactly once")
|
||||||
|
eq(calls[1][1], "update", "update is first")
|
||||||
|
eq(calls[1][2], fake, "update receives the backend as self")
|
||||||
|
eq(calls[1][3], 0.25, "update receives dt")
|
||||||
|
eq(calls[2][1], "begin", "beginFrame precedes endFrame")
|
||||||
|
eq(calls[2][3], "launcher", "beginFrame receives the frame kind")
|
||||||
|
eq(calls[2][4], subject, "beginFrame receives the drawn subject")
|
||||||
|
eq(calls[3][1], "end", "endFrame is last")
|
||||||
|
eq(calls[3][3], "launcher", "endFrame receives the frame kind")
|
||||||
|
eq(calls[3][4], subject, "endFrame receives the drawn subject")
|
||||||
|
|
||||||
|
-- Every callback is optional. Replacing and clearing a backend must not retain
|
||||||
|
-- callbacks from the old host across a restart or test process.
|
||||||
|
local partialCalls = 0
|
||||||
|
HostDisplay.setBackend({
|
||||||
|
endFrame = function() partialCalls = partialCalls + 1 end,
|
||||||
|
})
|
||||||
|
eq(HostDisplay.update(1), nil, "missing optional update remains a no-op")
|
||||||
|
eq(HostDisplay.beginFrame("editor", {}), nil,
|
||||||
|
"missing optional beginFrame remains a no-op")
|
||||||
|
HostDisplay.endFrame("editor", {})
|
||||||
|
eq(partialCalls, 1, "present optional callback still runs")
|
||||||
|
|
||||||
|
HostDisplay.setBackend(nil)
|
||||||
|
HostDisplay.endFrame("game", {})
|
||||||
|
eq(partialCalls, 1, "clearing backend detaches old callbacks")
|
||||||
|
|
||||||
|
T.finish("host display")
|
||||||
Reference in New Issue
Block a user