Merge pull request #1446 from 1Jamie/feat/launcher-fixes-and-patch-notes

This commit is contained in:
bryanthaboi
2026-08-16 20:33:11 -04:00
committed by GitHub
7 changed files with 344 additions and 290 deletions
+1 -1
View File
@@ -979,7 +979,7 @@ function LauncherView._updateControl(imp)
end end
-- idle / uptodate / error: offer a manual check, with no glow. -- idle / uptodate / error: offer a manual check, with no glow.
return status, Strings("Check for updates"), return status, Strings("Check for updates"),
function() pcall(imp.Check.start) end, false function() pcall(imp.Check.start, true) end, false
end end
-- ------------------------------------------------------------ game panel -- ------------------------------------------------------------ game panel
+4 -3
View File
@@ -155,11 +155,12 @@ local function drain()
end end
-- Begin (or, on a prior error, retry) an async check. Safe to call every frame: -- Begin (or, on a prior error, retry) an async check. Safe to call every frame:
-- once a check is in flight or has reached a terminal state it is a no-op. -- once a check is in flight or has reached a terminal state it is a no-op unless
function Check.start() -- force=true is passed (e.g. from an explicit button press).
function Check.start(force)
drain() drain()
if cache.status == "checking" or cache.status == "downloading" then return end if cache.status == "checking" or cache.status == "downloading" then return end
if requested and cache.status ~= "error" and cache.status ~= "idle" then return end if not force and requested and cache.status ~= "error" and cache.status ~= "idle" then return end
if not ensureWorker() then if not ensureWorker() then
cache = { status = "error", error = "background threads unavailable" } cache = { status = "error", error = "background threads unavailable" }
return return
+42 -7
View File
@@ -16,6 +16,10 @@ PatchNotes.FILES = {
"assets/PATCH_NOTES.md", "assets/PATCH_NOTES.md",
} }
PatchNotes.CACHE_FILES = {
"updates/notes_cache.json",
}
PatchNotes.REPO_FILES = { PatchNotes.REPO_FILES = {
"mobile/ios/app-repo.json", "mobile/ios/app-repo.json",
} }
@@ -48,6 +52,29 @@ local function readPath(path)
return nonempty(text) and text or nil return nonempty(text) and text or nil
end end
function PatchNotes.fromCache(engine)
for _, path in ipairs(PatchNotes.CACHE_FILES) do
local text = readPath(path)
if text then
local ok, doc = pcall(Json.decode, text)
if ok and type(doc) == "table" then
if engine and engine ~= "0.0.0-dev" then
if doc[engine] and nonempty(doc[engine]) then
return doc[engine], engine
end
else
for ver, notes in pairs(doc) do
if nonempty(notes) then
return notes, ver
end
end
end
end
end
end
return nil, nil
end
function PatchNotes.fromFile() function PatchNotes.fromFile()
for _, path in ipairs(PatchNotes.FILES) do for _, path in ipairs(PatchNotes.FILES) do
local text = readPath(path) local text = readPath(path)
@@ -88,6 +115,7 @@ function PatchNotes.fromRepo(engine)
return row.notes, row.version return row.notes, row.version
end end
end end
return nil, nil
end end
return list[1].notes, list[1].version return list[1].notes, list[1].version
end end
@@ -96,17 +124,24 @@ function PatchNotes.fromRepo(engine)
end end
function PatchNotes.body(Check) function PatchNotes.body(Check)
local notes, ver = PatchNotes.fromCheck(Check)
if notes then return notes, ver end
notes = PatchNotes.fromFile()
if notes then return notes, ver end
local Version = require("src.core.Version") local Version = require("src.core.Version")
local engine = (Version and Version.engine) or "?" local engine = (Version and Version.engine) or "?"
local notes, ver = PatchNotes.fromCheck(Check)
if notes and (engine == "0.0.0-dev" or ver == engine or ver == nil) then
return notes, ver or engine
end
notes, ver = PatchNotes.fromCache(engine)
if notes then return notes, ver end
notes = PatchNotes.fromFile()
if notes then return notes, engine end
notes, ver = PatchNotes.fromRepo(engine) notes, ver = PatchNotes.fromRepo(engine)
if notes then return notes, ver end if notes then return notes, ver end
return "No patch notes loaded yet for gen1recomp v" .. engine .. ".\n\n"
.. "They appear here after the launcher checks GitHub for the latest " return "Unable to fetch patch notes.", engine
.. "release.", engine
end end
return PatchNotes return PatchNotes
+25
View File
@@ -151,6 +151,28 @@ local function gatePasses(rel)
return not (info.minShell and info.minShell > shell) return not (info.minShell and info.minShell > shell)
end end
local function cacheNotes(ver, notes)
if not (ver and type(notes) == "string" and notes ~= "" and Json) then return end
if not (love and love.filesystem) then return end
pcall(function()
love.filesystem.createDirectory("updates")
local cachePath = "updates/notes_cache.json"
local existing = {}
if love.filesystem.getInfo and love.filesystem.getInfo(cachePath) then
local text = love.filesystem.read(cachePath)
if text then
local ok, doc = pcall(Json.decode, text)
if ok and type(doc) == "table" then existing = doc end
end
end
existing[ver] = notes
local ok, encoded = pcall(Json.encode, existing)
if ok and encoded then
love.filesystem.write(cachePath, encoded)
end
end)
end
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
-- check -- check
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
@@ -177,6 +199,9 @@ local function doCheck()
return return
end end
pending = rel pending = rel
if rel.version and type(rel.notes) == "string" and rel.notes ~= "" then
cacheNotes(rel.version, rel.notes)
end
-- Unstamped dev build: the working tree always looks "newer", so never -- Unstamped dev build: the working tree always looks "newer", so never
-- pester the developer with an update (contract item, Check design). -- pester the developer with an update (contract item, Check design).
+203 -279
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -68,6 +68,12 @@ do
"stashed notes name a release version") "stashed notes name a release version")
end end
do
local notes, ver = PatchNotes.fromRepo("999.999.999")
eq(notes, nil, "fromRepo returns nil when a specific engine version is missing")
eq(ver, nil, "fromRepo version is nil when missing")
end
do do
local f = assert(io.open("mobile/ios/app-repo.json", "rb")) local f = assert(io.open("mobile/ios/app-repo.json", "rb"))
local list = PatchNotes.parseRepo(f:read("*a")) local list = PatchNotes.parseRepo(f:read("*a"))
@@ -78,6 +84,15 @@ do
eq(notes, list[2].notes, "fromRepo returns that version's notes") eq(notes, list[2].notes, "fromRepo returns that version's notes")
end end
do
local oldVersion = package.loaded["src.core.Version"]
package.loaded["src.core.Version"] = { engine = "999.999.999" }
local body, ver = PatchNotes.body(nil)
eq(body, "Unable to fetch patch notes.", "returns Unable to fetch patch notes when version is uncached and unlisted")
eq(ver, "999.999.999", "returns the requested engine version")
package.loaded["src.core.Version"] = oldVersion
end
imp._appPatchNotes = true imp._appPatchNotes = true
local modal = drawAndCapture(imp) local modal = drawAndCapture(imp)
check(modal:find("Patch notes", 1, true) ~= nil, "the modal titles itself") check(modal:find("Patch notes", 1, true) ~= nil, "the modal titles itself")
@@ -0,0 +1,54 @@
-- Source file LuaJIT limits gate.
-- Verifies that every game engine source file compiles cleanly under
-- LuaJIT without exceeding LuaJIT's strict 200 local variables per-scope limit,
-- 60 upvalue limit, or bytecode compiler limits.
-- luajit tests/engine/luajit_source_limits_test.lua
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
-- Find all .lua files in a directory recursively.
local function findLuaFiles(dir, out)
out = out or {}
local p = io.popen("find " .. dir .. " -type f -name '*.lua'")
if p then
for line in p:lines() do
out[#out + 1] = line
end
p:close()
end
table.sort(out)
return out
end
local files = findLuaFiles("src")
findLuaFiles("tools/save-editor", files)
files[#files + 1] = "main.lua"
files[#files + 1] = "conf.lua"
check(#files > 50, "discovered project source files (found " .. tostring(#files) .. ")")
for _, path in ipairs(files) do
local f = assert(io.open(path, "rb"), "could not open " .. path)
local source = f:read("*a")
f:close()
-- Compile through LuaJIT loadstring: detects 'main function has more than 200 local variables'
-- or 'function has more than 200 local variables' across any function scope in the file.
local chunk, err = loadstring(source, "@" .. path)
check(chunk ~= nil, path .. " compiles under LuaJIT: " .. tostring(err))
end
-- Meta-test: prove that exceeding 200 locals fails the gate
do
local overflowLocals = {}
for i = 1, 201 do overflowLocals[i] = "v" .. i end
local badCode = "local " .. table.concat(overflowLocals, ", ")
local chunk, err = loadstring(badCode, "@overflow_test.lua")
check(chunk == nil, "LuaJIT strictly rejects chunks exceeding 200 locals")
check(tostring(err):find("200 local variables", 1, true) ~= nil, "error message specifies 200 local variable limit")
end
T.finish("luajit_source_limits")