Merge branch 'raw-json-maps' into main
Load map chunks from raw JSON at runtime instead of a baked DCF/DMF step, add a reusable perf timing module, fix a real PSP logDebug/logError Memory Stick I/O cost bug, and switch chunk tile storage to a dense minified grid. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"meshes":[{"file":"house_4_4.dmf","pos":[2,1,0]},{"file":"house_6_3.dmf","pos":[1,9,0]},{"file":"house_2_2.dmf","pos":[12,6,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]}
|
||||
@@ -0,0 +1 @@
|
||||
{"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[6,0],[2,0],[2,0],[2,0],[2,0],[2,0],[2,0],[7,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[4,0],[12,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[9,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]],"meshes":[{"file":"house_5_3.dmf","pos":[1,1,0]}],"entities":[{"type":"global","globalId":3,"pos":[8,8,1]},{"type":"item","itemId":1,"quantity":1,"pos":[12,2,0]}],"areas":[{"min":[11,3,0],"max":[16,9,10],"callbackId":1,"notify":3,"trigger":6}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"meshes":[{"file":"house_8_4.dmf","pos":[1,1,0]},{"file":"house_3_3.dmf","pos":[11,2,0]},{"file":"house_2_2.dmf","pos":[3,11,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]}
|
||||
@@ -0,0 +1 @@
|
||||
{"meshes":[{"file":"house_3_2.dmf","pos":[1,1,0]},{"file":"house_2_3.dmf","pos":[7,2,0]},{"file":"house_4_2.dmf","pos":[1,8,0]},{"file":"house_1_1.dmf","pos":[11,10,0]},{"file":"house_5_2.dmf","pos":[13,6,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]}
|
||||
@@ -0,0 +1 @@
|
||||
{"meshes":[{"file":"house_5_5.dmf","pos":[3,2,0]},{"file":"house_2_3.dmf","pos":[10,3,0]},{"file":"house_3_1.dmf","pos":[1,12,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]}
|
||||
@@ -0,0 +1 @@
|
||||
{"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,1],[4,1],[4,1],[4,1],[4,1],[4,1],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]],"meshes":[]}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"mesh": "maps/overworld/meshes/chunks/chunk_-1_0_0_0.dmf",
|
||||
"color": [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"texture": "tiles.png"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"mesh": "maps/overworld/meshes/chunks/chunk_0_0_0_0.dmf",
|
||||
"color": [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"texture": "tiles.png"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"mesh": "maps/overworld/meshes/chunks/chunk_0_1_0_0.dmf",
|
||||
"color": [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"texture": "tiles.png"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"mesh": "maps/overworld/meshes/chunks/chunk_1_0_0_0.dmf",
|
||||
"color": [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"texture": "tiles.png"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"mesh": "maps/overworld/meshes/chunks/chunk_1_1_0_0.dmf",
|
||||
"color": [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"texture": "tiles.png"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"mesh": "maps/overworld/meshes/chunks/chunk_2_0_0_0.dmf",
|
||||
"color": [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"texture": "tiles.png"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -60,6 +60,19 @@ target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||
DUSK_DISPLAY_OVERSCAN=6
|
||||
)
|
||||
|
||||
# logDebug()/logError() additionally write every call to a file on the
|
||||
# Memory Stick (ms0:/PSP/GAME/Dusk/*.log) - a real, measured source of
|
||||
# per-call hitching (fopen+write+fclose every log line), not just a
|
||||
# theoretical cost. Default off so a normal build doesn't pay it; opt in
|
||||
# with -DDUSK_PSP_LOG_FILE=ON when the log file itself is actually needed
|
||||
# (e.g. no way to see stdout on real hardware).
|
||||
set(DUSK_PSP_LOG_FILE OFF CACHE BOOL "Also write PSP debug/error logs to ms0:/PSP/GAME/Dusk/*.log (slow - Memory Stick I/O per call).")
|
||||
if(DUSK_PSP_LOG_FILE)
|
||||
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||
DUSK_PSP_LOG_FILE
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||
DUSK_ASSERTIONS_FAKED
|
||||
|
||||
@@ -74,6 +74,7 @@ add_subdirectory(engine)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(perf)
|
||||
add_subdirectory(rpg)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(system)
|
||||
|
||||
@@ -8,41 +8,284 @@
|
||||
#include "assetchunkloader.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/endian.h"
|
||||
#include "util/string.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/json/assetjsonloader.h"
|
||||
#include "display/color.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
#include "yyjson.h"
|
||||
|
||||
#define ASSET_CHUNK_TERRAIN_TEXTURE "tiles.png"
|
||||
|
||||
// Per-shape corner Z offsets (sw, se, ne, nw) as offsets from a tile's own
|
||||
// base Z, ported 1:1 from tools/asset/chunk/__main__.py's _RAMP_CORNERS.
|
||||
// TILE_SHAPE_NULL's row is never read - assetChunkTilesHaveTerrain/
|
||||
// assetChunkBuildTerrain skip those tiles outright.
|
||||
static const float_t CHUNK_RAMP_CORNERS[TILE_SHAPE_COUNT][4] = {
|
||||
[TILE_SHAPE_GROUND] = {0.0f, 0.0f, 0.0f, 0.0f},
|
||||
[TILE_SHAPE_RAMP_NORTH] = {0.0f, 0.0f, 1.0f, 1.0f},
|
||||
[TILE_SHAPE_RAMP_SOUTH] = {1.0f, 1.0f, 0.0f, 0.0f},
|
||||
[TILE_SHAPE_RAMP_EAST] = {0.0f, 1.0f, 1.0f, 0.0f},
|
||||
[TILE_SHAPE_RAMP_WEST] = {1.0f, 0.0f, 0.0f, 1.0f},
|
||||
[TILE_SHAPE_RAMP_NORTHEAST] = {0.0f, 0.0f, 1.0f, 0.0f},
|
||||
[TILE_SHAPE_RAMP_NORTHWEST] = {0.0f, 0.0f, 0.0f, 1.0f},
|
||||
[TILE_SHAPE_RAMP_SOUTHEAST] = {0.0f, 1.0f, 0.0f, 0.0f},
|
||||
[TILE_SHAPE_RAMP_SOUTHWEST] = {1.0f, 0.0f, 0.0f, 0.0f},
|
||||
[TILE_SHAPE_RAMP_NORTHEAST_INNER] = {0.0f, 1.0f, 1.0f, 1.0f},
|
||||
[TILE_SHAPE_RAMP_NORTHWEST_INNER] = {1.0f, 0.0f, 1.0f, 1.0f},
|
||||
[TILE_SHAPE_RAMP_SOUTHEAST_INNER] = {1.0f, 1.0f, 1.0f, 0.0f},
|
||||
[TILE_SHAPE_RAMP_SOUTHWEST_INNER] = {1.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
|
||||
errorret_t assetChunkLoaderAsync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertNotMainThread("Should be called from an async thread.");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if(loading->loading.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 - a dense, fixed-size CHUNK_TILE_COUNT grid,
|
||||
// one slot per (x, y) column at index x + y * CHUNK_WIDTH (position is
|
||||
// implicit in the index, not stored). Each slot is either `null` (empty
|
||||
// column, already zeroed = TILE_SHAPE_NULL) or a 2-element [type, z]
|
||||
// array.
|
||||
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) {
|
||||
if(idx >= CHUNK_TILE_COUNT) break;
|
||||
if(!yyjson_is_arr(tileVal) || yyjson_arr_size(tileVal) < 2) continue;
|
||||
|
||||
int32_t type = (int32_t)yyjson_get_int(yyjson_arr_get(tileVal, 0));
|
||||
int32_t z = (int32_t)yyjson_get_int(yyjson_arr_get(tileVal, 1));
|
||||
tiles[idx] = (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/<basename>.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 +298,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 +312,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 +334,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 +449,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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "engine.h"
|
||||
#include "util/memory.h"
|
||||
#include "perf/perf.h"
|
||||
#include "time/time.h"
|
||||
#include "input/input.h"
|
||||
#include "locale/localemanager.h"
|
||||
@@ -36,6 +37,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
ENGINE.version = DUSK_VERSION;
|
||||
|
||||
// Init systems. Order is important.
|
||||
perfInit();
|
||||
timeInit();
|
||||
consoleInit();
|
||||
errorChain(systemInit());
|
||||
@@ -104,6 +106,7 @@ errorret_t engineDispose(void) {
|
||||
errorChain(displayDispose());
|
||||
errorChain(saveDispose());
|
||||
errorChain(assetDispose());
|
||||
perfDispose();
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
perf.c
|
||||
)
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "perf.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "console/console.h"
|
||||
|
||||
#if defined(DUSK_LINUX)
|
||||
#include <time.h>
|
||||
#elif defined(DUSK_PSP)
|
||||
#include <pspthreadman.h>
|
||||
#endif
|
||||
|
||||
perf_t PERF;
|
||||
|
||||
void perfInit(void) {
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
memoryZero(&PERF, sizeof(perf_t));
|
||||
}
|
||||
|
||||
void perfDispose(void) {
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
}
|
||||
|
||||
uint64_t perfGetTick(void) {
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
#if defined(DUSK_LINUX)
|
||||
// TEST - CLOCK_MONOTONIC via clock_gettime(), Linux desktop only for
|
||||
// now. Not a cross-platform answer yet (see perfGetTick's doc
|
||||
// comment in perf.h) - just something real to look at while that
|
||||
// gets settled. Returns nanoseconds since some unspecified starting
|
||||
// point, so only differences between two calls are meaningful.
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
|
||||
#elif defined(DUSK_PSP)
|
||||
// TEST - sceKernelGetSystemTimeWide(), microseconds since boot at
|
||||
// ~1us resolution (the standard PSP homebrew idiom for elapsed-time
|
||||
// measurement - psprtc.h's sceRtcGetCurrentTick is for wall-clock
|
||||
// date/time instead, already used by timepsp.c for that purpose).
|
||||
// Scaled to nanoseconds to match every other platform's tick unit.
|
||||
return (uint64_t)sceKernelGetSystemTimeWide() * 1000ull;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void perfPushItem(
|
||||
const char_t *label, const char_t *file, const int32_t line
|
||||
) {
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
assertNotNull(label, "Perf item label cannot be NULL.");
|
||||
assertNotNull(file, "Perf item file cannot be NULL.");
|
||||
assertTrue(PERF.stackSize < PERF_STACK_SIZE, "Perf stack overflow.");
|
||||
|
||||
PERF.stack[PERF.stackSize++] = (perfitem_t){
|
||||
.label = label,
|
||||
.file = file,
|
||||
.line = line,
|
||||
.tick = perfGetTick()
|
||||
};
|
||||
}
|
||||
|
||||
void perfPopItem(void) {
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
assertTrue(PERF.stackSize > 0, "Perf stack underflow.");
|
||||
PERF.stackSize--;
|
||||
}
|
||||
|
||||
void perfReport(void) {
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
if(PERF.stackSize == 0) return;
|
||||
|
||||
const perfitem_t *item = &PERF.stack[PERF.stackSize - 1];
|
||||
uint64_t elapsedNs = perfGetTick() - item->tick;
|
||||
double_t elapsedMs = (double_t)elapsedNs / 1000000.0;
|
||||
consolePrint(
|
||||
"perf: %s (%s:%d, %.4fms)",
|
||||
item->label, item->file, item->line, elapsedMs
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "perfitem.h"
|
||||
|
||||
#define PERF_STACK_SIZE 256
|
||||
|
||||
typedef struct {
|
||||
perfitem_t stack[PERF_STACK_SIZE];
|
||||
uint32_t stackSize;
|
||||
} perf_t;
|
||||
|
||||
extern perf_t PERF;
|
||||
|
||||
/**
|
||||
* Initializes the perf system.
|
||||
*/
|
||||
void perfInit(void);
|
||||
|
||||
/**
|
||||
* Disposes/cleans up the perf system. Currently a no-op - here so
|
||||
* engine.c has a symmetric call to make once this needs real cleanup.
|
||||
*/
|
||||
void perfDispose(void);
|
||||
|
||||
/**
|
||||
* Returns a tick value representing "now", for measuring the duration
|
||||
* between a perfPush/perfPushItem and its matching perfPopItem. Always
|
||||
* nanoseconds since some unspecified starting point, on every platform
|
||||
* that implements it, so perfReport's ms conversion stays correct
|
||||
* everywhere without per-platform special-casing.
|
||||
*
|
||||
* PLACEHOLDER - currently only reads a real value on DUSK_LINUX desktop
|
||||
* builds (via clock_gettime(CLOCK_MONOTONIC)) and DUSK_PSP (via
|
||||
* sceKernelGetSystemTimeWide(), scaled from microseconds), as a test;
|
||||
* every other platform still gets a meaningless 0. A common,
|
||||
* cross-platform way of reading CPU cycles/time hasn't been settled on
|
||||
* yet - don't treat results as comparable across platforms until this is
|
||||
* replaced with a real per-platform implementation. Only the difference
|
||||
* between two calls is meaningful, never the raw value itself.
|
||||
*
|
||||
* @return A tick value, in nanoseconds. 0 on unimplemented platforms.
|
||||
*/
|
||||
uint64_t perfGetTick(void);
|
||||
|
||||
/**
|
||||
* Pushes a new item onto the perf stack, capturing the current tick (see
|
||||
* perfGetTick) as its start time. Prefer the perfPush() macro, which
|
||||
* supplies file/line automatically.
|
||||
*
|
||||
* @param label Short caller-supplied memo of what this item represents
|
||||
* (e.g. "frame start") - stored by pointer, not copied, so pass a
|
||||
* string literal or something that otherwise outlives the push.
|
||||
* @param file Source file the push originated from (e.g. __FILE__).
|
||||
* @param line Source line the push originated from (e.g. __LINE__).
|
||||
*/
|
||||
void perfPushItem(
|
||||
const char_t *label, const char_t *file, const int32_t line
|
||||
);
|
||||
|
||||
/**
|
||||
* Pops the most recently pushed perf item off the stack.
|
||||
*/
|
||||
void perfPopItem(void);
|
||||
|
||||
/**
|
||||
* Reports on the item currently at the top of the perf stack (the most
|
||||
* recently pushed one still awaiting its perfPopItem), if any.
|
||||
*/
|
||||
void perfReport(void);
|
||||
|
||||
/**
|
||||
* Pushes a new perf item onto the stack, tagged with the call site this
|
||||
* macro is expanded at.
|
||||
*
|
||||
* @param label Short memo of what this item represents (e.g. "frame
|
||||
* start") - see perfPushItem.
|
||||
*/
|
||||
#define perfPush(label) perfPushItem(label, __FILE__, __LINE__)
|
||||
|
||||
/**
|
||||
* Pops the most recently pushed perf item off the stack.
|
||||
*/
|
||||
#define perfPop() perfPopItem()
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct {
|
||||
const char_t *label;
|
||||
int32_t line;
|
||||
const char_t *file;
|
||||
uint64_t tick;
|
||||
} perfitem_t;
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "rpg/entity/item/entityitem.h"
|
||||
#include "rpg/overworld/maparea.h"
|
||||
#include "save/save.h"
|
||||
#include "time/time.h"
|
||||
|
||||
map_t MAP;
|
||||
|
||||
@@ -144,6 +145,15 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
||||
}
|
||||
|
||||
errorret_t mapUpdate() {
|
||||
// TEST ONLY - see MAP_CHUNK_LOAD_DELAY_TEST.
|
||||
if(MAP.chunkLoadCooldown > 0.0f) {
|
||||
MAP.chunkLoadCooldown -= TIME.delta;
|
||||
if(MAP.chunkLoadCooldown <= 0.0f) {
|
||||
MAP.chunkLoadCooldown = 0.0f;
|
||||
mapChunkLoadNext();
|
||||
}
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -226,7 +236,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,
|
||||
@@ -251,6 +261,11 @@ errorret_t mapChunkLoad(chunk_t *chunk) {
|
||||
}
|
||||
|
||||
void mapChunkLoadNext() {
|
||||
// TEST ONLY - see MAP_CHUNK_LOAD_DELAY_TEST. Skip starting anything new
|
||||
// until the cooldown set by the last load we started has expired;
|
||||
// mapUpdate() ticks it down and retries this once it hits zero.
|
||||
if(MAP.chunkLoadCooldown > 0.0f) return;
|
||||
|
||||
for(uint32_t slot = 0; slot < MAP_CHUNK_LOAD_CONCURRENCY; slot++) {
|
||||
if(MAP.loadingChunks[slot] != NULL) continue;
|
||||
if(MAP.loadQueueCount == 0) return;
|
||||
@@ -261,11 +276,12 @@ void mapChunkLoadNext() {
|
||||
}
|
||||
MAP.loadQueueCount--;
|
||||
MAP.loadingChunks[slot] = chunk;
|
||||
MAP.chunkLoadCooldown = MAP_CHUNK_LOAD_DELAY_TEST;
|
||||
|
||||
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,
|
||||
|
||||
@@ -18,7 +18,12 @@
|
||||
|
||||
// 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
|
||||
|
||||
// TEST ONLY - deliberately exaggerated delay (seconds) enforced between
|
||||
// starting one chunk load and starting the next, to observe its effect on
|
||||
// framerate. See MAP.chunkLoadCooldown / mapChunkLoadNext.
|
||||
#define MAP_CHUNK_LOAD_DELAY_TEST 0.05f
|
||||
|
||||
typedef struct map_s {
|
||||
// Name of the currently loaded map, or an empty string if none is
|
||||
@@ -32,6 +37,10 @@ typedef struct map_s {
|
||||
chunk_t *loadQueue[MAP_CHUNK_COUNT];
|
||||
uint32_t loadQueueCount;
|
||||
chunk_t *loadingChunks[MAP_CHUNK_LOAD_CONCURRENCY];
|
||||
|
||||
// TEST ONLY - seconds remaining before mapChunkLoadNext is allowed to
|
||||
// start another chunk load. See MAP_CHUNK_LOAD_DELAY_TEST.
|
||||
float_t chunkLoadCooldown;
|
||||
} map_t;
|
||||
|
||||
extern map_t MAP;
|
||||
|
||||
+22
-16
@@ -17,14 +17,17 @@ void logDebug(const char_t *message, ...) {
|
||||
vprintf(message, copy);
|
||||
va_end(copy);
|
||||
|
||||
// print to file
|
||||
FILE *file = fopen("ms0:/PSP/GAME/Dusk/debug.log", "a");
|
||||
if(file) {
|
||||
va_copy(copy, args);
|
||||
vfprintf(file, message, copy);
|
||||
va_end(copy);
|
||||
fclose(file);
|
||||
}
|
||||
#ifdef DUSK_PSP_LOG_FILE
|
||||
// print to file - fopen+fclose every call is real, measured Memory
|
||||
// Stick I/O cost (this is why the flag exists - see psp.cmake).
|
||||
FILE *file = fopen("ms0:/PSP/GAME/Dusk/debug.log", "a");
|
||||
if(file) {
|
||||
va_copy(copy, args);
|
||||
vfprintf(file, message, copy);
|
||||
va_end(copy);
|
||||
fclose(file);
|
||||
}
|
||||
#endif
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
@@ -39,14 +42,17 @@ void logError(const char_t *message, ...) {
|
||||
vfprintf(stderr, message, copy);
|
||||
va_end(copy);
|
||||
|
||||
// print to file
|
||||
FILE *file = fopen("ms0:/PSP/GAME/Dusk/error.log", "a");
|
||||
if(file) {
|
||||
va_copy(copy, args);
|
||||
vfprintf(file, message, copy);
|
||||
va_end(copy);
|
||||
fclose(file);
|
||||
}
|
||||
#ifdef DUSK_PSP_LOG_FILE
|
||||
// print to file - fopen+fclose every call is real, measured Memory
|
||||
// Stick I/O cost (this is why the flag exists - see psp.cmake).
|
||||
FILE *file = fopen("ms0:/PSP/GAME/Dusk/error.log", "a");
|
||||
if(file) {
|
||||
va_copy(copy, args);
|
||||
vfprintf(file, message, copy);
|
||||
va_end(copy);
|
||||
fclose(file);
|
||||
}
|
||||
#endif
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
Reference in New Issue
Block a user