From cf51ed1988dc844445b28c940b7f713dfa3a5edb Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Wed, 9 Sep 2026 21:21:50 -0500 Subject: [PATCH] Fixed cutscene item cutscene trying to recursively load --- src/dusk/rpg/cutscene/cutscene.h | 2 +- src/dusk/rpg/cutscene/item/cutsceneitem.c | 42 ++++++++++++++------- src/dusk/rpg/cutscene/item/cutsceneitem.h | 46 ++++++++++++++++++++--- 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/dusk/rpg/cutscene/cutscene.h b/src/dusk/rpg/cutscene/cutscene.h index 0a3a9f35..3076015e 100644 --- a/src/dusk/rpg/cutscene/cutscene.h +++ b/src/dusk/rpg/cutscene/cutscene.h @@ -86,7 +86,7 @@ typedef struct cutscene_s { #define CUTSCENE_CUTSCENE(CUTSCENE) \ { \ .type = CUTSCENE_ITEM_TYPE_CUTSCENE, \ - .cutscene = CUTSCENE_REFERENCE(CUTSCENE) \ + .cutsceneRef = { .cutscene = CUTSCENE_REFERENCE(CUTSCENE), .name = NULL } \ } #define CUTSCENE_CALLBACK(CALLBACK) \ diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index c556e6fa..3f6f0253 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -11,6 +11,7 @@ #include "asset/assetfile.h" #include "asset/loader/assetloader.h" #include "util/string.h" +#include "ui/overlay/uifatalerror.h" cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { [CUTSCENE_ITEM_TYPE_NULL] = { 0 }, @@ -364,7 +365,14 @@ void cutsceneCutsceneStart( const cutsceneitem_t *item, cutsceneitemdata_t *data ) { - if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene); + const cutscene_t *cutscene = item->cutsceneRef.cutscene; + if(cutscene == NULL && item->cutsceneRef.name != NULL) { + 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); } bool_t cutsceneCutsceneUpdate( @@ -374,6 +382,21 @@ bool_t cutsceneCutsceneUpdate( return false; } +const cutscene_t * cutsceneCutsceneResolve(const char_t *name) { + char_t path[ASSET_FILE_NAME_MAX]; + stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name); + + assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL); + errorret_t result = assetRequireLoaded(entry); + if(errorIsNotOk(result)) { + errorCatch(errorPrint(result)); + uiFatalErrorOpen(NULL); + return NULL; + } + + return &entry->data.cutscene.cutscene; +} + errorret_t cutsceneCutsceneLoad( yyjson_val *itemObj, cutsceneitem_t *item, @@ -385,23 +408,16 @@ errorret_t cutsceneCutsceneLoad( errorThrow("Cutscene item (CUTSCENE): missing 'name'"); } - // Referenced cutscene is a sibling asset (assets/cutscenes/.jsonc), - // resolved to a stable pointer right now rather than deferred to - // runtime - assetRequireLoaded re-enters assetUpdate() safely from - // within this sync load (see its ASSET_ENTRY_STATE_LOADING_SYNC case), - // and the entry is locked forever (never unlocked) so the pointer - // stays valid for the process lifetime, same convention as - // ui/screen/mainmenu/uimainmenu.c's MAIN_MENU_CUTSCENE_ENTRY. - char_t path[ASSET_FILE_NAME_MAX]; + // Only validate that the eventual "cutscenes/.jsonc" path would + // fit - the reference itself is not resolved/locked until this item + // actually runs (see cutsceneCutsceneStart/cutsceneCutsceneResolve). int32_t pathLen = stringFormat(NULL, 0, "cutscenes/%s.jsonc", name); if(pathLen < 0 || (size_t)pathLen >= ASSET_FILE_NAME_MAX) { errorThrow("Cutscene item (CUTSCENE): 'name' too long"); } - stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name); - assetentry_t *refEntry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL); - errorChain(assetRequireLoaded(refEntry)); - item->cutscene = &refEntry->data.cutscene.cutscene; + item->cutsceneRef.cutscene = NULL; + item->cutsceneRef.name = name; errorOk(); } diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index f73572e0..8f537fd2 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -55,6 +55,20 @@ typedef struct cutscene_s cutscene_t; +// A CUTSCENE item's target. C-authored cutscenes (the CUTSCENE_CUTSCENE +// macro in cutscene.h) resolve cutscene at compile time and leave name +// NULL. JSON-authored cutscenes (cutsceneCutsceneLoad) do the opposite - +// only name is set at parse time, since resolving/locking the referenced +// asset there would eagerly load every cutscene a cutscene merely +// references, even ones never played. cutsceneCutsceneStart resolves +// name to cutscene the first time this item actually runs (via +// cutsceneCutsceneResolve) and caches the result back into this field so +// replaying the same item never re-resolves it. +typedef struct { + const cutscene_t *cutscene; + const char_t *name; +} cutscenecutsceneref_t; + typedef enum { CUTSCENE_ITEM_TYPE_NULL, @@ -117,7 +131,7 @@ struct cutsceneitem_s { cutscenetextminihide_t textMiniHide; cutscenecallback_t callback; cutscenewait_t wait; - const cutscene_t *cutscene; + cutscenecutsceneref_t cutsceneRef; cutsceneentityteleport_t entityTeleport; cutsceneentitywalkto_t entityWalkTo; cutscenefade_t fade; @@ -235,7 +249,9 @@ bool_t cutsceneItemLookupType( /** * Starts a nested-cutscene item, handing control over to the - * referenced cutscene. + * referenced cutscene. If item->cutsceneRef.cutscene hasn't been + * resolved yet (a JSON-authored reference, only known by name so far), + * resolves and caches it first via cutsceneCutsceneResolve. * * @param item The cutscene item. * @param data Runtime data storage. @@ -245,6 +261,22 @@ void cutsceneCutsceneStart( cutsceneitemdata_t *data ); +/** + * Resolves a JSON-authored CUTSCENE item's referenced cutscene by name, + * locking (forever - never unlocked, matching cutsceneCutsceneLoad's old + * convention) and blocking on assets/cutscenes/.jsonc. Called + * lazily from cutsceneCutsceneStart the first time the item actually + * runs, not at parse time, so a cutscene referencing many others doesn't + * load every one of them up front. On failure, opens the fatal error + * overlay (matching cutsceneSystemLoad's runtime-failure handling) and + * returns NULL. + * + * @param name Bare cutscene name (without "cutscenes/" prefix or + * ".jsonc" suffix). + * @return The resolved cutscene, or NULL on failure. + */ +const cutscene_t * cutsceneCutsceneResolve(const char_t *name); + /** * Updates a nested-cutscene item. By the time this would run, control * has already moved on to the referenced cutscene, so this always @@ -262,10 +294,12 @@ bool_t cutsceneCutsceneUpdate( /** * Parses a CUTSCENE cutscene item's JSON fields into item. Kept inline here * (rather than its own file) since it's tightly bound to the asset/loader - * system itself - it resolves its 'name' to a sibling - * assets/cutscenes/.jsonc asset at parse time, mirroring how - * cutsceneCutsceneStart/cutsceneCutsceneUpdate stay inline in - * cutsceneitem.c rather than getting their own file. + * system itself. Only stores the 'name' string (which points into the + * cutscene asset's still-alive yyjson doc, so no copy is needed) - + * resolving it to the referenced assets/cutscenes/.jsonc asset is + * deferred to cutsceneCutsceneStart, the first time this item actually + * runs, so a cutscene referencing many others doesn't pay to load every + * one of them up front. * * @param itemObj The item's JSON object. * @param item Destination item, already zeroed by the caller.