DramaticShapeVoxelMod archival mirror · read-only
lib/ImageCache.lua

ImageCache.lua

lib/ImageCache.lua2.5 KB100644475acc543a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
-- 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