From 42099f7241f9377a87a2839ce193ed270434cc7c Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 10 Apr 2026 07:09:25 -0500 Subject: [PATCH] ECS Enhancements --- src/dusk/engine/engine.c | 12 +++- src/dusk/entity/component.c | 53 +++++++++++++++-- src/dusk/entity/component.h | 27 +++++++++ src/dusk/entity/component/CMakeLists.txt | 6 +- .../entity/component/display/CMakeLists.txt | 11 ++++ .../entity/component/display/entitycamera.c | 59 +++++++++++++++++++ .../entity/component/display/entitycamera.h | 50 ++++++++++++++++ .../entity/component/display/entityposition.c | 32 ++++++++++ .../component/{ => display}/entityposition.h | 17 ++++++ src/dusk/entity/component/entityposition.c | 17 ------ src/dusk/entity/componentlist.h | 6 +- src/dusk/entity/entity.c | 25 +++++++- src/dusk/entity/entity.h | 1 - src/dusk/entity/entitybase.h | 3 +- src/dusk/entity/entitymanager.c | 4 ++ src/dusk/entity/entitymanager.h | 2 + 16 files changed, 289 insertions(+), 36 deletions(-) create mode 100644 src/dusk/entity/component/display/CMakeLists.txt create mode 100644 src/dusk/entity/component/display/entitycamera.c create mode 100644 src/dusk/entity/component/display/entitycamera.h create mode 100644 src/dusk/entity/component/display/entityposition.c rename src/dusk/entity/component/{ => display}/entityposition.h (50%) delete mode 100644 src/dusk/entity/component/entityposition.c diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index ff5bf386..9a7a168f 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -40,8 +40,16 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(gameInit()); // FOF - entityid_t ent0 = entityManagerAdd(); - componentid_t ent0c0 = entityAddComponent(ent0, COMPONENT_TYPE_POSITION); + entityid_t cam = entityManagerAdd(); + componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION); + entityPositionLookAt( + cam, + camPos, + (vec3){ 0.0f, 0.0f, 0.0f }, + (vec3){ 0.0f, 1.0f, 0.0f }, + (vec3){ 5.0f, 5.0f, 5.0f } + ); + componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA); entityid_t ent1 = entityManagerAdd(); diff --git a/src/dusk/entity/component.c b/src/dusk/entity/component.c index 60f33115..83d3aba2 100644 --- a/src/dusk/entity/component.c +++ b/src/dusk/entity/component.c @@ -30,7 +30,8 @@ void componentInit( assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB"); assertTrue(type != COMPONENT_TYPE_NULL, "Cannot initialize null component"); - component_t *cmp = &ENTITY_MANAGER.entities[entityId].components[componentId]; + componentindex_t index = componentGetIndex(entityId, componentId); + component_t *cmp = &ENTITY_MANAGER.components[index]; memoryZero(cmp, sizeof(component_t)); cmp->type = type; @@ -40,16 +41,55 @@ void componentInit( } void * componentGetData( + const entityid_t entityId, + const componentid_t componentId, + const componenttype_t type +) { + assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); + assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); + assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB"); + assertTrue(type != COMPONENT_TYPE_NULL, "Cannot get data of null component"); + + componentindex_t index = componentGetIndex(entityId, componentId); + component_t *cmp = &ENTITY_MANAGER.components[index]; + assertTrue(cmp->type == type, "Component type mismatch"); + + return &cmp->data; +} + +componentindex_t componentGetIndex( const entityid_t entityId, const componentid_t componentId ) { assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); - - component_t *cmp = &ENTITY_MANAGER.entities[entityId].components[componentId]; - if(cmp->type == COMPONENT_TYPE_NULL) return NULL; + return (entityId * ENTITY_COMPONENT_COUNT_MAX) + componentId; +} - return &cmp->data; +entityid_t componentGetEntitiesWithComponent( + const componenttype_t type, + entityid_t outEntities[ENTITY_COUNT_MAX] +) { + assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB"); + assertTrue(type != COMPONENT_TYPE_NULL, "Cannot check NULL type"); + assertNotNull(outEntities, "Output entities array cannot be null"); + + entityid_t written = 0; + for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) { + entityid_t used = ENTITY_MANAGER.entitiesWithComponent[ + type * ENTITY_COUNT_MAX + i + ]; + if(used == 0xFF) continue; + outEntities[written++] = used; + assertTrue( + used == i, "Entity ID mismatch in entitiesWithComponent lookup" + ); + assertTrue( + (ENTITY_MANAGER.entities[used].state & ENTITY_STATE_ACTIVE) != 0, + "Inactive entity in entitiesWithComponent lookup" + ); + } + return written; } void componentDispose( @@ -59,7 +99,8 @@ void componentDispose( assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); - component_t *cmp = &ENTITY_MANAGER.entities[entityId].components[componentId]; + componentindex_t index = componentGetIndex(entityId, componentId); + component_t *cmp = &ENTITY_MANAGER.components[index]; if(cmp->type == COMPONENT_TYPE_NULL) return; if(COMPONENT_DEFINITIONS[cmp->type].dispose) { diff --git a/src/dusk/entity/component.h b/src/dusk/entity/component.h index a31cf81e..7afdc93e 100644 --- a/src/dusk/entity/component.h +++ b/src/dusk/entity/component.h @@ -60,13 +60,40 @@ void componentInit( * * @param entityId The entity ID. * @param componentId The component ID. + * @param type The type of the component to get, only used for assertion. * @return A pointer to the component data. */ void * componentGetData( + const entityid_t entityId, + const componentid_t componentId, + const componenttype_t type +); + +/** + * Gets the index of a component for the entity with component ID. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The index of the component in the component array. + */ +componentindex_t componentGetIndex( const entityid_t entityId, const componentid_t componentId ); +/** + * Gets the entity IDs of all entities with a component of the given type. + * + * @param type The type of the component to get entities for. + * @param outEntities An array to write the entity IDs to, must be at least + * ENTITY_COUNT_MAX in size. + * @return The number of entity IDs written to outEntities. + */ +entityid_t componentGetEntitiesWithComponent( + const componenttype_t type, + entityid_t outEntities[ENTITY_COUNT_MAX] +); + /** * Disposes of a component for the entity with component ID. * diff --git a/src/dusk/entity/component/CMakeLists.txt b/src/dusk/entity/component/CMakeLists.txt index 8cd69a57..6300fa3c 100644 --- a/src/dusk/entity/component/CMakeLists.txt +++ b/src/dusk/entity/component/CMakeLists.txt @@ -3,8 +3,4 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - entityposition.c -) \ No newline at end of file +add_subdirectory(display) \ No newline at end of file diff --git a/src/dusk/entity/component/display/CMakeLists.txt b/src/dusk/entity/component/display/CMakeLists.txt new file mode 100644 index 00000000..42d047d4 --- /dev/null +++ b/src/dusk/entity/component/display/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + entityposition.c + entitycamera.c +) \ No newline at end of file diff --git a/src/dusk/entity/component/display/entitycamera.c b/src/dusk/entity/component/display/entitycamera.c new file mode 100644 index 00000000..bbc5618f --- /dev/null +++ b/src/dusk/entity/component/display/entitycamera.c @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entity/entitymanager.h" +#include "display/framebuffer/framebuffer.h" +#include "display/screen/screen.h" + +void entityCameraInit(const entityid_t ent, const componentid_t comp) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + ent, comp, COMPONENT_TYPE_CAMERA + ); + cam->nearClip = 0.1f; + cam->farClip = 100.0f; + cam->projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE; + cam->perspective.fov = 60.0f; +} + +void entityCameraGetProjection( + const entityid_t ent, + const componentid_t comp, + mat4 out +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + ent, comp, COMPONENT_TYPE_CAMERA + ); + + if( + cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE || + cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED + ) { + glm_mat4_identity(out); + glm_perspective( + cam->perspective.fov, + SCREEN.aspect, + cam->nearClip, + cam->farClip, + out + ); + + if(cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) { + out[1][1] *= -1.0f; + } + } else if(cam->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { + glm_mat4_identity(out); + glm_ortho( + cam->orthographic.left, + cam->orthographic.right, + cam->orthographic.top, + cam->orthographic.bottom, + cam->nearClip, + cam->farClip, + out + ); + } +} \ No newline at end of file diff --git a/src/dusk/entity/component/display/entitycamera.h b/src/dusk/entity/component/display/entitycamera.h new file mode 100644 index 00000000..0d38e903 --- /dev/null +++ b/src/dusk/entity/component/display/entitycamera.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" +#include "display/camera/camera.h" + +typedef struct { + union { + struct { + float_t fov; + } perspective; + + struct { + float_t left; + float_t right; + float_t top; + float_t bottom; + } orthographic; + }; + + float_t nearClip; + float_t farClip; + cameraprojectiontype_t projType; +} entitycamera_t; + +/** + * Initializes an entity camera component. + * + * @param ent The entity ID. + * @param comp The component ID. + */ +void entityCameraInit(const entityid_t ent, const componentid_t comp); + +/** + * Renders out the projection matrix for the given camera. + * + * @param ent The entity ID. + * @param comp The component ID. + * @param out The output projection matrix. + */ +void entityCameraGetProjection( + const entityid_t ent, + const componentid_t comp, + mat4 out +); \ No newline at end of file diff --git a/src/dusk/entity/component/display/entityposition.c b/src/dusk/entity/component/display/entityposition.c new file mode 100644 index 00000000..af2a114e --- /dev/null +++ b/src/dusk/entity/component/display/entityposition.c @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entity/entitymanager.h" + +void entityPositionInit( + const entityid_t entityId, + const componentid_t componentId +) { + entityposition_t *pos = componentGetData( + entityId, componentId, COMPONENT_TYPE_POSITION + ); + + glm_mat4_identity(pos->transform); +} + +void entityPositionLookAt( + const entityid_t entityId, + const componentid_t componentId, + vec3 target, + vec3 up, + vec3 eye +) { + entityposition_t *pos = componentGetData( + entityId, componentId, COMPONENT_TYPE_POSITION + ); + glm_lookat(eye, target, up, pos->transform); +} \ No newline at end of file diff --git a/src/dusk/entity/component/entityposition.h b/src/dusk/entity/component/display/entityposition.h similarity index 50% rename from src/dusk/entity/component/entityposition.h rename to src/dusk/entity/component/display/entityposition.h index 2356b386..989f7a48 100644 --- a/src/dusk/entity/component/entityposition.h +++ b/src/dusk/entity/component/display/entityposition.h @@ -21,4 +21,21 @@ typedef struct { void entityPositionInit( const entityid_t entityId, const componentid_t componentId +); + +/** + * Transforms the entity's position to look at a target point. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param target The target point to look at. + * @param up The up vector for the look at transformation. + * @param eye The position of the camera/eye for the look at transformation. + */ +void entityPositionLookAt( + const entityid_t entityId, + const componentid_t componentId, + vec3 target, + vec3 up, + vec3 eye ); \ No newline at end of file diff --git a/src/dusk/entity/component/entityposition.c b/src/dusk/entity/component/entityposition.c deleted file mode 100644 index 91399d0b..00000000 --- a/src/dusk/entity/component/entityposition.c +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entity/entitymanager.h" - -void entityPositionInit( - const entityid_t entityId, - const componentid_t componentId -) { - entityposition_t *pos = componentGetData(entityId, componentId); - - glm_mat4_identity(pos->transform); -} \ No newline at end of file diff --git a/src/dusk/entity/componentlist.h b/src/dusk/entity/componentlist.h index d0934c31..88524620 100644 --- a/src/dusk/entity/componentlist.h +++ b/src/dusk/entity/componentlist.h @@ -5,6 +5,8 @@ * https://opensource.org/licenses/MIT */ -#include "entity/component/entityposition.h" +#include "entity/component/display/entityposition.h" +#include "entity/component/display/entitycamera.h" -X(POSITION, entityposition_t, position, entityPositionInit, NULL) \ No newline at end of file +X(POSITION, entityposition_t, position, entityPositionInit, NULL) +X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL) \ No newline at end of file diff --git a/src/dusk/entity/entity.c b/src/dusk/entity/entity.c index 9dda7f94..b5201fb8 100644 --- a/src/dusk/entity/entity.c +++ b/src/dusk/entity/entity.c @@ -14,6 +14,17 @@ void entityInit(const entityid_t entityId) { memoryZero(ent, sizeof(entity_t)); + // Mark all component types not using this entity. + for( + componenttype_t compType = 0; + compType < COMPONENT_TYPE_COUNT; + compType++ + ) { + ENTITY_MANAGER.entitiesWithComponent[ + compType * ENTITY_COUNT_MAX + entityId + ] = 0xFF; + } + ent->state |= ENTITY_STATE_ACTIVE; } @@ -21,11 +32,16 @@ componentid_t entityAddComponent( const entityid_t entityId, const componenttype_t type ) { + componentindex_t compInd; entity_t *ent = &ENTITY_MANAGER.entities[entityId]; for(componentid_t i = 0; i < ENTITY_COMPONENT_COUNT_MAX; i++) { - if(ent->components[i].type != COMPONENT_TYPE_NULL) continue; + compInd = componentGetIndex(entityId, i); + if(ENTITY_MANAGER.components[compInd].type != COMPONENT_TYPE_NULL) continue; componentInit(entityId, i, type); + ENTITY_MANAGER.entitiesWithComponent[ + type * ENTITY_COUNT_MAX + entityId + ] = entityId; return i; } @@ -34,11 +50,16 @@ componentid_t entityAddComponent( } void entityDispose(const entityid_t entityId) { + componentindex_t compInd; entity_t *ent = &ENTITY_MANAGER.entities[entityId]; for(componentid_t i = 0; i < ENTITY_COMPONENT_COUNT_MAX; i++) { - if(ent->components[i].type == COMPONENT_TYPE_NULL) continue; + compInd = componentGetIndex(entityId, i); + if(ENTITY_MANAGER.components[compInd].type == COMPONENT_TYPE_NULL) continue; componentDispose(entityId, i); + ENTITY_MANAGER.entitiesWithComponent[ + ENTITY_MANAGER.components[compInd].type * ENTITY_COUNT_MAX + entityId + ] = 0xFF; } ent->state = 0; diff --git a/src/dusk/entity/entity.h b/src/dusk/entity/entity.h index a103a621..4e639cfb 100644 --- a/src/dusk/entity/entity.h +++ b/src/dusk/entity/entity.h @@ -12,7 +12,6 @@ typedef struct { uint8_t state; - component_t components[ENTITY_COMPONENT_COUNT_MAX]; } entity_t; /** diff --git a/src/dusk/entity/entitybase.h b/src/dusk/entity/entitybase.h index e6906f47..0e449c61 100644 --- a/src/dusk/entity/entitybase.h +++ b/src/dusk/entity/entitybase.h @@ -12,4 +12,5 @@ #define ENTITY_COMPONENT_COUNT_MAX 24 typedef uint8_t entityid_t; -typedef uint8_t componentid_t; \ No newline at end of file +typedef uint8_t componentid_t; +typedef uint16_t componentindex_t; \ No newline at end of file diff --git a/src/dusk/entity/entitymanager.c b/src/dusk/entity/entitymanager.c index 16a4f257..e7b181f3 100644 --- a/src/dusk/entity/entitymanager.c +++ b/src/dusk/entity/entitymanager.c @@ -13,6 +13,10 @@ entitymanager_t ENTITY_MANAGER; void entityManagerInit(void) { memoryZero(&ENTITY_MANAGER, sizeof(entitymanager_t)); + memorySet( + ENTITY_MANAGER.entitiesWithComponent, 0xFF, + sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX + ); } entityid_t entityManagerAdd() { diff --git a/src/dusk/entity/entitymanager.h b/src/dusk/entity/entitymanager.h index 9a401c9e..739c14ac 100644 --- a/src/dusk/entity/entitymanager.h +++ b/src/dusk/entity/entitymanager.h @@ -10,6 +10,8 @@ typedef struct { entity_t entities[ENTITY_COUNT_MAX]; + component_t components[ENTITY_COUNT_MAX * ENTITY_COMPONENT_COUNT_MAX]; + entityid_t entitiesWithComponent[COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX]; } entitymanager_t; extern entitymanager_t ENTITY_MANAGER;