Async chunk loading

This commit is contained in:
2026-07-01 12:58:33 -05:00
parent a9ab8dc1d8
commit 9b75e5ed83
7 changed files with 146 additions and 98 deletions
+3 -1
View File
@@ -286,7 +286,9 @@ errorret_t assetUpdate(void) {
"Loader did not set entry state to error on failed load." "Loader did not set entry state to error on failed load."
); );
} else if(loading->entry->state == ASSET_ENTRY_STATE_LOADED) { } else if(loading->entry->state == ASSET_ENTRY_STATE_LOADED) {
eventInvoke(&loading->entry->onLoaded, loading->entry); assetentry_t *loadedEntry = loading->entry;
loading->entry = NULL;
eventInvoke(&loadedEntry->onLoaded, loadedEntry);
} }
loading++; loading++;
+1 -1
View File
@@ -23,7 +23,7 @@
#define ASSET_FILE_NAME "dusk.dsk" #define ASSET_FILE_NAME "dusk.dsk"
#define ASSET_HEADER_SIZE 3 #define ASSET_HEADER_SIZE 3
#define ASSET_LOADING_COUNT_MAX 4 #define ASSET_LOADING_COUNT_MAX 20
#define ASSET_ENTRY_COUNT_MAX 128 #define ASSET_ENTRY_COUNT_MAX 128
typedef struct asset_s { typedef struct asset_s {
+45 -5
View File
@@ -11,6 +11,8 @@
#include "util/endian.h" #include "util/endian.h"
#include "asset/loader/assetloading.h" #include "asset/loader/assetloading.h"
#include "asset/loader/assetentry.h" #include "asset/loader/assetentry.h"
#include "asset/loader/assetloader.h"
#include "asset/asset.h"
errorret_t assetChunkLoaderAsync(assetloading_t *loading) { errorret_t assetChunkLoaderAsync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL"); assertNotNull(loading, "Loading cannot be NULL");
@@ -48,16 +50,44 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
assertTrue(loading->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type."); assertTrue(loading->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type.");
assertIsMainThread("Must be called from the main thread."); assertIsMainThread("Must be called from the main thread.");
assetchunkoutput_t *out = &loading->entry->data.chunk;
switch(loading->loading.chunk.state) { switch(loading->loading.chunk.state) {
case ASSET_CHUNK_LOADING_STATE_INITIAL: case ASSET_CHUNK_LOADING_STATE_INITIAL:
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_READ_FILE; loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_READ_FILE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC; loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
errorOk(); errorOk();
break;
case ASSET_CHUNK_LOADING_STATE_PARSE: case ASSET_CHUNK_LOADING_STATE_PARSE:
break; break;
case ASSET_CHUNK_LOADING_STATE_LOAD_MODELS:
while(loading->loading.chunk.modelIndex < out->meshCount) {
uint8_t m = loading->loading.chunk.modelIndex;
if(out->modelEntries[m] == NULL) {
out->modelEntries[m] = assetLock(
out->modelNames[m], ASSET_LOADER_TYPE_MODEL, NULL
);
assertNotNull(
out->modelEntries[m], "Failed to lock model."
);
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
if(out->modelEntries[m]->state == ASSET_ENTRY_STATE_ERROR) {
assetLoaderErrorThrow(
loading, "Model failed to load: %s", out->modelNames[m]
);
}
if(out->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) {
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
loading->loading.chunk.modelIndex++;
}
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
errorOk();
default: default:
errorOk(); errorOk();
} }
@@ -78,7 +108,6 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
); );
} }
assetchunkoutput_t *out = &loading->entry->data.chunk;
size_t offset = 8; size_t offset = 8;
size_t tileSize = CHUNK_TILE_COUNT * sizeof(tile_t); size_t tileSize = CHUNK_TILE_COUNT * sizeof(tile_t);
@@ -104,17 +133,22 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
out->modelNames[m][nameLen] = '\0'; out->modelNames[m][nameLen] = '\0';
offset += nameLen + 1; offset += nameLen + 1;
memoryCopy( memoryCopy(out->meshOffsets[m], data + offset, sizeof(vec3));
out->meshOffsets[m], data + offset, sizeof(vec3)
);
offset += sizeof(vec3); offset += sizeof(vec3);
} }
memoryFree(data); memoryFree(data);
loading->loading.chunk.data = NULL; loading->loading.chunk.data = NULL;
if(out->meshCount == 0) {
loading->entry->state = ASSET_ENTRY_STATE_LOADED; loading->entry->state = ASSET_ENTRY_STATE_LOADED;
errorOk(); errorOk();
}
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_LOAD_MODELS;
loading->loading.chunk.modelIndex = 0;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
} }
errorret_t assetChunkDispose(assetentry_t *entry) { errorret_t assetChunkDispose(assetentry_t *entry) {
@@ -122,5 +156,11 @@ errorret_t assetChunkDispose(assetentry_t *entry) {
assertTrue(entry->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type."); assertTrue(entry->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type.");
assertIsMainThread("Must be called from the main thread."); assertIsMainThread("Must be called from the main thread.");
assetchunkoutput_t *out = &entry->data.chunk;
for(uint8_t m = 0; m < out->meshCount; m++) {
if(out->modelEntries[m] == NULL) continue;
assetUnlockEntry(out->modelEntries[m]);
out->modelEntries[m] = NULL;
}
errorOk(); errorOk();
} }
@@ -22,6 +22,7 @@ typedef enum {
ASSET_CHUNK_LOADING_STATE_INITIAL, ASSET_CHUNK_LOADING_STATE_INITIAL,
ASSET_CHUNK_LOADING_STATE_READ_FILE, ASSET_CHUNK_LOADING_STATE_READ_FILE,
ASSET_CHUNK_LOADING_STATE_PARSE, ASSET_CHUNK_LOADING_STATE_PARSE,
ASSET_CHUNK_LOADING_STATE_LOAD_MODELS,
ASSET_CHUNK_LOADING_STATE_DONE ASSET_CHUNK_LOADING_STATE_DONE
} assetchunkloadingstate_t; } assetchunkloadingstate_t;
@@ -29,6 +30,7 @@ typedef struct {
assetfile_t file; assetfile_t file;
assetchunkloadingstate_t state; assetchunkloadingstate_t state;
uint8_t *data; uint8_t *data;
uint8_t modelIndex;
} assetchunkloaderloading_t; } assetchunkloaderloading_t;
typedef struct { typedef struct {
@@ -36,6 +38,7 @@ typedef struct {
uint8_t meshCount; uint8_t meshCount;
char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX];
vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; vec3 meshOffsets[CHUNK_MESH_COUNT_MAX];
assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX];
} assetchunkoutput_t; } assetchunkoutput_t;
/** /**
+73 -79
View File
@@ -10,9 +10,10 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "asset/asset.h" #include "asset/asset.h"
#include "asset/loader/assetloader.h" #include "asset/loader/assetloader.h"
#include "console/console.h"
#include "event/event.h"
#include "rpg/entity/entity.h" #include "rpg/entity/entity.h"
#include "util/string.h" #include "util/string.h"
#include <unistd.h>
map_t MAP; map_t MAP;
@@ -113,8 +114,8 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
errorOk(); errorOk();
} }
void mapUpdate() { errorret_t mapUpdate() {
errorOk();
} }
errorret_t mapDispose() { errorret_t mapDispose() {
@@ -143,6 +144,8 @@ void mapChunkUnload(chunk_t *chunk) {
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
if(chunk->dcfEntry != NULL) { if(chunk->dcfEntry != NULL) {
eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded);
eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError);
assetUnlockEntry(chunk->dcfEntry); assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL; chunk->dcfEntry = NULL;
} }
@@ -158,9 +161,15 @@ void mapChunkUnload(chunk_t *chunk) {
errorret_t mapChunkLoad(chunk_t *chunk) { errorret_t mapChunkLoad(chunk_t *chunk) {
if(!mapIsLoaded()) errorThrow("No map loaded"); if(!mapIsLoaded()) errorThrow("No map loaded");
if(chunk->dcfEntry != NULL) {
eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded);
eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError);
assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL;
}
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
chunk->meshCount = 0; chunk->meshCount = 0;
chunk->dcfEntry = NULL;
char_t name[64]; char_t name[64];
stringFormat( stringFormat(
@@ -178,85 +187,12 @@ errorret_t mapChunkLoad(chunk_t *chunk) {
assetentry_t *entry = assetLock(name, ASSET_LOADER_TYPE_CHUNK, NULL); assetentry_t *entry = assetLock(name, ASSET_LOADER_TYPE_CHUNK, NULL);
assertNotNull(entry, "Failed to get chunk asset entry"); assertNotNull(entry, "Failed to get chunk asset entry");
eventSubscribe(&entry->onLoaded, mapChunkLoaded, chunk);
eventSubscribe(&entry->onError, mapChunkLoadError, chunk);
chunk->dcfEntry = entry; chunk->dcfEntry = entry;
errorOk(); errorOk();
} }
errorret_t mapWaitForChunks() {
// Phase 1: Wait for all DCF entries to finish loading.
bool_t anyPending;
do {
anyPending = false;
errorChain(assetUpdate());
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
chunk_t *chunk = &MAP.chunks[i];
if(chunk->dcfEntry == NULL) continue;
assetentrystate_t state = chunk->dcfEntry->state;
if(state == ASSET_ENTRY_STATE_LOADED) {
uint8_t meshCount = chunk->dcfEntry->data.chunk.meshCount;
memoryCopy(
chunk->tiles,
chunk->dcfEntry->data.chunk.tiles,
sizeof(chunk->tiles)
);
worldpos_t wp;
chunkPosToWorldPos(&chunk->position, &wp);
vec3 wpf = { (float_t)wp.x, (float_t)wp.y, (float_t)wp.z };
for(uint8_t m = 0; m < meshCount; m++) {
stringCopy(
chunk->modelNames[m],
chunk->dcfEntry->data.chunk.modelNames[m],
CHUNK_MESH_NAME_MAX
);
glm_vec3_copy(
chunk->dcfEntry->data.chunk.meshOffsets[m],
chunk->meshOffsets[m]
);
vec3 pos;
glm_vec3_add(wpf, chunk->meshOffsets[m], pos);
glm_translate_make(chunk->meshModels[m], pos);
}
assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL;
chunk->meshCount = meshCount;
} else if(state == ASSET_ENTRY_STATE_ERROR) {
assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL;
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
} else {
anyPending = true;
}
}
if(anyPending) usleep(1000);
} while(anyPending);
// Phase 2: Load model entries one at a time.
// Models are locked and waited on sequentially so that only one model
// entry occupies a loading slot at a time. This leaves the remaining
// slots free for the model's mesh and texture sub-assets, preventing
// the slot-exhaustion deadlock that occurs when multiple models are
// assigned slots simultaneously and each blocks waiting for sub-assets
// that have nowhere to run.
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
chunk_t *chunk = &MAP.chunks[i];
for(uint8_t m = 0; m < chunk->meshCount; m++) {
if(chunk->modelEntries[m] != NULL) continue;
chunk->modelEntries[m] = assetLock(
chunk->modelNames[m], ASSET_LOADER_TYPE_MODEL, NULL
);
if(chunk->modelEntries[m] == NULL) {
errorThrow("Failed to lock model: %s", chunk->modelNames[m]);
}
errorChain(assetRequireLoaded(chunk->modelEntries[m]));
}
}
errorOk();
}
chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) { chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) {
if(!mapIsLoaded()) return -1; if(!mapIsLoaded()) return -1;
@@ -298,3 +234,61 @@ tile_t mapGetTile(const worldpos_t position) {
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position); chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
return chunk->tiles[tileIndex]; return chunk->tiles[tileIndex];
} }
void mapChunkLoadError(void *params, void *user) {
assertNotNull(params, "mapChunkLoadError: params cannot be NULL");
assertNotNull(user, "mapChunkLoadError: user cannot be NULL");
chunk_t *chunk = (chunk_t *)user;
if(chunk->dcfEntry != (assetentry_t *)params) return;
consolePrint(
"Chunk load error: %d %d %d",
(int32_t)chunk->position.x,
(int32_t)chunk->position.y,
(int32_t)chunk->position.z
);
assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL;
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
}
void mapChunkLoaded(void *params, void *user) {
assertNotNull(params, "mapChunkLoaded: params cannot be NULL");
assertNotNull(user, "mapChunkLoaded: user cannot be NULL");
assetentry_t *entry = (assetentry_t *)params;
chunk_t *chunk = (chunk_t *)user;
if(chunk->dcfEntry != entry) return;
consolePrint(
"Chunk loaded: %d %d %d",
(int32_t)chunk->position.x,
(int32_t)chunk->position.y,
(int32_t)chunk->position.z
);
uint8_t meshCount = entry->data.chunk.meshCount;
memoryCopy(
chunk->tiles,
entry->data.chunk.tiles,
sizeof(chunk->tiles)
);
worldpos_t wp;
chunkPosToWorldPos(&chunk->position, &wp);
vec3 wpf = { (float_t)wp.x, (float_t)wp.y, (float_t)wp.z };
for(uint8_t m = 0; m < meshCount; m++) {
stringCopy(
chunk->modelNames[m],
entry->data.chunk.modelNames[m],
CHUNK_MESH_NAME_MAX
);
glm_vec3_copy(
entry->data.chunk.meshOffsets[m],
chunk->meshOffsets[m]
);
vec3 pos;
glm_vec3_add(wpf, chunk->meshOffsets[m], pos);
glm_translate_make(chunk->meshModels[m], pos);
chunk->modelEntries[m] = entry->data.chunk.modelEntries[m];
entry->data.chunk.modelEntries[m] = NULL;
}
assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL;
chunk->meshCount = meshCount;
}
+19 -8
View File
@@ -52,8 +52,10 @@ bool_t mapIsLoaded();
/** /**
* Updates the map. * Updates the map.
*
* @return An error code.
*/ */
void mapUpdate(); errorret_t mapUpdate();
/** /**
* Disposes of the map. * Disposes of the map.
@@ -78,8 +80,7 @@ errorret_t mapPositionSet(const chunkpos_t newPos);
void mapChunkUnload(chunk_t* chunk); void mapChunkUnload(chunk_t* chunk);
/** /**
* Loads a chunk. Starts async loading without blocking; call * Loads a chunk. Starts async loading without blocking.
* mapWaitForChunks() to block until all pending chunks are ready.
* *
* @param chunk The chunk to load. * @param chunk The chunk to load.
* @return An error code. * @return An error code.
@@ -87,13 +88,23 @@ void mapChunkUnload(chunk_t* chunk);
errorret_t mapChunkLoad(chunk_t* chunk); errorret_t mapChunkLoad(chunk_t* chunk);
/** /**
* Blocks the calling thread until every chunk in the active window has * Callback invoked when a chunk DCF asset fails to load. Fills the
* fully loaded its DCF data and all referenced DMF meshes. Intended to * chunk tiles with TILE_SHAPE_GROUND as a fallback.
* be called once at the start of each RPG update tick. * Always invoked on the main thread.
* *
* @return An error code. * @param params The failed assetentry_t.
* @param user The chunk_t that owns the entry.
*/ */
errorret_t mapWaitForChunks(); void mapChunkLoadError(void *params, void *user);
/**
* Callback invoked when a chunk DCF asset finishes loading.
* Always invoked on the main thread.
*
* @param params The loaded assetentry_t.
* @param user The chunk_t that owns the entry.
*/
void mapChunkLoaded(void *params, void *user);
/** /**
* Gets the index of a chunk, within the world, at the given position. * Gets the index of a chunk, within the world, at the given position.
+1 -3
View File
@@ -77,10 +77,8 @@ errorret_t rpgUpdate(void) {
} }
#endif #endif
errorChain(mapWaitForChunks());
// TODO: Do not update if the scene is not the map scene? // TODO: Do not update if the scene is not the map scene?
mapUpdate(); errorChain(mapUpdate());
// Update overworld ents. // Update overworld ents.
entity_t *ent = &ENTITIES[0]; entity_t *ent = &ENTITIES[0];