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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user