Files

66 lines
2.5 KiB
Lua

-- Voxel world mode: decoded pixels, kept.
--
-- Assets.imageData is deliberately uncached upstream -- "pixel-level reads
-- resolve the same way but stay uncached: the caller keeps the derived
-- product" (src/render/Assets.lua) -- which is the right contract for the
-- flat renderer, whose one caller decodes a strip once and keeps the strip.
--
-- This mod is not that caller. It reads the same handful of images over and
-- over, from several places that do not know about each other:
--
-- * the tileset atlas, decoded by Structures (its own cache), by
-- TerrainAtlas twice (the SGB bake and the RED++ rebake), by
-- TerrainAtlas again to learn a tile's shades, and by GlassMask;
-- * the FLOWER FRAME files, decoded inside patch() -- which runs every
-- time the animation step turns over, about three times a second, for
-- as long as the map is on screen. That one is not a load cost at all,
-- it is a recurring per-second cost on the render thread, and it was
-- the single clearest waste the first profile turned up.
--
-- So: one table, keyed by the path as the CALLER gave it, holding the
-- decoded ImageData. Registered with Assets.invalidate so a hot reload
-- drops it alongside every other downstream cache.
--
-- The entries are never evicted by size. That is deliberate and bounded:
-- what lands here is tileset art and animation frames -- a few dozen small
-- images for a whole session, tens of kilobytes each -- not per-map bakes,
-- which have their own eviction in TerrainAtlas.setLive.
-- the mod namespace (see main.lua): V.require loads a sibling module
local V = ...
local Assets = require("src.render.Assets")
local Perf = V.require("Perf")
local ImageCache = {}
local cache = {}
-- The decoded pixels for `path`, or nil when it cannot be read.
--
-- `false` is cached for an unreadable path, so a missing or corrupt asset
-- costs one failed decode for the session rather than one per frame -- the
-- same sticky-failure shape the rest of this mod uses for GPU objects.
function ImageCache.get(path)
if not path then return nil end
local hit = cache[path]
if hit ~= nil then
Perf.count("imageCache.hit")
return hit or nil
end
local t0 = Perf.now()
local ok, data = pcall(Assets.imageData, path)
Perf.add("ImageCache.decode", t0)
Perf.count("imageCache.miss")
cache[path] = (ok and data) or false
return cache[path] or nil
end
function ImageCache.invalidate()
cache = {}
end
Assets.register(ImageCache.invalidate)
return ImageCache