Docs
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user