mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 20:20:19 +02:00
graphics and stutters part 1
This commit is contained in:
+72
-5
@@ -2,6 +2,13 @@
|
||||
-- data, cached by map id. The cache is keyed so a mod that patches one
|
||||
-- map record after boot (or the dev-mode hot reload) can drop just that
|
||||
-- entry instead of every map's SpriteBatches.
|
||||
--
|
||||
-- The resident set is trimmed LRU (trim) so exploring a large world never
|
||||
-- holds every visited map's GPU objects at once. Maps are built on demand
|
||||
-- and cheaply: a map's tile layer draws windowed to the camera (see
|
||||
-- TileRenderer), so there is no per-map batch to construct up front and thus
|
||||
-- nothing to stream -- OverworldState:rebuildNeighbors just loads each
|
||||
-- neighbor directly.
|
||||
|
||||
local Assets = require("src.render.Assets")
|
||||
local Map = require("src.world.Map")
|
||||
@@ -10,9 +17,20 @@ local TileRenderer = require("src.render.TileRenderer")
|
||||
local MapLoader = {}
|
||||
|
||||
local cache = {}
|
||||
-- mapId -> monotonic access stamp, for LRU eviction
|
||||
local lru = {}
|
||||
local accessSeq = 0
|
||||
-- max map renderers kept resident. Comfortably above the largest
|
||||
-- current+neighbor set (~15 in dense overworld), so protected maps are
|
||||
-- never the ones evicted; this only caps the lingering trail behind you.
|
||||
local RESIDENT_CAP = 32
|
||||
|
||||
function MapLoader.load(data, mapId)
|
||||
if cache[mapId] then return cache[mapId] end
|
||||
local function touch(mapId)
|
||||
accessSeq = accessSeq + 1
|
||||
lru[mapId] = accessSeq
|
||||
end
|
||||
|
||||
local function build(data, mapId)
|
||||
local def = data.maps[mapId]
|
||||
assert(def, "unknown map: " .. tostring(mapId) ..
|
||||
" (not in the maps registry)")
|
||||
@@ -25,26 +43,75 @@ function MapLoader.load(data, mapId)
|
||||
local map = Map.new(def, tilesetDef)
|
||||
map.renderer = TileRenderer.new(map, data)
|
||||
cache[mapId] = map
|
||||
touch(mapId)
|
||||
return map
|
||||
end
|
||||
|
||||
function MapLoader.load(data, mapId)
|
||||
local m = cache[mapId]
|
||||
if m then touch(mapId); return m end
|
||||
return build(data, mapId)
|
||||
end
|
||||
|
||||
-- the live instance for a map id, or nil when it has not been loaded;
|
||||
-- callers that must not build a map (invalidation, tests) use this
|
||||
function MapLoader.cached(mapId)
|
||||
return cache[mapId]
|
||||
local m = cache[mapId]
|
||||
if m then touch(mapId) end
|
||||
return m
|
||||
end
|
||||
|
||||
-- evict one resident map, releasing its renderer's GPU objects. Callers
|
||||
-- must ensure the map is not the current map and not drawn as a connected
|
||||
-- strip (nothing live may hold its renderer) -- MapLoader.trim guarantees
|
||||
-- this via its `protected` set.
|
||||
function MapLoader.evict(mapId)
|
||||
local m = cache[mapId]
|
||||
if not m then return false end
|
||||
cache[mapId] = nil
|
||||
lru[mapId] = nil
|
||||
local r = m.renderer
|
||||
if r and r.release then pcall(r.release, r) end
|
||||
return true
|
||||
end
|
||||
|
||||
-- keep the resident renderer set bounded. `protected` (mapId -> true) is
|
||||
-- never evicted (the current map and everything drawn as a connected
|
||||
-- strip); the rest is trimmed least-recently-used down to RESIDENT_CAP.
|
||||
function MapLoader.trim(protected)
|
||||
local n = 0
|
||||
for _ in pairs(cache) do n = n + 1 end
|
||||
if n <= RESIDENT_CAP then return end
|
||||
local ids = {}
|
||||
for id in pairs(cache) do
|
||||
if not (protected and protected[id]) then ids[#ids + 1] = id end
|
||||
end
|
||||
table.sort(ids, function(a, b) return (lru[a] or 0) < (lru[b] or 0) end)
|
||||
local over = n - RESIDENT_CAP
|
||||
for _, id in ipairs(ids) do
|
||||
if over <= 0 then break end
|
||||
MapLoader.evict(id)
|
||||
over = over - 1
|
||||
end
|
||||
end
|
||||
|
||||
-- drop one map so the next load re-reads its record and rebuilds its
|
||||
-- renderer. Callers holding the old instance keep it -- OverworldState
|
||||
-- re-points self.map itself (WorldAPI:invalidateMap).
|
||||
-- renderer. Deliberately does NOT release the old renderer: callers
|
||||
-- holding the old instance keep drawing it until they re-point themselves
|
||||
-- (OverworldState re-points self.map / self.neighbors via setMap /
|
||||
-- rebuildNeighbors), so releasing here would free a batch still in use.
|
||||
-- The orphaned instance is reclaimed by GC; MapLoader.evict is the path
|
||||
-- that releases eagerly, and it only runs on maps nothing live holds.
|
||||
function MapLoader.invalidate(mapId)
|
||||
local had = cache[mapId] ~= nil
|
||||
cache[mapId] = nil
|
||||
lru[mapId] = nil
|
||||
return had
|
||||
end
|
||||
|
||||
function MapLoader.invalidateAll()
|
||||
cache = {}
|
||||
lru = {}
|
||||
end
|
||||
|
||||
-- kept as the pre-v2 name
|
||||
|
||||
Reference in New Issue
Block a user