mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
118 lines
3.3 KiB
Lua
118 lines
3.3 KiB
Lua
-- The live extension-point catalog: every registry, every event name the
|
|
-- engine emits, every hook name it calls.
|
|
--
|
|
-- Registries come from Schemas.REGISTRIES. Events and hooks are read back
|
|
-- out of the source rather than from a hand-kept list, because a hand-kept
|
|
-- list is exactly the thing that drifts -- adding a Runtime.emit and
|
|
-- forgetting the catalog entry is how a seam ships untested. The
|
|
-- parity-gate meta-test (tests/engine/gate_meta_coverage.lua) walks these
|
|
-- three sets, so a new seam is in the coverage requirement the moment its
|
|
-- call site exists.
|
|
|
|
local Schemas = require("src.mods.Schemas")
|
|
local FsIo = require("tests.fs_io")
|
|
|
|
local Catalog = {}
|
|
|
|
local function luaFilesUnder(dir)
|
|
return FsIo.luaFilesUnder(dir)
|
|
end
|
|
|
|
local function scan(dirs, patterns)
|
|
local found = {}
|
|
for _, dir in ipairs(dirs) do
|
|
for _, path in ipairs(luaFilesUnder(dir)) do
|
|
local handle = io.open(path, "r")
|
|
if handle then
|
|
local body = handle:read("*a")
|
|
handle:close()
|
|
for _, pattern in ipairs(patterns) do
|
|
for name in body:gmatch(pattern) do
|
|
local list = found[name] or {}
|
|
found[name] = list
|
|
list[#list + 1] = path
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return found
|
|
end
|
|
|
|
local function sortedKeys(map)
|
|
local keys = {}
|
|
for key in pairs(map) do keys[#keys + 1] = key end
|
|
table.sort(keys)
|
|
return keys
|
|
end
|
|
|
|
local registries, events, hooks, eventSites, hookSites
|
|
|
|
function Catalog.registries()
|
|
if not registries then registries = sortedKeys(Schemas.REGISTRIES) end
|
|
return registries
|
|
end
|
|
|
|
-- Runtime.emit is the engine's channel; a bare bus:emit inside src counts
|
|
-- too (the loader emits mods.loaded straight off its own bus)
|
|
function Catalog.events()
|
|
if not events then
|
|
eventSites = scan({ "src" }, {
|
|
'Runtime%.emit%("([%w%._]+)"',
|
|
'events:emit%("([%w%._]+)"',
|
|
})
|
|
events = sortedKeys(eventSites)
|
|
end
|
|
return events
|
|
end
|
|
|
|
-- A hook whose Runtime.call lives in a shared raiser rather than at the site
|
|
-- that decides the value. Gold's trainer art is not in field.playerPics, so
|
|
-- its screens resolve their own path and hand it to Sprites.playerPic
|
|
-- (src/pokemon/Sprites.lua); those callers are player.sprite sites and the
|
|
-- generation gate has to see them as such.
|
|
local INDIRECT_HOOKS = {
|
|
["player.sprite"] = "playerPic%(",
|
|
}
|
|
|
|
function Catalog.hooks()
|
|
if not hooks then
|
|
hookSites = scan({ "src" }, {
|
|
'Runtime%.call%("([%w%._]+)"',
|
|
'hooks:call%("([%w%._]+)"',
|
|
})
|
|
for name, pattern in pairs(INDIRECT_HOOKS) do
|
|
local list = hookSites[name] or {}
|
|
hookSites[name] = list
|
|
for _, path in ipairs(luaFilesUnder("src")) do
|
|
local handle = io.open(path, "r")
|
|
if handle then
|
|
local body = handle:read("*a")
|
|
handle:close()
|
|
if body:match(pattern) then list[#list + 1] = path end
|
|
end
|
|
end
|
|
end
|
|
hooks = sortedKeys(hookSites)
|
|
end
|
|
return hooks
|
|
end
|
|
|
|
function Catalog.eventSites(name)
|
|
Catalog.events()
|
|
return eventSites[name] or {}
|
|
end
|
|
|
|
function Catalog.hookSites(name)
|
|
Catalog.hooks()
|
|
return hookSites[name] or {}
|
|
end
|
|
|
|
-- mods may only emit under "mod.<id>."; those are not engine seams and
|
|
-- carry no coverage requirement
|
|
function Catalog.isModEvent(name)
|
|
return name:sub(1, 4) == "mod."
|
|
end
|
|
|
|
return Catalog
|