Re-add CUTSCENE_INSERT item with its own dedicated load buffer
Splicing a JSON-authored INSERT target by name previously resolved through the same shared CUTSCENE_LOADED_ITEMS/POOL buffer as whatever outer cutscene was currently running, so loading it would clobber the outer cutscene's own items mid-playback - that's why it was reverted. Gives INSERT its own smaller CUTSCENE_INSERT_ITEMS/POOL buffer and lets the cutscene asset loader be told which buffer set to parse into, so an INSERT's dynamic load no longer stomps the running cutscene. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -94,9 +94,27 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assetcutsceneloaderinput_t *input = loading->entry->input != NULL
|
||||||
|
? &loading->entry->input->cutscene
|
||||||
|
: NULL;
|
||||||
|
const bool_t toInsert =
|
||||||
|
input != NULL && input->dest == ASSET_CUTSCENE_LOAD_DEST_INSERT;
|
||||||
|
const char_t *filePath = (input != NULL && input->path[0] != '\0')
|
||||||
|
? input->path
|
||||||
|
: loading->entry->name;
|
||||||
|
|
||||||
|
cutsceneitem_t *targetItems =
|
||||||
|
toInsert ? CUTSCENE_INSERT_ITEMS : CUTSCENE_LOADED_ITEMS;
|
||||||
|
const size_t targetItemsMax =
|
||||||
|
toInsert ? CUTSCENE_INSERT_ITEMS_MAX : CUTSCENE_LOADED_ITEMS_MAX;
|
||||||
|
uint8_t *targetPool = toInsert ? CUTSCENE_INSERT_POOL : CUTSCENE_LOADED_POOL;
|
||||||
|
const size_t targetPoolMax =
|
||||||
|
toInsert ? CUTSCENE_INSERT_POOL_SIZE_MAX : CUTSCENE_LOADED_POOL_SIZE_MAX;
|
||||||
|
cutscene_t *targetScene = toInsert ? &CUTSCENE_INSERT_SCENE : &CUTSCENE_LOADED_SCENE;
|
||||||
|
|
||||||
assetfile_t *file = &loading->loading.cutscene.file;
|
assetfile_t *file = &loading->loading.cutscene.file;
|
||||||
assetLoaderErrorChain(loading,
|
assetLoaderErrorChain(loading,
|
||||||
assetFileInit(file, loading->entry->name, NULL, NULL)
|
assetFileInit(file, filePath, NULL, NULL)
|
||||||
);
|
);
|
||||||
|
|
||||||
if(file->size > ASSET_CUTSCENE_FILE_SIZE_MAX) {
|
if(file->size > ASSET_CUTSCENE_FILE_SIZE_MAX) {
|
||||||
@@ -137,10 +155,10 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t itemCount = yyjson_arr_size(itemsArr);
|
size_t itemCount = yyjson_arr_size(itemsArr);
|
||||||
if(itemCount > CUTSCENE_LOADED_ITEMS_MAX) {
|
if(itemCount > targetItemsMax) {
|
||||||
yyjson_doc_free(doc);
|
yyjson_doc_free(doc);
|
||||||
assetLoaderErrorThrow(
|
assetLoaderErrorThrow(
|
||||||
loading, "Cutscene has more than CUTSCENE_LOADED_ITEMS_MAX items"
|
loading, "Cutscene has more items than its target buffer allows"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,20 +174,20 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t poolSize = assetCutsceneComputePoolSize(itemsArr);
|
size_t poolSize = assetCutsceneComputePoolSize(itemsArr);
|
||||||
if(poolSize > CUTSCENE_LOADED_POOL_SIZE_MAX) {
|
if(poolSize > targetPoolMax) {
|
||||||
yyjson_doc_free(doc);
|
yyjson_doc_free(doc);
|
||||||
assetLoaderErrorThrow(
|
assetLoaderErrorThrow(
|
||||||
loading, "Cutscene needs more pool space than CUTSCENE_LOADED_POOL_SIZE_MAX"
|
loading, "Cutscene needs more pool space than its target buffer allows"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything structural has validated by this point, so it's safe to
|
// Everything structural has validated by this point, so it's safe to
|
||||||
// start overwriting the shared CUTSCENE_LOADED_ITEMS/POOL buffers - a
|
// start overwriting the target's shared items/pool buffers - a per-item
|
||||||
// per-item parse failure past this point still leaves them partially
|
// parse failure past this point still leaves them partially overwritten,
|
||||||
// overwritten, but that's fine: any load failure here already sends the
|
// but that's fine: any load failure here already sends the game into the
|
||||||
// game into the fatal-error overlay (see cutsceneSystemLoad/
|
// fatal-error overlay (see cutsceneSystemLoad/cutsceneCutsceneResolve/
|
||||||
// cutsceneCutsceneResolve), so nothing keeps running against stale data.
|
// cutsceneInsertResolve), so nothing keeps running against stale data.
|
||||||
memoryZero(CUTSCENE_LOADED_ITEMS, itemCount * sizeof(cutsceneitem_t));
|
memoryZero(targetItems, 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);
|
||||||
@@ -177,8 +195,8 @@ 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, &CUTSCENE_LOADED_ITEMS[index],
|
loading, itemVal, &targetItems[index],
|
||||||
CUTSCENE_LOADED_POOL, &poolOffset, index
|
targetPool, &poolOffset, index
|
||||||
);
|
);
|
||||||
if(errorIsNotOk(itemResult)) {
|
if(errorIsNotOk(itemResult)) {
|
||||||
yyjson_doc_free(doc);
|
yyjson_doc_free(doc);
|
||||||
@@ -190,11 +208,12 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
|
|||||||
// Takes ownership of doc - marker/cutscene-reference name fields point
|
// Takes ownership of doc - marker/cutscene-reference name fields point
|
||||||
// directly into it (see cutsceneCutsceneRefLoad/cutsceneMarkerLoad), so
|
// directly into it (see cutsceneCutsceneRefLoad/cutsceneMarkerLoad), so
|
||||||
// it must outlive this load, replacing whatever the previously loaded
|
// it must outlive this load, replacing whatever the previously loaded
|
||||||
// cutscene left behind.
|
// cutscene left behind for this same destination.
|
||||||
cutsceneLoadedSetDoc(doc);
|
if(toInsert) cutsceneInsertSetDoc(doc);
|
||||||
CUTSCENE_LOADED_SCENE.itemCount = (uint8_t)itemCount;
|
else cutsceneLoadedSetDoc(doc);
|
||||||
CUTSCENE_LOADED_SCENE.pause = pause;
|
targetScene->itemCount = (uint8_t)itemCount;
|
||||||
CUTSCENE_LOADED_SCENE.dataSize = 0;
|
targetScene->pause = pause;
|
||||||
|
targetScene->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;
|
||||||
|
|||||||
@@ -18,8 +18,26 @@
|
|||||||
typedef struct assetloading_s assetloading_t;
|
typedef struct assetloading_s assetloading_t;
|
||||||
typedef struct assetentry_s assetentry_t;
|
typedef struct assetentry_s assetentry_t;
|
||||||
|
|
||||||
|
// Which fixed, shared buffer set (see cutscenesystem.h) a load should
|
||||||
|
// parse into - MAIN for the one JSON-authored cutscene resident via
|
||||||
|
// cutsceneSystemLoad/cutsceneCutsceneResolve, INSERT for the one resident
|
||||||
|
// via cutsceneInsertResolve (see CUTSCENE_ITEM_TYPE_INSERT).
|
||||||
|
typedef enum {
|
||||||
|
ASSET_CUTSCENE_LOAD_DEST_MAIN,
|
||||||
|
ASSET_CUTSCENE_LOAD_DEST_INSERT
|
||||||
|
} assetcutsceneloaddest_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *nothing;
|
assetcutsceneloaddest_t dest;
|
||||||
|
|
||||||
|
// Optional file path override, same trick as assetjsonloaderinput_t -
|
||||||
|
// when path[0] is non-zero this is read instead of loading->entry->name,
|
||||||
|
// so a synthetic cache key (e.g. "insert:cutscenes/foo.jsonc", used to
|
||||||
|
// avoid a single entry being shared - and its dest silently ignored -
|
||||||
|
// between two different logical loads of the same file, see
|
||||||
|
// asset.c/assetGetEntry's name-only dedup) can still point at the real
|
||||||
|
// file.
|
||||||
|
char_t path[ASSET_FILE_NAME_MAX];
|
||||||
} assetcutsceneloaderinput_t;
|
} assetcutsceneloaderinput_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -73,13 +91,15 @@ errorret_t assetCutsceneParseItem(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 directly into the fixed, shared
|
* and, in the same call, parses them directly into one of the fixed,
|
||||||
* CUTSCENE_LOADED_ITEMS/CUTSCENE_LOADED_POOL buffers (see cutscenesystem.h)
|
* shared buffer sets in cutscenesystem.h - CUTSCENE_LOADED_ITEMS/POOL by
|
||||||
* - no heap allocation at all, since only one cutscene is ever resident.
|
* default, or CUTSCENE_INSERT_ITEMS/POOL when
|
||||||
* None of that work touches the main thread (every item type's load
|
* loading->entry->input->cutscene.dest is ASSET_CUTSCENE_LOAD_DEST_INSERT -
|
||||||
* callback is pure parsing now; CUTSCENE items defer their nested asset
|
* no heap allocation at all, since only one cutscene is ever resident per
|
||||||
* resolution to cutsceneCutsceneStart), so doing it all here keeps a large
|
* destination. None of that work touches the main thread (every item
|
||||||
* cutscene from ever blocking a frame.
|
* type's load 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.
|
||||||
|
|||||||
@@ -62,3 +62,20 @@ typedef struct cutscene_s {
|
|||||||
.type = CUTSCENE_ITEM_TYPE_CUTSCENE, \
|
.type = CUTSCENE_ITEM_TYPE_CUTSCENE, \
|
||||||
.cutsceneRef = { .cutscene = CUTSCENE_REFERENCE(CUTSCENE), .name = NULL } \
|
.cutsceneRef = { .cutscene = CUTSCENE_REFERENCE(CUTSCENE), .name = NULL } \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splices another CUTSCENE(NAME, ...)-declared cutscene's items into this
|
||||||
|
* one in place, resuming right after this item once the referenced
|
||||||
|
* cutscene's items run out - unlike CUTSCENE_CUTSCENE (a one-way jump that
|
||||||
|
* never returns). Has no dedicated item header of its own, same rationale
|
||||||
|
* as CUTSCENE_CUTSCENE - cutsceneInsertStart/Update/Load are defined
|
||||||
|
* inline in cutsceneitem.h/.c, tightly bound to the asset/loader system.
|
||||||
|
*
|
||||||
|
* @param CUTSCENE The referenced cutscene's NAME, as passed to
|
||||||
|
* CUTSCENE(...).
|
||||||
|
*/
|
||||||
|
#define CUTSCENE_INSERT(CUTSCENE) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_INSERT, \
|
||||||
|
.insert = { .cutscene = CUTSCENE_REFERENCE(CUTSCENE), .name = NULL } \
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,6 +30,21 @@ void cutsceneLoadedSetDoc(yyjson_doc *doc) {
|
|||||||
CUTSCENE_LOADED_DOC = doc;
|
CUTSCENE_LOADED_DOC = doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cutsceneitem_t CUTSCENE_INSERT_ITEMS[CUTSCENE_INSERT_ITEMS_MAX];
|
||||||
|
uint8_t CUTSCENE_INSERT_POOL[CUTSCENE_INSERT_POOL_SIZE_MAX];
|
||||||
|
cutscene_t CUTSCENE_INSERT_SCENE = {
|
||||||
|
.items = CUTSCENE_INSERT_ITEMS,
|
||||||
|
.itemCount = 0,
|
||||||
|
.pause = CUTSCENE_PAUSE_NONE,
|
||||||
|
.dataSize = 0
|
||||||
|
};
|
||||||
|
static yyjson_doc *CUTSCENE_INSERT_DOC = NULL;
|
||||||
|
|
||||||
|
void cutsceneInsertSetDoc(yyjson_doc *doc) {
|
||||||
|
if(CUTSCENE_INSERT_DOC != NULL) yyjson_doc_free(CUTSCENE_INSERT_DOC);
|
||||||
|
CUTSCENE_INSERT_DOC = doc;
|
||||||
|
}
|
||||||
|
|
||||||
void cutsceneSystemInit() {
|
void cutsceneSystemInit() {
|
||||||
memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t));
|
memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t));
|
||||||
}
|
}
|
||||||
@@ -55,6 +70,7 @@ void cutsceneSystemPrepare(
|
|||||||
CUTSCENE_SYSTEM.textCache[0] = '\0';
|
CUTSCENE_SYSTEM.textCache[0] = '\0';
|
||||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
||||||
CUTSCENE_SYSTEM.onComplete = NULL;
|
CUTSCENE_SYSTEM.onComplete = NULL;
|
||||||
|
CUTSCENE_SYSTEM.insertStackSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
|
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
|
||||||
@@ -127,6 +143,21 @@ void cutsceneSystemNext() {
|
|||||||
|
|
||||||
CUTSCENE_SYSTEM.currentItem++;
|
CUTSCENE_SYSTEM.currentItem++;
|
||||||
|
|
||||||
|
// Run off the end of the current scene? If it was spliced in via
|
||||||
|
// CUTSCENE_ITEM_TYPE_INSERT, pop back to whatever it was inserted into
|
||||||
|
// and resume right after the INSERT item - possibly chaining through
|
||||||
|
// several pops if that lands on the end of its own parent in turn (an
|
||||||
|
// insert as the very last item, or a nested insert-of-an-insert).
|
||||||
|
while(
|
||||||
|
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount &&
|
||||||
|
CUTSCENE_SYSTEM.insertStackSize > 0
|
||||||
|
) {
|
||||||
|
cutsceneinsertframe_t frame =
|
||||||
|
CUTSCENE_SYSTEM.insertStack[--CUTSCENE_SYSTEM.insertStackSize];
|
||||||
|
CUTSCENE_SYSTEM.scene = frame.scene;
|
||||||
|
CUTSCENE_SYSTEM.currentItem = frame.currentItem + 1;
|
||||||
|
}
|
||||||
|
|
||||||
// End of the cutscene?
|
// End of the cutscene?
|
||||||
if(
|
if(
|
||||||
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount
|
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount
|
||||||
@@ -149,6 +180,7 @@ void cutsceneSystemNext() {
|
|||||||
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;
|
||||||
|
CUTSCENE_SYSTEM.insertStackSize = 0;
|
||||||
|
|
||||||
if(onComplete != NULL) onComplete(userData);
|
if(onComplete != NULL) onComplete(userData);
|
||||||
return;
|
return;
|
||||||
@@ -160,6 +192,27 @@ void cutsceneSystemNext() {
|
|||||||
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
|
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) {
|
||||||
|
assertNotNull(
|
||||||
|
CUTSCENE_SYSTEM.scene,
|
||||||
|
"cutsceneSystemInsertCutscene called with no cutscene running"
|
||||||
|
);
|
||||||
|
assertTrue(
|
||||||
|
CUTSCENE_SYSTEM.insertStackSize < CUTSCENE_INSERT_STACK_MAX,
|
||||||
|
"Cutscene INSERT nesting exceeds CUTSCENE_INSERT_STACK_MAX"
|
||||||
|
);
|
||||||
|
|
||||||
|
CUTSCENE_SYSTEM.insertStack[CUTSCENE_SYSTEM.insertStackSize++] =
|
||||||
|
(cutsceneinsertframe_t){
|
||||||
|
.scene = CUTSCENE_SYSTEM.scene,
|
||||||
|
.currentItem = CUTSCENE_SYSTEM.currentItem
|
||||||
|
};
|
||||||
|
|
||||||
|
CUTSCENE_SYSTEM.scene = cutscene;
|
||||||
|
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
||||||
|
cutsceneSystemNext();
|
||||||
|
}
|
||||||
|
|
||||||
void cutsceneSystemSetOnComplete(cutscenecallback_t onComplete) {
|
void cutsceneSystemSetOnComplete(cutscenecallback_t onComplete) {
|
||||||
assertNotNull(
|
assertNotNull(
|
||||||
CUTSCENE_SYSTEM.scene,
|
CUTSCENE_SYSTEM.scene,
|
||||||
@@ -279,6 +332,8 @@ 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;
|
||||||
|
CUTSCENE_SYSTEM.insertStackSize = 0;
|
||||||
|
|
||||||
cutsceneLoadedSetDoc(NULL);
|
cutsceneLoadedSetDoc(NULL);
|
||||||
|
cutsceneInsertSetDoc(NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,52 @@ extern cutscene_t CUTSCENE_LOADED_SCENE;
|
|||||||
*/
|
*/
|
||||||
void cutsceneLoadedSetDoc(yyjson_doc *doc);
|
void cutsceneLoadedSetDoc(yyjson_doc *doc);
|
||||||
|
|
||||||
|
// A second, much smaller buffer set dedicated to CUTSCENE_ITEM_TYPE_INSERT
|
||||||
|
// (see cutsceneSystemInsertCutscene) - kept entirely separate from
|
||||||
|
// CUTSCENE_LOADED_ITEMS/POOL so that a JSON-authored INSERT item resolving
|
||||||
|
// its target by name (via cutsceneInsertResolve) never overwrites the
|
||||||
|
// buffer the currently-running outer cutscene's own items live in. Sized
|
||||||
|
// small deliberately - an inserted snippet is meant to be a short splice,
|
||||||
|
// not a full cutscene.
|
||||||
|
#define CUTSCENE_INSERT_ITEMS_MAX 16
|
||||||
|
#define CUTSCENE_INSERT_POOL_SIZE_MAX 512
|
||||||
|
|
||||||
|
extern cutsceneitem_t CUTSCENE_INSERT_ITEMS[CUTSCENE_INSERT_ITEMS_MAX];
|
||||||
|
extern uint8_t CUTSCENE_INSERT_POOL[CUTSCENE_INSERT_POOL_SIZE_MAX];
|
||||||
|
|
||||||
|
// The cutscene_t for whatever was last parsed into CUTSCENE_INSERT_ITEMS/
|
||||||
|
// CUTSCENE_INSERT_POOL - .items always points at CUTSCENE_INSERT_ITEMS.
|
||||||
|
// Only one JSON-authored INSERT target is ever resident at a time, same
|
||||||
|
// restriction as CUTSCENE_LOADED_SCENE - a JSON-authored INSERT whose
|
||||||
|
// target itself contains a JSON-authored INSERT would overwrite this out
|
||||||
|
// from under itself, so that isn't supported (compile-time CUTSCENE_INSERT
|
||||||
|
// nesting is unaffected, since it never touches asset loading).
|
||||||
|
extern cutscene_t CUTSCENE_INSERT_SCENE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as cutsceneLoadedSetDoc, but for whatever yyjson_doc currently
|
||||||
|
* backs CUTSCENE_INSERT_ITEMS' string fields. Called by the cutscene asset
|
||||||
|
* loader once a fresh parse into CUTSCENE_INSERT_ITEMS/CUTSCENE_INSERT_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 cutsceneInsertSetDoc(yyjson_doc *doc);
|
||||||
|
|
||||||
|
// Maximum nesting depth of CUTSCENE_ITEM_TYPE_INSERT items - how many
|
||||||
|
// "return to the item after this one" frames cutsceneSystemInsertCutscene
|
||||||
|
// may have pushed at once (see CUTSCENE_SYSTEM.insertStack).
|
||||||
|
#define CUTSCENE_INSERT_STACK_MAX 4
|
||||||
|
|
||||||
|
// One "return address" pushed by cutsceneSystemInsertCutscene - the scene
|
||||||
|
// and item index to resume at once the inserted cutscene's items run out.
|
||||||
|
typedef struct {
|
||||||
|
const cutscene_t *scene;
|
||||||
|
uint8_t currentItem;
|
||||||
|
} cutsceneinsertframe_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const cutscene_t *scene;
|
const cutscene_t *scene;
|
||||||
uint8_t currentItem;
|
uint8_t currentItem;
|
||||||
@@ -85,6 +131,12 @@ typedef struct {
|
|||||||
|
|
||||||
// See cutsceneSystemSetOnComplete.
|
// See cutsceneSystemSetOnComplete.
|
||||||
cutscenecallback_t onComplete;
|
cutscenecallback_t onComplete;
|
||||||
|
|
||||||
|
// Return stack pushed by CUTSCENE_ITEM_TYPE_INSERT items - see
|
||||||
|
// cutsceneSystemInsertCutscene. Reset (insertStackSize = 0) whenever a
|
||||||
|
// cutscene freshly starts or the system is disposed.
|
||||||
|
cutsceneinsertframe_t insertStack[CUTSCENE_INSERT_STACK_MAX];
|
||||||
|
uint8_t insertStackSize;
|
||||||
} cutscenesystem_t;
|
} cutscenesystem_t;
|
||||||
|
|
||||||
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||||
@@ -129,6 +181,30 @@ void cutsceneSystemStartCutsceneAndGoToMarker(
|
|||||||
const char_t *marker
|
const char_t *marker
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splices cutscene's items into the running cutscene in place, resuming
|
||||||
|
* the current cutscene right after the CUTSCENE_ITEM_TYPE_INSERT item that
|
||||||
|
* called this once cutscene's items run out - unlike
|
||||||
|
* cutsceneSystemStartCutscene (a one-way jump that replaces the running
|
||||||
|
* cutscene outright). Pushes a return frame onto
|
||||||
|
* CUTSCENE_SYSTEM.insertStack; cutsceneSystemNext pops it once cutscene
|
||||||
|
* ends. Deliberately does not touch pause flags, interact entities,
|
||||||
|
* "last created" state, the text cache or userData - those all keep
|
||||||
|
* whatever the outer cutscene set, since this is meant to feel like
|
||||||
|
* pasting cutscene's items in place rather than starting an independent
|
||||||
|
* cutscene. Because of this, cutscene's own .pause/.dataSize are ignored.
|
||||||
|
* Asserts if no cutscene is currently running, or if inserts are already
|
||||||
|
* nested CUTSCENE_INSERT_STACK_MAX deep.
|
||||||
|
*
|
||||||
|
* Note: cutsceneRestart() resets the insert stack (via
|
||||||
|
* cutsceneSystemPrepare), so a RESTART item used from inside an inserted
|
||||||
|
* cutscene restarts just that inserted cutscene and does not return to
|
||||||
|
* whatever it was inserted into.
|
||||||
|
*
|
||||||
|
* @param cutscene The cutscene whose items to splice in.
|
||||||
|
*/
|
||||||
|
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locks, loads and immediately starts a cutscene asset by file name,
|
* Locks, loads and immediately starts a cutscene asset by file name,
|
||||||
* e.g. cutsceneSystemLoad("main_menu.jsonc") loads and starts
|
* e.g. cutsceneSystemLoad("main_menu.jsonc") loads and starts
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "asset/assetfile.h"
|
#include "asset/assetfile.h"
|
||||||
#include "asset/loader/assetloader.h"
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "ui/overlay/uifatalerror.h"
|
#include "ui/overlay/uifatalerror.h"
|
||||||
|
|
||||||
@@ -333,6 +334,13 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
|||||||
.update = cutsceneRegularBattleUpdate,
|
.update = cutsceneRegularBattleUpdate,
|
||||||
.jsonName = "REGULAR_BATTLE",
|
.jsonName = "REGULAR_BATTLE",
|
||||||
.load = cutsceneItemLoadNoop
|
.load = cutsceneItemLoadNoop
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_INSERT] = {
|
||||||
|
.init = cutsceneInsertStart,
|
||||||
|
.update = cutsceneInsertUpdate,
|
||||||
|
.jsonName = "INSERT",
|
||||||
|
.load = cutsceneInsertLoad
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -414,10 +422,18 @@ errorret_t cutsceneCutsceneLoad(
|
|||||||
cutsceneitem_t *item,
|
cutsceneitem_t *item,
|
||||||
uint8_t *pool,
|
uint8_t *pool,
|
||||||
size_t *poolOffset
|
size_t *poolOffset
|
||||||
|
) {
|
||||||
|
return cutsceneCutsceneRefLoad(itemObj, &item->cutsceneRef, "CUTSCENE");
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t cutsceneCutsceneRefLoad(
|
||||||
|
yyjson_val *itemObj,
|
||||||
|
cutscenecutsceneref_t *outRef,
|
||||||
|
const char_t *itemTypeLabel
|
||||||
) {
|
) {
|
||||||
const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name"));
|
const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name"));
|
||||||
if(name == NULL) {
|
if(name == NULL) {
|
||||||
errorThrow("Cutscene item (CUTSCENE): missing 'name'");
|
errorThrow("Cutscene item (%s): missing 'name'", itemTypeLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only validate that the eventual "cutscenes/<name>.jsonc" path would
|
// Only validate that the eventual "cutscenes/<name>.jsonc" path would
|
||||||
@@ -425,11 +441,69 @@ errorret_t cutsceneCutsceneLoad(
|
|||||||
// actually runs (see cutsceneCutsceneStart/cutsceneCutsceneResolve).
|
// actually runs (see cutsceneCutsceneStart/cutsceneCutsceneResolve).
|
||||||
int32_t pathLen = stringFormat(NULL, 0, "cutscenes/%s.jsonc", name);
|
int32_t pathLen = stringFormat(NULL, 0, "cutscenes/%s.jsonc", name);
|
||||||
if(pathLen < 0 || (size_t)pathLen >= ASSET_FILE_NAME_MAX) {
|
if(pathLen < 0 || (size_t)pathLen >= ASSET_FILE_NAME_MAX) {
|
||||||
errorThrow("Cutscene item (CUTSCENE): 'name' too long");
|
errorThrow("Cutscene item (%s): 'name' too long", itemTypeLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
item->cutsceneRef.cutscene = NULL;
|
outRef->cutscene = NULL;
|
||||||
item->cutsceneRef.name = name;
|
outRef->name = name;
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cutsceneInsertStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
const cutscene_t *cutscene = item->insert.cutscene;
|
||||||
|
if(cutscene == NULL && item->insert.name != NULL) {
|
||||||
|
// Not cached back into item - same reasoning as cutsceneCutsceneStart,
|
||||||
|
// but resolved via cutsceneInsertResolve (the dedicated INSERT buffer)
|
||||||
|
// rather than cutsceneCutsceneResolve, since item itself may live
|
||||||
|
// inside the currently-running outer cutscene's own CUTSCENE_LOADED_*
|
||||||
|
// buffer and must not be clobbered by this resolve.
|
||||||
|
cutscene = cutsceneInsertResolve(item->insert.name);
|
||||||
|
}
|
||||||
|
if(cutscene != NULL) cutsceneSystemInsertCutscene(cutscene);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneInsertUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cutscene_t * cutsceneInsertResolve(const char_t *name) {
|
||||||
|
char_t path[ASSET_FILE_NAME_MAX];
|
||||||
|
stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name);
|
||||||
|
|
||||||
|
char_t key[ASSET_FILE_NAME_MAX];
|
||||||
|
stringFormat(key, sizeof(key), "insert:%s", path);
|
||||||
|
|
||||||
|
assetloaderinput_t input;
|
||||||
|
memoryZero(&input, sizeof(input));
|
||||||
|
input.cutscene.dest = ASSET_CUTSCENE_LOAD_DEST_INSERT;
|
||||||
|
stringCopy(input.cutscene.path, path, ASSET_FILE_NAME_MAX);
|
||||||
|
|
||||||
|
assetentry_t *entry = assetLock(key, ASSET_LOADER_TYPE_CUTSCENE, &input);
|
||||||
|
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)) {
|
||||||
|
errorCatch(errorPrint(result));
|
||||||
|
uiFatalErrorOpen(NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CUTSCENE_INSERT_SCENE;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t cutsceneInsertLoad(
|
||||||
|
yyjson_val *itemObj,
|
||||||
|
cutsceneitem_t *item,
|
||||||
|
uint8_t *pool,
|
||||||
|
size_t *poolOffset
|
||||||
|
) {
|
||||||
|
return cutsceneCutsceneRefLoad(itemObj, &item->insert, "INSERT");
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,15 +57,25 @@
|
|||||||
|
|
||||||
typedef struct cutscene_s cutscene_t;
|
typedef struct cutscene_s cutscene_t;
|
||||||
|
|
||||||
// A CUTSCENE item's target. C-authored cutscenes (the CUTSCENE_CUTSCENE
|
// A CUTSCENE (or INSERT) item's target. C-authored cutscenes (the
|
||||||
// macro in cutscene.h) resolve cutscene at compile time and leave name
|
// CUTSCENE_CUTSCENE/CUTSCENE_INSERT macros in cutscene.h) resolve cutscene
|
||||||
// NULL. JSON-authored cutscenes (cutsceneCutsceneLoad) do the opposite -
|
// at compile time and leave name NULL - safe to hold onto indefinitely,
|
||||||
// only name is set at parse time, since resolving/locking the referenced
|
// since C-authored cutscenes are compiled directly into their own static
|
||||||
// asset there would eagerly load every cutscene a cutscene merely
|
// const arrays, never a shared load buffer. JSON-authored cutscenes
|
||||||
// references, even ones never played. cutsceneCutsceneStart resolves
|
// (cutsceneCutsceneLoad/cutsceneInsertLoad) 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/cutsceneInsertStart resolve
|
||||||
// name to cutscene the first time this item actually runs (via
|
// name to cutscene the first time this item actually runs (via
|
||||||
// cutsceneCutsceneResolve) and caches the result back into this field so
|
// cutsceneCutsceneResolve/cutsceneInsertResolve respectively) but do NOT
|
||||||
// replaying the same item never re-resolves it.
|
// cache the result back into this field - with only one shared, fixed
|
||||||
|
// buffer per destination (see cutscenesystem.h), every JSON-authored
|
||||||
|
// reference resolves to the same fixed address every time anyway, and
|
||||||
|
// item itself may live inside that very buffer, so caching a pointer into
|
||||||
|
// it here would go stale (or, worse, get clobbered mid-resolve) the
|
||||||
|
// moment a later load overwrites it. Shared by both CUTSCENE
|
||||||
|
// (item->cutsceneRef) and INSERT (item->insert), since both need exactly
|
||||||
|
// this "resolved pointer, or a name to lazily resolve" shape.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const cutscene_t *cutscene;
|
const cutscene_t *cutscene;
|
||||||
const char_t *name;
|
const char_t *name;
|
||||||
@@ -81,6 +91,7 @@ struct cutsceneitem_s {
|
|||||||
cutscenecallback_t callback;
|
cutscenecallback_t callback;
|
||||||
cutscenewait_t wait;
|
cutscenewait_t wait;
|
||||||
cutscenecutsceneref_t cutsceneRef;
|
cutscenecutsceneref_t cutsceneRef;
|
||||||
|
cutscenecutsceneref_t insert;
|
||||||
cutsceneentityteleport_t entityTeleport;
|
cutsceneentityteleport_t entityTeleport;
|
||||||
cutsceneentitywalkto_t entityWalkTo;
|
cutsceneentitywalkto_t entityWalkTo;
|
||||||
cutscenefade_t fade;
|
cutscenefade_t fade;
|
||||||
@@ -197,10 +208,11 @@ bool_t cutsceneItemLookupType(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a nested-cutscene item, handing control over to the
|
* Starts a nested-cutscene item, handing control over to the referenced
|
||||||
* referenced cutscene. If item->cutsceneRef.cutscene hasn't been
|
* cutscene. If item->cutsceneRef.cutscene hasn't been resolved yet (a
|
||||||
* resolved yet (a JSON-authored reference, only known by name so far),
|
* JSON-authored reference, only known by name so far), resolves it first
|
||||||
* resolves and caches it first via cutsceneCutsceneResolve.
|
* via cutsceneCutsceneResolve (not cached back - see cutscenecutsceneref_t's
|
||||||
|
* doc comment for why).
|
||||||
*
|
*
|
||||||
* @param item The cutscene item.
|
* @param item The cutscene item.
|
||||||
* @param data Runtime data storage.
|
* @param data Runtime data storage.
|
||||||
@@ -262,3 +274,89 @@ errorret_t cutsceneCutsceneLoad(
|
|||||||
uint8_t *pool,
|
uint8_t *pool,
|
||||||
size_t *poolOffset
|
size_t *poolOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared JSON parsing body for both CUTSCENE and INSERT items - both need
|
||||||
|
* exactly a 'name' string field, validated to fit the eventual
|
||||||
|
* "cutscenes/<name>.jsonc" asset path. Only stores the name (see
|
||||||
|
* cutsceneCutsceneLoad for why resolving is deferred).
|
||||||
|
*
|
||||||
|
* @param itemObj The item's JSON object.
|
||||||
|
* @param outRef Destination ref, already zeroed by the caller.
|
||||||
|
* @param itemTypeLabel Item type name used only in the error message on
|
||||||
|
* failure (e.g. "CUTSCENE" or "INSERT").
|
||||||
|
* @return Error code indicating success or failure of the parse.
|
||||||
|
*/
|
||||||
|
errorret_t cutsceneCutsceneRefLoad(
|
||||||
|
yyjson_val *itemObj,
|
||||||
|
cutscenecutsceneref_t *outRef,
|
||||||
|
const char_t *itemTypeLabel
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an INSERT item, splicing the referenced cutscene's items into the
|
||||||
|
* running cutscene in place - unlike CUTSCENE (a one-way jump that replaces
|
||||||
|
* the running cutscene outright), control returns to the item right after
|
||||||
|
* this one once the inserted items run out. If item->insert.cutscene hasn't
|
||||||
|
* been resolved yet (a JSON-authored reference, only known by name so
|
||||||
|
* far), resolves it first via cutsceneInsertResolve - not
|
||||||
|
* cutsceneCutsceneResolve, so the referenced cutscene lands in the
|
||||||
|
* dedicated CUTSCENE_INSERT_ITEMS/POOL buffer rather than overwriting
|
||||||
|
* CUTSCENE_LOADED_ITEMS/POOL out from under whatever outer cutscene is
|
||||||
|
* currently running (see cutscenesystem.h's CUTSCENE_INSERT_ITEMS_MAX doc
|
||||||
|
* comment). Not cached back into item, same reasoning as
|
||||||
|
* cutsceneCutsceneStart.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneInsertStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a JSON-authored INSERT item's referenced cutscene by name,
|
||||||
|
* locking (under a synthetic "insert:cutscenes/<name>.jsonc" cache key,
|
||||||
|
* distinct from the plain "cutscenes/<name>.jsonc" key cutsceneCutsceneResolve
|
||||||
|
* uses for the same file, so a load driven by an INSERT item is never
|
||||||
|
* silently shared with - or overwritten by - a load driven by a CUTSCENE
|
||||||
|
* item, or vice versa - see assetGetEntry's name-only dedup) and blocking
|
||||||
|
* on assets/cutscenes/<name>.jsonc, parsed into CUTSCENE_INSERT_ITEMS/POOL
|
||||||
|
* rather than CUTSCENE_LOADED_ITEMS/POOL. On failure, opens the fatal
|
||||||
|
* error overlay 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 * cutsceneInsertResolve(const char_t *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an INSERT item. By the time this would run, control has already
|
||||||
|
* moved on to the inserted items, so this always reports incomplete.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns false always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneInsertUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an INSERT cutscene item's JSON fields into item.
|
||||||
|
*
|
||||||
|
* @param itemObj The item's JSON object.
|
||||||
|
* @param item Destination item, already zeroed by the caller.
|
||||||
|
* @param pool Unused (INSERT needs no pool storage).
|
||||||
|
* @param poolOffset Unused (INSERT needs no pool storage).
|
||||||
|
* @return Error code indicating success or failure of the parse.
|
||||||
|
*/
|
||||||
|
errorret_t cutsceneInsertLoad(
|
||||||
|
yyjson_val *itemObj,
|
||||||
|
cutsceneitem_t *item,
|
||||||
|
uint8_t *pool,
|
||||||
|
size_t *poolOffset
|
||||||
|
);
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ typedef enum {
|
|||||||
CUTSCENE_ITEM_TYPE_IDLE,
|
CUTSCENE_ITEM_TYPE_IDLE,
|
||||||
CUTSCENE_ITEM_TYPE_UI_SHOW,
|
CUTSCENE_ITEM_TYPE_UI_SHOW,
|
||||||
CUTSCENE_ITEM_TYPE_REGULAR_BATTLE,
|
CUTSCENE_ITEM_TYPE_REGULAR_BATTLE,
|
||||||
|
CUTSCENE_ITEM_TYPE_INSERT,
|
||||||
|
|
||||||
CUTSCENE_ITEM_TYPE_COUNT
|
CUTSCENE_ITEM_TYPE_COUNT
|
||||||
} cutsceneitemtype_t;
|
} cutsceneitemtype_t;
|
||||||
|
|||||||
@@ -177,6 +177,201 @@ static void test_cutsceneSystemDisposeResetsState(void **state) {
|
|||||||
assert_null(CUTSCENE_SYSTEM.entityInteracted);
|
assert_null(CUTSCENE_SYSTEM.entityInteracted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- CUTSCENE_INSERT ---------------------------------------------------
|
||||||
|
|
||||||
|
static uint8_t insertOrderLog[8];
|
||||||
|
static uint8_t insertOrderLogCount;
|
||||||
|
|
||||||
|
static void recordOrder0(void *userData) { insertOrderLog[insertOrderLogCount++] = 0; }
|
||||||
|
static void recordOrder1(void *userData) { insertOrderLog[insertOrderLogCount++] = 1; }
|
||||||
|
static void recordOrder2(void *userData) { insertOrderLog[insertOrderLogCount++] = 2; }
|
||||||
|
static void recordOrderA(void *userData) { insertOrderLog[insertOrderLogCount++] = 3; }
|
||||||
|
static void recordOrderB(void *userData) { insertOrderLog[insertOrderLogCount++] = 4; }
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_SNIPPET, 0, NONE,
|
||||||
|
CUTSCENE_CALLBACK(recordOrderA),
|
||||||
|
CUTSCENE_WAIT(2.0f),
|
||||||
|
CUTSCENE_CALLBACK(recordOrderB)
|
||||||
|
);
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_OUTER, 0, NONE,
|
||||||
|
CUTSCENE_CALLBACK(recordOrder0),
|
||||||
|
CUTSCENE_WAIT(1.0f),
|
||||||
|
CUTSCENE_CALLBACK(recordOrder1),
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_SNIPPET),
|
||||||
|
CUTSCENE_CALLBACK(recordOrder2)
|
||||||
|
);
|
||||||
|
|
||||||
|
static void test_cutsceneInsertSplicesItemsInPlaceThenResumes(void **state) {
|
||||||
|
|
||||||
|
insertOrderLogCount = 0;
|
||||||
|
cutsceneSystemInit();
|
||||||
|
cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_OUTER);
|
||||||
|
|
||||||
|
TIME.delta = 1.5f;// elapses the outer WAIT(1.0)
|
||||||
|
cutsceneSystemUpdate();// order0 done -> starts the outer WAIT
|
||||||
|
cutsceneSystemUpdate();// the WAIT elapses -> starts order1
|
||||||
|
cutsceneSystemUpdate();// order1 done -> starts the INSERT, cascading into orderA
|
||||||
|
|
||||||
|
assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_SNIPPET);
|
||||||
|
|
||||||
|
TIME.delta = 2.5f;// elapses the snippet's WAIT(2.0)
|
||||||
|
cutsceneSystemUpdate();// orderA done -> starts snippet's WAIT
|
||||||
|
cutsceneSystemUpdate();// snippet's WAIT elapses -> starts orderB
|
||||||
|
cutsceneSystemUpdate();// orderB done -> pops back, starts order2
|
||||||
|
|
||||||
|
assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_OUTER);
|
||||||
|
|
||||||
|
cutsceneSystemUpdate();// order2 done -> cutscene ends naturally
|
||||||
|
assert_null(CUTSCENE_SYSTEM.scene);
|
||||||
|
|
||||||
|
const uint8_t expected[] = { 0, 1, 3, 4, 2 };
|
||||||
|
assert_int_equal(insertOrderLogCount, sizeof(expected));
|
||||||
|
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_AS_LAST_ITEM_SNIPPET, 0, NONE,
|
||||||
|
CUTSCENE_CALLBACK(recordOrderA)
|
||||||
|
);
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_AS_LAST_ITEM_OUTER, 0, NONE,
|
||||||
|
CUTSCENE_CALLBACK(recordOrder0),
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_AS_LAST_ITEM_SNIPPET)
|
||||||
|
);
|
||||||
|
|
||||||
|
static void test_cutsceneInsertAsLastItemEndsCutsceneNaturally(
|
||||||
|
void **state
|
||||||
|
) {
|
||||||
|
|
||||||
|
insertOrderLogCount = 0;
|
||||||
|
cutsceneSystemInit();
|
||||||
|
cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER);
|
||||||
|
|
||||||
|
cutsceneSystemUpdate();// order0 done -> starts INSERT, cascades to orderA
|
||||||
|
assert_ptr_equal(
|
||||||
|
CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET
|
||||||
|
);
|
||||||
|
|
||||||
|
// orderA is the snippet's only item, and the snippet is the outer's last
|
||||||
|
// item -- finishing it should pop straight through to the natural end
|
||||||
|
// of the whole cutscene, not get stuck.
|
||||||
|
cutsceneSystemUpdate();
|
||||||
|
assert_null(CUTSCENE_SYSTEM.scene);
|
||||||
|
|
||||||
|
const uint8_t expected[] = { 0, 3 };
|
||||||
|
assert_int_equal(insertOrderLogCount, sizeof(expected));
|
||||||
|
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_NESTED_INNER, 0, NONE,
|
||||||
|
CUTSCENE_CALLBACK(recordOrderA)
|
||||||
|
);
|
||||||
|
|
||||||
|
// INSERT is MID's first item, so entering MID (from OUTER) cascades
|
||||||
|
// straight through into INNER within the same Start chain, one level
|
||||||
|
// deeper than a single insert.
|
||||||
|
CUTSCENE(TEST_INSERT_NESTED_MID, 0, NONE,
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_NESTED_INNER),
|
||||||
|
CUTSCENE_CALLBACK(recordOrderB)
|
||||||
|
);
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_NESTED_OUTER, 0, NONE,
|
||||||
|
CUTSCENE_CALLBACK(recordOrder0),
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_NESTED_MID),
|
||||||
|
CUTSCENE_CALLBACK(recordOrder2)
|
||||||
|
);
|
||||||
|
|
||||||
|
static void test_cutsceneInsertNestsThroughMultipleLevels(void **state) {
|
||||||
|
|
||||||
|
insertOrderLogCount = 0;
|
||||||
|
cutsceneSystemInit();
|
||||||
|
cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_NESTED_OUTER);
|
||||||
|
|
||||||
|
// order0 done -> starts INSERT(MID), cascading through INSERT(INNER) ->
|
||||||
|
// orderA, all within this one Update call's Start chain.
|
||||||
|
cutsceneSystemUpdate();
|
||||||
|
assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_NESTED_INNER);
|
||||||
|
|
||||||
|
cutsceneSystemUpdate();// orderA done -> pops to MID, starts orderB
|
||||||
|
assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_NESTED_MID);
|
||||||
|
|
||||||
|
cutsceneSystemUpdate();// orderB done -> pops to OUTER, starts order2
|
||||||
|
assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_NESTED_OUTER);
|
||||||
|
|
||||||
|
cutsceneSystemUpdate();// order2 done -> ends naturally
|
||||||
|
assert_null(CUTSCENE_SYSTEM.scene);
|
||||||
|
|
||||||
|
const uint8_t expected[] = { 0, 3, 4, 2 };
|
||||||
|
assert_int_equal(insertOrderLogCount, sizeof(expected));
|
||||||
|
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL5, 0, NONE,
|
||||||
|
CUTSCENE_CALLBACK(recordOrderA)
|
||||||
|
);
|
||||||
|
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL4, 0, NONE,
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL5)
|
||||||
|
);
|
||||||
|
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL3, 0, NONE,
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL4)
|
||||||
|
);
|
||||||
|
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL2, 0, NONE,
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL3)
|
||||||
|
);
|
||||||
|
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL1, 0, NONE,
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL2)
|
||||||
|
);
|
||||||
|
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL0, 0, NONE,
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL1)
|
||||||
|
);
|
||||||
|
|
||||||
|
static void test_cutsceneInsertAssertsWhenNestingExceedsStackMax(
|
||||||
|
void **state
|
||||||
|
) {
|
||||||
|
|
||||||
|
cutsceneSystemInit();
|
||||||
|
|
||||||
|
// 5 levels of INSERT deep, one past CUTSCENE_INSERT_STACK_MAX (4) --
|
||||||
|
// all cascade synchronously within this one call, since each level's
|
||||||
|
// only item is the INSERT starting immediately.
|
||||||
|
expect_assert_failure(
|
||||||
|
cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_OVERFLOW_LEVEL0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void noopInsertCallback(void *userData) {}
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_PERSIST_SNIPPET, 0, ALL,
|
||||||
|
CUTSCENE_CALLBACK(noopInsertCallback)
|
||||||
|
);
|
||||||
|
|
||||||
|
CUTSCENE(TEST_INSERT_PERSIST_OUTER, 0, NPC,
|
||||||
|
CUTSCENE_INSERT(TEST_INSERT_PERSIST_SNIPPET)
|
||||||
|
);
|
||||||
|
|
||||||
|
static void test_cutsceneInsertDoesNotResetPauseOrInteractEntities(
|
||||||
|
void **state
|
||||||
|
) {
|
||||||
|
|
||||||
|
cutsceneSystemInit();
|
||||||
|
entityInit(&ENTITIES[3], ENTITY_TYPE_PLAYER);
|
||||||
|
entityInit(&ENTITIES[4], ENTITY_TYPE_NPC);
|
||||||
|
|
||||||
|
cutsceneSystemStartCutsceneWith(
|
||||||
|
&CUTSCENE_TEST_INSERT_PERSIST_OUTER, &ENTITIES[3], &ENTITIES[4]
|
||||||
|
);
|
||||||
|
|
||||||
|
// The INSERT item's Start already cascaded into the snippet by now.
|
||||||
|
assert_ptr_equal(
|
||||||
|
CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_PERSIST_SNIPPET
|
||||||
|
);
|
||||||
|
assert_ptr_equal(CUTSCENE_SYSTEM.entityInteract, &ENTITIES[3]);
|
||||||
|
assert_ptr_equal(CUTSCENE_SYSTEM.entityInteracted, &ENTITIES[4]);
|
||||||
|
// The snippet declares ALL, but INSERT doesn't apply it -- the outer
|
||||||
|
// cutscene's NPC pause, already in effect, stands.
|
||||||
|
assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NPC);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
const struct CMUnitTest tests[] = {
|
const struct CMUnitTest tests[] = {
|
||||||
cmocka_unit_test(test_cutsceneSystemStartSetsUpInitialItem),
|
cmocka_unit_test(test_cutsceneSystemStartSetsUpInitialItem),
|
||||||
@@ -188,6 +383,11 @@ int main(int argc, char** argv) {
|
|||||||
cmocka_unit_test(test_cutsceneSystemGetAreaId),
|
cmocka_unit_test(test_cutsceneSystemGetAreaId),
|
||||||
cmocka_unit_test(test_cutsceneSystemGetTextMiniId),
|
cmocka_unit_test(test_cutsceneSystemGetTextMiniId),
|
||||||
cmocka_unit_test(test_cutsceneSystemDisposeResetsState),
|
cmocka_unit_test(test_cutsceneSystemDisposeResetsState),
|
||||||
|
cmocka_unit_test(test_cutsceneInsertSplicesItemsInPlaceThenResumes),
|
||||||
|
cmocka_unit_test(test_cutsceneInsertAsLastItemEndsCutsceneNaturally),
|
||||||
|
cmocka_unit_test(test_cutsceneInsertNestsThroughMultipleLevels),
|
||||||
|
cmocka_unit_test(test_cutsceneInsertAssertsWhenNestingExceedsStackMax),
|
||||||
|
cmocka_unit_test(test_cutsceneInsertDoesNotResetPauseOrInteractEntities),
|
||||||
};
|
};
|
||||||
|
|
||||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||||
|
|||||||
Reference in New Issue
Block a user