# 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](architecture.md#asset-system) 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 ```c // 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: ```c 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: ```c 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: ```c 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`): ```c 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.