Switch cutscene system to a set of pre deffed buffers

This commit is contained in:
2026-09-13 09:58:57 -05:00
parent 6c11d8165d
commit 49b97ba508
12 changed files with 184 additions and 104 deletions
+1 -12
View File
@@ -27,17 +27,7 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
) )
set(DUSK_BACKTRACE ON CACHE BOOL "Enable backtrace support for assert failures.") set(DUSK_BACKTRACE ON CACHE BOOL "Enable backtrace support for assert failures.")
set(DUSK_TRACK_MEMORY ON CACHE BOOL "Track memoryAllocate() call sites to help find memory leaks.")
# Records the file/line of every memoryAllocate() call into a fixed-size
# tracking table (removed again on memoryFree()) so a leak still present at
# engineDispose()'s memoryGetAllocatedCount() check can be traced back to
# where it was allocated. Linux only for now.
set(DUSK_TRACK_MEMORY OFF CACHE BOOL "Track memoryAllocate() call sites to help find memory leaks.")
if(DUSK_TRACK_MEMORY)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_TRACK_MEMORY
)
endif()
# Define platform-specific macros. # Define platform-specific macros.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
@@ -56,5 +46,4 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_TIME_DYNAMIC DUSK_TIME_DYNAMIC
DUSK_NETWORK_IPV6 DUSK_NETWORK_IPV6
DUSK_THREAD_PTHREAD DUSK_THREAD_PTHREAD
DUSK_TRACK_MEMORY
) )
+6
View File
@@ -50,6 +50,12 @@ if(DUSK_BACKTRACE)
) )
endif() endif()
if(DUSK_TRACK_MEMORY)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_TRACK_MEMORY
)
endif()
# Includes # Includes
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
@@ -76,20 +76,13 @@ size_t assetCutsceneComputePoolSize(yyjson_val *itemsArr) {
return size; return size;
} }
void assetCutsceneFreeParsed(assetcutsceneoutput_t *out) { // Raw file-read scratch space - fixed-size (ASSET_CUTSCENE_FILE_SIZE_MAX is
if(out->items != NULL) { // already a hard cap below) rather than malloc'd per load. Safe to share:
memoryFree(out->items); // ASSET only ever runs one async load at a time on its single load thread
out->items = NULL; // (see assetUpdateAsync), and yyjson_read copies everything it needs out
} // of this buffer into the doc before this function returns, so it's free
if(out->pool != NULL) { // to be overwritten by the next load.
memoryFree(out->pool); static uint8_t CUTSCENE_FILE_BUFFER[ASSET_CUTSCENE_FILE_SIZE_MAX];
out->pool = NULL;
}
if(out->doc != NULL) {
yyjson_doc_free(out->doc);
out->doc = NULL;
}
}
errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) { errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL"); assertNotNull(loading, "Loading cannot be NULL");
@@ -113,9 +106,10 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
} }
size_t fileSize = (size_t)file->size; size_t fileSize = (size_t)file->size;
uint8_t *buffer = memoryAllocate(fileSize);
assetLoaderErrorChain(loading, assetFileOpen(file)); assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(loading, assetFileRead(file, buffer, fileSize)); assetLoaderErrorChain(
loading, assetFileRead(file, CUTSCENE_FILE_BUFFER, fileSize)
);
assertTrue( assertTrue(
file->lastRead == file->size, file->lastRead == file->size,
"Failed to read entire cutscene file." "Failed to read entire cutscene file."
@@ -127,48 +121,55 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
// type's load callback just parses fields - see cutsceneCutsceneLoad's // type's load callback just parses fields - see cutsceneCutsceneLoad's
// doc comment for how CUTSCENE items avoid needing one), so it happens // doc comment for how CUTSCENE items avoid needing one), so it happens
// right here instead of bouncing to the sync phase. // right here instead of bouncing to the sync phase.
assetcutsceneoutput_t *out = &loading->entry->data.cutscene;
yyjson_doc *doc = yyjson_read( yyjson_doc *doc = yyjson_read(
(char *)buffer, fileSize, (char *)CUTSCENE_FILE_BUFFER, fileSize,
YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS
); );
memoryFree(buffer);
if(doc == NULL) { if(doc == NULL) {
assetLoaderErrorThrow(loading, "Failed to parse cutscene JSON"); assetLoaderErrorThrow(loading, "Failed to parse cutscene JSON");
} }
out->doc = doc;
yyjson_val *root = yyjson_doc_get_root(doc); yyjson_val *root = yyjson_doc_get_root(doc);
yyjson_val *itemsArr = yyjson_obj_get(root, "items"); yyjson_val *itemsArr = yyjson_obj_get(root, "items");
if(!yyjson_is_arr(itemsArr)) { if(!yyjson_is_arr(itemsArr)) {
assetCutsceneFreeParsed(out); yyjson_doc_free(doc);
assetLoaderErrorThrow(loading, "Cutscene JSON is missing an 'items' array"); assetLoaderErrorThrow(loading, "Cutscene JSON is missing an 'items' array");
} }
size_t itemCount = yyjson_arr_size(itemsArr); size_t itemCount = yyjson_arr_size(itemsArr);
if(itemCount > UINT8_MAX) { if(itemCount > CUTSCENE_LOADED_ITEMS_MAX) {
assetCutsceneFreeParsed(out); yyjson_doc_free(doc);
assetLoaderErrorThrow(loading, "Cutscene has more than 255 items"); assetLoaderErrorThrow(
loading, "Cutscene has more than CUTSCENE_LOADED_ITEMS_MAX items"
);
} }
cutscenepause_t pause = CUTSCENE_PAUSE_DEFAULT; cutscenepause_t pause = CUTSCENE_PAUSE_DEFAULT;
yyjson_val *pauseArr = yyjson_obj_get(root, "pause"); yyjson_val *pauseArr = yyjson_obj_get(root, "pause");
if(yyjson_is_arr(pauseArr)) { if(yyjson_is_arr(pauseArr)) {
if(!cutsceneJsonParsePauseFlags(pauseArr, &pause)) { if(!cutsceneJsonParsePauseFlags(pauseArr, &pause)) {
assetCutsceneFreeParsed(out); yyjson_doc_free(doc);
assetLoaderErrorThrow( assetLoaderErrorThrow(
loading, "Cutscene JSON has an invalid 'pause' flag" loading, "Cutscene JSON has an invalid 'pause' flag"
); );
} }
} }
out->items = memoryAllocate(itemCount * sizeof(cutsceneitem_t));
memoryZero(out->items, itemCount * sizeof(cutsceneitem_t));
size_t poolSize = assetCutsceneComputePoolSize(itemsArr); size_t poolSize = assetCutsceneComputePoolSize(itemsArr);
out->pool = poolSize > 0 ? memoryAllocate(poolSize) : NULL; if(poolSize > CUTSCENE_LOADED_POOL_SIZE_MAX) {
yyjson_doc_free(doc);
assetLoaderErrorThrow(
loading, "Cutscene needs more pool space than CUTSCENE_LOADED_POOL_SIZE_MAX"
);
}
// Everything structural has validated by this point, so it's safe to
// start overwriting the shared CUTSCENE_LOADED_ITEMS/POOL buffers - a
// per-item parse failure past this point still leaves them partially
// overwritten, but that's fine: any load failure here already sends the
// game into the fatal-error overlay (see cutsceneSystemLoad/
// cutsceneCutsceneResolve), so nothing keeps running against stale data.
memoryZero(CUTSCENE_LOADED_ITEMS, itemCount * sizeof(cutsceneitem_t));
size_t poolOffset = 0; size_t poolOffset = 0;
yyjson_arr_iter itemIter = yyjson_arr_iter_with(itemsArr); yyjson_arr_iter itemIter = yyjson_arr_iter_with(itemsArr);
@@ -176,19 +177,24 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
uint8_t index = 0; uint8_t index = 0;
while((itemVal = yyjson_arr_iter_next(&itemIter)) != NULL) { while((itemVal = yyjson_arr_iter_next(&itemIter)) != NULL) {
errorret_t itemResult = assetCutsceneParseItem( errorret_t itemResult = assetCutsceneParseItem(
loading, itemVal, &out->items[index], out->pool, &poolOffset, index loading, itemVal, &CUTSCENE_LOADED_ITEMS[index],
CUTSCENE_LOADED_POOL, &poolOffset, index
); );
if(errorIsNotOk(itemResult)) { if(errorIsNotOk(itemResult)) {
assetCutsceneFreeParsed(out); yyjson_doc_free(doc);
errorChain(itemResult); errorChain(itemResult);
} }
index++; index++;
} }
out->cutscene.items = out->items; // Takes ownership of doc - marker/cutscene-reference name fields point
out->cutscene.itemCount = (uint8_t)itemCount; // directly into it (see cutsceneCutsceneRefLoad/cutsceneMarkerLoad), so
out->cutscene.pause = pause; // it must outlive this load, replacing whatever the previously loaded
out->cutscene.dataSize = 0; // cutscene left behind.
cutsceneLoadedSetDoc(doc);
CUTSCENE_LOADED_SCENE.itemCount = (uint8_t)itemCount;
CUTSCENE_LOADED_SCENE.pause = pause;
CUTSCENE_LOADED_SCENE.dataSize = 0;
loading->loading.cutscene.state = ASSET_CUTSCENE_LOADING_STATE_PARSE; loading->loading.cutscene.state = ASSET_CUTSCENE_LOADING_STATE_PARSE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
@@ -224,7 +230,10 @@ errorret_t assetCutsceneDispose(assetentry_t *entry) {
assertTrue(entry->type == ASSET_LOADER_TYPE_CUTSCENE, "Invalid type."); assertTrue(entry->type == ASSET_LOADER_TYPE_CUTSCENE, "Invalid type.");
assertIsMainThread("Must be called from the main thread."); assertIsMainThread("Must be called from the main thread.");
assetCutsceneFreeParsed(&entry->data.cutscene); // Nothing to free here - a loaded cutscene's data lives in the fixed,
// shared CUTSCENE_LOADED_SCENE/ITEMS/POOL globals (see cutscenesystem.h),
// not per-entry storage; this entry only ever drove the read+parse
// pipeline into them.
errorOk(); errorOk();
} }
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "asset/assetfile.h" #include "asset/assetfile.h"
#include "rpg/cutscene/cutscene.h" #include "rpg/cutscene/cutscene.h"
#include "rpg/cutscene/cutscenesystem.h"
#include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/item/json/cutscenejsonpool.h" #include "rpg/cutscene/item/json/cutscenejsonpool.h"
#include "yyjson.h" #include "yyjson.h"
@@ -33,10 +34,13 @@ typedef struct {
} assetcutsceneloaderloading_t; } assetcutsceneloaderloading_t;
typedef struct { typedef struct {
cutscene_t cutscene; // Nothing - a loaded cutscene's data lives entirely in the fixed,
cutsceneitem_t *items; // shared CUTSCENE_LOADED_SCENE/ITEMS/POOL globals (see cutscenesystem.h)
uint8_t *pool; // that assetCutsceneLoaderAsync parses directly into, not per-entry
yyjson_doc *doc; // storage. This entry only ever exists to drive the read+parse
// pipeline, and is unlocked (see cutsceneSystemLoad/
// cutsceneCutsceneResolve) the instant that finishes.
uint8_t unused;
} assetcutsceneoutput_t; } assetcutsceneoutput_t;
/** /**
@@ -67,22 +71,15 @@ errorret_t assetCutsceneParseItem(
const uint8_t index const uint8_t index
); );
/**
* Frees whatever of out->items/out->pool/out->doc is currently non-NULL
* and clears them - shared by both the normal disposer and every parse
* failure path's cleanup.
*
* @param out The output to free.
*/
void assetCutsceneFreeParsed(assetcutsceneoutput_t *out);
/** /**
* Asynchronous loader for cutscene assets. Reads the raw JSONC file bytes * Asynchronous loader for cutscene assets. Reads the raw JSONC file bytes
* and, in the same call, parses them into a heap-allocated cutsceneitem_t * and, in the same call, parses them directly into the fixed, shared
* array + pool - none of that work touches the main thread (every item * CUTSCENE_LOADED_ITEMS/CUTSCENE_LOADED_POOL buffers (see cutscenesystem.h)
* type's load callback is pure parsing now; CUTSCENE items defer their * - no heap allocation at all, since only one cutscene is ever resident.
* nested asset resolution to cutsceneCutsceneStart), so doing it all here * None of that work touches the main thread (every item type's load
* keeps a large cutscene from ever blocking a frame. * callback is pure parsing now; CUTSCENE items defer their nested asset
* resolution to cutsceneCutsceneStart), so doing it all here keeps a large
* cutscene from ever blocking a frame.
* *
* @param loading Loading information for the asset being loaded. * @param loading Loading information for the asset being loaded.
* @return Error code indicating success or failure of the load operation. * @return Error code indicating success or failure of the load operation.
+23 -1
View File
@@ -15,6 +15,21 @@
cutscenesystem_t CUTSCENE_SYSTEM; cutscenesystem_t CUTSCENE_SYSTEM;
cutsceneitem_t CUTSCENE_LOADED_ITEMS[CUTSCENE_LOADED_ITEMS_MAX];
uint8_t CUTSCENE_LOADED_POOL[CUTSCENE_LOADED_POOL_SIZE_MAX];
cutscene_t CUTSCENE_LOADED_SCENE = {
.items = CUTSCENE_LOADED_ITEMS,
.itemCount = 0,
.pause = CUTSCENE_PAUSE_NONE,
.dataSize = 0
};
static yyjson_doc *CUTSCENE_LOADED_DOC = NULL;
void cutsceneLoadedSetDoc(yyjson_doc *doc) {
if(CUTSCENE_LOADED_DOC != NULL) yyjson_doc_free(CUTSCENE_LOADED_DOC);
CUTSCENE_LOADED_DOC = doc;
}
void cutsceneSystemInit() { void cutsceneSystemInit() {
memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t)); memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t));
} }
@@ -69,13 +84,18 @@ void cutsceneSystemLoad(const char_t *file) {
assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL); assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL);
errorret_t result = assetRequireLoaded(entry); errorret_t result = assetRequireLoaded(entry);
// Released the instant loading finishes - the loader already parsed
// everything we need into CUTSCENE_LOADED_SCENE/ITEMS/POOL, so nothing
// further depends on this entry (see cutscenesystem.h's doc comment on
// CUTSCENE_LOADED_ITEMS_MAX for why only one cutscene stays resident).
assetUnlockEntry(entry);
if(errorIsNotOk(result)) { if(errorIsNotOk(result)) {
errorCatch(errorPrint(result)); errorCatch(errorPrint(result));
uiFatalErrorOpen(NULL); uiFatalErrorOpen(NULL);
return; return;
} }
cutsceneSystemStartCutscene(&entry->data.cutscene.cutscene); cutsceneSystemStartCutscene(&CUTSCENE_LOADED_SCENE);
} }
void cutsceneRestart(void) { void cutsceneRestart(void) {
@@ -259,4 +279,6 @@ void cutsceneSystemDispose() {
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED; CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
CUTSCENE_SYSTEM.textCache[0] = '\0'; CUTSCENE_SYSTEM.textCache[0] = '\0';
CUTSCENE_SYSTEM.onComplete = NULL; CUTSCENE_SYSTEM.onComplete = NULL;
cutsceneLoadedSetDoc(NULL);
} }
+33
View File
@@ -26,6 +26,39 @@ typedef struct entity_s entity_t;
// CUTSCENE_ITEM_TYPE_KEYBOARD item is never truncated caching it here. // CUTSCENE_ITEM_TYPE_KEYBOARD item is never truncated caching it here.
#define CUTSCENE_TEXT_CACHE_MAX 64 #define CUTSCENE_TEXT_CACHE_MAX 64
// Only one JSON-authored cutscene is ever loaded/playing at a time - a
// CUTSCENE item hands control off completely (see cutsceneCutsceneStart)
// and cutsceneSystemLoad/cutsceneCutsceneResolve release their asset lock
// the instant loading finishes - so there's no benefit to malloc'ing a
// fresh items/pool buffer per load. Instead the asset loader
// (assetCutsceneLoaderAsync) parses directly into these fixed, static
// buffers. Bump these if a real cutscene ever needs more; C-authored
// cutscenes (the CUTSCENE(...) macro) don't use these at all - they're
// compiled directly into their own static const arrays.
#define CUTSCENE_LOADED_ITEMS_MAX 128
#define CUTSCENE_LOADED_POOL_SIZE_MAX 4096
extern cutsceneitem_t CUTSCENE_LOADED_ITEMS[CUTSCENE_LOADED_ITEMS_MAX];
extern uint8_t CUTSCENE_LOADED_POOL[CUTSCENE_LOADED_POOL_SIZE_MAX];
// The cutscene_t for whatever was last parsed into CUTSCENE_LOADED_ITEMS/
// CUTSCENE_LOADED_POOL - .items always points at CUTSCENE_LOADED_ITEMS;
// itemCount/pause/dataSize are overwritten by the loader on each load.
extern cutscene_t CUTSCENE_LOADED_SCENE;
/**
* Frees whatever yyjson_doc currently backs CUTSCENE_LOADED_ITEMS' string
* fields (marker/cutscene-reference names point directly into it - see
* cutsceneCutsceneRefLoad/cutsceneMarkerLoad) and takes ownership of doc
* instead. Called by the cutscene asset loader once a fresh parse into
* CUTSCENE_LOADED_ITEMS/CUTSCENE_LOADED_POOL completes successfully, and
* from cutsceneSystemDispose to release the last one at shutdown.
*
* @param doc The new doc to take ownership of (NULL just frees/clears the
* current one).
*/
void cutsceneLoadedSetDoc(yyjson_doc *doc);
typedef struct { typedef struct {
const cutscene_t *scene; const cutscene_t *scene;
uint8_t currentItem; uint8_t currentItem;
+10 -4
View File
@@ -373,10 +373,13 @@ void cutsceneCutsceneStart(
) { ) {
const cutscene_t *cutscene = item->cutsceneRef.cutscene; const cutscene_t *cutscene = item->cutsceneRef.cutscene;
if(cutscene == NULL && item->cutsceneRef.name != NULL) { if(cutscene == NULL && item->cutsceneRef.name != NULL) {
// Not cached back into item - with only one shared, fixed load buffer
// (see cutscenesystem.h's CUTSCENE_LOADED_ITEMS_MAX doc comment),
// every JSON-authored reference resolves to the same fixed address
// every time anyway, and item itself lives inside that very buffer -
// caching a pointer into it here would go stale (or, worse, get
// clobbered mid-resolve) the moment a later load overwrites it.
cutscene = cutsceneCutsceneResolve(item->cutsceneRef.name); cutscene = cutsceneCutsceneResolve(item->cutsceneRef.name);
// Cache the resolved pointer back so replaying this same item (e.g.
// a looping intro) never re-resolves/re-locks the asset again.
((cutsceneitem_t *)item)->cutsceneRef.cutscene = cutscene;
} }
if(cutscene != NULL) cutsceneSystemStartCutscene(cutscene); if(cutscene != NULL) cutsceneSystemStartCutscene(cutscene);
} }
@@ -394,13 +397,16 @@ const cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL); assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL);
errorret_t result = assetRequireLoaded(entry); errorret_t result = assetRequireLoaded(entry);
// Released the instant loading finishes - see cutsceneSystemLoad's doc
// comment for why nothing further depends on this entry once loaded.
assetUnlockEntry(entry);
if(errorIsNotOk(result)) { if(errorIsNotOk(result)) {
errorCatch(errorPrint(result)); errorCatch(errorPrint(result));
uiFatalErrorOpen(NULL); uiFatalErrorOpen(NULL);
return NULL; return NULL;
} }
return &entry->data.cutscene.cutscene; return &CUTSCENE_LOADED_SCENE;
} }
errorret_t cutsceneCutsceneLoad( errorret_t cutsceneCutsceneLoad(
@@ -22,8 +22,8 @@ ENTITY_GLOBAL_CALLBACK(3) {
} }
ENTITY_GLOBAL_CALLBACK(4) { ENTITY_GLOBAL_CALLBACK(4) {
create->entity->interact.type = ENTITY_INTERACT_CUTSCENE; create->entity->interact.type = ENTITY_INTERACT_CUTSCENE_NAME;
create->entity->interact.data.cutscene = cutsceneCutsceneResolve("battletest"); create->entity->interact.data.cutsceneName = "battletest";
} }
static const entityglobaldef_t ENTITY_GLOBAL_LIST[] = { static const entityglobaldef_t ENTITY_GLOBAL_LIST[] = {
@@ -27,6 +27,15 @@ void entityInteractWith(entity_t *player, entity_t *target) {
); );
break; break;
case ENTITY_INTERACT_CUTSCENE_NAME: {
const cutscene_t *cutscene =
cutsceneCutsceneResolve(target->interact.data.cutsceneName);
if(cutscene != NULL) {
cutsceneSystemStartCutsceneWith(cutscene, player, target);
}
break;
}
case ENTITY_INTERACT_PRINT: case ENTITY_INTERACT_PRINT:
uiTextboxMainSetText(target->interact.data.message); uiTextboxMainSetText(target->interact.data.message);
@@ -18,6 +18,7 @@ typedef enum {
ENTITY_INTERACT_NULL = 0, ENTITY_INTERACT_NULL = 0,
ENTITY_INTERACT_CUTSCENE, ENTITY_INTERACT_CUTSCENE,
ENTITY_INTERACT_CUTSCENE_NAME,
ENTITY_INTERACT_PRINT, ENTITY_INTERACT_PRINT,
ENTITY_INTERACT_CALLBACK, ENTITY_INTERACT_CALLBACK,
@@ -28,7 +29,18 @@ typedef enum {
* Per-type data for an entity's interact component. * Per-type data for an entity's interact component.
*/ */
typedef union { typedef union {
// A compile-time cutscene_t* (e.g. CUTSCENE_REFERENCE(...)) - safe to
// hold onto indefinitely, since C-authored cutscenes are compiled
// directly into their own static const arrays, never the shared
// CUTSCENE_LOADED_ITEMS buffer (see cutscenesystem.h).
const cutscene_t *cutscene; const cutscene_t *cutscene;
// A bare cutscene name (as passed to cutsceneCutsceneResolve), resolved
// lazily at interact time rather than once up front - only one
// JSON-authored cutscene is ever resident at a time (see
// CUTSCENE_LOADED_ITEMS_MAX's doc comment), so resolving early and
// holding onto the result here would go stale the moment any other
// cutscene loads in the meantime.
const char_t *cutsceneName;
const char_t *message; const char_t *message;
void (*callback)(entity_t *player, entity_t *target); void (*callback)(entity_t *player, entity_t *target);
} entityinteractdata_t; } entityinteractdata_t;
+17 -21
View File
@@ -26,21 +26,15 @@
#define UI_MAIN_MENU_INDEX_QUIT 2 #define UI_MAIN_MENU_INDEX_QUIT 2
uimainmenu_t UI_MAIN_MENU; uimainmenu_t UI_MAIN_MENU;
assetentry_t *MAIN_MENU_CUTSCENE_ENTRY = NULL;
cutscene_t *uiMainMenuLoadCutscene(void) { const cutscene_t *uiMainMenuLoadCutscene(void) {
if(MAIN_MENU_CUTSCENE_ENTRY == NULL) { // Delegates entirely to cutsceneCutsceneResolve rather than keeping our
MAIN_MENU_CUTSCENE_ENTRY = assetLock( // own locked-forever entry, since only one JSON-authored cutscene is
"cutscenes/main_menu.jsonc", ASSET_LOADER_TYPE_CUTSCENE, NULL // ever resident at a time (see CUTSCENE_LOADED_ITEMS_MAX's doc comment
); // in cutscenesystem.h) - caching an entry pointer here across calls
} // would go stale the moment any other cutscene loads in the meantime.
errorret_t result = assetRequireLoaded(MAIN_MENU_CUTSCENE_ENTRY); // May return NULL on failure (already reported via uiFatalErrorOpen).
if(errorIsNotOk(result)) { return cutsceneCutsceneResolve("main_menu");
errorCatch(errorPrint(result));
assertTrue(false, "Failed to load main menu cutscene asset");
}
return &MAIN_MENU_CUTSCENE_ENTRY->data.cutscene.cutscene;
} }
void uiMainMenuSelectSaveResult(const uint8_t slotIndex, void *user) { void uiMainMenuSelectSaveResult(const uint8_t slotIndex, void *user) {
@@ -79,9 +73,9 @@ void uiMainMenuOpenSelectSave(void *userData) {
void uiMainMenuStartGame(void) { void uiMainMenuStartGame(void) {
if(CUTSCENE_SYSTEM.scene == NULL) { if(CUTSCENE_SYSTEM.scene == NULL) {
cutsceneSystemStartCutsceneAndGoToMarker( const cutscene_t *cutscene = uiMainMenuLoadCutscene();
uiMainMenuLoadCutscene(), "NEW_GAME" if(cutscene == NULL) return;
); cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "NEW_GAME");
} else { } else {
cutsceneGoTo("NEW_GAME"); cutsceneGoTo("NEW_GAME");
} }
@@ -91,9 +85,9 @@ void uiMainMenuStartGame(void) {
void uiMainMenuOptions(void) { void uiMainMenuOptions(void) {
if(CUTSCENE_SYSTEM.scene == NULL) { if(CUTSCENE_SYSTEM.scene == NULL) {
cutsceneSystemStartCutsceneAndGoToMarker( const cutscene_t *cutscene = uiMainMenuLoadCutscene();
uiMainMenuLoadCutscene(), "OPTIONS" if(cutscene == NULL) return;
); cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "OPTIONS");
} else { } else {
cutsceneGoTo("OPTIONS"); cutsceneGoTo("OPTIONS");
} }
@@ -101,7 +95,9 @@ void uiMainMenuOptions(void) {
void uiMainMenuQuit(void) { void uiMainMenuQuit(void) {
if(CUTSCENE_SYSTEM.scene == NULL) { if(CUTSCENE_SYSTEM.scene == NULL) {
cutsceneSystemStartCutsceneAndGoToMarker(uiMainMenuLoadCutscene(), "QUIT"); const cutscene_t *cutscene = uiMainMenuLoadCutscene();
if(cutscene == NULL) return;
cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "QUIT");
} else { } else {
cutsceneGoTo("QUIT"); cutsceneGoTo("QUIT");
} }
+9 -8
View File
@@ -39,17 +39,18 @@ errorret_t uiMainMenuInit(void);
void uiMainMenuOpen(void); void uiMainMenuOpen(void);
/** /**
* Lazily locks/loads the main menu cutscene asset (assets/cutscenes/ * Loads (or re-loads) the main menu cutscene asset (assets/cutscenes/
* main_menu.jsonc, parsed directly at load time rather than compiled in) * main_menu.jsonc, parsed directly at load time rather than compiled in)
* and returns it, asserting on failure. Kept resident for the rest of the * and returns it - the shared accessor for the "Start Game"/Options/Quit
* process once loaded - the persistent cutscene is what actually drives * flows below to jump/restart it. Only one JSON-authored cutscene is ever
* this whole screen (opening this panel is its first item's job, see * resident at a time (see CUTSCENE_LOADED_ITEMS_MAX's doc comment in
* UI_SHOW/rpg/cutscene/item/ui/cutsceneuishow.h), this is just the shared * cutscenesystem.h), so this resolves fresh on every call rather than
* accessor for the "Start Game" flow below to jump/restart it. * caching anything.
* *
* @return The main menu cutscene. * @return The main menu cutscene, or NULL on failure (already reported via
* uiFatalErrorOpen).
*/ */
cutscene_t *uiMainMenuLoadCutscene(void); const cutscene_t *uiMainMenuLoadCutscene(void);
/** /**
* Starts the "Start Game" flow: jumps the already-running main menu * Starts the "Start Game" flow: jumps the already-running main menu