mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
Merge pull request #1200 from HimioneGranger/upstream-host-display-api
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 NxDisplay = require("src.core.NxDisplay")
|
||||
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.
|
||||
do
|
||||
@@ -430,6 +431,7 @@ function love.load(args)
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
HostDisplay.update(dt)
|
||||
SwitchDiagnostics.maybeFlush(false)
|
||||
-- NX only (no-op elsewhere): follow dock/undock without waiting for SDL.
|
||||
NxDisplay.sync()
|
||||
@@ -477,11 +479,27 @@ function love.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
if editorMode then return EditorApp.draw() end
|
||||
if TouchEditor then return TouchEditor.draw() end
|
||||
if Importer then return Importer:draw() end
|
||||
if editorMode then
|
||||
HostDisplay.beginFrame("editor", EditorApp)
|
||||
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
|
||||
|
||||
HostDisplay.beginFrame("game", Game)
|
||||
Game:draw()
|
||||
-- frame capture requested by a driver
|
||||
if Game.capturePath then
|
||||
@@ -496,6 +514,7 @@ function love.draw()
|
||||
end
|
||||
end)
|
||||
end
|
||||
HostDisplay.endFrame("game", Game)
|
||||
end
|
||||
|
||||
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