Move cutscene JSON parsing entirely into the async loader phase
File read, yyjson parse, and all item dispatch now happen in assetCutsceneLoaderAsync - none of it touches the main thread now that CUTSCENE items defer their nested asset resolution to start time. The sync phase is now just a state flip, kept only so assetUpdate()'s onLoaded dispatch (which only fires for entries finishing via the sync path) still runs. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -101,8 +101,6 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNull(loading->loading.cutscene.buffer, "Buffer already defined?");
|
|
||||||
|
|
||||||
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, loading->entry->name, NULL, NULL)
|
||||||
@@ -125,35 +123,17 @@ errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
|
|||||||
assetLoaderErrorChain(loading, assetFileClose(file));
|
assetLoaderErrorChain(loading, assetFileClose(file));
|
||||||
assetLoaderErrorChain(loading, assetFileDispose(file));
|
assetLoaderErrorChain(loading, assetFileDispose(file));
|
||||||
|
|
||||||
loading->loading.cutscene.buffer = buffer;
|
// Parsing is pure CPU work with no main-thread dependency (every item
|
||||||
loading->loading.cutscene.size = fileSize;
|
// type's load callback just parses fields - see cutsceneCutsceneLoad's
|
||||||
loading->loading.cutscene.state = ASSET_CUTSCENE_LOADING_STATE_PARSE;
|
// doc comment for how CUTSCENE items avoid needing one), so it happens
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
// right here instead of bouncing to the sync phase.
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t assetCutsceneLoaderSync(assetloading_t *loading) {
|
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_CUTSCENE, "Invalid type.");
|
|
||||||
assertIsMainThread("Must be called from the main thread.");
|
|
||||||
|
|
||||||
if(loading->loading.cutscene.state == ASSET_CUTSCENE_LOADING_STATE_INITIAL) {
|
|
||||||
loading->loading.cutscene.state = ASSET_CUTSCENE_LOADING_STATE_READ_FILE;
|
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
assetcutsceneoutput_t *out = &loading->entry->data.cutscene;
|
assetcutsceneoutput_t *out = &loading->entry->data.cutscene;
|
||||||
uint8_t *buffer = loading->loading.cutscene.buffer;
|
|
||||||
assertNotNull(buffer, "Cutscene data should have been loaded by now.");
|
|
||||||
size_t bufferSize = loading->loading.cutscene.size;
|
|
||||||
|
|
||||||
yyjson_doc *doc = yyjson_read(
|
yyjson_doc *doc = yyjson_read(
|
||||||
(char *)buffer, bufferSize,
|
(char *)buffer, fileSize,
|
||||||
YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS
|
YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS
|
||||||
);
|
);
|
||||||
memoryFree(buffer);
|
memoryFree(buffer);
|
||||||
loading->loading.cutscene.buffer = NULL;
|
|
||||||
|
|
||||||
if(doc == NULL) {
|
if(doc == NULL) {
|
||||||
assetLoaderErrorThrow(loading, "Failed to parse cutscene JSON");
|
assetLoaderErrorThrow(loading, "Failed to parse cutscene JSON");
|
||||||
@@ -210,6 +190,31 @@ errorret_t assetCutsceneLoaderSync(assetloading_t *loading) {
|
|||||||
out->cutscene.pause = pause;
|
out->cutscene.pause = pause;
|
||||||
out->cutscene.dataSize = 0;
|
out->cutscene.dataSize = 0;
|
||||||
|
|
||||||
|
loading->loading.cutscene.state = ASSET_CUTSCENE_LOADING_STATE_PARSE;
|
||||||
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t assetCutsceneLoaderSync(assetloading_t *loading) {
|
||||||
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertTrue(loading->type == ASSET_LOADER_TYPE_CUTSCENE, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
|
if(loading->loading.cutscene.state == ASSET_CUTSCENE_LOADING_STATE_INITIAL) {
|
||||||
|
loading->loading.cutscene.state = ASSET_CUTSCENE_LOADING_STATE_READ_FILE;
|
||||||
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else already happened in the async phase - see
|
||||||
|
// assetCutsceneLoaderAsync. This flip still has to happen from here
|
||||||
|
// rather than the async phase setting LOADED directly, since
|
||||||
|
// assetUpdate() only fires an entry's onLoaded callback for entries
|
||||||
|
// that finish via this sync path.
|
||||||
|
assertTrue(
|
||||||
|
loading->loading.cutscene.state == ASSET_CUTSCENE_LOADING_STATE_PARSE,
|
||||||
|
"Unexpected cutscene loading state."
|
||||||
|
);
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ typedef enum {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
assetfile_t file;
|
assetfile_t file;
|
||||||
assetcutsceneloadingstate_t state;
|
assetcutsceneloadingstate_t state;
|
||||||
uint8_t *buffer;
|
|
||||||
size_t size;
|
|
||||||
} assetcutsceneloaderloading_t;
|
} assetcutsceneloaderloading_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -80,8 +78,11 @@ 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
|
||||||
* into the loading buffer so the sync phase can parse without blocking the
|
* and, in the same call, parses them into a heap-allocated cutsceneitem_t
|
||||||
* main thread on I/O.
|
* array + pool - none of that work touches the main thread (every item
|
||||||
|
* 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.
|
||||||
@@ -89,8 +90,11 @@ void assetCutsceneFreeParsed(assetcutsceneoutput_t *out);
|
|||||||
errorret_t assetCutsceneLoaderAsync(assetloading_t *loading);
|
errorret_t assetCutsceneLoaderAsync(assetloading_t *loading);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronous loader for cutscene assets. Parses the JSONC previously read
|
* Synchronous loader for cutscene assets. All actual work already
|
||||||
* by the async phase into a heap-allocated cutsceneitem_t array + pool.
|
* happened in the async phase - this only flips the entry to LOADED,
|
||||||
|
* which must happen through this sync callback (rather than the async
|
||||||
|
* phase setting it directly) so assetUpdate()'s onLoaded dispatch, which
|
||||||
|
* only fires for entries finishing via the sync path, still runs.
|
||||||
*
|
*
|
||||||
* @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.
|
||||||
|
|||||||
Reference in New Issue
Block a user