Brodgar client

hafen.asset: The Files Your Addon Ships

One loader for every file in your addon’s folder. An image, a font, a 3D model and a data file come through the same door, typed by the file’s extension. hafen.asset() is the collection of the files this addon holds. Unprotected: an asset is a client-side file you shipped.

local icon  = hafen.asset():get("icon.png")        -- an image
local face  = hafen.asset():get("fonts/Inter.ttf") -- a font
local chair = hafen.asset():get("props/chair.glb") -- a mesh
local theme = hafen.asset():get("theme.json")      -- data (its text)
local song  = hafen.asset():get("songs/air.mid")   -- data (its bytes)

One flow for a local file: load once, keep the handle, hand the handle to whatever uses it. The use sites take a handle and nothing else. A path string passed to a sprite or an object raises pointing back here.

The types

Extensions asset:type() You get Use it with
.png .jpg .jpeg .gif .bmp "image" A GPU texture, alpha preserved. graphics:image/graphics:aimage, a sprite, a button’s face, a menu entry’s icon.
.ttf .otf "font" A FontHandle whose family is registered, so $font[…] works. font =, rule:font, widget:rule().
.glb .gltf "mesh" Parsed glTF 2.0 static geometry and its textures. An object.
Any other "data" The file’s bytes, and its text decoded as UTF-8. hafen.json():parse, string.byte, anything that takes a string.
Rule Detail
Recommended formats PNG for images (transparency), .glb for models (a single file). Every file outside the three decoded kinds is data.
Size limit A file is read whole into memory: 128 MB. A larger one raises naming its size before being read.
One argument, always :get(path) takes a path and no options table. Loading a file is expensive and happens once. Configuring a use is cheap and happens many times (face:derive():size(11), face:derive():size(24):bold(true)).
Synchronous Call :get from setup code (Load, SessionEnteredWorld, a command), never inside a draw callback. It is one call from Lua’s side however big the file, so the instruction budget cannot see it. A large model or image is a dropped frame, timed by where you called :get.

Paths are addon-relative and sandboxed

Rule Detail
Relative to your addon folder "icon.png", "img/sign.png", "props/tree.glb". Absolute paths and .. escapes are rejected. An internal a/../b normalises and lands inside.
The check is on the file reached A link inside your folder pointing outside is refused like any other outside path. External .gltf buffers and textures resolve relative to the model file through the same check. :files(dir) lists only what the same check passes.

Interning

The same path is the same handle, for every type: the key is the resolved path, so get("./icon.png") == get("img/../icon.png"), and repeating a load is free. asset:path() answers the spelling of the first load.

local function get(path) return hafen.asset():get(path) end
assert(get("icon.png") == get("icon.png"))
assert(get("./icon.png") == get("img/../icon.png"))       -- the key is the resolved path

Identity is stable while the asset is alive. hafen.asset():remove(asset) drops it from the cache. The next :get(path) re-reads the file into a new object, never equal to the old handle. A freed asset is never served or listed again.

What this door does not open

Not here Where
A remote file hafen.http, protected by a host allowlist the user approves. A remote asset would be an async load in a synchronous API, an untrusted binary in the font and texture paths, and a tracking channel. :get handed a URL says so rather than reporting a missing file.
A .res image (action icons, HUD art) graphics:resource(name, x, y).
A .res image as a theme’s own art {res = name} in a rule’s bg, border or picture.
A minimap drawing of ground you explored grid:image(level).
A .res sound hafen.sound():get(name).
A .res prop in the world hafen.virtual.
A built-in font hafen.font():get(name).

The client already owns every .res and addresses it by name: no sandbox, no cache of yours, no lifetime. This namespace is your files.

Pages

Page Covers
Collection Loading, listing, freeing, and naming the files a folder of yours holds, with every refusal.
Handles What each kind of loaded file answers: the shared verbs, and those an image, a font, a mesh and a data file add.

See Also