From 717902462b200fa3a541c76c26f187fdbbeb42c0 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sun, 30 Aug 2026 10:14:38 -0500 Subject: [PATCH] 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 --- src/dusk/CMakeLists.txt | 1 - src/dusk/asset/CMakeLists.txt | 1 - src/dusk/asset/asset.c | 8 +- src/dusk/asset/assetbatch.c | 165 ----------- src/dusk/asset/assetbatch.h | 124 -------- src/dusk/asset/loader/assetentry.c | 18 +- src/dusk/asset/loader/assetentry.h | 42 +-- src/dusk/event/CMakeLists.txt | 9 - src/dusk/event/event.c | 76 ----- src/dusk/event/event.h | 64 ----- src/dusk/input/input.c | 1 - src/dusk/rpg/overworld/map.c | 43 +-- src/dusk/rpg/overworld/map.h | 8 +- src/dusk/ui/overlay/uifullbox.c | 10 +- src/dusk/ui/overlay/uifullbox.h | 20 +- src/dusk/ui/overlay/uiloading.c | 27 +- src/dusk/ui/overlay/uiloading.h | 40 ++- test/CMakeLists.txt | 1 - test/event/CMakeLists.txt | 9 - test/event/test_event.c | 443 ----------------------------- 20 files changed, 116 insertions(+), 994 deletions(-) delete mode 100644 src/dusk/asset/assetbatch.c delete mode 100644 src/dusk/asset/assetbatch.h delete mode 100644 src/dusk/event/CMakeLists.txt delete mode 100644 src/dusk/event/event.c delete mode 100644 src/dusk/event/event.h delete mode 100644 test/event/CMakeLists.txt delete mode 100644 test/event/test_event.c diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index 900072b8..70894fd9 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -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) diff --git a/src/dusk/asset/CMakeLists.txt b/src/dusk/asset/CMakeLists.txt index 0e87c96f..39e32cab 100644 --- a/src/dusk/asset/CMakeLists.txt +++ b/src/dusk/asset/CMakeLists.txt @@ -7,7 +7,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC asset.c - assetbatch.c assetfile.c ) diff --git a/src/dusk/asset/asset.c b/src/dusk/asset/asset.c index 1a26fc16..bcb33dfc 100644 --- a/src/dusk/asset/asset.c +++ b/src/dusk/asset/asset.c @@ -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; } diff --git a/src/dusk/asset/assetbatch.c b/src/dusk/asset/assetbatch.c deleted file mode 100644 index 3bb27927..00000000 --- a/src/dusk/asset/assetbatch.c +++ /dev/null @@ -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 - -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); - } -} diff --git a/src/dusk/asset/assetbatch.h b/src/dusk/asset/assetbatch.h deleted file mode 100644 index 20aaac2e..00000000 --- a/src/dusk/asset/assetbatch.h +++ /dev/null @@ -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); diff --git a/src/dusk/asset/loader/assetentry.c b/src/dusk/asset/loader/assetentry.c index 9e9ad477..cfa5697f 100644 --- a/src/dusk/asset/loader/assetentry.c +++ b/src/dusk/asset/loader/assetentry.c @@ -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(); diff --git a/src/dusk/asset/loader/assetentry.h b/src/dusk/asset/loader/assetentry.h index 94eff3fc..493af0b1 100644 --- a/src/dusk/asset/loader/assetentry.h +++ b/src/dusk/asset/loader/assetentry.h @@ -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; }; /** diff --git a/src/dusk/event/CMakeLists.txt b/src/dusk/event/CMakeLists.txt deleted file mode 100644 index 69958ead..00000000 --- a/src/dusk/event/CMakeLists.txt +++ /dev/null @@ -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 -) diff --git a/src/dusk/event/event.c b/src/dusk/event/event.c deleted file mode 100644 index 1051fb51..00000000 --- a/src/dusk/event/event.c +++ /dev/null @@ -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); - } -} diff --git a/src/dusk/event/event.h b/src/dusk/event/event.h deleted file mode 100644 index b94b3f59..00000000 --- a/src/dusk/event/event.h +++ /dev/null @@ -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); diff --git a/src/dusk/input/input.c b/src/dusk/input/input.c index ad9c0bd7..5ef56e81 100644 --- a/src/dusk/input/input.c +++ b/src/dusk/input/input.c @@ -11,7 +11,6 @@ #include "util/string.h" #include "util/math.h" #include "time/time.h" -#include "event/event.h" input_t INPUT; diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index c4f44824..c3c3b56c 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -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. diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h index 154f32f6..35e221d7 100644 --- a/src/dusk/rpg/overworld/map.h +++ b/src/dusk/rpg/overworld/map.h @@ -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 diff --git a/src/dusk/ui/overlay/uifullbox.c b/src/dusk/ui/overlay/uifullbox.c index 0fdb6e74..dab307ba 100644 --- a/src/dusk/ui/overlay/uifullbox.c +++ b/src/dusk/ui/overlay/uifullbox.c @@ -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); + } } } diff --git a/src/dusk/ui/overlay/uifullbox.h b/src/dusk/ui/overlay/uifullbox.h index 5c1accd3..46950a43 100644 --- a/src/dusk/ui/overlay/uifullbox.h +++ b/src/dusk/ui/overlay/uifullbox.h @@ -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; diff --git a/src/dusk/ui/overlay/uiloading.c b/src/dusk/ui/overlay/uiloading.c index 3f6a045b..ad52345a 100644 --- a/src/dusk/ui/overlay/uiloading.c +++ b/src/dusk/ui/overlay/uiloading.c @@ -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, diff --git a/src/dusk/ui/overlay/uiloading.h b/src/dusk/ui/overlay/uiloading.h index 096cc57b..531d4f23 100644 --- a/src/dusk/ui/overlay/uiloading.h +++ b/src/dusk/ui/overlay/uiloading.h @@ -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); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 309ad5e6..986b88bf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,7 +8,6 @@ add_subdirectory(assert) add_subdirectory(asset) add_subdirectory(console) add_subdirectory(error) -add_subdirectory(event) add_subdirectory(thread) add_subdirectory(display) add_subdirectory(rpg) diff --git a/test/event/CMakeLists.txt b/test/event/CMakeLists.txt deleted file mode 100644 index 8614a292..00000000 --- a/test/event/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -include(dusktest) - -# Tests -dusktest(test_event.c) diff --git a/test/event/test_event.c b/test/event/test_event.c deleted file mode 100644 index 350daeb7..00000000 --- a/test/event/test_event.c +++ /dev/null @@ -1,443 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "event/event.h" - -typedef struct { - int32_t callCount; - void *lastParams; - void *lastUser; -} eventrecord_t; - -// Distinct function pointers are required per-subscriber - the same callback -// pointer cannot be subscribed twice, even with a different user pointer. -static void helper_recordA(void *params, void *user) { - eventrecord_t *record = (eventrecord_t *)user; - record->callCount++; - record->lastParams = params; - record->lastUser = user; -} -static void helper_recordB(void *params, void *user) { - eventrecord_t *record = (eventrecord_t *)user; - record->callCount++; - record->lastParams = params; - record->lastUser = user; -} -static void helper_recordC(void *params, void *user) { - eventrecord_t *record = (eventrecord_t *)user; - record->callCount++; - record->lastParams = params; - record->lastUser = user; -} - -// Records via `params` instead of `user` - for cases exercising a NULL user -// (e.g. no users array), where dereferencing `user` would crash. -static void helper_recordViaParams(void *params, void *user) { - eventrecord_t *record = (eventrecord_t *)params; - record->callCount++; - record->lastParams = params; - record->lastUser = user; -} - -#define EVENT_CAPACITY 4 - -// --- eventInit --- - -static void test_eventInitSetsUpBackingArrays(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - memset(callbacks, 0xAA, sizeof(callbacks)); - memset(users, 0xAA, sizeof(users)); - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - - assert_ptr_equal(event.callbacks, callbacks); - assert_ptr_equal(event.users, users); - assert_int_equal(event.size, EVENT_CAPACITY); - assert_int_equal(event.count, 0); - for(int32_t i = 0; i < EVENT_CAPACITY; i++) { - assert_null(callbacks[i]); - assert_null(users[i]); - } -} - -static void test_eventInitAllowsNullUsersArray(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - - event_t event; - eventInit(&event, callbacks, NULL, EVENT_CAPACITY); - - assert_null(event.users); -} - -static void test_eventInitResetClearsSubscribersOnly(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t record = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &record); - assert_int_equal(event.count, 1); - - // Re-init (reset) with the same backing arrays. - eventInit(&event, callbacks, users, EVENT_CAPACITY); - - assert_ptr_equal(event.callbacks, callbacks); - assert_int_equal(event.count, 0); - assert_null(callbacks[0]); - assert_null(users[0]); -} - -static void test_eventInitNullEventAsserts(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - expect_assert_failure(eventInit(NULL, callbacks, NULL, EVENT_CAPACITY)); -} - -static void test_eventInitNullCallbacksAsserts(void **state) { - event_t event; - expect_assert_failure(eventInit(&event, NULL, NULL, EVENT_CAPACITY)); -} - -static void test_eventInitZeroSizeAsserts(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - event_t event; - expect_assert_failure(eventInit(&event, callbacks, NULL, 0)); -} - -// --- eventSubscribe --- - -static void test_eventSubscribeAddsCallback(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t record = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &record); - - assert_int_equal(event.count, 1); - assert_ptr_equal(event.callbacks[0], helper_recordA); - assert_ptr_equal(event.users[0], &record); -} - -static void test_eventSubscribeMultiple(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }, recordB = { 0 }, recordC = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - eventSubscribe(&event, helper_recordB, &recordB); - eventSubscribe(&event, helper_recordC, &recordC); - - assert_int_equal(event.count, 3); -} - -static void test_eventSubscribeNullUserLeavesSlotNull(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, NULL); - - assert_null(event.users[0]); -} - -static void test_eventSubscribeNullUserAfterUnsubscribeStaysNull(void **state) { - // The vacated slot must actually be cleared, not left holding a stale - // user pointer from whatever previously occupied it. - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }, recordB = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - eventSubscribe(&event, helper_recordB, &recordB); - eventUnsubscribe(&event, helper_recordA); - - eventSubscribe(&event, helper_recordC, NULL); - - for(int32_t i = 0; i < event.count; i++) { - if(event.callbacks[i] == helper_recordC) { - assert_null(event.users[i]); - return; - } - } - fail_msg("helper_recordC was not found after subscribing."); -} - -static void test_eventSubscribeUserWithoutUsersArrayAsserts(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - eventrecord_t record = { 0 }; - - event_t event; - eventInit(&event, callbacks, NULL, EVENT_CAPACITY); - - expect_assert_failure(eventSubscribe(&event, helper_recordA, &record)); -} - -static void test_eventSubscribeDuplicateCallbackAsserts(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t record = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &record); - - expect_assert_failure(eventSubscribe(&event, helper_recordA, &record)); -} - -static void test_eventSubscribeSameCallbackDifferentUserStillAsserts(void **state) { - // Documented as "the same (callback, user) pair may only be subscribed - // once", implying a different user should be fine - but the actual - // uniqueness check only looks at the callback pointer, so this asserts - // too. Captures actual behavior; flag if the doc/implementation should - // instead match on the (callback, user) pair. - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }, recordB = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - - expect_assert_failure(eventSubscribe(&event, helper_recordA, &recordB)); -} - -static void test_eventSubscribeCapacityExceededAsserts(void **state) { - eventcallback_t callbacks[2]; - void *users[2]; - eventrecord_t recordA = { 0 }, recordB = { 0 }, recordC = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, 2); - eventSubscribe(&event, helper_recordA, &recordA); - eventSubscribe(&event, helper_recordB, &recordB); - - expect_assert_failure(eventSubscribe(&event, helper_recordC, &recordC)); -} - -static void test_eventSubscribeNullEventAsserts(void **state) { - expect_assert_failure(eventSubscribe(NULL, helper_recordA, NULL)); -} - -static void test_eventSubscribeNullCallbackAsserts(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - event_t event; - eventInit(&event, callbacks, NULL, EVENT_CAPACITY); - expect_assert_failure(eventSubscribe(&event, NULL, NULL)); -} - -// --- eventUnsubscribe --- - -static void test_eventUnsubscribeMiddleSwapsLastIntoPlace(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }, recordB = { 0 }, recordC = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - eventSubscribe(&event, helper_recordB, &recordB); - eventSubscribe(&event, helper_recordC, &recordC); - - eventUnsubscribe(&event, helper_recordB); - - assert_int_equal(event.count, 2); - assert_ptr_equal(event.callbacks[0], helper_recordA); - // recordC (previously last) was swapped into the vacated middle slot. - assert_ptr_equal(event.callbacks[1], helper_recordC); - assert_ptr_equal(event.users[1], &recordC); - // The old tail slot is fully cleared. - assert_null(event.callbacks[2]); - assert_null(event.users[2]); -} - -static void test_eventUnsubscribeLastElement(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }, recordB = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - eventSubscribe(&event, helper_recordB, &recordB); - - eventUnsubscribe(&event, helper_recordB); - - assert_int_equal(event.count, 1); - assert_ptr_equal(event.callbacks[0], helper_recordA); - assert_null(event.callbacks[1]); -} - -static void test_eventUnsubscribeNotSubscribedIsNoop(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - - eventUnsubscribe(&event, helper_recordB); - - assert_int_equal(event.count, 1); - assert_ptr_equal(event.callbacks[0], helper_recordA); -} - -static void test_eventUnsubscribeThenResubscribeSucceeds(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - eventUnsubscribe(&event, helper_recordA); - - // Would have asserted (duplicate) had the removal not actually happened. - eventSubscribe(&event, helper_recordA, &recordA); - - assert_int_equal(event.count, 1); -} - -static void test_eventUnsubscribeWithNullUsersArrayIsSafe(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - - event_t event; - eventInit(&event, callbacks, NULL, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, NULL); - eventUnsubscribe(&event, helper_recordA); - - assert_int_equal(event.count, 0); -} - -static void test_eventUnsubscribeNullEventAsserts(void **state) { - expect_assert_failure(eventUnsubscribe(NULL, helper_recordA)); -} - -static void test_eventUnsubscribeNullCallbackAsserts(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - event_t event; - eventInit(&event, callbacks, NULL, EVENT_CAPACITY); - expect_assert_failure(eventUnsubscribe(&event, NULL)); -} - -// --- eventInvoke --- - -static void test_eventInvokeCallsAllSubscribersWithParamsAndUser(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }, recordB = { 0 }; - int32_t params = 123; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - eventSubscribe(&event, helper_recordB, &recordB); - - eventInvoke(&event, ¶ms); - - assert_int_equal(recordA.callCount, 1); - assert_ptr_equal(recordA.lastParams, ¶ms); - assert_ptr_equal(recordA.lastUser, &recordA); - - assert_int_equal(recordB.callCount, 1); - assert_ptr_equal(recordB.lastParams, ¶ms); - assert_ptr_equal(recordB.lastUser, &recordB); -} - -static void test_eventInvokeWithNoSubscribersDoesNothing(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - - // No subscribers exist, so there is nothing to observe beyond "no crash". - eventInvoke(&event, NULL); -} - -static void test_eventInvokeWithNullUsersArrayPassesNull(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - eventrecord_t record = { 0 }; - - event_t event; - eventInit(&event, callbacks, NULL, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordViaParams, NULL); - - eventInvoke(&event, &record); - - assert_int_equal(record.callCount, 1); - assert_ptr_equal(record.lastParams, &record); - assert_null(record.lastUser); -} - -static void test_eventInvokeSkipsUnsubscribedCallback(void **state) { - eventcallback_t callbacks[EVENT_CAPACITY]; - void *users[EVENT_CAPACITY]; - eventrecord_t recordA = { 0 }, recordB = { 0 }; - - event_t event; - eventInit(&event, callbacks, users, EVENT_CAPACITY); - eventSubscribe(&event, helper_recordA, &recordA); - eventSubscribe(&event, helper_recordB, &recordB); - eventUnsubscribe(&event, helper_recordA); - - eventInvoke(&event, NULL); - - assert_int_equal(recordA.callCount, 0); - assert_int_equal(recordB.callCount, 1); -} - -static void test_eventInvokeNullEventAsserts(void **state) { - expect_assert_failure(eventInvoke(NULL, NULL)); -} - -int main(void) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_eventInitSetsUpBackingArrays), - cmocka_unit_test(test_eventInitAllowsNullUsersArray), - cmocka_unit_test(test_eventInitResetClearsSubscribersOnly), - cmocka_unit_test(test_eventInitNullEventAsserts), - cmocka_unit_test(test_eventInitNullCallbacksAsserts), - cmocka_unit_test(test_eventInitZeroSizeAsserts), - - cmocka_unit_test(test_eventSubscribeAddsCallback), - cmocka_unit_test(test_eventSubscribeMultiple), - cmocka_unit_test(test_eventSubscribeNullUserLeavesSlotNull), - cmocka_unit_test(test_eventSubscribeNullUserAfterUnsubscribeStaysNull), - cmocka_unit_test(test_eventSubscribeUserWithoutUsersArrayAsserts), - cmocka_unit_test(test_eventSubscribeDuplicateCallbackAsserts), - cmocka_unit_test(test_eventSubscribeSameCallbackDifferentUserStillAsserts), - cmocka_unit_test(test_eventSubscribeCapacityExceededAsserts), - cmocka_unit_test(test_eventSubscribeNullEventAsserts), - cmocka_unit_test(test_eventSubscribeNullCallbackAsserts), - - cmocka_unit_test(test_eventUnsubscribeMiddleSwapsLastIntoPlace), - cmocka_unit_test(test_eventUnsubscribeLastElement), - cmocka_unit_test(test_eventUnsubscribeNotSubscribedIsNoop), - cmocka_unit_test(test_eventUnsubscribeThenResubscribeSucceeds), - cmocka_unit_test(test_eventUnsubscribeWithNullUsersArrayIsSafe), - cmocka_unit_test(test_eventUnsubscribeNullEventAsserts), - cmocka_unit_test(test_eventUnsubscribeNullCallbackAsserts), - - cmocka_unit_test(test_eventInvokeCallsAllSubscribersWithParamsAndUser), - cmocka_unit_test(test_eventInvokeWithNoSubscribersDoesNothing), - cmocka_unit_test(test_eventInvokeWithNullUsersArrayPassesNull), - cmocka_unit_test(test_eventInvokeSkipsUnsubscribedCallback), - cmocka_unit_test(test_eventInvokeNullEventAsserts), - }; - return cmocka_run_group_tests(tests, NULL, NULL); -}