From b1be1deb79a1f2fe252c6df75a0dd580d4a9a07c Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 21 Aug 2025 22:58:39 -0500 Subject: [PATCH] ecs work --- src/display/CMakeLists.txt | 3 ++ src/display/camera.h | 2 + src/display/display.c | 18 +++++++- src/display/mesh/mesh.h | 2 +- src/ecs/ecscomponent.c | 90 ++++++++++++++++++++++++++++---------- src/ecs/ecscomponent.h | 40 ++++++++++++++--- src/ecs/ecssystem.c | 10 +++++ src/ecs/ecssystem.h | 13 +++++- src/engine/engine.c | 2 +- src/scene/scenetree.c | 80 +++++++++++++++++++++++---------- src/scene/scenetree.h | 18 +++++++- 11 files changed, 223 insertions(+), 55 deletions(-) diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index 52bda08..cdde149 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -10,6 +10,9 @@ target_sources(${DUSK_TARGET_NAME} camera.c ) +# Subdirectories +add_subdirectory(mesh) + if(DUSK_TARGET_SYSTEM STREQUAL "linux") target_compile_definitions(${DUSK_TARGET_NAME} PRIVATE diff --git a/src/display/camera.h b/src/display/camera.h index 3e85015..1fdf4b5 100644 --- a/src/display/camera.h +++ b/src/display/camera.h @@ -14,6 +14,8 @@ typedef struct { extern camera_t CAMERA_DATA[ECS_ENTITY_COUNT_MAX]; extern ecscomponent_t CAMERA_COMPONENT; +extern ecsid_t CAMERA_MAIN; + /** * Initializes the camera component. diff --git a/src/display/display.c b/src/display/display.c index 7aafd16..504a47e 100644 --- a/src/display/display.c +++ b/src/display/display.c @@ -8,8 +8,17 @@ #include "display/display.h" #include "console/console.h" +#include "display/mesh/mesh.h" + display_t DISPLAY; +mesh_t mesh; +meshvertex_t triangle[3] = { + {{255, 0, 0, 255}, {0.0f, 0.0f}, {0.0f, 1.0f}}, // Vertex 1 + {{0, 255, 0, 255}, {1.0f, 0.0f}, {1.0f, 1.0f}}, // Vertex 2 + {{0, 0, 255, 255}, {0.5f, 1.0f}, {0.5f, 0.0f}} // Vertex 3 +}; + errorret_t displayInit(void) { #if DUSK_DISPLAY_SDL2 if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) { @@ -53,6 +62,8 @@ errorret_t displayInit(void) { glEnableClientState(GL_VERTEX_ARRAY); #endif + meshInit(&mesh, MESH_PRIMITIVE_TRIANGLES, 3, triangle); + // For now, we just return an OK error. errorOk(); } @@ -68,17 +79,22 @@ errorret_t displayUpdate(void) { default: break; - } + } } SDL_GL_SwapWindow(DISPLAY.window); #endif + meshDraw(&mesh, 0, -1); + + // For now, we just return an OK error. errorOk(); } errorret_t displayDispose(void) { + meshDispose(&mesh); + #if DUSK_DISPLAY_SDL2 if(DISPLAY.glContext) { SDL_GL_DeleteContext(DISPLAY.glContext); diff --git a/src/display/mesh/mesh.h b/src/display/mesh/mesh.h index 1437238..d851e58 100644 --- a/src/display/mesh/mesh.h +++ b/src/display/mesh/mesh.h @@ -4,7 +4,7 @@ // https://opensource.org/licenses/MIT #pragma once -#include "display/render.h" +#include "display/display.h" typedef enum { #if DUSK_DISPLAY_SDL2 diff --git a/src/ecs/ecscomponent.c b/src/ecs/ecscomponent.c index 5d49f1c..d7af6e2 100644 --- a/src/ecs/ecscomponent.c +++ b/src/ecs/ecscomponent.c @@ -6,60 +6,104 @@ */ #include "ecscomponent.h" +#include "ecssystem.h" #include "assert/assert.h" #include "util/memory.h" -bool_t ecsComponentDataHas(const ecscomponent_t *cmp, const ecsid_t id) { +void ecsComponentInitialize(ecscomponent_t *cmp) { assertNotNull(cmp, "Component pointer cannot be NULL."); - assertTrue(id >= 0 && id < ECS_ENTITY_COUNT_MAX, "Invalid entity ID."); assertTrue( - (cmp->componentFlags & ECS_COMPONENT_FLAG_INITIALIZED) != 0, - "Component must be initialized before checking data." + ECS_SYSTEM.componentCount < ECS_SYSTEM_ECS_COMPONENTS_MAX, + "ECS Component count exceeded maximum limit." ); + memoryZero(cmp->data, sizeof(ecscomponent_t)); + + if(cmp->callbacks.init) cmp->callbacks.init(); + cmp->componentFlags |= ECS_COMPONENT_FLAG_INITIALIZED; + + ECS_SYSTEM.components[ECS_SYSTEM.componentCount++] = cmp; +} + +bool_t ecsComponentIsInitialized(const ecscomponent_t *cmp) { + assertNotNull(cmp, "Component pointer cannot be NULL."); + return (cmp->componentFlags & ECS_COMPONENT_FLAG_INITIALIZED) != 0; +} + + +bool_t ecsComponentDataHas(const ecscomponent_t *cmp, const ecsid_t id) { + assertTrue( + id >= 0 && id < ECS_ENTITY_COUNT_MAX, + "Invalid entity ID." + ); + if(!ecsComponentIsInitialized(cmp)) return false; + return cmp->entityFlags[id] & ECS_COMPONENT_ENTITY_FLAG_USED; } void * ecsComponentDataGet(const ecscomponent_t *cmp, const ecsid_t id) { assertTrue(ecsComponentDataHas(cmp, id), "No data for entity ID."); - - // Calculate the offset for the entity's data. return (void *)((uint8_t *)cmp->data + (id * cmp->dataSize)); } void * ecsComponentDataAdd(ecscomponent_t *cmp, const ecsid_t id) { - assertNotNull(cmp, "Component pointer cannot be NULL."); + if(!ecsComponentIsInitialized(cmp)) ecsComponentInitialize(cmp); + assertTrue(id >= 0 && id < ECS_ENTITY_COUNT_MAX, "Invalid entity ID."); - - // Initialize the component if it hasn't been initialized yet. - if((cmp->componentFlags & ECS_COMPONENT_FLAG_INITIALIZED) == 0) { - if(cmp->componentInit) cmp->componentInit(); - cmp->componentFlags |= ECS_COMPONENT_FLAG_INITIALIZED; - } - assertFalse(ecsComponentDataHas(cmp, id), "Entity already has data."); - // Zero out the entity's data. memoryZero( (uint8_t *)cmp->data + (id * cmp->dataSize), cmp->dataSize ); - - // Mark the entity as having data. - cmp->entityFlags[id] = ECS_COMPONENT_ENTITY_FLAG_USED; + cmp->entityFlags[id] |= ECS_COMPONENT_ENTITY_FLAG_USED; + if(cmp->callbacks.entityAdd) cmp->callbacks.entityAdd(id); + cmp->entitiesWithData[id] = id; + cmp->entitiesWithDataCount++; return ecsComponentDataGet(cmp, id); } void ecsComponentDataRemove(ecscomponent_t *cmp, const ecsid_t id) { - assertNotNull(cmp, "Component pointer cannot be NULL."); - assertTrue(id >= 0 && id < ECS_ENTITY_COUNT_MAX, "Invalid entity ID."); assertTrue( - (cmp->componentFlags & ECS_COMPONENT_FLAG_INITIALIZED) != 0, - "Component must be initialized before removing data." + ecsComponentIsInitialized(cmp), "Component was never initialized." ); assertTrue(ecsComponentDataHas(cmp, id), "Entity does not have data."); - // Clear the entity's data flag. cmp->entityFlags[id] = 0; + + // Remove entity from the entitiesWithData array by finding its index and + // shifting the rest of the array down. Use memoryCopy to avoid + // unnecessary loops. + uint32_t index = 0; + for(; index < cmp->entitiesWithDataCount; index++) { + if(cmp->entitiesWithData[index] == id) break; + } + assertTrue( + index < cmp->entitiesWithDataCount, + "Entity not found in entitiesWithData?" + ); + memoryCopy( + &cmp->entitiesWithData[index], + &cmp->entitiesWithData[index + 1], + sizeof(ecsid_t) * (cmp->entitiesWithDataCount - index - 1) + ); + + if(cmp->callbacks.entityRemove) cmp->callbacks.entityRemove(id); +} + +void ecsComponentDispose(ecscomponent_t *cmp) { + assertNotNull(cmp, "Component pointer cannot be NULL."); + assertTrue( + ecsComponentIsInitialized(cmp), + "Component was never initialized." + ); + + for(uint32_t i = 0; i < cmp->entitiesWithDataCount; i++) { + if(cmp->callbacks.entityRemove) + cmp->callbacks.entityRemove(cmp->entitiesWithData[i]); + } + memoryZero(cmp->entityFlags, sizeof(cmp->entityFlags)); + cmp->entitiesWithDataCount = 0; + cmp->componentFlags = 0; } \ No newline at end of file diff --git a/src/ecs/ecscomponent.h b/src/ecs/ecscomponent.h index 7963b39..8731c19 100644 --- a/src/ecs/ecscomponent.h +++ b/src/ecs/ecscomponent.h @@ -12,30 +12,53 @@ #define ECS_COMPONENT_FLAG_INITIALIZED (1 << 0) +typedef struct { + void (*init)(); + void (*entityAdd)(const ecsid_t id); + void (*entityRemove)(const ecsid_t id); +} ecscomponentcallbacks_t; + typedef struct { void *data; size_t dataSize; uint8_t entityFlags[ECS_ENTITY_COUNT_MAX]; uint8_t componentFlags; + ecscomponentcallbacks_t callbacks; - void (*componentInit)(); + ecsid_t entitiesWithData[ECS_ENTITY_COUNT_MAX]; + uint32_t entitiesWithDataCount;; } ecscomponent_t; /** * Initializes an ECS Component. * * @param dPointer Pointer to the data that the component owns. - * @param cInit Function to call to initialize the component. + * @param cbs Callback functions for the component. */ -#define ecsComponentInit(dPointer, cInit) \ +#define ecsComponentInit(dPointer, cbs) \ (ecscomponent_t){ \ .data = dPointer, \ .dataSize = sizeof(*dPointer), \ .entityFlags = 0, \ .componentFlags = 0, \ - .componentInit = cInit \ + .callbacks = (cbs) \ } +/** + * Initializes an ECS Component. + * + * @param cmp Pointer to the ecscomponent_t to initialize. + */ +void ecsComponentInitialize(ecscomponent_t *cmp); + +/** + * Checks if the component is initialized. + * + * @param cmp Pointer to the ecscomponent_t. + * @return True if the component is initialized, false otherwise. + */ +bool_t ecsComponentIsInitialized(const ecscomponent_t *cmp); + /** * Checks if the component has data for a specific entity. * @@ -68,4 +91,11 @@ void * ecsComponentDataAdd(ecscomponent_t *cmp, const ecsid_t id); * @param cmp Pointer to the ecscomponent_t. * @param id The ID of the entity to remove data for. */ -void ecsComponentDataRemove(ecscomponent_t *cmp, const ecsid_t id); \ No newline at end of file +void ecsComponentDataRemove(ecscomponent_t *cmp, const ecsid_t id); + +/** + * Disposes the component, freeing any resources it holds. + * + * @param cmp Pointer to the ecscomponent_t to dispose. + */ +void ecsComponentDispose(ecscomponent_t *cmp); \ No newline at end of file diff --git a/src/ecs/ecssystem.c b/src/ecs/ecssystem.c index 5737eac..a5eec2d 100644 --- a/src/ecs/ecssystem.c +++ b/src/ecs/ecssystem.c @@ -56,4 +56,14 @@ void ecsEntityRemove(const ecsid_t id) { ECS_SYSTEM.availableCount <= ECS_ENTITY_COUNT_MAX, "Available count exceeded maximum limit" ); +} + +void ecsSystemDispose() { + for(uint32_t i = 0; i < ECS_SYSTEM.componentCount; i++) { + ecscomponent_t *cmp = ECS_SYSTEM.components[i]; + ecsComponentDispose(cmp); + assertTrue(cmp->entitiesWithDataCount == 0, "Component still has data."); + } + + memoryZero(&ECS_SYSTEM, sizeof(ecssystem_t)); } \ No newline at end of file diff --git a/src/ecs/ecssystem.h b/src/ecs/ecssystem.h index e0de899..2de4a39 100644 --- a/src/ecs/ecssystem.h +++ b/src/ecs/ecssystem.h @@ -7,6 +7,9 @@ #pragma once #include "ecsentity.h" +#include "ecscomponent.h" + +#define ECS_SYSTEM_ECS_COMPONENTS_MAX 64 typedef struct { ecsentity_t entities[ECS_ENTITY_COUNT_MAX]; @@ -14,6 +17,9 @@ typedef struct { ecsentity_t *available[ECS_ENTITY_COUNT_MAX]; uint32_t availableCount; + ecscomponent_t *components[ECS_SYSTEM_ECS_COMPONENTS_MAX]; + uint32_t componentCount; + ecsid_t root; } ecssystem_t; @@ -38,4 +44,9 @@ ecsid_t ecsEntityAdd(); * * @param id The ID of the entity to destroy. */ -void ecsEntityRemove(const ecsid_t id); \ No newline at end of file +void ecsEntityRemove(const ecsid_t id); + +/** + * Dispose the ECS system, freeing all resources. + */ +void ecsSystemDispose(); \ No newline at end of file diff --git a/src/engine/engine.c b/src/engine/engine.c index bc2db00..ecf6580 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -25,7 +25,6 @@ errorret_t engineInit(void) { timeInit(); consoleInit(); ecsSystemInit(); - sceneTreeInit(); errorChain(displayInit()); ecsid_t sceneTest = sceneTestAdd(); @@ -43,6 +42,7 @@ errorret_t engineUpdate(void) { } errorret_t engineDispose(void) { + ecsSystemDispose(); errorChain(displayDispose()); consoleDispose(); diff --git a/src/scene/scenetree.c b/src/scene/scenetree.c index 4357e5a..20c9569 100644 --- a/src/scene/scenetree.c +++ b/src/scene/scenetree.c @@ -10,7 +10,11 @@ scenetreenode_t SCENE_TREE_DATA[ECS_ENTITY_COUNT_MAX] = { 0 }; ecscomponent_t SCENE_TREE_COMPONENT = ecsComponentInit( SCENE_TREE_DATA, - sceneTreeInit + ((ecscomponentcallbacks_t){ + .init = sceneTreeInit, + .entityAdd = sceneTreeEntityAdded, + .entityRemove = sceneTreeEntityRemoved + }) ); void sceneTreeInit(void) { @@ -22,12 +26,12 @@ void sceneTreeInit(void) { ecsid_t sceneTreeParentGet(const ecsid_t child) { assertTrue(child >= 0 && child < ECS_ENTITY_COUNT_MAX, "Invalid child ID"); + if(!ecsComponentDataHas(&SCENE_TREE_COMPONENT, child)) return -1; return SCENE_TREE_DATA[child].parent; } uint8_t sceneTreeChildGetAll(const ecsid_t id, ecsid_t *children) { - assertTrue(id >= 0 && id < ECS_ENTITY_COUNT_MAX, "Invalid parent ID"); - + if(!ecsComponentDataHas(&SCENE_TREE_COMPONENT, id)) return 0; if(children == NULL) return SCENE_TREE_DATA[id].childCount; memoryCopy( @@ -40,17 +44,12 @@ uint8_t sceneTreeChildGetAll(const ecsid_t id, ecsid_t *children) { } void sceneTreeChildAdd(const ecsid_t parent, const ecsid_t child) { - assertTrue(parent >= 0 && parent < ECS_ENTITY_COUNT_MAX, "Invalid parent ID"); - assertTrue(child >= 0 && child < ECS_ENTITY_COUNT_MAX, "Invalid child ID"); - assertTrue(parent != child, "Child cannot be a child of itself."); - assertFalse( - sceneTreeChildInTree(parent, child), - "Child is already in the parent's tree" - ); - assertFalse( - sceneTreeChildInTree(child, parent), - "Child cannot be a deep child of itself." - ); + if(!ecsComponentDataHas(&SCENE_TREE_COMPONENT, parent)) { + ecsComponentDataAdd(&SCENE_TREE_COMPONENT, parent); + } + if(!ecsComponentDataHas(&SCENE_TREE_COMPONENT, child)) { + ecsComponentDataAdd(&SCENE_TREE_COMPONENT, child); + } scenetreenode_t *parentNode = &SCENE_TREE_DATA[parent]; assertTrue(parentNode->childCount < SCENE_ITEM_CHILD_MAX, "Parent full."); @@ -67,10 +66,15 @@ void sceneTreeChildRemove( const ecsid_t parent, const ecsid_t child ) { - assertTrue(parent >= 0 && parent < ECS_ENTITY_COUNT_MAX, "Invalid parent ID"); - assertTrue(child >= 0 && child < ECS_ENTITY_COUNT_MAX, "Invalid child ID"); - assertTrue(parent != child, "Child cannot be a child of itself."); - + assertTrue( + ecsComponentDataHas(&SCENE_TREE_COMPONENT, parent), + "SCENE TREE ECS lacks Parent." + ); + assertTrue( + ecsComponentDataHas(&SCENE_TREE_COMPONENT, child), + "SCENE TREE ECS lacks Child." + ); + scenetreenode_t *childNode = &SCENE_TREE_DATA[child]; scenetreenode_t *parentNode = &SCENE_TREE_DATA[parent]; assertTrue(childNode->parent == parent, "Child does not belong to parent"); @@ -90,25 +94,48 @@ void sceneTreeChildRemove( // Clear child's parent childNode->parent = -1; + + if(childNode->childCount == 0) { + ecsComponentDataRemove(&SCENE_TREE_COMPONENT, child); + } + + if(parentNode->childCount == 0) { + ecsComponentDataRemove(&SCENE_TREE_COMPONENT, parent); + } } void sceneTreeChildRemoveAll(const ecsid_t parent) { - assertTrue(parent >= 0 && parent < ECS_ENTITY_COUNT_MAX, "Invalid parent ID"); - + assertTrue( + ecsComponentDataHas(&SCENE_TREE_COMPONENT, parent), + "SCENE TREE ECS lacks Parent." + ); + scenetreenode_t *parentNode = &SCENE_TREE_DATA[parent]; for(uint8_t i = 0; i < parentNode->childCount; i++) { ecsid_t child = parentNode->children[i]; SCENE_TREE_DATA[child].parent = -1; + + if(SCENE_TREE_DATA[child].childCount == 0) { + ecsComponentDataRemove(&SCENE_TREE_COMPONENT, child); + } } - // Reset child count - parentNode->childCount = 0; + ecsComponentDataRemove(&SCENE_TREE_COMPONENT, parent); } bool_t sceneTreeChildInTree( const ecsid_t parent, const ecsid_t child ) { + assertTrue( + ecsComponentDataHas(&SCENE_TREE_COMPONENT, parent), + "SCENE TREE ECS lacks Parent." + ); + assertTrue( + ecsComponentDataHas(&SCENE_TREE_COMPONENT, child), + "SCENE TREE ECS lacks Child." + ); + assertTrue(parent >= 0 && parent < ECS_ENTITY_COUNT_MAX, "Invalid parent ID"); assertTrue(child >= 0 && child < ECS_ENTITY_COUNT_MAX, "Invalid child ID"); assertTrue(parent != child, "Child cannot be a child of itself."); @@ -121,4 +148,13 @@ bool_t sceneTreeChildInTree( } return false; +} + +void sceneTreeEntityAdded(const ecsid_t id) { + SCENE_TREE_DATA[id].parent = -1; // Default parent to -1 (no parent) +} + +void sceneTreeEntityRemoved(const ecsid_t id) { + if(SCENE_TREE_DATA[id].parent == -1) return; + sceneTreeChildRemove(SCENE_TREE_DATA[id].parent, id); } \ No newline at end of file diff --git a/src/scene/scenetree.h b/src/scene/scenetree.h index 6c9f965..92c9b88 100644 --- a/src/scene/scenetree.h +++ b/src/scene/scenetree.h @@ -82,4 +82,20 @@ void sceneTreeChildRemoveAll(const ecsid_t parent); * @param child The ID of the child scene item to check. * @return True if the child is in the parent's children, false otherwise. */ -bool_t sceneTreeChildInTree(const ecsid_t parent, const ecsid_t child); \ No newline at end of file +bool_t sceneTreeChildInTree(const ecsid_t parent, const ecsid_t child); + +/** + * Callback for when an entity is added to the ECS. + * This will initialize the scene tree data for the entity. + * + * @param id The ID of the entity being added. + */ +void sceneTreeEntityAdded(const ecsid_t id); + +/** + * Callback for when an entity is removed from the ECS. + * This will clean up any scene tree data associated with the entity. + * + * @param id The ID of the entity being removed. + */ +void sceneTreeEntityRemoved(const ecsid_t id); \ No newline at end of file