diff --git a/src/dusk/rpg/cutscene/cutscenesystem.c b/src/dusk/rpg/cutscene/cutscenesystem.c index 08541258..c9dce79f 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.c +++ b/src/dusk/rpg/cutscene/cutscenesystem.c @@ -16,8 +16,6 @@ cutscenesystem_t CUTSCENE_SYSTEM; -static yyjson_doc *CUTSCENE_LOADED_DOC = NULL; - void cutsceneSystemInit() { memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t)); CUTSCENE_SYSTEM.loadedScene.items = CUTSCENE_SYSTEM.loadedItems; @@ -37,8 +35,6 @@ void cutsceneSystemDispose() { CUTSCENE_SYSTEM.textCache[0] = '\0'; CUTSCENE_SYSTEM.onComplete = NULL; CUTSCENE_SYSTEM.loadedFile[0] = '\0'; - - cutsceneLoadedSetDoc(NULL); } void cutsceneSystemPrepare( @@ -120,41 +116,26 @@ void cutsceneSystemLoad(const char_t *file) { return; } - // Steal the parsed doc before unlocking - assetJsonDispose would - // otherwise free it out before it's been parsed below. - yyjson_doc *doc = entry->data.json; - entry->data.json = NULL; - assetUnlockEntry(entry); - // Force this now-zero-ref entry to actually go away right away, rather - // than leaving it languishing as LOADED until something else happens to - // trigger a reap - assetRequireLoaded treats an already-LOADED entry as - // an instant cache hit with no reparsing, so a stale leftover entry for - // this same path would otherwise make a later load of it wrongly skip - // reloading, still returning CUTSCENE_SYSTEM.loadedScene even after - // some other cutscene has since overwritten it. - assetReapUnused(); - + // Parse straight out of the entry's own doc while still locked - every + // cutsceneitem_t field that could reference it owns its string data by + // value (see cutscenecutsceneref_t/cutscenemarker_t's doc comments), so + // nothing needs to survive past this call and the entry can just be + // unlocked like any other asset once parsing finishes. errorret_t parseResult = cutsceneParseDoc( - doc, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, + entry->data.json, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, CUTSCENE_LOADED_ITEMS_MAX ); + assetUnlockEntry(entry); if(errorIsNotOk(parseResult)) { - yyjson_doc_free(doc); errorCatch(errorPrint(parseResult)); uiFatalErrorOpen(NULL); return; } - cutsceneLoadedSetDoc(doc); cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene); stringCopy(CUTSCENE_SYSTEM.loadedFile, file, ASSET_FILE_NAME_MAX - 1); } -void cutsceneLoadedSetDoc(yyjson_doc *doc) { - if(CUTSCENE_LOADED_DOC != NULL) yyjson_doc_free(CUTSCENE_LOADED_DOC); - CUTSCENE_LOADED_DOC = doc; -} - void cutsceneRestart(void) { assertNotNull( CUTSCENE_SYSTEM.scene, "cutsceneRestart called with no cutscene running" diff --git a/src/dusk/rpg/cutscene/cutscenesystem.h b/src/dusk/rpg/cutscene/cutscenesystem.h index edec0f2e..57266768 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.h +++ b/src/dusk/rpg/cutscene/cutscenesystem.h @@ -158,41 +158,32 @@ void cutsceneSystemInsertCutscene(const cutscene_t *cutscene); * Loads and immediately starts a cutscene asset by file name, e.g. * cutsceneSystemLoad("main_menu.jsonc") loads and starts * assets/cutscenes/main_menu.jsonc, parsing it into - * CUTSCENE_SYSTEM.loadedScene/loadedItems - the underlying JSON asset - * entry is unlocked and reaped immediately once that's done (see - * CUTSCENE_LOADED_ITEMS_MAX's doc comment for why), so it's always - * re-read+re-parsed fresh, never assumed still resident from a previous - * call. Opens the fatal error overlay (see uiFatalErrorOpen) instead of - * starting anything if the asset fails to load. Records file into - * CUTSCENE_SYSTEM.loadedFile once it starts running. + * CUTSCENE_SYSTEM.loadedScene/loadedItems. Locks the underlying JSON + * asset entry just long enough to parse it, then unlocks it like any + * other asset - every cutsceneitem_t field that could reference the + * parsed doc owns its string data by value (see cutscenecutsceneref_t/ + * cutscenemarker_t's doc comments), so nothing needs to keep it resident + * past this call, and a later load of the same file is a normal cache + * hit (or a fresh re-read, if the entry was reaped meanwhile) rather than + * something this function has to force either way. Opens the fatal error + * overlay (see uiFatalErrorOpen) instead of starting anything if the + * asset fails to load. Records file into CUTSCENE_SYSTEM.loadedFile once + * it starts running. * * @param file Cutscene file name (with .jsonc extension), relative to * assets/cutscenes/. */ void cutsceneSystemLoad(const char_t *file); -/** - * Frees whatever yyjson_doc was parsed into CUTSCENE_SYSTEM.loadedItems - * last and takes ownership of doc instead. Called by - * cutsceneSystemLoad/cutsceneCutsceneResolve once a fresh parse into - * loadedItems 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); - /** * Restarts the currently running cutscene from its first item, * preserving whatever interact/interacted entities triggered it and * whatever completion callback was armed. If CUTSCENE_SYSTEM.loadedFile * is set (i.e. the running cutscene came from cutsceneSystemLoad), this - * re-reads and re-parses that file via cutsceneSystemLoad rather than - * just rerunning whatever's still resident in loadedScene/loadedItems, so - * a restart always reflects the file's current contents - otherwise it's - * just cutsceneSystemStartCutsceneWith on the same cutscene_t. Asserts if - * no cutscene is running. + * re-invokes cutsceneSystemLoad on that same filename rather than just + * rerunning whatever's still resident in loadedScene/loadedItems - + * otherwise it's just cutsceneSystemStartCutsceneWith on the same + * cutscene_t. Asserts if no cutscene is running. */ void cutsceneRestart(void); diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index feb93d13..ece89a4a 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -410,32 +410,22 @@ cutscene_t * cutsceneCutsceneResolve(const char_t *name) { return NULL; } - // Steal the parsed doc before unlocking - assetJsonDispose would - // otherwise free it out before it's been parsed below. - yyjson_doc *doc = entry->data.json; - entry->data.json = NULL; - assetUnlockEntry(entry); - // Force this now-zero-ref entry to actually go away right away, rather - // than leaving it languishing as LOADED until something else happens to - // trigger a reap - assetRequireLoaded treats an already-LOADED entry as - // an instant cache hit with no reparsing, so a stale leftover entry for - // this same path would otherwise make a later resolve of it wrongly - // skip loading, still returning CUTSCENE_SYSTEM.loadedScene even after - // some other cutscene has since overwritten it. - assetReapUnused(); - + // Parse straight out of the entry's own doc while still locked - every + // cutsceneitem_t field that could reference it owns its string data by + // value (see cutscenecutsceneref_t/cutscenemarker_t's doc comments), so + // nothing needs to survive past this call and the entry can just be + // unlocked like any other asset once parsing finishes. errorret_t parseResult = cutsceneParseDoc( - doc, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, + entry->data.json, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, CUTSCENE_LOADED_ITEMS_MAX ); + assetUnlockEntry(entry); if(errorIsNotOk(parseResult)) { - yyjson_doc_free(doc); errorCatch(errorPrint(parseResult)); uiFatalErrorOpen(NULL); return NULL; } - cutsceneLoadedSetDoc(doc); return &CUTSCENE_SYSTEM.loadedScene; } @@ -501,25 +491,17 @@ void cutsceneInsertResolveAndSplice(const char_t *name) { return; } - // Steal the parsed doc before unlocking - assetJsonDispose would - // otherwise free it out before it's been parsed below. - yyjson_doc *doc = entry->data.json; - entry->data.json = NULL; - assetUnlockEntry(entry); - // See cutsceneCutsceneResolve's matching comment - forces a stale - // leftover entry for this same path out immediately, so a later resolve - // never wrongly skips reloading it. - assetReapUnused(); - // Local scratch, not a shared buffer - this target is spliced straight // into the running scene below and never referenced again afterward, so - // it only needs to live for the rest of this call (see this function's - // doc comment for why the doc can be freed immediately too). + // it only needs to live for the rest of this call. Parsed straight out + // of the entry's own doc while still locked, then unlocked like any + // other asset - see cutsceneCutsceneResolve's matching comment for why + // nothing needs to keep the doc itself resident past the parse. cutsceneitem_t items[CUTSCENE_INSERT_ITEMS_MAX]; cutscene_t scene = { .items = items }; errorret_t parseResult = - cutsceneParseDoc(doc, &scene, items, CUTSCENE_INSERT_ITEMS_MAX); - yyjson_doc_free(doc); + cutsceneParseDoc(entry->data.json, &scene, items, CUTSCENE_INSERT_ITEMS_MAX); + assetUnlockEntry(entry); if(errorIsNotOk(parseResult)) { errorCatch(errorPrint(parseResult)); uiFatalErrorOpen(NULL); diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index d0a36cdd..215c12f8 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -227,8 +227,9 @@ void cutsceneCutsceneStart( /** * Resolves a JSON-authored CUTSCENE item's referenced cutscene by name: * locks+loads assets/cutscenes/.jsonc, parses it into - * CUTSCENE_SYSTEM.loadedItems/loadedScene, then unlocks and reaps the asset entry - * immediately (see CUTSCENE_LOADED_ITEMS_MAX's doc comment for why). + * CUTSCENE_SYSTEM.loadedItems/loadedScene, then unlocks the asset entry + * like any other asset - the parsed items don't reference the doc past + * that (see cutsceneInsertResolveAndSplice's matching comment for why). * 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 @@ -315,14 +316,14 @@ void cutsceneInsertStart( * splices it into the running cutscene (CUTSCENE_SYSTEM.scene): locks+ * loads assets/cutscenes/.jsonc as a plain ASSET_LOADER_TYPE_JSON * asset, parses it into a small scratch buffer local to this call - * (capacity CUTSCENE_INSERT_ITEMS_MAX), then immediately frees the parsed - * doc - unlike cutsceneCutsceneResolve's CUTSCENE_SYSTEM.loadedScene, nothing - * needs to persist past this call: cutsceneSystemInsertCutscene copies the - * scratch items into the running scene's own array, and every cutscene - * item field that could reference the doc's memory owns its string data - * by value (see cutscenecutsceneref_t/cutscenemarker_t's doc comments), so - * the doc is disposable the moment parsing finishes. On failure, opens the - * fatal error overlay, matching cutsceneCutsceneResolve. + * (capacity CUTSCENE_INSERT_ITEMS_MAX) straight out of the asset entry's + * own doc, then unlocks the entry like any other asset - nothing needs + * to keep the doc itself resident past the parse: every cutscene item + * field that could reference it owns its string data by value (see + * cutscenecutsceneref_t/cutscenemarker_t's doc comments), and + * cutsceneSystemInsertCutscene copies the scratch items into the running + * scene's own array right after. On failure, opens the fatal error + * overlay, matching cutsceneCutsceneResolve. * * @param name Bare cutscene name (without "cutscenes/" prefix or * ".jsonc" suffix).