Files
dusk/.claude/asset.md
T
2026-06-18 14:59:21 -05:00

116 lines
4.0 KiB
Markdown

# 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.