enum ImageStore
Swift
enum ImageStore
Path → UIImage cache with a VRAM budget, async decode, and LRU eviction.
UI thread only, except for the decode itself.
Why the budget evicts and visibility does not. The obvious policy — drop a poster once it scrolls off — thrashes: reverse direction and every image you just discarded has to be fetched and decoded again. Visibility belongs in priority; a byte budget belongs in lifetime. An entry is only evicted when the cache is over budget, and never on the frame it was drawn, so a grid larger than the budget degrades to re-decoding rather than flickering mid-frame.
Sources/LavaUI/Image.swift:79
Properties
nonisolated(unsafe) static var budgetBytes: Int
Bytes of decoded image the cache will hold before evicting. Generous by default: a screenful of covers is a few MB, and evicting sooner than necessary just means decoding again.
Sources/LavaUI/Image.swift:105
static var count: Int { get }
Not documented.
Sources/LavaUI/Image.swift:328
static var residentByteCount: Int { get }
Bytes currently held, for tests and diagnostics.
Sources/LavaUI/Image.swift:327
Methods
static func contentKey(data: [UInt8], maxPixelSize: UInt32) -> String
Cache identity for bytes with no path: a hash of the content, so the same image registered twice is one texture and the caller invents no name.
Public for the same reason as key, and for one more: the compositor
derives this key independently, from the bytes it received, and a
client that spelled it differently would be talking about a different
texture. One implementation, both sides.
FNV-1a with the length mixed in. A collision means two unrelated images share a texture, which is worth caring about — and takes roughly 2³² distinct images in one session to become likely, which is why 64 bits is enough here and would not be for an untrusted store.
Sources/LavaUI/Image.swift:159
static func endFrame(into editor: Editor)
Called by LavaApp.run once a frame has been emitted.
Evicting after emit rather than before is what makes "not drawn this frame" mean anything: at the start of a frame nothing has been drawn yet, so every entry would look idle and the cache would throw away the images it is about to paint. Running it here also means a cache that is over budget with nothing new arriving still drains — eviction on insert alone stops the moment loading does, and leaves the cache permanently over its limit.
Sources/LavaUI/Image.swift:116
@discardableResult static func imageIfLoaded(path: String, maxPixelSize: UInt32 = 0, turn: ImageTurn = .none, into editor: Editor) -> UIImage?
Cached image, or nil while it loads.
Returns nil the first time and decodes on a worker; when the pixels arrive they are uploaded on the main thread and a redraw is requested, so the next frame gets the image. Callers draw a placeholder for the nil case — which they need anyway, because a real client is waiting on the network too.
maxPixelSize caps the longer edge at decode time (0 = native). Pass
the size it will be drawn at. Two reasons, and the second is the one
that bites: the pixels you don't decode cost nothing to hold, and
ImageAtlas refuses anything wider than one cell, so an oversized
decode silently costs a whole texture binding per image.
Sources/LavaUI/Image.swift:212
static func isLoading(path: String, maxPixelSize: UInt32 = 0, turn: ImageTurn = .none) -> Bool
Whether a decode for this file is running right now.
The one thing imageIfLoaded's nil does not distinguish: still on its
way, or asked for and come back empty. A caller that keeps the previous
picture on screen while the next one decodes has to tell those apart,
or a file that will never arrive leaves a stale frame up for ever.
Ask before calling imageIfLoaded — that call starts a new decode,
after which the answer is trivially yes.
Sources/LavaUI/Image.swift:192
static func key(path: String, maxPixelSize: UInt32, turn: ImageTurn = .none) -> String
Cache identity for a file decoded at a given cap and turn. 0 and
.none add no segment, so existing callers keep their key and two
callers that both want a picture the right way up share one texture.
Public because a GPUResourceHost outside this module has to stamp
the same key into the UIImage it returns — this cache looks entries
up by it, so a host that spelled it differently would register an
image and then miss it on every subsequent frame.
Sources/LavaUI/Image.swift:139
@discardableResult static func load(path: String, into editor: Editor) -> UIImage?
Cached image, loaded synchronously. Kept for assets an app needs before its first frame — an icon, a brand mark — where a placeholder would be worse than a stall.
Sources/LavaUI/Image.swift:172
static func loadAsset(named name: String, assetsRoot: String, into editor: Editor) -> UIImage?
Resolve a file under assetsRoot (fonts/ / assets/ / root).
Sources/LavaUI/Image.swift:284
static func loadAsset(named name: String, bundle: Bundle, into editor: Editor) -> UIImage?
Load a resource from a SwiftPM / app Bundle (prefer the app's
Bundle.module for brand art — not the engine or LavaUI bundles).
Sources/LavaUI/Image.swift:306
static func releaseAll(into editor: Editor)
Hands every cached image back to the host and empties the cache.
Called on the way out of LavaApp.run, and that is not tidiness. A
texture the renderer holds for a client is not freed when the client's
process ends: under a compositor the registration counts a user, and
there is nothing on the wire tying that user to the process that asked
— ReleaseImage says so itself. So an app that exits with a full cache
leaves every image it ever opened resident for as long as the desktop
lives. A photograph viewer walking a library measured 2 GiB of that,
with no viewer running.
It does not close the hole, only the common half of it: an exit through the frame loop reaches here, a crash or a kill does not. The other half belongs on the compositor's side of the wire, where a registration should be leased to the session that made it.
Sources/LavaUI/Image.swift:345
static func touch(_ image: UIImage)
Marks an image as used. Called from the draw list on every emit, which is what makes "least recently used" mean "least recently drawn" rather than least recently asked for.
Sources/LavaUI/Image.swift:124