/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "dusktest.h" #include "util/memory.h" #include "entity/entitymanager.h" #include "entity/component/trigger/entitytrigger.h" #include "entity/component/trigger/entitytriggerevents.h" static uint32_t CALLBACK_ENTER_COUNT; static uint32_t CALLBACK_ACTIVE_COUNT; static uint32_t CALLBACK_MOVE_COUNT; static uint32_t CALLBACK_LEAVE_COUNT; static entityid_t CALLBACK_LAST_OTHER; static void *CALLBACK_LAST_USER; static void test_resetCallbackCounts(void) { CALLBACK_ENTER_COUNT = 0; CALLBACK_ACTIVE_COUNT = 0; CALLBACK_MOVE_COUNT = 0; CALLBACK_LEAVE_COUNT = 0; CALLBACK_LAST_OTHER = ENTITY_ID_INVALID; CALLBACK_LAST_USER = NULL; } static void test_onEnter( entitymanager_t *mgr, const entityid_t triggerEntityId, const componentid_t triggerComponentId, const entityid_t otherEntityId, const componentid_t otherComponentId, void *user ) { CALLBACK_ENTER_COUNT++; CALLBACK_LAST_OTHER = otherEntityId; CALLBACK_LAST_USER = user; } static void test_onActive( entitymanager_t *mgr, const entityid_t triggerEntityId, const componentid_t triggerComponentId, const entityid_t otherEntityId, const componentid_t otherComponentId, void *user ) { CALLBACK_ACTIVE_COUNT++; } static void test_onMove( entitymanager_t *mgr, const entityid_t triggerEntityId, const componentid_t triggerComponentId, const entityid_t otherEntityId, const componentid_t otherComponentId, void *user ) { CALLBACK_MOVE_COUNT++; } static void test_onLeave( entitymanager_t *mgr, const entityid_t triggerEntityId, const componentid_t triggerComponentId, const entityid_t otherEntityId, const componentid_t otherComponentId, void *user ) { CALLBACK_LEAVE_COUNT++; } static void test_entityTriggerDefaultsAndAccessors(void **state) { entitymanager_t mgr; entityManagerInit(&mgr); entityid_t entity = entityManagerAdd(&mgr); componentid_t trig = entityAddComponent( &mgr, entity, COMPONENT_TYPE_TRIGGER ); // Defaults: 1x1x1 cube, only bit 0 of the collide mask set, no occupants. physicsshape_t shape = entityTriggerGetShape(&mgr, entity, trig); assert_int_equal(shape.type, PHYSICS_SHAPE_CUBE); assert_float_equal(shape.data.cube.halfExtents[0], 0.5f, 0.0001f); assert_int_equal(entityTriggerGetCollideMask(&mgr, entity, trig), 0x1); assert_int_equal(entityTriggerGetOccupantCount(&mgr, entity, trig), 0); assert_int_equal( entityTriggerGetOccupantEntityId(&mgr, entity, trig, 0), ENTITY_ID_INVALID ); assert_false(entityTriggerIsOccupyingEntity(&mgr, entity, trig, 5)); physicsshape_t sphere = { .type = PHYSICS_SHAPE_SPHERE, .data.sphere.radius = 2.0f }; entityTriggerSetShape(&mgr, entity, trig, sphere); physicsshape_t got = entityTriggerGetShape(&mgr, entity, trig); assert_int_equal(got.type, PHYSICS_SHAPE_SPHERE); assert_float_equal(got.data.sphere.radius, 2.0f, 0.0001f); entityTriggerSetCollideMask(&mgr, entity, trig, 0x2); assert_int_equal(entityTriggerGetCollideMask(&mgr, entity, trig), 0x2); entityManagerDispose(&mgr); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_entityTriggerSerializeRoundTrip(void **state) { entitymanager_t mgr; entityManagerInit(&mgr); entityid_t entity = entityManagerAdd(&mgr); componentid_t trig = entityAddComponent( &mgr, entity, COMPONENT_TYPE_TRIGGER ); physicsshape_t sphere = { .type = PHYSICS_SHAPE_SPHERE, .data.sphere.radius = 3.0f }; entityTriggerSetShape(&mgr, entity, trig, sphere); entityTriggerSetCollideMask(&mgr, entity, trig, 0x4); yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); yyjson_mut_val *root = yyjson_mut_obj(doc); yyjson_mut_doc_set_root(doc, root); assert_true( errorIsOk(entityTriggerSerialize(&mgr, entity, trig, doc, root)) ); size_t len = 0; char_t *jsonStr = yyjson_mut_write(doc, 0, &len); assert_non_null(jsonStr); yyjson_mut_doc_free(doc); yyjson_doc *readDoc = yyjson_read(jsonStr, len, 0); free(jsonStr); assert_non_null(readDoc); entityid_t entity2 = entityManagerAdd(&mgr); componentid_t trig2 = entityAddComponent( &mgr, entity2, COMPONENT_TYPE_TRIGGER ); assert_true(errorIsOk(entityTriggerDeserialize( &mgr, entity2, trig2, yyjson_doc_get_root(readDoc) ))); yyjson_doc_free(readDoc); physicsshape_t got = entityTriggerGetShape(&mgr, entity2, trig2); assert_int_equal(got.type, PHYSICS_SHAPE_SPHERE); assert_float_equal(got.data.sphere.radius, 3.0f, 0.0001f); assert_int_equal(entityTriggerGetCollideMask(&mgr, entity2, trig2), 0x4); entityManagerDispose(&mgr); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_entityTriggerEventSubscription(void **state) { test_resetCallbackCounts(); entitymanager_t mgr; entityManagerInit(&mgr); entityid_t entity = entityManagerAdd(&mgr); componentid_t trig = entityAddComponent( &mgr, entity, COMPONENT_TYPE_TRIGGER ); int32_t userValue = 42; entityTriggerOnEnterAdd(&mgr, entity, trig, test_onEnter, &userValue); entityTriggerOnActiveAdd(&mgr, entity, trig, test_onActive, NULL); entityTriggerOnMoveAdd(&mgr, entity, trig, test_onMove, NULL); entityTriggerOnLeaveAdd(&mgr, entity, trig, test_onLeave, NULL); entityTriggerFireEnter(&mgr, entity, trig, 7, 0); assert_int_equal(CALLBACK_ENTER_COUNT, 1); assert_int_equal(CALLBACK_LAST_OTHER, 7); assert_ptr_equal(CALLBACK_LAST_USER, &userValue); entityTriggerFireActive(&mgr, entity, trig, 7, 0); assert_int_equal(CALLBACK_ACTIVE_COUNT, 1); entityTriggerFireMove(&mgr, entity, trig, 7, 0); assert_int_equal(CALLBACK_MOVE_COUNT, 1); entityTriggerFireLeave(&mgr, entity, trig, 7, 0); assert_int_equal(CALLBACK_LEAVE_COUNT, 1); // After removing, the callback no longer fires. entityTriggerOnEnterRemove(&mgr, entity, trig, test_onEnter); entityTriggerFireEnter(&mgr, entity, trig, 8, 0); assert_int_equal(CALLBACK_ENTER_COUNT, 1); entityManagerDispose(&mgr); assert_int_equal(memoryGetAllocatedCount(), 0); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_entityTriggerDefaultsAndAccessors), cmocka_unit_test(test_entityTriggerSerializeRoundTrip), cmocka_unit_test(test_entityTriggerEventSubscription), }; return cmocka_run_group_tests(tests, NULL, NULL); }