Scene tree

This commit is contained in:
2025-08-21 09:52:15 -05:00
parent c8390bdb12
commit 4688358d6a
10 changed files with 346 additions and 15 deletions

View File

@@ -25,10 +25,12 @@ target_sources(${DUSK_TARGET_NAME}
add_subdirectory(assert)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(ecs)
add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(scene)
add_subdirectory(thread)
add_subdirectory(time)
add_subdirectory(util)

View File

@@ -13,5 +13,7 @@ if(DUSK_TARGET_SYSTEM STREQUAL "linux")
target_compile_definitions(${DUSK_TARGET_NAME}
PRIVATE
DUSK_DISPLAY_SDL2=1
DISPLAY_WINDOW_WIDTH_DEFAULT=960
DISPLAY_WINDOW_HEIGHT_DEFAULT=720
)
endif()

10
src/ecs/CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
ecs.c
)

59
src/ecs/ecs.c Normal file
View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "ecs.h"
#include "util/memory.h"
#include "assert/assert.h"
ecs_t ECS;
void ecsInit() {
memoryZero(&ECS, sizeof(ecs_t));
// Fill ECS ids
for(uint32_t i = 0; i < ECS_ENTITY_COUNT_MAX; i++) {
ECS.entities[i].id = i;
}
// Fill the available array.
for(uint32_t i = 0; i < ECS_ENTITY_COUNT_MAX; i++) {
ECS.available[i] = &ECS.entities[i];
}
ECS.availableCount = ECS_ENTITY_COUNT_MAX;
// Reserve root entity
ECS.root = ecsEntityCreate();
}
ecsentityid_t ecsEntityCreate() {
assertTrue(ECS.availableCount > 0, "No available entities to create");
// Pop off the last available entity.
ecsentity_t *entity = ECS.available[--ECS.availableCount];
assertTrue((entity->flags & ECS_ENTITY_FLAG_USED) == 0, "Entity is used.");
assertTrue(entity->id >= 0, "Entity is invalid.");
assertTrue(entity->id < ECS_ENTITY_COUNT_MAX, "Entity ID out of bounds");
entity->flags |= ECS_ENTITY_FLAG_USED;
return entity->id;
}
void ecsEntityDispose(const ecsentityid_t id) {
assertTrue(id < ECS_ENTITY_COUNT_MAX, "Invalid entity ID");
ecsentity_t *entity = &ECS.entities[id];
assertTrue(entity->id >= 0, "Entity is invalid.");
assertTrue((entity->flags & ECS_ENTITY_FLAG_USED) != 0, "Entity is not used.");
// Mark the entity as available.
ECS.available[ECS.availableCount++] = entity;
assertTrue(
ECS.availableCount <= ECS_ENTITY_COUNT_MAX,
"Available count exceeded maximum limit"
);
}

51
src/ecs/ecs.h Normal file
View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef int_fast16_t ecsentityid_t;
#define ECS_ENTITY_COUNT_MAX 2048
#define ECS_ENTITY_FLAG_USED (1 << 0)
typedef struct ecsentity_s {
ecsentityid_t id;
uint8_t flags;
} ecsentity_t;
typedef struct ecs_s {
ecsentity_t entities[ECS_ENTITY_COUNT_MAX];
ecsentity_t *available[ECS_ENTITY_COUNT_MAX];
uint32_t availableCount;
ecsentityid_t root;
} ecs_t;
extern ecs_t ECS;
/**
* Initialize the given ECS system.
*/
void ecsInit();
/**
* Create a new entity in the ECS. This locks an id and gives it to the caller,
* who will be responsible for managing the entity's lifecycle.
*
* @return The ID of the newly created entity.
*/
ecsentityid_t ecsEntityCreate();
/**
* Disposes an entity in the ECS. This will free the entity's ID and make it
* available for reuse.
*
* @param id The ID of the entity to destroy.
*/
void ecsEntityDispose(const ecsentityid_t id);

View File

@@ -10,6 +10,8 @@
#include "time/time.h"
#include "console/console.h"
#include "display/display.h"
#include "ecs/ecs.h"
#include "scene/scenetree.h"
engine_t ENGINE;
@@ -20,6 +22,8 @@ errorret_t engineInit(void) {
// Init systems. Order is important.
timeInit();
consoleInit();
ecsInit();
sceneTreeInit();
errorChain(displayInit());
errorOk();

10
src/scene/CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
scenetree.c
)

View File

@@ -1,15 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
int padding;
} scene_t;
extern scene_t *SCENE;

117
src/scene/scenetree.c Normal file
View File

@@ -0,0 +1,117 @@
// Copyright (c) 2025 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "scenetree.h"
#include "util/memory.h"
#include "assert/assert.h"
scenetree_t SCENE_TREE;
void sceneTreeInit(void) {
memoryZero(&SCENE_TREE, sizeof(scenetree_t));
// Default all items to have no parent
for (ecsentityid_t i = 0; i < ECS_ENTITY_COUNT_MAX; i++) {
SCENE_TREE.items[i].parent = -1;
}
}
ecsentityid_t sceneTreeParentGet(const ecsentityid_t child) {
assertTrue(child >= 0 && child < ECS_ENTITY_COUNT_MAX, "Invalid child ID");
return SCENE_TREE.items[child].parent;
}
uint8_t sceneTreeChildGetAll(const ecsentityid_t id, ecsentityid_t *children) {
assertTrue(id >= 0 && id < ECS_ENTITY_COUNT_MAX, "Invalid parent ID");
const scenetreenode_t *node = &SCENE_TREE.items[id];
if(children == NULL) return node->childCount;
return node->childCount;
}
void sceneTreeChildAdd(const ecsentityid_t parent, const ecsentityid_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."
);
scenetreenode_t *parentNode = &SCENE_TREE.items[parent];
assertTrue(parentNode->childCount < SCENE_ITEM_CHILD_MAX, "Parent full.");
// Add child to parent's children array
parentNode->children[parentNode->childCount] = child;
parentNode->childCount++;
// Set child's parent
SCENE_TREE.items[child].parent = parent;
}
void sceneTreeChildRemove(
const ecsentityid_t parent,
const ecsentityid_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.");
scenetreenode_t *childNode = &SCENE_TREE.items[child];
scenetreenode_t *parentNode = &SCENE_TREE.items[parent];
assertTrue(childNode->parent == parent, "Child does not belong to parent");
assertTrue(parentNode->childCount > 0, "Parent has no children");
// Remove child from parent's children array
for (uint8_t i = 0; i < parentNode->childCount; i++) {
if (parentNode->children[i] == child) {
// Shift remaining children down
for (uint8_t j = i; j < parentNode->childCount - 1; j++) {
parentNode->children[j] = parentNode->children[j + 1];
}
parentNode->childCount--;
break;
}
}
// Clear child's parent
childNode->parent = -1;
}
void sceneTreeChildRemoveAll(const ecsentityid_t parent) {
assertTrue(parent >= 0 && parent < ECS_ENTITY_COUNT_MAX, "Invalid parent ID");
scenetreenode_t *parentNode = &SCENE_TREE.items[parent];
for(uint8_t i = 0; i < parentNode->childCount; i++) {
ecsentityid_t child = parentNode->children[i];
SCENE_TREE.items[child].parent = -1;
}
// Reset child count
parentNode->childCount = 0;
}
bool_t sceneTreeChildInTree(
const ecsentityid_t parent,
const ecsentityid_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.");
scenetreenode_t *childNode = &SCENE_TREE.items[child];
while(childNode) {
if(childNode->parent == parent) return true;
if(childNode->parent == -1) break; // No parent means we've reached the root
childNode = &SCENE_TREE.items[childNode->parent];
}
return false;
}

91
src/scene/scenetree.h Normal file
View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "ecs/ecs.h"
#define SCENE_ITEM_CHILD_MAX 16
typedef struct {
ecsentityid_t children[SCENE_ITEM_CHILD_MAX];
uint8_t childCount;
ecsentityid_t parent;
} scenetreenode_t;
typedef struct scene_s {
scenetreenode_t items[ECS_ENTITY_COUNT_MAX];
} scenetree_t;
extern scenetree_t SCENE_TREE;
/**
* Initialize the scene tree.
*
* This will initialize the ECS system and prepare the scene tree for use.
*/
void sceneTreeInit(void);
/**
* Create a new scene item in the scene tree.
* This will create a new entity in the ECS and return its ID.
*
* @return The ID of the newly created scene item.
*/
ecsentityid_t sceneTreeCreate(void);
/**
* Get the parent of a given scene item.
*
* @param id The ID of the scene item.
* @return The ID of the parent scene item, or -1 if it has no parent.
*/
ecsentityid_t sceneTreeParentGet(const ecsentityid_t child);
/**
* Get the children of a given scene item. If children is NULL only the count
* will be returned.
*
* @param id The ID of the scene item.
* @param children Pointer to an array where the children IDs will be stored.
* @return The number of children found.
*/
uint8_t sceneTreeChildGetAll(const ecsentityid_t id, ecsentityid_t *children);
/**
* Add a child to a parent in the scene tree.
*
* @param parent The ID of the parent scene item.
* @param child The ID of the child scene item to add.
*/
void sceneTreeChildAdd(const ecsentityid_t parent, const ecsentityid_t child);
/**
* Remove a child from a parent in the scene tree.
*
* @param prnt The ID of the parent scene item.
* @param child The ID of the child scene item to remove.
*/
void sceneTreeChildRemove(const ecsentityid_t prnt, const ecsentityid_t child);
/**
* Remove all children from a parent in the scene tree.
*
* @param parent The ID of the parent scene item.
*/
void sceneTreeChildRemoveAll(const ecsentityid_t parent);
/**
* Returns true if the child is within the parent's children, recursively.
*
* @param parent The ID of the parent scene item.
* @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 ecsentityid_t parent,
const ecsentityid_t child
);