Switch cutscene system to a set of pre deffed buffers
This commit is contained in:
@@ -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
|
|
||||||
)
|
)
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user