4.0 KiB
Asset System
Source: src/dusk/asset/
All game assets are packed into dusk.dsk (a zip archive) at build time and served from it at runtime. The asset system manages async loading, reference counting, and platform-specific archive location.
See architecture.md for the high-level overview.
Asset archive
The archive is opened at assetInit(). assetFileExists(filename) checks for a file without loading it. The file path format inside the archive matches the layout of the assets/ source directory.
On each platform, assetInitPlatform() locates the .dsk file (e.g. adjacent to the binary on Linux, on the SD card on PSP).
Entry lifecycle
An assetentry_t represents one file being managed by the system. States:
NOT_STARTED → PENDING_ASYNC → LOADING_ASYNC → PENDING_SYNC → LOADING_SYNC → LOADED
└→ ERROR
- PENDING_ASYNC / LOADING_ASYNC: the background thread is handling I/O (file reads, decompression).
- PENDING_SYNC / LOADING_SYNC: the main thread needs to finish loading (e.g. uploading to GPU), triggered during
assetUpdate().
The async/sync split exists because GPU operations must happen on the main thread.
Using assets
// Acquire a loaded entry (blocks until loaded):
assetentry_t *entry = assetLock(filename, ASSET_LOADER_TYPE_TEXTURE, &input);
errorChain(assetRequireLoaded(entry));
// Use the loaded data:
texture_t *tex = &entry->data.texture.texture;
// Release when done:
assetUnlockEntry(entry);
assetLock finds-or-creates an entry and increments its reference count. assetUnlock / assetUnlockEntry decrements it; when it reaches zero the entry is reclaimed at the next assetUpdate().
To subscribe to async completion instead of blocking:
eventSubscribe(&entry->onLoaded, myCallback, myUser);
Loader types
| Type constant | File | Output struct accessed via |
|---|---|---|
ASSET_LOADER_TYPE_TEXTURE |
.png etc. |
entry->data.texture.texture |
ASSET_LOADER_TYPE_TILESET |
tileset descriptor | entry->data.tileset.tileset |
ASSET_LOADER_TYPE_MESH |
mesh data | entry->data.mesh.mesh |
ASSET_LOADER_TYPE_LOCALE |
.po file |
internal to locale manager |
ASSET_LOADER_TYPE_JSON |
.json |
entry->data.json.* |
Each loader type registers loadAsync, loadSync, and dispose callbacks in ASSET_LOADER_CALLBACKS[].
The async callback runs on the loader thread; the sync callback runs on the main thread during assetUpdate(). Most loaders do file I/O async and GPU upload sync.
Error handling inside loaders
Use these macros instead of errorThrow / errorChain inside loader callbacks — they also set the entry state to ERROR:
assetLoaderErrorChain(loading, someCall());
assetLoaderErrorThrow(loading, "Descriptive message");
Low-level file I/O (asset/assetfile.h)
assetfile_t wraps a zip_file_t handle and provides streaming reads:
assetFileInit(&file, "textures/player.png", NULL, NULL);
assetFileOpen(&file);
assetFileRead(&file, buffer, size);
assetFileClose(&file);
assetFileDispose(&file);
// Read entire file into a malloc'd buffer:
uint8_t *buf; size_t size;
assetFileReadEntire(&file, &buf, &size); // caller frees buf
For line-by-line text parsing (assetfilelinereader_t):
assetFileLineReaderInit(&reader, &file, readBuf, readBufSize, outBuf, outBufSize);
while(!reader.eof) {
errorChain(assetFileLineReaderNext(&reader));
// reader.outBuffer contains the line, reader.lineLength its length
}
Background loader thread
assetUpdateAsync(thread) is the thread entry point. It calls assetUpdate() in a loop, sleeping briefly between iterations, until threadShouldStop() returns true. The main thread also calls assetUpdate() once per frame to process the sync phase.
Up to ASSET_LOADING_COUNT_MAX (4) entries can be loading concurrently.
Up to ASSET_ENTRY_COUNT_MAX (128) entries can exist at once.