Asyncify other loaders

This commit is contained in:
2026-06-01 15:10:58 -05:00
parent c4c93097cd
commit 1f3a29f89d
9 changed files with 252 additions and 73 deletions
+4 -4
View File
@@ -12,7 +12,7 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
[ASSET_LOADER_TYPE_MESH] = {
.loadSync = assetMeshLoaderSync,
.loadAsync = NULL,
.loadAsync = assetMeshLoaderAsync,
.dispose = assetMeshDispose
},
@@ -24,19 +24,19 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
[ASSET_LOADER_TYPE_TILESET] = {
.loadSync = assetTilesetLoaderSync,
.loadAsync = NULL,
.loadAsync = assetTilesetLoaderAsync,
.dispose = assetTilesetDispose
},
[ASSET_LOADER_TYPE_LOCALE] = {
.loadSync = assetLocaleLoaderSync,
.loadAsync = NULL,
.loadAsync = assetLocaleLoaderAsync,
.dispose = assetLocaleDispose
},
[ASSET_LOADER_TYPE_JSON] = {
.loadSync = assetJsonLoaderSync,
.loadAsync = NULL,
.loadAsync = assetJsonLoaderAsync,
.dispose = assetJsonDispose
},
};
+42 -15
View File
@@ -12,29 +12,28 @@
#include "asset/loader/assetloading.h"
#include "asset/loader/assetentry.h"
errorret_t assetMeshLoaderSync(assetloading_t *loading) {
errorret_t assetMeshLoaderAsync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_MESH, "Invalid type.");
if(loading->loading.mesh.state != ASSET_MESH_LOADING_STATE_READ_FILE) {
errorOk();
}
assetmeshoutput_t *out = &loading->entry->data.mesh;
assetfile_t *file = &loading->loading.mesh.file;
assetmeshinputaxis_t axis = loading->entry->input->mesh;
assetLoaderErrorChain(loading, assetFileInit(
file, loading->entry->name, NULL, NULL
));
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
assetLoaderErrorChain(loading, assetFileOpen(file));
// Skip the 80-byte STL header
// Skip the 80-byte STL header.
assetLoaderErrorChain(loading, assetFileRead(file, NULL, 80));
if(file->lastRead != 80) {
assetLoaderErrorThrow(loading, "Failed to skip STL header.");
}
uint32_t triangleCount;
assetLoaderErrorChain(
loading, assetFileRead(file, &triangleCount, sizeof(uint32_t))
);
assetLoaderErrorChain(loading, assetFileRead(file, &triangleCount, sizeof(uint32_t)));
if(file->lastRead != sizeof(uint32_t)) {
assetLoaderErrorThrow(loading, "Failed to read tri count");
}
@@ -76,9 +75,7 @@ errorret_t assetMeshLoaderSync(assetloading_t *loading) {
verts[i * 3 + j].uv[1] = 0.0f;
for(uint8_t k = 0; k < 3; k++) {
verts[i * 3 + j].pos[k] = endianLittleToHostFloat(
triData.positions[j][k]
);
verts[i * 3 + j].pos[k] = endianLittleToHostFloat(triData.positions[j][k]);
}
switch(axis) {
@@ -124,12 +121,42 @@ errorret_t assetMeshLoaderSync(assetloading_t *loading) {
}
assetFileDispose(file);
ret = meshInit(
&out->mesh, MESH_PRIMITIVE_TYPE_TRIANGLES, triangleCount * 3, verts
loading->loading.mesh.triangleCount = triangleCount;
loading->loading.mesh.state = ASSET_MESH_LOADING_STATE_CREATE_MESH;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
errorret_t assetMeshLoaderSync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_MESH, "Invalid type.");
switch(loading->loading.mesh.state) {
case ASSET_MESH_LOADING_STATE_INITIAL:
loading->loading.mesh.state = ASSET_MESH_LOADING_STATE_READ_FILE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
errorOk();
break;
case ASSET_MESH_LOADING_STATE_CREATE_MESH:
break;
default:
errorOk();
}
assetmeshoutput_t *out = &loading->entry->data.mesh;
assertNotNull(out->vertices, "Mesh vertices should have been loaded by now.");
errorret_t ret = meshInit(
&out->mesh,
MESH_PRIMITIVE_TYPE_TRIANGLES,
loading->loading.mesh.triangleCount * 3,
out->vertices
);
if(errorIsNotOk(ret)) {
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
memoryFree(verts);
memoryFree(out->vertices);
out->vertices = NULL;
assetLoaderErrorChain(loading, ret);
}
@@ -25,8 +25,17 @@ typedef enum {
typedef assetmeshinputaxis_t assetmeshloaderinput_t;
typedef enum {
ASSET_MESH_LOADING_STATE_INITIAL,
ASSET_MESH_LOADING_STATE_READ_FILE,
ASSET_MESH_LOADING_STATE_CREATE_MESH,
ASSET_MESH_LOADING_STATE_DONE
} assetmeshloadingstate_t;
typedef struct {
assetfile_t file;
assetmeshloadingstate_t state;
uint32_t triangleCount;
} assetmeshloaderloading_t;
typedef struct {
@@ -44,5 +53,6 @@ typedef struct {
assertStructSize(assetmeshstltriangle_t, 50);
errorret_t assetMeshLoaderAsync(assetloading_t *loading);
errorret_t assetMeshLoaderSync(assetloading_t *loading);
errorret_t assetMeshDispose(assetentry_t *entry);
@@ -12,67 +12,97 @@
#include "asset/loader/assetloading.h"
#include "asset/loader/assetentry.h"
errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
if(loading->loading.tileset.state != ASSET_TILESET_LOADING_STATE_READ_FILE) {
errorOk();
}
assertNull(loading->loading.tileset.data, "Data already defined?");
assetfile_t *file = &loading->loading.tileset.file;
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
uint8_t *data = memoryAllocate(file->size);
assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(loading, assetFileRead(file, data, file->size));
assetLoaderErrorChain(loading, assetFileClose(file));
assetLoaderErrorChain(loading, assetFileDispose(file));
assertTrue(file->lastRead == file->size, "Failed to read entire tileset file.");
loading->loading.tileset.data = data;
loading->loading.tileset.state = ASSET_TILESET_LOADING_STATE_PARSE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
assetfile_t *file = &loading->loading.tileset.file;
switch(loading->loading.tileset.state) {
case ASSET_TILESET_LOADING_STATE_INITIAL:
loading->loading.tileset.state = ASSET_TILESET_LOADING_STATE_READ_FILE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
errorOk();
break;
case ASSET_TILESET_LOADING_STATE_PARSE:
break;
default:
errorOk();
}
uint8_t *data = loading->loading.tileset.data;
assertNotNull(data, "Tileset data should have been loaded by now.");
tileset_t *out = &loading->entry->data.tileset;
assetLoaderErrorChain(
loading, assetFileInit(file, loading->entry->name, NULL, NULL)
);
uint8_t *entire = memoryAllocate(file->size);
assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(loading, assetFileRead(file, entire, file->size));
assetLoaderErrorChain(loading, assetFileClose(file));
assetLoaderErrorChain(loading, assetFileDispose(file));
assertTrue(file->lastRead == file->size, "Failed to read entire file.");
if(entire[0] != 'D' || entire[1] != 'T' || entire[2] != 'F') {
memoryFree(entire);
if(data[0] != 'D' || data[1] != 'T' || data[2] != 'F') {
memoryFree(data);
assetLoaderErrorThrow(loading, "Invalid tileset header");
}
if(entire[3] != 0x00) {
memoryFree(entire);
if(data[3] != 0x00) {
memoryFree(data);
assetLoaderErrorThrow(loading, "Unsupported tileset version");
}
out->tileWidth = endianLittleToHost16(*(uint16_t *)(entire + 4));
out->tileHeight = endianLittleToHost16(*(uint16_t *)(entire + 6));
out->columns = endianLittleToHost16(*(uint16_t *)(entire + 8));
out->rows = endianLittleToHost16(*(uint16_t *)(entire + 10));
out->tileWidth = endianLittleToHost16(*(uint16_t *)(data + 4));
out->tileHeight = endianLittleToHost16(*(uint16_t *)(data + 6));
out->columns = endianLittleToHost16(*(uint16_t *)(data + 8));
out->rows = endianLittleToHost16(*(uint16_t *)(data + 10));
if(out->tileWidth == 0) {
memoryFree(entire);
memoryFree(data);
assetLoaderErrorThrow(loading, "Tile width cannot be 0");
}
if(out->tileHeight == 0) {
memoryFree(entire);
memoryFree(data);
assetLoaderErrorThrow(loading, "Tile height cannot be 0");
}
if(out->columns == 0) {
memoryFree(entire);
memoryFree(data);
assetLoaderErrorThrow(loading, "Column count cannot be 0");
}
if(out->rows == 0) {
memoryFree(entire);
memoryFree(data);
assetLoaderErrorThrow(loading, "Row count cannot be 0");
}
out->uv[0] = endianLittleToHostFloat(*(float *)(entire + 16));
out->uv[1] = endianLittleToHostFloat(*(float *)(entire + 20));
out->uv[0] = endianLittleToHostFloat(*(float *)(data + 16));
out->uv[1] = endianLittleToHostFloat(*(float *)(data + 20));
if(out->uv[1] < 0.0f || out->uv[1] > 1.0f) {
memoryFree(entire);
memoryFree(data);
assetLoaderErrorThrow(loading, "Invalid v0 value in tileset");
}
out->tileCount = out->columns * out->rows;
memoryFree(entire);
memoryFree(data);
loading->loading.tileset.data = NULL;
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
errorOk();
@@ -16,11 +16,45 @@ typedef struct {
void *nothing;
} assettilesetloaderinput_t;
typedef enum {
ASSET_TILESET_LOADING_STATE_INITIAL,
ASSET_TILESET_LOADING_STATE_READ_FILE,
ASSET_TILESET_LOADING_STATE_PARSE,
ASSET_TILESET_LOADING_STATE_DONE
} assettilesetloadingstate_t;
typedef struct {
assetfile_t file;
assettilesetloadingstate_t state;
uint8_t *data;
} assettilesetloaderloading_t;
typedef tileset_t assettilesetoutput_t;
/**
* Asynchronous loader for tileset assets. Reads the raw DTF file bytes into
* the loading buffer so the sync phase can parse without blocking the main
* thread on I/O.
*
* @param loading Loading information for the asset being loaded.
* @return Error code indicating success or failure of the load operation.
*/
errorret_t assetTilesetLoaderAsync(assetloading_t *loading);
/**
* Synchronous loader for tileset assets. Parses the DTF binary previously
* read by the async phase and populates the output tileset_t.
*
* @param loading Loading information for the asset being loaded.
* @return Error code indicating success or failure of the load operation.
*/
errorret_t assetTilesetLoaderSync(assetloading_t *loading);
/**
* Disposer for tileset assets.
*
* @param entry Asset entry containing the tileset to dispose.
* @return Error code indicating success or failure of the dispose operation.
*/
errorret_t assetTilesetDispose(assetentry_t *entry);
+41 -18
View File
@@ -11,40 +11,63 @@
#include "asset/loader/assetloading.h"
#include "asset/loader/assetentry.h"
errorret_t assetJsonLoaderSync(assetloading_t *loading) {
errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
if(loading->loading.json.state != ASSET_JSON_LOADING_STATE_READ_FILE) {
errorOk();
}
assertNull(loading->loading.json.buffer, "Buffer already defined?");
assetfile_t *file = &loading->loading.json.file;
assetLoaderErrorChain(
loading, assetFileInit(file, loading->entry->name, NULL, NULL)
);
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
if(file->size > ASSET_JSON_FILE_SIZE_MAX) {
assetLoaderErrorThrow(loading, "JSON exceeds maximum allowed size");
}
uint8_t *buffer = memoryAllocate(file->size);
assetLoaderErrorChain(
loading, assetFileOpen(file)
);
assetLoaderErrorChain(
loading, assetFileRead(file, buffer, file->size)
);
assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(loading, assetFileRead(file, buffer, file->size));
assertTrue(file->lastRead == file->size, "Failed to read entire JSON file.");
assetLoaderErrorChain(
loading, assetFileClose(file)
);
assetLoaderErrorChain(
loading, assetFileDispose(file)
);
assetLoaderErrorChain(loading, assetFileClose(file));
assetLoaderErrorChain(loading, assetFileDispose(file));
loading->loading.json.buffer = buffer;
loading->loading.json.state = ASSET_JSON_LOADING_STATE_PARSE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
errorret_t assetJsonLoaderSync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
switch(loading->loading.json.state) {
case ASSET_JSON_LOADING_STATE_INITIAL:
loading->loading.json.state = ASSET_JSON_LOADING_STATE_READ_FILE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
errorOk();
break;
case ASSET_JSON_LOADING_STATE_PARSE:
break;
default:
errorOk();
}
uint8_t *buffer = loading->loading.json.buffer;
assertNotNull(buffer, "JSON buffer should have been loaded by now.");
loading->entry->data.json = yyjson_read(
(char *)buffer,
file->size,
loading->loading.json.file.size,
YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS
);
memoryFree(buffer);
loading->loading.json.buffer = NULL;
if(!loading->entry->data.json) {
assetLoaderErrorThrow(loading, "Failed to parse JSON");
@@ -15,10 +15,22 @@ typedef struct assetloading_s assetloading_t;
typedef struct assetentry_s assetentry_t;
typedef struct { void *nothing; } assetjsonloaderinput_t;
typedef enum {
ASSET_JSON_LOADING_STATE_INITIAL,
ASSET_JSON_LOADING_STATE_READ_FILE,
ASSET_JSON_LOADING_STATE_PARSE,
ASSET_JSON_LOADING_STATE_DONE
} assetjsonloadingstate_t;
typedef struct {
assetfile_t file;
assetjsonloadingstate_t state;
uint8_t *buffer;
} assetjsonloaderloading_t;
typedef yyjson_doc * assetjsonoutput_t;
errorret_t assetJsonLoaderAsync(assetloading_t *loading);
errorret_t assetJsonLoaderSync(assetloading_t *loading);
errorret_t assetJsonDispose(assetentry_t *entry);
@@ -14,9 +14,12 @@
#include "asset/loader/assetloading.h"
#include "asset/loader/assetentry.h"
errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
if(loading->loading.locale.state != ASSET_LOCALE_LOADER_STATE_LOAD_HEADER) {
errorOk();
}
assetlocalefile_t *localeFile = &loading->entry->data.locale;
memoryZero(localeFile, sizeof(assetlocalefile_t));
@@ -27,6 +30,29 @@ errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
assetLoaderErrorChain(loading, assetLocaleGetString(localeFile, "", 0, buffer, sizeof(buffer)));
assetLoaderErrorChain(loading, assetLocaleParseHeader(localeFile, buffer, sizeof(buffer)));
loading->loading.locale.state = ASSET_LOCALE_LOADER_STATE_DONE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
switch(loading->loading.locale.state) {
case ASSET_LOCALE_LOADER_STATE_INITIAL:
loading->loading.locale.state = ASSET_LOCALE_LOADER_STATE_LOAD_HEADER;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
errorOk();
break;
case ASSET_LOCALE_LOADER_STATE_DONE:
break;
default:
errorOk();
}
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
errorOk();
}
@@ -14,8 +14,16 @@ typedef struct assetentry_s assetentry_t;
/** Input passed to the locale loader — currently unused. */
typedef struct { void *nothing; } assetlocaleloaderinput_t;
typedef enum {
ASSET_LOCALE_LOADER_STATE_INITIAL,
ASSET_LOCALE_LOADER_STATE_LOAD_HEADER,
ASSET_LOCALE_LOADER_STATE_DONE
} assetlocaleloaderstate_t;
/** Per-slot scratch data used while the locale file is loading. */
typedef struct { void *nothing; } assetlocaleloaderloading_t;
typedef struct {
assetlocaleloaderstate_t state;
} assetlocaleloaderloading_t;
/** Maximum number of distinct plural forms a locale file may declare. */
#define ASSET_LOCALE_FILE_PLURAL_FORM_COUNT 6
@@ -96,10 +104,19 @@ typedef struct {
typedef assetlocalefile_t assetlocaleoutput_t;
/**
* Synchronous loader callback. Opens and validates the locale file, reads the
* PO header, and parses plural-form rules. Sets entry state to
* `ASSET_ENTRY_STATE_LOADED` on success or `ASSET_ENTRY_STATE_ERROR` on
* failure.
* Asynchronous loader callback. Opens the locale file, reads the PO header,
* and parses plural-form rules. All I/O happens here so the main thread is
* not blocked. Sets entry state to `ASSET_ENTRY_STATE_PENDING_SYNC` on
* success or `ASSET_ENTRY_STATE_ERROR` on failure.
*
* @param loading The loading slot for this asset entry.
* @return OK on success, error otherwise.
*/
errorret_t assetLocaleLoaderAsync(assetloading_t *loading);
/**
* Synchronous loader callback. Confirms the async phase completed and marks
* the entry as `ASSET_ENTRY_STATE_LOADED`.
*
* @param loading The loading slot for this asset entry.
* @return OK on success, error otherwise.