From 470c0eba7a6599a8623da11d7a967cb6a26b64d5 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 10 Jul 2026 12:57:31 -0500 Subject: [PATCH] Whatever, some minor map chunking improvements --- src/dusk/asset/asset.h | 2 +- src/dusk/rpg/overworld/map.c | 133 +++++++++++++++++------------- src/dusk/rpg/overworld/map.h | 24 +++++- src/dusk/rpg/overworld/maparea.c | 2 +- src/dusk/rpg/overworld/worldpos.h | 10 --- 5 files changed, 99 insertions(+), 72 deletions(-) diff --git a/src/dusk/asset/asset.h b/src/dusk/asset/asset.h index 04468023..ed09a854 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 32 +#define ASSET_LOADING_COUNT_MAX 10 #define ASSET_ENTRY_COUNT_MAX 64 typedef struct asset_s { diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index 984f4299..20ad8de1 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -21,21 +21,13 @@ errorret_t mapInit() { memoryZero(&MAP, sizeof(map_t)); MAP.loaded = true; - MAP.loadPosition = (chunkpos_t){ - -(MAP_CHUNK_SKIN), - -(MAP_CHUNK_SKIN), - -(MAP_CHUNK_SKIN) - }; - chunkindex_t i = 0; - for(chunkunit_t z = 0; z < MAP_LOADED_CHUNK_DEPTH; z++) { - for(chunkunit_t y = 0; y < MAP_LOADED_CHUNK_HEIGHT; y++) { - for(chunkunit_t x = 0; x < MAP_LOADED_CHUNK_WIDTH; x++) { + for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) { + for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) { + for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) { chunk_t *chunk = &MAP.chunks[i++]; chunk->position = (chunkpos_t){ - MAP.loadPosition.x + (chunkunit_t)x, - MAP.loadPosition.y + (chunkunit_t)y, - MAP.loadPosition.z + (chunkunit_t)z + (chunkunit_t)x, (chunkunit_t)y, (chunkunit_t)z }; errorChain(mapChunkLoad(chunk)); } @@ -54,48 +46,23 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { if(!mapIsLoaded()) errorThrow("No map loaded"); if(chunkPositionIsEqual(newPos, MAP.chunkPosition)) errorOk(); - // If the new render window still fits inside the current loaded area, - // just remap chunkOrder — no asset I/O needed. - const chunkpos_t lp = MAP.loadPosition; - if( - newPos.x >= lp.x && - newPos.y >= lp.y && - newPos.z >= lp.z && - newPos.x + MAP_CHUNK_WIDTH <= lp.x + MAP_LOADED_CHUNK_WIDTH && - newPos.y + MAP_CHUNK_HEIGHT <= lp.y + MAP_LOADED_CHUNK_HEIGHT && - newPos.z + MAP_CHUNK_DEPTH <= lp.z + MAP_LOADED_CHUNK_DEPTH - ) { - MAP.chunkPosition = newPos; - mapRebuildChunkOrder(); - errorOk(); - } - - // Render window fell outside the loaded area — re-centre the load - // window on the new render position (skin buffer on every side). - const chunkpos_t newLoadPos = { - newPos.x - MAP_CHUNK_SKIN, - newPos.y - MAP_CHUNK_SKIN, - newPos.z - MAP_CHUNK_SKIN - }; - // Separate loaded chunks into "keep" and "free" buckets. - chunkindex_t chunksFreed[MAP_LOADED_CHUNK_COUNT]; + chunkindex_t chunksFreed[MAP_CHUNK_COUNT]; uint32_t freedCount = 0; // Use a boolean grid so the inner load loop can check O(1). - bool_t posLoaded[MAP_LOADED_CHUNK_WIDTH][MAP_LOADED_CHUNK_HEIGHT] - [MAP_LOADED_CHUNK_DEPTH]; + bool_t posLoaded[MAP_CHUNK_WIDTH][MAP_CHUNK_HEIGHT][MAP_CHUNK_DEPTH]; memoryZero(posLoaded, sizeof(posLoaded)); - for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) { + for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { chunk_t *chunk = &MAP.chunks[i]; - chunkunit_t rx = chunk->position.x - newLoadPos.x; - chunkunit_t ry = chunk->position.y - newLoadPos.y; - chunkunit_t rz = chunk->position.z - newLoadPos.z; + chunkunit_t rx = chunk->position.x - newPos.x; + chunkunit_t ry = chunk->position.y - newPos.y; + chunkunit_t rz = chunk->position.z - newPos.z; if( - rx >= 0 && rx < MAP_LOADED_CHUNK_WIDTH && - ry >= 0 && ry < MAP_LOADED_CHUNK_HEIGHT && - rz >= 0 && rz < MAP_LOADED_CHUNK_DEPTH + rx >= 0 && rx < MAP_CHUNK_WIDTH && + ry >= 0 && ry < MAP_CHUNK_HEIGHT && + rz >= 0 && rz < MAP_CHUNK_DEPTH ) { posLoaded[rx][ry][rz] = true; } else { @@ -104,23 +71,22 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { } } - for(chunkunit_t z = 0; z < MAP_LOADED_CHUNK_DEPTH; z++) { - for(chunkunit_t y = 0; y < MAP_LOADED_CHUNK_HEIGHT; y++) { - for(chunkunit_t x = 0; x < MAP_LOADED_CHUNK_WIDTH; x++) { + for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) { + for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) { + for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) { if(posLoaded[x][y][z]) continue; assertTrue(freedCount > 0, "No free chunk slot available."); chunk_t *chunk = &MAP.chunks[chunksFreed[--freedCount]]; chunk->position = (chunkpos_t){ - newLoadPos.x + (chunkunit_t)x, - newLoadPos.y + (chunkunit_t)y, - newLoadPos.z + (chunkunit_t)z + newPos.x + (chunkunit_t)x, + newPos.y + (chunkunit_t)y, + newPos.z + (chunkunit_t)z }; errorChain(mapChunkLoad(chunk)); } } } - MAP.loadPosition = newLoadPos; MAP.chunkPosition = newPos; mapRebuildChunkOrder(); errorOk(); @@ -131,13 +97,16 @@ errorret_t mapUpdate() { } errorret_t mapDispose() { - for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) { + for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { mapChunkUnload(&MAP.chunks[i]); } errorOk(); } void mapChunkUnload(chunk_t *chunk) { + mapChunkLoadQueueRemove(chunk); + if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL; + for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) { if(chunk->entities[i] == 0xFF) continue; entity_t *entity = &ENTITIES[chunk->entities[i]]; @@ -169,6 +138,9 @@ void mapChunkUnload(chunk_t *chunk) { errorret_t mapChunkLoad(chunk_t *chunk) { if(!mapIsLoaded()) errorThrow("No map loaded"); + mapChunkLoadQueueRemove(chunk); + if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL; + if(chunk->dcfEntry != NULL) { eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded); eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError); @@ -196,6 +168,35 @@ errorret_t mapChunkLoad(chunk_t *chunk) { errorOk(); } + assertTrue( + MAP.loadQueueCount < MAP_CHUNK_COUNT, + "Chunk load queue overflow" + ); + MAP.loadQueue[MAP.loadQueueCount++] = chunk; + mapChunkLoadNext(); + errorOk(); +} + +void mapChunkLoadNext() { + if(MAP.loadingChunk != NULL) return; + if(MAP.loadQueueCount == 0) return; + + chunk_t *chunk = MAP.loadQueue[0]; + for(uint32_t i = 1; i < MAP.loadQueueCount; i++) { + MAP.loadQueue[i - 1] = MAP.loadQueue[i]; + } + MAP.loadQueueCount--; + MAP.loadingChunk = chunk; + + char_t name[64]; + stringFormat( + name, sizeof(name), + "chunks/%d_%d_%d.dcf", + (int32_t)chunk->position.x, + (int32_t)chunk->position.y, + (int32_t)chunk->position.z + ); + assetentry_t *entry = assetLock(name, ASSET_LOADER_TYPE_CHUNK, NULL); assertNotNull(entry, "Failed to get chunk asset entry"); chunk->dcfEntry = entry; @@ -206,16 +207,26 @@ errorret_t mapChunkLoad(chunk_t *chunk) { // a subscription that would never trigger. if(entry->state == ASSET_ENTRY_STATE_LOADED) { mapChunkLoaded(entry, chunk); - errorOk(); + return; } if(entry->state == ASSET_ENTRY_STATE_ERROR) { mapChunkLoadError(entry, chunk); - errorOk(); + return; } eventSubscribe(&entry->onLoaded, mapChunkLoaded, chunk); eventSubscribe(&entry->onError, mapChunkLoadError, chunk); - errorOk(); +} + +void mapChunkLoadQueueRemove(chunk_t *chunk) { + for(uint32_t i = 0; i < MAP.loadQueueCount; i++) { + if(MAP.loadQueue[i] != chunk) continue; + for(uint32_t j = i + 1; j < MAP.loadQueueCount; j++) { + MAP.loadQueue[j - 1] = MAP.loadQueue[j]; + } + MAP.loadQueueCount--; + return; + } } @@ -327,7 +338,7 @@ entity_t * mapSpawnEntity( void mapRebuildChunkOrder() { memoryZero(MAP.chunkOrder, sizeof(MAP.chunkOrder)); - for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) { + for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { chunk_t *chunk = &MAP.chunks[i]; const chunkpos_t rel = { chunk->position.x - MAP.chunkPosition.x, @@ -360,6 +371,9 @@ void mapChunkLoadError(void *params, void *user) { assetUnlockEntry(chunk->dcfEntry); chunk->dcfEntry = NULL; memorySet(chunk->tiles, 0x00, sizeof(chunk->tiles)); + + if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL; + mapChunkLoadNext(); } void mapChunkLoaded(void *params, void *user) { @@ -418,4 +432,7 @@ void mapChunkLoaded(void *params, void *user) { // chunk asset entry (and therefore its model locks) alive for as long as // this chunk_t is displaying it. Released in mapChunkUnload instead. chunk->meshCount = meshCount; + + if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL; + mapChunkLoadNext(); } diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h index c56cecd3..f8b09bf8 100644 --- a/src/dusk/rpg/overworld/map.h +++ b/src/dusk/rpg/overworld/map.h @@ -15,10 +15,15 @@ typedef struct map_s { bool_t loaded; - chunk_t chunks[MAP_LOADED_CHUNK_COUNT]; + chunk_t chunks[MAP_CHUNK_COUNT]; chunk_t *chunkOrder[MAP_CHUNK_COUNT]; chunkpos_t chunkPosition; - chunkpos_t loadPosition; + + // Only one chunk may be mid-load (asset locked & awaiting onLoaded/ + // onError) at any given time - everything else waits here in FIFO order. + chunk_t *loadQueue[MAP_CHUNK_COUNT]; + uint32_t loadQueueCount; + chunk_t *loadingChunk; } map_t; extern map_t MAP; @@ -74,6 +79,21 @@ void mapChunkUnload(chunk_t* chunk); */ errorret_t mapChunkLoad(chunk_t* chunk); +/** + * Starts loading the next queued chunk, if no chunk is currently mid-load. + * Called after mapChunkLoad enqueues a chunk, and again after the + * currently-loading chunk finishes (or is unloaded) to advance the queue. + */ +void mapChunkLoadNext(); + +/** + * Removes a chunk from the load queue if present. Used when a chunk is + * re-queued or unloaded before its turn to load has come up. + * + * @param chunk The chunk to remove from the load queue. + */ +void mapChunkLoadQueueRemove(chunk_t *chunk); + /** * Callback invoked when a chunk DCF asset fails to load. Fills the * chunk tiles with TILE_SHAPE_GROUND as a fallback. diff --git a/src/dusk/rpg/overworld/maparea.c b/src/dusk/rpg/overworld/maparea.c index a0e4da21..95efe628 100644 --- a/src/dusk/rpg/overworld/maparea.c +++ b/src/dusk/rpg/overworld/maparea.c @@ -73,7 +73,7 @@ bool_t mapAreaIsChunkOverlappingOrInside( bool_t mapAreaCanUnload(const maparea_t *area) { assertNotNull(area, "Map area pointer cannot be NULL"); - for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) { + for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { if(mapAreaIsChunkOverlappingOrInside(area, &MAP.chunks[i])) return false; } diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h index f05a0069..32758288 100644 --- a/src/dusk/rpg/overworld/worldpos.h +++ b/src/dusk/rpg/overworld/worldpos.h @@ -30,16 +30,6 @@ #define MAP_CHUNK_DEPTH 4 #define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH) -// Extra chunks loaded on every side beyond the render window. -// The render window can drift MAP_CHUNK_SKIN chunks in any direction -// before a load/unload cycle is triggered. -#define MAP_CHUNK_SKIN 1 -#define MAP_LOADED_CHUNK_WIDTH (MAP_CHUNK_WIDTH + MAP_CHUNK_SKIN) -#define MAP_LOADED_CHUNK_HEIGHT (MAP_CHUNK_HEIGHT + MAP_CHUNK_SKIN) -#define MAP_LOADED_CHUNK_DEPTH (MAP_CHUNK_DEPTH + MAP_CHUNK_SKIN) -#define MAP_LOADED_CHUNK_COUNT \ - (MAP_LOADED_CHUNK_WIDTH * MAP_LOADED_CHUNK_HEIGHT * MAP_LOADED_CHUNK_DEPTH) - #define ENTITY_COUNT 32 typedef int16_t worldunit_t;