Fix fatal async asset load errors, remove unused event system

An async asset load failure crashed the whole game via errorThrow,
while the identical sync failure just logged and continued - a single
missing/corrupted asset could take down the process. assetUpdate now
handles the async error path the same way as sync (invoke onError,
keep running).

Also removes event.h/event.c and assetbatch, which existed only to
support multiple subscribers per asset event but had no real caller
that ever used more than one (assetbatch itself had zero callers
anywhere). Asset entries, uifullbox, and uiloading now use plain
single-callback + user-pointer fields instead of the generic
array-backed event_t.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 10:14:38 -05:00
parent 1d73b9d224
commit 717902462b
20 changed files with 116 additions and 994 deletions
-1
View File
@@ -53,7 +53,6 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
# Subdirs
add_subdirectory(animation)
add_subdirectory(event)
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(console)
-1
View File
@@ -7,7 +7,6 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
asset.c
assetbatch.c
assetfile.c
)
+5 -3
View File
@@ -322,7 +322,9 @@ errorret_t assetUpdate(void) {
} else if(loading->entry->state == ASSET_ENTRY_STATE_LOADED) {
assetentry_t *loadedEntry = loading->entry;
loading->entry = NULL;
eventInvoke(&loadedEntry->onLoaded, loadedEntry);
if(loadedEntry->onLoaded) {
loadedEntry->onLoaded(loadedEntry, loadedEntry->onLoadedUser);
}
}
loading++;
@@ -346,8 +348,8 @@ errorret_t assetUpdate(void) {
assetentry_t *errEntry = loading->entry;
loading->entry = NULL;
threadMutexUnlock(&loading->mutex);
eventInvoke(&errEntry->onError, errEntry);
errorThrow("Failed to load asset asynchronously.");
if(errEntry->onError) errEntry->onError(errEntry, errEntry->onErrorUser);
loading++;
break;
}
-165
View File
@@ -1,165 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetbatch.h"
#include "asset.h"
#include "assert/assert.h"
#include "util/memory.h"
#include <unistd.h>
void assetBatchInit(
assetbatch_t *batch,
const uint16_t count,
const assetbatchdesc_t *descs
) {
assertNotNull(batch, "Batch cannot be NULL.");
assertNotNull(descs, "Descs cannot be NULL.");
assertTrue(count > 0, "Count must be greater than 0.");
assertTrue(
count <= ASSET_BATCH_COUNT_MAX, "Count exceeds ASSET_BATCH_COUNT_MAX."
);
memoryZero(batch, sizeof(assetbatch_t));
batch->count = count;
eventInit(
&batch->onLoaded,
batch->onLoadedCallbacks, batch->onLoadedUsers, ASSET_BATCH_EVENT_MAX
);
eventInit(
&batch->onEntryLoaded,
batch->onEntryLoadedCallbacks,
batch->onEntryLoadedUsers,
ASSET_BATCH_EVENT_MAX
);
eventInit(
&batch->onError,
batch->onErrorCallbacks, batch->onErrorUsers, ASSET_BATCH_EVENT_MAX
);
eventInit(
&batch->onEntryError,
batch->onEntryErrorCallbacks,
batch->onEntryErrorUsers,
ASSET_BATCH_EVENT_MAX
);
for(uint16_t i = 0; i < count; i++) {
batch->inputs[i] = descs[i].input;
batch->entries[i] = assetLock(
descs[i].path, descs[i].type, &batch->inputs[i]
);
if(batch->entries[i]->state == ASSET_ENTRY_STATE_LOADED) {
// Already loaded (cached) - count it now, no subscription needed.
batch->loadedCount++;
} else if(batch->entries[i]->state == ASSET_ENTRY_STATE_ERROR) {
batch->errorCount++;
} else {
eventSubscribe(
&batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb, batch
);
eventSubscribe(
&batch->entries[i]->onError, assetBatchEntryOnErrorCb, batch
);
}
}
}
void assetBatchLock(assetbatch_t *batch) {
assertNotNull(batch, "Batch cannot be NULL.");
for(uint16_t i = 0; i < batch->count; i++) {
assetEntryLock(batch->entries[i]);
}
}
void assetBatchUnlock(assetbatch_t *batch) {
assertNotNull(batch, "Batch cannot be NULL.");
for(uint16_t i = 0; i < batch->count; i++) {
assetEntryUnlock(batch->entries[i]);
}
}
bool_t assetBatchIsLoaded(const assetbatch_t *batch) {
assertNotNull(batch, "Batch cannot be NULL.");
for(uint16_t i = 0; i < batch->count; i++) {
if(batch->entries[i]->state != ASSET_ENTRY_STATE_LOADED) return false;
}
return true;
}
bool_t assetBatchHasError(const assetbatch_t *batch) {
assertNotNull(batch, "Batch cannot be NULL.");
for(uint16_t i = 0; i < batch->count; i++) {
if(batch->entries[i]->state == ASSET_ENTRY_STATE_ERROR) return true;
}
return false;
}
errorret_t assetBatchRequireLoaded(assetbatch_t *batch) {
assertNotNull(batch, "Batch cannot be NULL.");
bool_t allDone;
do {
allDone = true;
for(uint16_t i = 0; i < batch->count; i++) {
const assetentrystate_t state = batch->entries[i]->state;
if(state == ASSET_ENTRY_STATE_ERROR) {
errorThrow("Asset '%s' failed to load.", batch->entries[i]->name);
}
if(state != ASSET_ENTRY_STATE_LOADED) {
allDone = false;
}
}
if(!allDone) {
usleep(1000);
errorChain(assetUpdate());
}
} while(!allDone);
errorOk();
}
void assetBatchDispose(assetbatch_t *batch) {
assertNotNull(batch, "Batch cannot be NULL.");
for(uint16_t i = 0; i < batch->count; i++) {
if(batch->entries[i]) {
// Unsubscribe while we still hold a lock so the entry is live.
eventUnsubscribe(&batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb);
eventUnsubscribe(&batch->entries[i]->onError, assetBatchEntryOnErrorCb);
assetUnlockEntry(batch->entries[i]);
}
}
memoryZero(batch, sizeof(assetbatch_t));
}
void assetBatchEntryOnLoadedCb(void *params, void *user) {
assetentry_t *entry = (assetentry_t *)params;
assetbatch_t *batch = (assetbatch_t *)user;
batch->loadedCount++;
eventInvoke(&batch->onEntryLoaded, entry);
if((uint16_t)(batch->loadedCount + batch->errorCount) >= batch->count) {
if(batch->errorCount == 0) {
eventInvoke(&batch->onLoaded, batch);
} else {
eventInvoke(&batch->onError, batch);
}
}
}
void assetBatchEntryOnErrorCb(void *params, void *user) {
assetentry_t *entry = (assetentry_t *)params;
assetbatch_t *batch = (assetbatch_t *)user;
batch->errorCount++;
eventInvoke(&batch->onEntryError, entry);
if((uint16_t)(batch->loadedCount + batch->errorCount) >= batch->count) {
eventInvoke(&batch->onError, batch);
}
}
-124
View File
@@ -1,124 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/loader/assetentry.h"
#include "asset/loader/assetloader.h"
#include "event/event.h"
#define ASSET_BATCH_COUNT_MAX 64
#define ASSET_BATCH_EVENT_MAX 4
typedef struct {
const char_t *path;
assetloadertype_t type;
assetloaderinput_t input;
} assetbatchdesc_t;
typedef struct {
assetentry_t *entries[ASSET_BATCH_COUNT_MAX];
assetloaderinput_t inputs[ASSET_BATCH_COUNT_MAX];
uint16_t count;
uint16_t loadedCount;
uint16_t errorCount;
/** Fires once when every entry loaded. params = assetbatch_t * */
event_t onLoaded;
eventcallback_t onLoadedCallbacks[ASSET_BATCH_EVENT_MAX];
void *onLoadedUsers[ASSET_BATCH_EVENT_MAX];
/** Fires each time a single entry loads. params = assetentry_t * */
event_t onEntryLoaded;
eventcallback_t onEntryLoadedCallbacks[ASSET_BATCH_EVENT_MAX];
void *onEntryLoadedUsers[ASSET_BATCH_EVENT_MAX];
/** Fires when all entries finish (any with errors). params: assetbatch_t * */
event_t onError;
eventcallback_t onErrorCallbacks[ASSET_BATCH_EVENT_MAX];
void *onErrorUsers[ASSET_BATCH_EVENT_MAX];
/** Fires each time a single entry errors. params = assetentry_t * */
event_t onEntryError;
eventcallback_t onEntryErrorCallbacks[ASSET_BATCH_EVENT_MAX];
void *onEntryErrorUsers[ASSET_BATCH_EVENT_MAX];
} assetbatch_t;
/**
* Initialises the batch from an array of descriptors. Each entry is locked
* and queued for loading immediately.
*
* @param batch Batch to initialise.
* @param descs Array of entry descriptors (need not outlive this call).
* @param count Number of descriptors (must be <= ASSET_BATCH_COUNT_MAX).
*/
void assetBatchInit(
assetbatch_t *batch,
uint16_t count,
const assetbatchdesc_t *descs
);
/**
* Acquires one additional lock on every entry in the batch.
*
* @param batch Batch to lock.
*/
void assetBatchLock(assetbatch_t *batch);
/**
* Releases one lock from every entry in the batch. When an entry's lock
* count reaches zero it will be reaped on the next assetUpdate.
*
* @param batch Batch to unlock.
*/
void assetBatchUnlock(assetbatch_t *batch);
/**
* Returns true if every entry in the batch has finished loading.
*
* @param batch Batch to query.
*/
bool_t assetBatchIsLoaded(const assetbatch_t *batch);
/**
* Returns true if any entry in the batch is in an error state.
*
* @param batch Batch to query.
*/
bool_t assetBatchHasError(const assetbatch_t *batch);
/**
* Blocks until every entry is loaded. Returns an error if any entry fails.
*
* @param batch Batch to wait on.
*/
errorret_t assetBatchRequireLoaded(assetbatch_t *batch);
/**
* Releases the batch's lock on every entry and clears the batch. After this
* call the batch struct may be reused with assetBatchInit.
*
* @param batch Batch to dispose.
*/
void assetBatchDispose(assetbatch_t *batch);
/**
* Event trampoline invoked when a batch entry finishes loading.
* Increments the loaded counter and fires batch-level events.
*
* @param params The loaded assetentry_t pointer.
* @param user The owning assetbatch_t pointer.
*/
void assetBatchEntryOnLoadedCb(void *params, void *user);
/**
* Event trampoline invoked when a batch entry fails to load.
* Increments the error counter and fires batch-level events.
*
* @param params The errored assetentry_t pointer.
* @param user The owning assetbatch_t pointer.
*/
void assetBatchEntryOnErrorCb(void *params, void *user);
+1 -17
View File
@@ -35,22 +35,6 @@ void assetEntryInit(
entry->input = NULL;
}
refInit(&entry->refs, entry, NULL, NULL, NULL);
eventInit(
&entry->onLoaded,
entry->onLoadedCallbacks, entry->onLoadedUsers,
ASSET_ENTRY_EVENT_MAX
);
eventInit(
&entry->onUnloaded,
entry->onUnloadedCallbacks, entry->onUnloadedUsers,
ASSET_ENTRY_EVENT_MAX
);
eventInit(
&entry->onError,
entry->onErrorCallbacks, entry->onErrorUsers,
ASSET_ENTRY_EVENT_MAX
);
}
void assetEntryLock(assetentry_t *entry) {
@@ -97,7 +81,7 @@ errorret_t assetEntryDispose(assetentry_t *entry) {
"Asset entry still refed at dispose time."
);
eventInvoke(&entry->onUnloaded, entry);
if(entry->onUnloaded) entry->onUnloaded(entry, entry->onUnloadedUser);
errorChain(ASSET_LOADER_CALLBACKS[entry->type].dispose(entry));
memoryZero(entry, sizeof(assetentry_t));
errorOk();
+22 -20
View File
@@ -7,7 +7,6 @@
#pragma once
#include "asset/loader/assetloading.h"
#include "event/event.h"
#include "util/ref.h"
typedef enum {
@@ -20,11 +19,17 @@ typedef enum {
ASSET_ENTRY_STATE_ERROR
} assetentrystate_t;
/** Maximum number of subscribers for each per-entry event. */
#define ASSET_ENTRY_EVENT_MAX 2
typedef struct assetentry_s assetentry_t;
/**
* A single asset entry callback. Each entry supports at most one subscriber
* per event - a second assignment without clearing the first is a bug.
*
* @param entry The assetentry_t the event fired on.
* @param user The user pointer passed alongside the callback.
*/
typedef void (*assetentrycallback_t)(assetentry_t *entry, void *user);
struct assetentry_s {
char_t name[ASSET_FILE_NAME_MAX];
assetloadertype_t type;
@@ -33,30 +38,27 @@ struct assetentry_s {
ref_t refs;
assetloaderinput_t *input;
assetloaderinput_t inputData;
/**
* Fired once when loading completes successfully (params = assetentry_t *).
* Always invoked on the main thread.
*/
event_t onLoaded;
eventcallback_t onLoadedCallbacks[ASSET_ENTRY_EVENT_MAX];
void *onLoadedUsers[ASSET_ENTRY_EVENT_MAX];
/**
* Fired once when the entry is disposed/reaped (params = assetentry_t *).
* The asset data is still accessible when the callback runs.
* Fired once when loading completes successfully.
* Always invoked on the main thread.
*/
event_t onUnloaded;
eventcallback_t onUnloadedCallbacks[ASSET_ENTRY_EVENT_MAX];
void *onUnloadedUsers[ASSET_ENTRY_EVENT_MAX];
assetentrycallback_t onLoaded;
void *onLoadedUser;
/**
* Fired once when loading fails (params = assetentry_t *).
* Fired once when the entry is disposed/reaped. The asset data is still
* accessible when the callback runs. Always invoked on the main thread.
*/
assetentrycallback_t onUnloaded;
void *onUnloadedUser;
/**
* Fired once when loading fails.
* Always invoked on the main thread.
*/
event_t onError;
eventcallback_t onErrorCallbacks[ASSET_ENTRY_EVENT_MAX];
void *onErrorUsers[ASSET_ENTRY_EVENT_MAX];
assetentrycallback_t onError;
void *onErrorUser;
};
/**
-9
View File
@@ -1,9 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
event.c
)
-76
View File
@@ -1,76 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "event.h"
#include "assert/assert.h"
#include "util/memory.h"
void eventInit(
event_t *event,
eventcallback_t *callbacks,
void **users,
size_t size
) {
assertNotNull(event, "event must not be NULL");
assertNotNull((void *)callbacks, "callbacks must not be NULL");
assertTrue(size > 0, "size must be greater than 0");
event->callbacks = callbacks;
event->users = users;
event->size = size;
event->count = 0;
memoryZero(callbacks, sizeof(eventcallback_t) * size);
if(users) memoryZero(users, sizeof(void *) * size);
}
void eventSubscribe(event_t *event, eventcallback_t callback, void *user) {
assertNotNull(event, "event must not be NULL");
assertNotNull(callback, "callback must not be NULL");
// Ensure callback isn't already susbcribed
for(uint32_t i = 0; i < event->count; i++) {
if(event->callbacks[i] != callback) continue;
assertUnreachable("Callback already registered, cannot subscribe twice.");
}
assertTrue(event->count < event->size, "event subscriber capacity exceeded");
event->callbacks[event->count] = callback;
if(user) {
assertNotNull(event->users, "Cannot add user pointer.");
event->users[event->count] = user;
}
event->count++;
}
void eventUnsubscribe(event_t *event, eventcallback_t callback) {
assertNotNull(event, "event must not be NULL");
assertNotNull(callback, "callback must not be NULL");
for(uint32_t i = 0; i < event->count; i++) {
if(event->callbacks[i] != callback) continue;
uint32_t last = event->count - 1;
if(i != last) {
event->callbacks[i] = event->callbacks[last];
if(event->users) event->users[i] = event->users[last];
}
event->callbacks[last] = NULL;
if(event->users) event->users[last] = NULL;
event->count--;
return;
}
}
void eventInvoke(const event_t *event, void *params) {
assertNotNull(event, "event must not be NULL");
for(uint32_t i = 0; i < event->count; i++) {
void *u = event->users ? event->users[i] : NULL;
event->callbacks[i](params, u);
}
}
-64
View File
@@ -1,64 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef void (*eventcallback_t)(void *params, void *user);
typedef struct {
eventcallback_t *callbacks;
void **users;
size_t size;
uint32_t count;
} event_t;
/**
* Initializes an event, binding it to the provided backing arrays and clearing
* all subscribers. May also be called to reset an event (re-clears subscribers
* without changing the backing arrays or size).
*
* @param event The event to initialize.
* @param callbacks Caller-owned array of at least `size` callback slots.
* @param users Array of user pointers, matching each callback, or NULL.
* @param size Capacity of both arrays, must match.
*/
void eventInit(
event_t *event,
eventcallback_t *callbacks,
void **users,
size_t size
);
/**
* Subscribes a callback to an event. The callback is invoked with params and
* the provided user pointer each time the event fires. The same (callback,
* user) pair may only be subscribed once.
*
* @param event The event to subscribe to.
* @param callback The function to call when the event fires.
* @param user Arbitrary pointer forwarded to the callback unchanged.
*/
void eventSubscribe(event_t *event, eventcallback_t callback, void *user);
/**
* Removes a previously subscribed (callback, user) pair. Does nothing if the
* pair is not currently subscribed.
*
* @param event The event to unsubscribe from.
* @param callback The callback that was passed to eventSubscribe.
*/
void eventUnsubscribe(event_t *event, eventcallback_t callback);
/**
* Invokes all subscribed callbacks, passing params and each subscriber's user
* pointer.
*
* @param event The event to invoke.
* @param params Arbitrary pointer forwarded to every callback unchanged.
*/
void eventInvoke(const event_t *event, void *params);
-1
View File
@@ -11,7 +11,6 @@
#include "util/string.h"
#include "util/math.h"
#include "time/time.h"
#include "event/event.h"
input_t INPUT;
+26 -17
View File
@@ -11,7 +11,6 @@
#include "asset/asset.h"
#include "asset/loader/assetloader.h"
#include "console/console.h"
#include "event/event.h"
#include "util/string.h"
#include "rpg/entity/global/entityglobal.h"
#include "rpg/entity/item/entityitem.h"
@@ -137,8 +136,10 @@ void mapChunkUnload(chunk_t *chunk) {
memorySet(chunk->areas, 0xFF, sizeof(chunk->areas));
if(chunk->dcfEntry != NULL) {
eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded);
eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError);
chunk->dcfEntry->onLoaded = NULL;
chunk->dcfEntry->onLoadedUser = NULL;
chunk->dcfEntry->onError = NULL;
chunk->dcfEntry->onErrorUser = NULL;
assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL;
}
@@ -159,8 +160,10 @@ errorret_t mapChunkLoad(chunk_t *chunk) {
mapChunkLoadingSlotClear(chunk);
if(chunk->dcfEntry != NULL) {
eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded);
eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError);
chunk->dcfEntry->onLoaded = NULL;
chunk->dcfEntry->onLoadedUser = NULL;
chunk->dcfEntry->onError = NULL;
chunk->dcfEntry->onErrorUser = NULL;
assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL;
}
@@ -244,8 +247,12 @@ void mapChunkLoadNext() {
continue;
}
eventSubscribe(&entry->onLoaded, mapChunkLoaded, chunk);
eventSubscribe(&entry->onError, mapChunkLoadError, chunk);
assertNull(entry->onLoaded, "Entry already has an onLoaded subscriber.");
assertNull(entry->onError, "Entry already has an onError subscriber.");
entry->onLoaded = mapChunkLoaded;
entry->onLoadedUser = chunk;
entry->onError = mapChunkLoadError;
entry->onErrorUser = chunk;
}
}
@@ -385,10 +392,9 @@ void mapRebuildChunkOrder() {
}
}
void mapChunkLoadError(void *params, void *user) {
assertNotNull(params, "mapChunkLoadError: params cannot be NULL");
void mapChunkLoadError(assetentry_t *entry, void *user) {
assertNotNull(entry, "mapChunkLoadError: entry cannot be NULL");
assertNotNull(user, "mapChunkLoadError: user cannot be NULL");
assetentry_t *entry = (assetentry_t *)params;
chunk_t *chunk = (chunk_t *)user;
if(chunk->dcfEntry != entry) return;
consolePrint(
@@ -397,8 +403,10 @@ void mapChunkLoadError(void *params, void *user) {
(int32_t)chunk->position.y,
(int32_t)chunk->position.z
);
eventUnsubscribe(&entry->onLoaded, mapChunkLoaded);
eventUnsubscribe(&entry->onError, mapChunkLoadError);
entry->onLoaded = NULL;
entry->onLoadedUser = NULL;
entry->onError = NULL;
entry->onErrorUser = NULL;
assetUnlockEntry(chunk->dcfEntry);
chunk->dcfEntry = NULL;
memorySet(chunk->tiles, 0x00, sizeof(chunk->tiles));
@@ -407,10 +415,9 @@ void mapChunkLoadError(void *params, void *user) {
mapChunkLoadNext();
}
void mapChunkLoaded(void *params, void *user) {
assertNotNull(params, "mapChunkLoaded: params cannot be NULL");
void mapChunkLoaded(assetentry_t *entry, void *user) {
assertNotNull(entry, "mapChunkLoaded: entry cannot be NULL");
assertNotNull(user, "mapChunkLoaded: user cannot be NULL");
assetentry_t *entry = (assetentry_t *)params;
chunk_t *chunk = (chunk_t *)user;
if(chunk->dcfEntry != entry) return;
// consolePrint(
@@ -457,8 +464,10 @@ void mapChunkLoaded(void *params, void *user) {
// modelEntries must still be intact for that next reuse to copy from.
chunk->modelEntries[m] = entry->data.chunk.modelEntries[m];
}
eventUnsubscribe(&entry->onLoaded, mapChunkLoaded);
eventUnsubscribe(&entry->onError, mapChunkLoadError);
entry->onLoaded = NULL;
entry->onLoadedUser = NULL;
entry->onError = NULL;
entry->onErrorUser = NULL;
// Deliberately keep chunk->dcfEntry locked and set - it is what keeps the
// chunk asset entry (and therefore its model locks) alive for as long as
// this chunk_t is displaying it. Released in mapChunkUnload instead.
+4 -4
View File
@@ -101,19 +101,19 @@ void mapChunkLoadQueueRemove(chunk_t *chunk);
* chunk tiles with TILE_SHAPE_GROUND as a fallback.
* Always invoked on the main thread.
*
* @param params The failed assetentry_t.
* @param entry The failed assetentry_t.
* @param user The chunk_t that owns the entry.
*/
void mapChunkLoadError(void *params, void *user);
void mapChunkLoadError(assetentry_t *entry, void *user);
/**
* Callback invoked when a chunk DCF asset finishes loading.
* Always invoked on the main thread.
*
* @param params The loaded assetentry_t.
* @param entry The loaded assetentry_t.
* @param user The chunk_t that owns the entry.
*/
void mapChunkLoaded(void *params, void *user);
void mapChunkLoaded(assetentry_t *entry, void *user);
/**
* Rebuilds chunkOrder from the loaded chunks that fall within the
+3 -7
View File
@@ -20,12 +20,6 @@ uifullbox_t UI_FULLBOX_OVER;
void uiFullboxInit(uifullbox_t *fullbox) {
assertNotNull(fullbox, "fullbox must not be NULL");
memoryZero(fullbox, sizeof(uifullbox_t));
eventInit(
&fullbox->onTransitionEnd,
fullbox->onTransitionEndCallbacks,
fullbox->onTransitionEndUsers,
4
);
}
void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) {
@@ -35,7 +29,9 @@ void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) {
fullbox->time += delta;
if(fullbox->time >= fullbox->duration) {
fullbox->time = fullbox->duration;
eventInvoke(&fullbox->onTransitionEnd, fullbox);
if(fullbox->onTransitionEnd) {
fullbox->onTransitionEnd(fullbox, fullbox->onTransitionEndUser);
}
}
}
+14 -6
View File
@@ -9,18 +9,26 @@
#include "error/error.h"
#include "display/color.h"
#include "animation/easing.h"
#include "event/event.h"
typedef struct {
typedef struct uifullbox_s uifullbox_t;
/**
* Callback fired once when a fullbox transition completes.
*
* @param fullbox The uifullbox_t the transition ran on.
* @param user The user pointer passed alongside the callback.
*/
typedef void (*uifullboxcallback_t)(uifullbox_t *fullbox, void *user);
struct uifullbox_s {
color_t fromColor;
color_t toColor;
float_t duration;
float_t time;
easingtype_t easing;
eventcallback_t onTransitionEndCallbacks[4];
void *onTransitionEndUsers[4];
event_t onTransitionEnd;
} uifullbox_t;
uifullboxcallback_t onTransitionEnd;
void *onTransitionEndUser;
};
extern uifullbox_t UI_FULLBOX_UNDER;
extern uifullbox_t UI_FULLBOX_OVER;
+9 -18
View File
@@ -20,12 +20,6 @@ uiloading_t UI_LOADING;
errorret_t uiLoadingInit(void) {
memoryZero(&UI_LOADING, sizeof(uiloading_t));
eventInit(
&UI_LOADING.onTransitionEnd,
UI_LOADING.onTransitionEndCallbacks,
UI_LOADING.onTransitionEndUsers,
4
);
errorChain(assetLocaleGetString(
&LOCALE.entry->data.locale,
@@ -43,7 +37,9 @@ errorret_t uiLoadingUpdate(void) {
UI_LOADING.time += TIME.delta;
if(UI_LOADING.time >= UI_LOADING.duration) {
UI_LOADING.time = UI_LOADING.duration;
eventInvoke(&UI_LOADING.onTransitionEnd, &UI_LOADING);
if(UI_LOADING.onTransitionEnd) {
UI_LOADING.onTransitionEnd(&UI_LOADING, UI_LOADING.onTransitionEndUser);
}
}
}
errorOk();
@@ -76,26 +72,21 @@ errorret_t uiLoadingDraw(void) {
return spriteBatchFlush();
}
static void uiLoadingTransition(
void uiLoadingTransition(
const float_t from,
const float_t to,
const eventcallback_t callback,
const uiloadingcallback_t callback,
void *user
) {
UI_LOADING.fromAlpha = from;
UI_LOADING.toAlpha = to;
UI_LOADING.duration = UI_LOADING_FADE_DURATION;
UI_LOADING.time = 0.0f;
eventInit(
&UI_LOADING.onTransitionEnd,
UI_LOADING.onTransitionEndCallbacks,
UI_LOADING.onTransitionEndUsers,
4
);
if(callback) eventSubscribe(&UI_LOADING.onTransitionEnd, callback, user);
UI_LOADING.onTransitionEnd = callback;
UI_LOADING.onTransitionEndUser = user;
}
void uiLoadingShow(eventcallback_t callback, void *user) {
void uiLoadingShow(uiloadingcallback_t callback, void *user) {
uiLoadingTransition(0.0f, 1.0f, callback, user);
uiFullboxTransition(
&UI_FULLBOX_OVER,
@@ -106,7 +97,7 @@ void uiLoadingShow(eventcallback_t callback, void *user) {
);
}
void uiLoadingHide(eventcallback_t callback, void *user) {
void uiLoadingHide(uiloadingcallback_t callback, void *user) {
uiLoadingTransition(1.0f, 0.0f, callback, user);
uiFullboxTransition(
&UI_FULLBOX_OVER,
+32 -8
View File
@@ -7,22 +7,30 @@
#pragma once
#include "error/error.h"
#include "event/event.h"
#define UI_LOADING_FADE_DURATION 0.5f
#define UI_LOADING_MARGIN 8.0f
#define UI_LOADING_TEXT_MAX 32
typedef struct {
typedef struct uiloading_s uiloading_t;
/**
* Callback fired once when a loading indicator fade transition completes.
*
* @param loading The uiloading_t the transition ran on.
* @param user The user pointer passed alongside the callback.
*/
typedef void (*uiloadingcallback_t)(uiloading_t *loading, void *user);
struct uiloading_s {
float_t fromAlpha;
float_t toAlpha;
float_t duration;
float_t time;
eventcallback_t onTransitionEndCallbacks[4];
void *onTransitionEndUsers[4];
event_t onTransitionEnd;
uiloadingcallback_t onTransitionEnd;
void *onTransitionEndUser;
char_t text[UI_LOADING_TEXT_MAX];
} uiloading_t;
};
extern uiloading_t UI_LOADING;
@@ -48,13 +56,29 @@ errorret_t uiLoadingUpdate(void);
*/
errorret_t uiLoadingDraw(void);
/**
* Begins a loading indicator fade transition, replacing any transition
* already in progress along with its pending callback.
*
* @param from Starting alpha.
* @param to Ending alpha.
* @param callback Called when the transition completes. May be NULL.
* @param user Forwarded to the callback unchanged.
*/
void uiLoadingTransition(
const float_t from,
const float_t to,
const uiloadingcallback_t callback,
void *user
);
/**
* Fades the loading indicator in. Invokes callback when fully visible.
*
* @param callback Called when the fade-in completes. May be NULL.
* @param user Forwarded to the callback unchanged.
*/
void uiLoadingShow(eventcallback_t callback, void *user);
void uiLoadingShow(uiloadingcallback_t callback, void *user);
/**
* Fades the loading indicator out. Invokes callback when fully hidden.
@@ -62,4 +86,4 @@ void uiLoadingShow(eventcallback_t callback, void *user);
* @param callback Called when the fade-out completes. May be NULL.
* @param user Forwarded to the callback unchanged.
*/
void uiLoadingHide(eventcallback_t callback, void *user);
void uiLoadingHide(uiloadingcallback_t callback, void *user);