diff --git a/src/dusk/asset/asset.c b/src/dusk/asset/asset.c index 6ac7a4e9..f914092c 100644 --- a/src/dusk/asset/asset.c +++ b/src/dusk/asset/asset.c @@ -286,7 +286,9 @@ errorret_t assetUpdate(void) { "Loader did not set entry state to error on failed load." ); } 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++; diff --git a/src/dusk/asset/asset.h b/src/dusk/asset/asset.h index a560aa63..2520f94d 100644 --- a/src/dusk/asset/asset.h +++ b/src/dusk/asset/asset.h @@ -23,7 +23,7 @@ #define ASSET_FILE_NAME "dusk.dsk" #define ASSET_HEADER_SIZE 3 -#define ASSET_LOADING_COUNT_MAX 4 +#define ASSET_LOADING_COUNT_MAX 20 #define ASSET_ENTRY_COUNT_MAX 128 typedef struct asset_s { diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.c b/src/dusk/asset/loader/chunk/assetchunkloader.c index 408be108..871f64c0 100644 --- a/src/dusk/asset/loader/chunk/assetchunkloader.c +++ b/src/dusk/asset/loader/chunk/assetchunkloader.c @@ -11,6 +11,8 @@ #include "util/endian.h" #include "asset/loader/assetloading.h" #include "asset/loader/assetentry.h" +#include "asset/loader/assetloader.h" +#include "asset/asset.h" errorret_t assetChunkLoaderAsync(assetloading_t *loading) { 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."); assertIsMainThread("Must be called from the main thread."); + assetchunkoutput_t *out = &loading->entry->data.chunk; + switch(loading->loading.chunk.state) { case ASSET_CHUNK_LOADING_STATE_INITIAL: loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_READ_FILE; loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC; errorOk(); - break; case ASSET_CHUNK_LOADING_STATE_PARSE: 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: errorOk(); } @@ -78,7 +108,6 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) { ); } - assetchunkoutput_t *out = &loading->entry->data.chunk; size_t offset = 8; size_t tileSize = CHUNK_TILE_COUNT * sizeof(tile_t); @@ -104,16 +133,21 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) { out->modelNames[m][nameLen] = '\0'; offset += nameLen + 1; - memoryCopy( - out->meshOffsets[m], data + offset, sizeof(vec3) - ); + memoryCopy(out->meshOffsets[m], data + offset, sizeof(vec3)); offset += sizeof(vec3); } memoryFree(data); loading->loading.chunk.data = NULL; - loading->entry->state = ASSET_ENTRY_STATE_LOADED; + if(out->meshCount == 0) { + loading->entry->state = ASSET_ENTRY_STATE_LOADED; + 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(); } @@ -122,5 +156,11 @@ errorret_t assetChunkDispose(assetentry_t *entry) { assertTrue(entry->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type."); 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(); } diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.h b/src/dusk/asset/loader/chunk/assetchunkloader.h index 775a28cd..7f17de3e 100644 --- a/src/dusk/asset/loader/chunk/assetchunkloader.h +++ b/src/dusk/asset/loader/chunk/assetchunkloader.h @@ -22,6 +22,7 @@ typedef enum { ASSET_CHUNK_LOADING_STATE_INITIAL, ASSET_CHUNK_LOADING_STATE_READ_FILE, ASSET_CHUNK_LOADING_STATE_PARSE, + ASSET_CHUNK_LOADING_STATE_LOAD_MODELS, ASSET_CHUNK_LOADING_STATE_DONE } assetchunkloadingstate_t; @@ -29,6 +30,7 @@ typedef struct { assetfile_t file; assetchunkloadingstate_t state; uint8_t *data; + uint8_t modelIndex; } assetchunkloaderloading_t; typedef struct { @@ -36,6 +38,7 @@ typedef struct { uint8_t meshCount; char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; + assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX]; } assetchunkoutput_t; /** diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index db12600f..8cb922ad 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -10,9 +10,10 @@ #include "assert/assert.h" #include "asset/asset.h" #include "asset/loader/assetloader.h" +#include "console/console.h" +#include "event/event.h" #include "rpg/entity/entity.h" #include "util/string.h" -#include map_t MAP; @@ -113,8 +114,8 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { errorOk(); } -void mapUpdate() { - +errorret_t mapUpdate() { + errorOk(); } errorret_t mapDispose() { @@ -143,6 +144,8 @@ void mapChunkUnload(chunk_t *chunk) { memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); if(chunk->dcfEntry != NULL) { + eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded); + eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError); assetUnlockEntry(chunk->dcfEntry); chunk->dcfEntry = NULL; } @@ -158,9 +161,15 @@ void mapChunkUnload(chunk_t *chunk) { errorret_t mapChunkLoad(chunk_t *chunk) { 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)); chunk->meshCount = 0; - chunk->dcfEntry = NULL; char_t name[64]; stringFormat( @@ -178,85 +187,12 @@ errorret_t mapChunkLoad(chunk_t *chunk) { assetentry_t *entry = assetLock(name, ASSET_LOADER_TYPE_CHUNK, NULL); assertNotNull(entry, "Failed to get chunk asset entry"); + eventSubscribe(&entry->onLoaded, mapChunkLoaded, chunk); + eventSubscribe(&entry->onError, mapChunkLoadError, chunk); chunk->dcfEntry = entry; 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) { if(!mapIsLoaded()) return -1; @@ -298,3 +234,61 @@ tile_t mapGetTile(const worldpos_t position) { chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position); 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; +} diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h index 0d89d94f..61fa2750 100644 --- a/src/dusk/rpg/overworld/map.h +++ b/src/dusk/rpg/overworld/map.h @@ -52,8 +52,10 @@ bool_t mapIsLoaded(); /** * Updates the map. + * + * @return An error code. */ -void mapUpdate(); +errorret_t mapUpdate(); /** * Disposes of the map. @@ -78,8 +80,7 @@ errorret_t mapPositionSet(const chunkpos_t newPos); void mapChunkUnload(chunk_t* chunk); /** - * Loads a chunk. Starts async loading without blocking; call - * mapWaitForChunks() to block until all pending chunks are ready. + * Loads a chunk. Starts async loading without blocking. * * @param chunk The chunk to load. * @return An error code. @@ -87,13 +88,23 @@ void mapChunkUnload(chunk_t* chunk); errorret_t mapChunkLoad(chunk_t* chunk); /** - * Blocks the calling thread until every chunk in the active window has - * fully loaded its DCF data and all referenced DMF meshes. Intended to - * be called once at the start of each RPG update tick. + * Callback invoked when a chunk DCF asset fails to load. Fills the + * chunk tiles with TILE_SHAPE_GROUND as a fallback. + * 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. diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index c29c8cde..c27581f9 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -77,10 +77,8 @@ errorret_t rpgUpdate(void) { } #endif - errorChain(mapWaitForChunks()); - // TODO: Do not update if the scene is not the map scene? - mapUpdate(); + errorChain(mapUpdate()); // Update overworld ents. entity_t *ent = &ENTITIES[0];