From 9688e88a7cc3b69c304b280a5e14ebc30aa98162 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Wed, 9 Sep 2026 14:05:19 -0500 Subject: [PATCH] Load raw chunk JSON at runtime instead of baked DCF/DMF Experimental: skip the offline tools/asset/chunk bake step entirely. The chunk asset loader now parses the same JSON schema tools/asset/chunk already consumed (tiles/meshes/entities/areas) directly at load time, generating the terrain mesh in C instead of reading pre-baked .dcf/.dmf bytes. The raw assetsraw/maps/*/chunks/*.json files are now the shipped source directly under assets/, replacing the old baked chunk/mesh/model output. Also drops MAP_CHUNK_LOAD_CONCURRENCY to 1 for now, per PSP hardware testing (8 crashed, 4 untested further - 1 is the known-safe value). Co-Authored-By: Claude Sonnet 5 --- assets/maps/overworld/chunks/-1_0_0.dcf | Bin 2252 -> 0 bytes assets/maps/overworld/chunks/0_0_0.dcf | Bin 2199 -> 0 bytes assets/maps/overworld/chunks/0_1_0.dcf | Bin 2251 -> 0 bytes assets/maps/overworld/chunks/1_0_0.dcf | Bin 2339 -> 0 bytes assets/maps/overworld/chunks/1_1_0.dcf | Bin 2251 -> 0 bytes assets/maps/overworld/chunks/2_0_0.dcf | Bin 2119 -> 0 bytes .../maps/overworld/chunks/chunk_-1_0_0.json | 0 .../maps/overworld/chunks/chunk_0_0_0.json | 0 .../maps/overworld/chunks/chunk_0_1_0.json | 0 .../maps/overworld/chunks/chunk_1_0_0.json | 0 .../maps/overworld/chunks/chunk_1_1_0.json | 0 .../maps/overworld/chunks/chunk_2_0_0.json | 0 .../meshes/chunks/chunk_-1_0_0_0.dmf | Bin 26172 -> 0 bytes .../overworld/meshes/chunks/chunk_0_0_0_0.dmf | Bin 28932 -> 0 bytes .../overworld/meshes/chunks/chunk_0_1_0_0.dmf | Bin 25332 -> 0 bytes .../overworld/meshes/chunks/chunk_1_0_0_0.dmf | Bin 27492 -> 0 bytes .../overworld/meshes/chunks/chunk_1_1_0_0.dmf | Bin 26652 -> 0 bytes .../overworld/meshes/chunks/chunk_2_0_0_0.dmf | Bin 30252 -> 0 bytes .../models/chunks/chunk_-1_0_0_0.json | 10 - .../models/chunks/chunk_0_0_0_0.json | 10 - .../models/chunks/chunk_0_1_0_0.json | 10 - .../models/chunks/chunk_1_0_0_0.json | 10 - .../models/chunks/chunk_1_1_0_0.json | 10 - .../models/chunks/chunk_2_0_0_0.json | 10 - assetsraw/maps/overworld/map.json | 1 - .../asset/loader/chunk/assetchunkloader.c | 500 +++++++++++++----- .../asset/loader/chunk/assetchunkloader.h | 45 +- src/dusk/rpg/overworld/map.c | 4 +- src/dusk/rpg/overworld/map.h | 2 +- 29 files changed, 412 insertions(+), 200 deletions(-) delete mode 100644 assets/maps/overworld/chunks/-1_0_0.dcf delete mode 100644 assets/maps/overworld/chunks/0_0_0.dcf delete mode 100644 assets/maps/overworld/chunks/0_1_0.dcf delete mode 100644 assets/maps/overworld/chunks/1_0_0.dcf delete mode 100644 assets/maps/overworld/chunks/1_1_0.dcf delete mode 100644 assets/maps/overworld/chunks/2_0_0.dcf rename {assetsraw => assets}/maps/overworld/chunks/chunk_-1_0_0.json (100%) rename {assetsraw => assets}/maps/overworld/chunks/chunk_0_0_0.json (100%) rename {assetsraw => assets}/maps/overworld/chunks/chunk_0_1_0.json (100%) rename {assetsraw => assets}/maps/overworld/chunks/chunk_1_0_0.json (100%) rename {assetsraw => assets}/maps/overworld/chunks/chunk_1_1_0.json (100%) rename {assetsraw => assets}/maps/overworld/chunks/chunk_2_0_0.json (100%) delete mode 100644 assets/maps/overworld/meshes/chunks/chunk_-1_0_0_0.dmf delete mode 100644 assets/maps/overworld/meshes/chunks/chunk_0_0_0_0.dmf delete mode 100644 assets/maps/overworld/meshes/chunks/chunk_0_1_0_0.dmf delete mode 100644 assets/maps/overworld/meshes/chunks/chunk_1_0_0_0.dmf delete mode 100644 assets/maps/overworld/meshes/chunks/chunk_1_1_0_0.dmf delete mode 100644 assets/maps/overworld/meshes/chunks/chunk_2_0_0_0.dmf delete mode 100644 assets/maps/overworld/models/chunks/chunk_-1_0_0_0.json delete mode 100644 assets/maps/overworld/models/chunks/chunk_0_0_0_0.json delete mode 100644 assets/maps/overworld/models/chunks/chunk_0_1_0_0.json delete mode 100644 assets/maps/overworld/models/chunks/chunk_1_0_0_0.json delete mode 100644 assets/maps/overworld/models/chunks/chunk_1_1_0_0.json delete mode 100644 assets/maps/overworld/models/chunks/chunk_2_0_0_0.json delete mode 100644 assetsraw/maps/overworld/map.json diff --git a/assets/maps/overworld/chunks/-1_0_0.dcf b/assets/maps/overworld/chunks/-1_0_0.dcf deleted file mode 100644 index 51344b7066b2ca4562ffa3f63a176aa02aa234ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2252 zcmZ>9c4J^=U|?VbVh|WCG{iNyNm{!Tx6KflQTyo=4zvzudYO-&p6F#S%$|X^ALb51 zG`c$p@d=qnsyre4(B(-rpOAWV^9k_@nKvpA3Bl2Dz#k4QxrqhE`uSz4MdkTLIVt+N z`6;P6#rnw^rFq#9c4J^=U|?VbVh|W;8sZAVB(2;@*k*`|;c7pv{6#Hu*`N``L?X=&RZpoqn32o@ znMEuHh($ diff --git a/assets/maps/overworld/chunks/0_1_0.dcf b/assets/maps/overworld/chunks/0_1_0.dcf deleted file mode 100644 index 5048d3e434abc0519dcbec2c1cd3b5be3e4464bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2251 zcmeH_(F(#K6oyYwuaj%*qPrGFuOZc>tu~Fx>b6HubBB>&$O5}jIR1M4JV$=qZTBz( zfQbq{{;lQ};4vpL+mc;7tML3Q23b6Z`!Z{D|_&BeWOIQkHM&<@}~ zFm4Pp=0oYw*{nkKjeWxUJ#7v*NyMGC57E{IX+*(lNlstA*ixm9hU-a;? qtLmcSXGu!YNk&(6iR^O?paa0cW;8=K*(fak!lpDuHdox6SNIFw13BgZ diff --git a/assets/maps/overworld/chunks/1_0_0.dcf b/assets/maps/overworld/chunks/1_0_0.dcf deleted file mode 100644 index ed0d0ba7d3fa764049cdf0c63a356ff8388679d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2339 zcmZ>9c4J^=U|?VbVh|W;8sZ8}l3wn@v>76T%YJnAxYW@~e5Bh?t8k^2x#;l)Z0F{9-F1048<}x_qQEM1)2s76Js7?W&T2q9%0YG&J Jz;0k*008ijRJZ^D diff --git a/assets/maps/overworld/chunks/1_1_0.dcf b/assets/maps/overworld/chunks/1_1_0.dcf deleted file mode 100644 index aca3ec243ec5c619398d4f2f5d3366fb4740428e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2251 zcmZ>9c4J^=U|?VbVh|XmQNsbkBTUjN+z8u@>Ry;T(DlK@Nu|-v!No^67hN5?Jh6Oq zb8zv|%|lm*E>A2U-5gweV$B;>2MNK^a2TB7Fq*yxXS~olzp&&c78L8}m!%e!=NILq z=;!9Aq~;XsCufxAWkacWLm)JWH_*!}&d)!&Os#?=P$@pO#_@(QwGH+_wLtYC000B%Ij{f# diff --git a/assets/maps/overworld/chunks/2_0_0.dcf b/assets/maps/overworld/chunks/2_0_0.dcf deleted file mode 100644 index 61c1694666ab5754293b341bf3a18447ce8550b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2119 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 H|KI%|fwA31 diff --git a/assets/maps/overworld/meshes/chunks/chunk_0_0_0_0.dmf b/assets/maps/overworld/meshes/chunks/chunk_0_0_0_0.dmf deleted file mode 100644 index 8e70f8fdfcfd8e088383447582fede2b41731f34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28932 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 diff --git a/assets/maps/overworld/meshes/chunks/chunk_0_1_0_0.dmf b/assets/maps/overworld/meshes/chunks/chunk_0_1_0_0.dmf deleted file mode 100644 index 5d16a93aaa8807b47435d367386076d40252f544..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25332 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 pmD_$imWFbzKfdFT0mW#S5bgOK;V6 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 diff --git a/assets/maps/overworld/meshes/chunks/chunk_1_1_0_0.dmf b/assets/maps/overworld/meshes/chunks/chunk_1_1_0_0.dmf deleted file mode 100644 index e9e5aace3863ee5accc34dd7e5511db1f9238f52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26652 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 diff --git a/assets/maps/overworld/meshes/chunks/chunk_2_0_0_0.dmf b/assets/maps/overworld/meshes/chunks/chunk_2_0_0_0.dmf deleted file mode 100644 index 7d390ce7063abdb7dd71caa3343fe5795eea1a05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30252 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.chunk.state != ASSET_CHUNK_LOADING_STATE_READ_FILE) { - errorOk(); +static bool_t assetChunkTilesHaveTerrain(const tile_t *tiles) { + for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) { + if(tiles[i].shape != TILE_SHAPE_NULL) return true; + } + return false; +} + +// Reads the "tiles" array - one quad's worth of column data per entry, +// last one in the array wins per (x, y) column - matching +// tools/asset/chunk/__main__.py:390-403. +static void assetChunkParseTiles(yyjson_val *root, tile_t *tiles) { + memoryZero(tiles, CHUNK_TILE_COUNT * sizeof(tile_t)); + + yyjson_val *tilesArr = yyjson_obj_get(root, "tiles"); + if(!yyjson_is_arr(tilesArr)) return; + + size_t idx, len; + yyjson_val *tileVal; + yyjson_arr_foreach(tilesArr, idx, len, tileVal) { + yyjson_val *posArr = yyjson_obj_get(tileVal, "pos"); + if(!yyjson_is_arr(posArr) || yyjson_arr_size(posArr) < 3) continue; + + int32_t x = (int32_t)yyjson_get_int(yyjson_arr_get(posArr, 0)); + int32_t y = (int32_t)yyjson_get_int(yyjson_arr_get(posArr, 1)); + int32_t z = (int32_t)yyjson_get_int(yyjson_arr_get(posArr, 2)); + if(x < 0 || x >= CHUNK_WIDTH || y < 0 || y >= CHUNK_HEIGHT) continue; + + int32_t type = (int32_t)yyjson_get_int(yyjson_obj_get(tileVal, "type")); + tiles[x + y * CHUNK_WIDTH] = (tile_t){ + .shape = (tileshape_t)type, .z = (uint8_t)z + }; + } +} + +// Resolves "meshes" (external building references, by bare filename) into +// out->modelNames/meshOffsets starting at *ioIndex, advancing it as +// entries are written. Filenames are resolved as +// "models/buildings/.json" - a hardcoded stand-in for +// tools/asset/chunk/__main__.py's find_model() recursive search, valid +// as long as every building model lives flat under that one directory +// (true today - see the raw-json-maps plan notes for the tradeoff). +static void assetChunkParseMeshes( + yyjson_val *root, assetchunkoutput_t *out, uint8_t *ioIndex +) { + yyjson_val *meshesArr = yyjson_obj_get(root, "meshes"); + if(!yyjson_is_arr(meshesArr)) return; + + size_t idx, len; + yyjson_val *meshVal; + yyjson_arr_foreach(meshesArr, idx, len, meshVal) { + if(*ioIndex >= CHUNK_MESH_COUNT_MAX) break; + + yyjson_val *fileVal = yyjson_obj_get(meshVal, "file"); + if(!fileVal || !yyjson_is_str(fileVal)) continue; + + char_t baseName[CHUNK_MESH_NAME_MAX]; + stringCopy(baseName, yyjson_get_str(fileVal), sizeof(baseName)); + char_t *dot = stringFindLastChar(baseName, '.'); + if(dot != NULL) *dot = '\0'; + + uint8_t m = *ioIndex; + stringFormat( + out->modelNames[m], CHUNK_MESH_NAME_MAX, + "models/buildings/%s.json", baseName + ); + + vec3 offset = {0.0f, 0.0f, 0.0f}; + yyjson_val *posArr = yyjson_obj_get(meshVal, "pos"); + if(yyjson_is_arr(posArr) && yyjson_arr_size(posArr) >= 3) { + offset[0] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 0)); + offset[1] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 1)); + offset[2] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 2)); + } + memoryCopy(out->meshOffsets[m], offset, sizeof(vec3)); + + (*ioIndex)++; + } +} + +static void assetChunkParseEntities(yyjson_val *root, assetchunkoutput_t *out) { + out->entitySpawnCount = 0; + + yyjson_val *arr = yyjson_obj_get(root, "entities"); + if(!yyjson_is_arr(arr)) return; + + size_t idx, len; + yyjson_val *val; + yyjson_arr_foreach(arr, idx, len, val) { + if(out->entitySpawnCount >= CHUNK_ENTITY_SPAWN_COUNT_MAX) break; + + const char_t *type = yyjson_get_str(yyjson_obj_get(val, "type")); + yyjson_val *posArr = yyjson_obj_get(val, "pos"); + if(type == NULL || !yyjson_is_arr(posArr) || yyjson_arr_size(posArr) < 3) { + continue; + } + worldpos_t pos = { + .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 0)), + .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 1)), + .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 2)), + }; + + chunkentityspawn_t *spawn = &out->entitySpawns[out->entitySpawnCount]; + if(stringEquals(type, "global")) { + spawn->kind = CHUNK_ENTITY_SPAWN_KIND_GLOBAL; + spawn->globalId = + (uint16_t)yyjson_get_int(yyjson_obj_get(val, "globalId")); + spawn->itemId = 0; + spawn->itemQuantity = 0; + } else if(stringEquals(type, "item")) { + spawn->kind = CHUNK_ENTITY_SPAWN_KIND_ITEM; + spawn->globalId = 0; + spawn->itemId = (uint16_t)yyjson_get_int(yyjson_obj_get(val, "itemId")); + spawn->itemQuantity = + (uint8_t)yyjson_get_int(yyjson_obj_get(val, "quantity")); + } else { + continue; + } + spawn->position = pos; + out->entitySpawnCount++; + } +} + +static void assetChunkParseAreas(yyjson_val *root, assetchunkoutput_t *out) { + out->areaSpawnCount = 0; + + yyjson_val *arr = yyjson_obj_get(root, "areas"); + if(!yyjson_is_arr(arr)) return; + + size_t idx, len; + yyjson_val *val; + yyjson_arr_foreach(arr, idx, len, val) { + if(out->areaSpawnCount >= CHUNK_AREA_COUNT_MAX) break; + + yyjson_val *minArr = yyjson_obj_get(val, "min"); + yyjson_val *maxArr = yyjson_obj_get(val, "max"); + if( + !yyjson_is_arr(minArr) || yyjson_arr_size(minArr) < 3 || + !yyjson_is_arr(maxArr) || yyjson_arr_size(maxArr) < 3 + ) continue; + + chunkareaspawn_t *area = &out->areaSpawns[out->areaSpawnCount]; + area->min = (worldpos_t){ + .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 0)), + .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 1)), + .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 2)), + }; + area->max = (worldpos_t){ + .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 0)), + .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 1)), + .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 2)), + }; + area->callbackId = + (uint16_t)yyjson_get_int(yyjson_obj_get(val, "callbackId")); + area->notify = (uint8_t)yyjson_get_int(yyjson_obj_get(val, "notify")); + area->trigger = (uint8_t)yyjson_get_int(yyjson_obj_get(val, "trigger")); + out->areaSpawnCount++; + } +} + +// Six vertices (2 CCW triangles) for one tile's quad, matching +// tools/asset/chunk/__main__.py's _tile_quad. Corner order: SW=(x,y), +// SE=(x+1,y), NE=(x+1,y+1), NW=(x,y+1). NORTH=+Y, EAST=+X. +static void assetChunkAppendTileQuad( + meshvertex_t *verts, uint32_t *vertCount, + const uint32_t x, const uint32_t y, + const float_t sw, const float_t se, const float_t ne, const float_t nw +) { + const float_t u0 = (float_t)x / (float_t)CHUNK_WIDTH; + const float_t u1 = (float_t)(x + 1) / (float_t)CHUNK_WIDTH; + const float_t v0 = (float_t)y / (float_t)CHUNK_HEIGHT; + const float_t v1 = (float_t)(y + 1) / (float_t)CHUNK_HEIGHT; + const float_t fx = (float_t)x; + const float_t fy = (float_t)y; + + meshvertex_t *v = verts + *vertCount; + v[0] = (meshvertex_t){ .uv = {u0, v0}, .pos = {fx, fy, sw} }; + v[1] = (meshvertex_t){ .uv = {u1, v0}, .pos = {fx + 1, fy, se} }; + v[2] = (meshvertex_t){ .uv = {u1, v1}, .pos = {fx + 1, fy + 1, ne} }; + v[3] = (meshvertex_t){ .uv = {u0, v0}, .pos = {fx, fy, sw} }; + v[4] = (meshvertex_t){ .uv = {u1, v1}, .pos = {fx + 1, fy + 1, ne} }; + v[5] = (meshvertex_t){ .uv = {u0, v1}, .pos = {fx, fy + 1, nw} }; + *vertCount += 6; +} + +// Builds a mesh_t straight from the tile grid - one quad per non-null +// tile column, shaped by CHUNK_RAMP_CORNERS - the runtime equivalent of +// tools/asset/chunk/__main__.py's build_terrain_verts. Only called once +// assetChunkTilesHaveTerrain has confirmed at least one real tile exists. +// Mirrors assetMeshLoaderSync's meshInit/meshFlush/free-after-upload +// sequence (assetmeshloader.c) since this produces the same +// assetmeshoutput_t shape, just from generated verts instead of a file. +static errorret_t assetChunkBuildTerrain( + const tile_t *tiles, assetmeshoutput_t *out +) { + meshvertex_t *verts = memoryAllocate( + CHUNK_TILE_COUNT * 6 * sizeof(meshvertex_t) + ); + uint32_t vertCount = 0; + + for(uint32_t y = 0; y < CHUNK_HEIGHT; y++) { + for(uint32_t x = 0; x < CHUNK_WIDTH; x++) { + const tile_t *tile = &tiles[x + y * CHUNK_WIDTH]; + if(tile->shape == TILE_SHAPE_NULL) continue; + + const float_t *corners = CHUNK_RAMP_CORNERS[tile->shape]; + const float_t fz = (float_t)tile->z; + assetChunkAppendTileQuad( + verts, &vertCount, x, y, + (fz + corners[0]) * WORLD_LAYER_HEIGHT, + (fz + corners[1]) * WORLD_LAYER_HEIGHT, + (fz + corners[2]) * WORLD_LAYER_HEIGHT, + (fz + corners[3]) * WORLD_LAYER_HEIGHT + ); + } } - assertNull(loading->loading.chunk.data, "Data already defined?"); + assertTrue(vertCount > 0, "Terrain build called with no terrain tiles."); - assetfile_t *file = &loading->loading.chunk.file; - assetLoaderErrorChain(loading, - assetFileInit(file, loading->entry->name, NULL, NULL) + out->vertices = verts; + errorret_t ret = meshInit( + &out->mesh, MESH_PRIMITIVE_TYPE_TRIANGLES, (int32_t)vertCount, verts ); + if(errorIsNotOk(ret)) { + memoryFree(out->vertices); + out->vertices = NULL; + errorChain(ret); + } + out->meshInitialized = true; - 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 chunk file." - ); + ret = meshFlush(&out->mesh, 0, (int32_t)vertCount); + if(errorIsNotOk(ret)) { + meshDispose(&out->mesh); + out->meshInitialized = false; + memoryFree(out->vertices); + out->vertices = NULL; + errorChain(ret); + } + + #if defined(DUSK_OPENGL) && !defined(DUSK_OPENGL_LEGACY) + memoryFree(out->vertices); + out->vertices = NULL; + #endif - loading->loading.chunk.data = data; - loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_PARSE; - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; errorOk(); } @@ -55,8 +302,8 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) { 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; + loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_PARSE; + loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; errorOk(); case ASSET_CHUNK_LOADING_STATE_PARSE: @@ -69,9 +316,7 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) { out->modelEntries[m] = assetLock( out->modelNames[m], ASSET_LOADER_TYPE_MODEL, NULL ); - assertNotNull( - out->modelEntries[m], "Failed to lock model." - ); + assertNotNull(out->modelEntries[m], "Failed to lock model."); loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; errorOk(); } @@ -93,126 +338,103 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) { errorOk(); } - uint8_t *data = loading->loading.chunk.data; - assertNotNull(data, "Chunk data should have been loaded by now."); - - if(data[0] != 'D' || data[1] != 'C' || data[2] != 'F') { - memoryFree(data); - assetLoaderErrorThrow(loading, "Invalid chunk file header"); - } - - uint32_t version = endianLittleToHost32(*(uint32_t *)(data + 4)); - if(version != ASSET_CHUNK_FILE_VERSION) { - memoryFree(data); + // --- PARSE: lock+require the chunk's own JSON as a sub-asset, exactly + // like assetmodelloader.c's ASSET_MODEL_LOADING_STATE_LOCK_ASSETS does + // for its model descriptor, then read everything out of it before + // unlocking (the yyjson_doc/vals only stay valid while jsonEntry does). + char_t jsonKey[ASSET_FILE_NAME_MAX]; + stringFormat(jsonKey, sizeof(jsonKey), "json:%s", loading->entry->name); + assetloaderinput_t jsonInput; + memoryZero(&jsonInput, sizeof(jsonInput)); + stringCopy( + jsonInput.json.path, loading->entry->name, ASSET_FILE_NAME_MAX + ); + assetentry_t *jsonEntry = + assetLock(jsonKey, ASSET_LOADER_TYPE_JSON, &jsonInput); + errorret_t jsonRet = assetRequireLoaded(jsonEntry); + if(errorIsNotOk(jsonRet)) { + assetUnlockEntry(jsonEntry); assetLoaderErrorThrow( - loading, "Unsupported chunk file version %u", version + loading, "Failed to load chunk JSON: %s", loading->entry->name ); } - size_t offset = 8; + yyjson_val *root = yyjson_doc_get_root(jsonEntry->data.json); - size_t tileSize = CHUNK_TILE_COUNT * sizeof(tile_t); - out->tiles = memoryAllocate(tileSize); - memoryCopy(out->tiles, data + offset, tileSize); - offset += tileSize; + tile_t tiles[CHUNK_TILE_COUNT]; + assetChunkParseTiles(root, tiles); + out->hasTerrain = assetChunkTilesHaveTerrain(tiles); - for(size_t t = 0; t < CHUNK_TILE_COUNT; t++) { - uint32_t *shape = (uint32_t *)&out->tiles[t].shape; - *shape = endianLittleToHost32(*shape); - } + uint8_t meshIndex = out->hasTerrain ? 1 : 0; + assetChunkParseMeshes(root, out, &meshIndex); + out->meshCount = meshIndex; - out->meshCount = data[offset]; - offset += sizeof(uint8_t); - assertTrue( - out->meshCount <= CHUNK_MESH_COUNT_MAX, - "Chunk mesh count exceeds maximum." - ); + assetChunkParseEntities(root, out); + assetChunkParseAreas(root, out); - for(uint8_t m = 0; m < out->meshCount; m++) { - uint8_t nameLen = 0; - while( - data[offset + nameLen] != '\0' && - nameLen < CHUNK_MESH_NAME_MAX - 1 - ) { - nameLen++; - } - memoryCopy(out->modelNames[m], data + offset, nameLen); - out->modelNames[m][nameLen] = '\0'; - offset += nameLen + 1; + assetUnlockEntry(jsonEntry); - memoryCopy(out->meshOffsets[m], data + offset, sizeof(vec3)); - offset += sizeof(vec3); - out->meshOffsets[m][0] = endianLittleToHostFloat(out->meshOffsets[m][0]); - out->meshOffsets[m][1] = endianLittleToHostFloat(out->meshOffsets[m][1]); - out->meshOffsets[m][2] = endianLittleToHostFloat(out->meshOffsets[m][2]); - } + out->tiles = memoryAllocate(CHUNK_TILE_COUNT * sizeof(tile_t)); + memoryCopy(out->tiles, tiles, CHUNK_TILE_COUNT * sizeof(tile_t)); - out->entitySpawnCount = data[offset]; - offset += sizeof(uint8_t); - assertTrue( - out->entitySpawnCount <= CHUNK_ENTITY_SPAWN_COUNT_MAX, - "Chunk entity spawn count exceeds maximum." - ); - - for(uint8_t s = 0; s < out->entitySpawnCount; s++) { - chunkentityspawn_t *spawn = &out->entitySpawns[s]; - spawn->kind = (chunkentityspawnkind_t)data[offset]; - offset += sizeof(uint8_t); - - uint16_t a; - memoryCopy(&a, data + offset, sizeof(uint16_t)); - a = endianLittleToHost16(a); - offset += sizeof(uint16_t); - - uint8_t b = data[offset]; - offset += sizeof(uint8_t); - - if(spawn->kind == CHUNK_ENTITY_SPAWN_KIND_ITEM) { - spawn->globalId = 0; - spawn->itemId = a; - spawn->itemQuantity = b; - } else { - spawn->globalId = a; - spawn->itemId = 0; - spawn->itemQuantity = 0; + // Terrain: build the mesh, then wrap it in synthetic (non-cache, + // non-refcounted) mesh/model entries so modelEntries[0] behaves exactly + // like a real file-backed model entry for every downstream consumer - + // see the header's doc comment on hasTerrain/terrainMeshEntry. + if(out->hasTerrain) { + assetmeshoutput_t terrainMesh; + memoryZero(&terrainMesh, sizeof(terrainMesh)); + errorret_t terrainRet = assetChunkBuildTerrain(out->tiles, &terrainMesh); + if(errorIsNotOk(terrainRet)) { + memoryFree(out->tiles); + out->tiles = NULL; + assetLoaderErrorThrow( + loading, "Failed to build chunk terrain mesh: %s", + loading->entry->name + ); } - spawn->position = worldPosReadLE(data, &offset); + assetloaderinput_t texInput = { .texture = TEXTURE_FORMAT_RGBA }; + assetentry_t *texEntry = assetLock( + ASSET_CHUNK_TERRAIN_TEXTURE, ASSET_LOADER_TYPE_TEXTURE, &texInput + ); + errorret_t texRet = assetRequireLoaded(texEntry); + if(errorIsNotOk(texRet)) { + assetUnlockEntry(texEntry); + meshDispose(&terrainMesh.mesh); + memoryFree(out->tiles); + out->tiles = NULL; + assetLoaderErrorThrow( + loading, "Failed to load chunk terrain texture: %s", + loading->entry->name + ); + } + out->terrainTexEntry = texEntry; + + out->terrainMeshEntry = memoryAllocate(sizeof(assetentry_t)); + memoryZero(out->terrainMeshEntry, sizeof(assetentry_t)); + out->terrainMeshEntry->type = ASSET_LOADER_TYPE_MESH; + out->terrainMeshEntry->state = ASSET_ENTRY_STATE_LOADED; + out->terrainMeshEntry->data.mesh = terrainMesh; + + out->terrainModelEntry = memoryAllocate(sizeof(assetentry_t)); + memoryZero(out->terrainModelEntry, sizeof(assetentry_t)); + out->terrainModelEntry->type = ASSET_LOADER_TYPE_MODEL; + out->terrainModelEntry->state = ASSET_ENTRY_STATE_LOADED; + out->terrainModelEntry->data.model.meshEntry = out->terrainMeshEntry; + out->terrainModelEntry->data.model.texEntry = out->terrainTexEntry; + out->terrainModelEntry->data.model.color = COLOR_WHITE; + + out->modelEntries[0] = out->terrainModelEntry; } - out->areaSpawnCount = data[offset]; - offset += sizeof(uint8_t); - assertTrue( - out->areaSpawnCount <= CHUNK_AREA_COUNT_MAX, - "Chunk area spawn count exceeds maximum." - ); - - for(uint8_t s = 0; s < out->areaSpawnCount; s++) { - chunkareaspawn_t *area = &out->areaSpawns[s]; - area->min = worldPosReadLE(data, &offset); - area->max = worldPosReadLE(data, &offset); - - uint16_t callbackId; - memoryCopy(&callbackId, data + offset, sizeof(uint16_t)); - area->callbackId = endianLittleToHost16(callbackId); - offset += sizeof(uint16_t); - - area->notify = data[offset]; - offset += sizeof(uint8_t); - area->trigger = data[offset]; - offset += sizeof(uint8_t); - } - - memoryFree(data); - loading->loading.chunk.data = NULL; - 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->loading.chunk.modelIndex = out->hasTerrain ? 1 : 0; loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; errorOk(); } @@ -231,8 +453,36 @@ errorret_t assetChunkDispose(assetentry_t *entry) { for(uint8_t m = 0; m < out->meshCount; m++) { if(out->modelEntries[m] == NULL) continue; + if(out->hasTerrain && m == 0) continue; // synthetic - freed below. assetUnlockEntry(out->modelEntries[m]); out->modelEntries[m] = NULL; } + + if(out->hasTerrain) { + if(out->terrainMeshEntry != NULL) { + assetmeshoutput_t *meshOut = &out->terrainMeshEntry->data.mesh; + if(meshOut->meshInitialized) { + meshDispose(&meshOut->mesh); + meshOut->meshInitialized = false; + } + if(meshOut->vertices != NULL) { + memoryFree(meshOut->vertices); + meshOut->vertices = NULL; + } + memoryFree(out->terrainMeshEntry); + out->terrainMeshEntry = NULL; + } + if(out->terrainModelEntry != NULL) { + memoryFree(out->terrainModelEntry); + out->terrainModelEntry = NULL; + } + if(out->terrainTexEntry != NULL) { + assetUnlockEntry(out->terrainTexEntry); + out->terrainTexEntry = NULL; + } + out->modelEntries[0] = NULL; + out->hasTerrain = false; + } + errorOk(); } diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.h b/src/dusk/asset/loader/chunk/assetchunkloader.h index ef871ab6..38e76032 100644 --- a/src/dusk/asset/loader/chunk/assetchunkloader.h +++ b/src/dusk/asset/loader/chunk/assetchunkloader.h @@ -9,8 +9,6 @@ #include "asset/assetfile.h" #include "rpg/overworld/chunk.h" -#define ASSET_CHUNK_FILE_VERSION 5 - typedef struct assetloading_s assetloading_t; typedef struct assetentry_s assetentry_t; @@ -20,16 +18,17 @@ typedef struct { 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; typedef struct { - assetfile_t file; assetchunkloadingstate_t state; - uint8_t *data; + + // Index into assetchunkoutput_t.modelNames/modelEntries the external + // (non-terrain) model polling loop has reached - see + // ASSET_CHUNK_LOADING_STATE_LOAD_MODELS. uint8_t modelIndex; } assetchunkloaderloading_t; @@ -61,6 +60,27 @@ typedef struct { vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX]; + // Terrain mesh generated at load time from the tile grid (no baked + // .dmf/.json - see assetChunkBuildTerrain in assetchunkloader.c). NULL + // (with hasTerrain false) for a chunk with no non-null tiles, in which + // case modelEntries[0] (if meshCount > 0) is the first external mesh + // instead - matches the old baked pipeline's "only write a terrain + // model if it has vertices" behavior. + // + // terrainMeshEntry/terrainModelEntry are heap-allocated, plain + // assetentry_t values that are never passed to assetLock/assetGetEntry/ + // assetUnlockEntry (only a real, name-keyed, ref-counted entry may be) + // - they exist purely so modelEntries[0] can point at something with + // the same ->state/->data.model/->data.mesh.mesh shape every other + // model/mesh entry has, so chunk.c/map.c/sceneoverworld.c don't need to + // know terrain isn't file-backed. Freed directly (not assetUnlockEntry) + // in assetChunkDispose. terrainTexEntry is a real, ref-counted texture + // entry (the shared "tiles.png") and must be assetUnlockEntry'd there. + bool_t hasTerrain; + assetentry_t *terrainMeshEntry; + assetentry_t *terrainModelEntry; + assetentry_t *terrainTexEntry; + uint8_t entitySpawnCount; chunkentityspawn_t entitySpawns[CHUNK_ENTITY_SPAWN_COUNT_MAX]; @@ -69,9 +89,10 @@ typedef struct { } assetchunkoutput_t; /** - * Asynchronous loader for chunk assets. Reads the raw DCF file bytes into - * the loading buffer so the sync phase can parse without blocking the - * main thread on I/O. + * Async loader stub for chunk assets. Chunk loading is performed entirely + * on the main thread (it delegates to the JSON asset loader and to + * meshInit/meshFlush, both main-thread-only); this callback is never + * reached. * * @param loading Loading information for the asset being loaded. * @return Error code indicating success or failure of the load operation. @@ -79,9 +100,11 @@ typedef struct { errorret_t assetChunkLoaderAsync(assetloading_t *loading); /** - * Synchronous loader for chunk assets. Validates the DCF binary previously - * read by the async phase and populates the output assetchunkoutput_t with - * tile data and model paths. + * Synchronous loader for chunk assets. Locks the chunk's JSON descriptor + * as a sub-asset, parses tiles/external mesh references/entity spawns/ + * area spawns, generates the terrain mesh directly from the tile grid, + * and locks/waits for every referenced model (and the terrain's shared + * tileset texture) to finish loading. * * @param loading Loading information for the asset being loaded. * @return Error code indicating success or failure of the load operation. diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index a0b16ca6..c22dfc8d 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -226,7 +226,7 @@ errorret_t mapChunkLoad(chunk_t *chunk) { char_t name[MAP_FILE_PATH_MAX]; stringFormat( name, sizeof(name), - "maps/%s/chunks/%d_%d_%d.dcf", + "maps/%s/chunks/chunk_%d_%d_%d.json", MAP.name, (int32_t)chunk->position.x, (int32_t)chunk->position.y, @@ -265,7 +265,7 @@ void mapChunkLoadNext() { char_t name[MAP_FILE_PATH_MAX]; stringFormat( name, sizeof(name), - "maps/%s/chunks/%d_%d_%d.dcf", + "maps/%s/chunks/chunk_%d_%d_%d.json", MAP.name, (int32_t)chunk->position.x, (int32_t)chunk->position.y, diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h index 2a907ff0..d2eecc97 100644 --- a/src/dusk/rpg/overworld/map.h +++ b/src/dusk/rpg/overworld/map.h @@ -18,7 +18,7 @@ // Number of chunks that may be mid-load (asset locked & awaiting onLoaded/ // onError) at the same time - everything past this waits in loadQueue. -#define MAP_CHUNK_LOAD_CONCURRENCY 2 +#define MAP_CHUNK_LOAD_CONCURRENCY 1 typedef struct map_s { // Name of the currently loaded map, or an empty string if none is