Skip to content

Commit 24de46a

Browse files
committed
refactor(ecs-core): modify ecs structure for better readability and maintanability
1 parent 3c41aa6 commit 24de46a

22 files changed

Lines changed: 808 additions & 472 deletions

engine/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ set(COMMON_SOURCES
5959
engine/src/core/scene/SceneManager.cpp
6060
engine/src/ecs/Entity.cpp
6161
engine/src/ecs/Components.cpp
62+
engine/src/ecs/GroupManager.cpp
6263
engine/src/ecs/ComponentArray.cpp
6364
engine/src/ecs/Coordinator.cpp
6465
engine/src/ecs/System.cpp

engine/src/ecs/ComponentArray.cpp

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ namespace nexo::ecs {
3939

4040
void TypeErasedComponentArray::insertRaw(Entity entity, const void* componentData)
4141
{
42-
if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity);
43-
4442
ensureSparseCapacity(entity);
4543

4644
if (hasComponent(entity)) {
@@ -66,8 +64,6 @@ namespace nexo::ecs {
6664

6765
void TypeErasedComponentArray::insertRawWithConstructor(Entity entity, void (*constructor)(void* memoryDst))
6866
{
69-
if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity);
70-
7167
ensureSparseCapacity(entity);
7268

7369
if (hasComponent(entity)) {
@@ -76,17 +72,25 @@ namespace nexo::ecs {
7672
}
7773

7874
const size_t newIndex = m_size;
79-
m_sparse[entity] = newIndex;
80-
m_dense.push_back(entity);
8175

82-
// Resize component data vector if needed
76+
// Resize component data vector first, before committing bookkeeping
8377
const size_t requiredSize = (m_size + 1) * m_componentSize;
8478
if (m_componentData.size() < requiredSize) {
8579
m_componentData.resize(requiredSize);
8680
}
8781

88-
// Call the constructor to initialize the component in place
89-
constructor(m_componentData.data());
82+
// Call the constructor at the correct offset
83+
try {
84+
constructor(m_componentData.data() + newIndex * m_componentSize);
85+
} catch (...) {
86+
// Shrink buffer back on failure, bookkeeping was not yet committed
87+
m_componentData.resize(m_size * m_componentSize);
88+
throw;
89+
}
90+
91+
// Commit bookkeeping only after successful construction
92+
m_sparse[entity] = newIndex;
93+
m_dense.push_back(entity);
9094
++m_size;
9195
}
9296

@@ -244,11 +248,14 @@ namespace nexo::ecs {
244248
std::byte* data1 = m_componentData.data() + index1 * m_componentSize;
245249
std::byte* data2 = m_componentData.data() + index2 * m_componentSize;
246250

247-
// Use a temporary buffer for swapping
248-
std::vector<std::byte> temp(m_componentSize);
249-
std::memcpy(temp.data(), data1, m_componentSize);
251+
// Lazily resize the reusable swap buffer on first use
252+
if (m_swapBuffer.size() < m_componentSize) {
253+
m_swapBuffer.resize(m_componentSize);
254+
}
255+
256+
std::memcpy(m_swapBuffer.data(), data1, m_componentSize);
250257
std::memcpy(data1, data2, m_componentSize);
251-
std::memcpy(data2, temp.data(), m_componentSize);
258+
std::memcpy(data2, m_swapBuffer.data(), m_componentSize);
252259
}
253260

254261
void TypeErasedComponentArray::shrinkIfNeeded()

engine/src/ecs/ComponentArray.hpp

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,7 @@ namespace nexo::ecs {
100100
*
101101
* @param entity The entity to add the component to
102102
* @param componentData Pointer to the raw component data
103-
* @throws OutOfRange if entity ID exceeds MAX_ENTITIES
104-
*
105-
* @pre The entity must be a valid entity ID
103+
* @pre The entity must be a valid entity ID (validated by EntityManager)
106104
* @pre componentData must point to valid memory of component's size
107105
*/
108106
virtual void insertRaw(Entity entity, const void* componentData) = 0;
@@ -112,9 +110,7 @@ namespace nexo::ecs {
112110
*
113111
* @param entity The entity to add the component to
114112
* @param constructor Pointer to the constructor function or functor
115-
* @throws OutOfRange if entity ID exceeds MAX_ENTITIES
116-
*
117-
* @pre The entity must be a valid entity ID
113+
* @pre The entity must be a valid entity ID (validated by EntityManager)
118114
*/
119115
virtual void insertRawWithConstructor(Entity entity, void (*constructor)(void* memoryDst)) = 0;
120116

@@ -154,8 +150,8 @@ namespace nexo::ecs {
154150
* @tparam T The component type stored in this array
155151
* @tparam capacity Initial capacity for the sparse array
156152
*
157-
* @note This class is not thread-safe. Access should be synchronized externally when
158-
* used in multithreaded contexts.
153+
* @warning Not thread-safe. The ECS assumes a single-threaded execution model.
154+
* All component array operations must be performed from the main ECS thread.
159155
*/
160156
template<typename T, size_t capacity = 1024>
161157
requires(capacity >= 1)
@@ -211,14 +207,11 @@ namespace nexo::ecs {
211207
*
212208
* @param entity The entity to add the component to
213209
* @param component The component instance to add
214-
* @throws OutOfRange if entity ID exceeds MAX_ENTITIES
215210
*
216-
* @pre The entity must be a valid entity ID
211+
* @pre The entity must be a valid entity ID (validated by EntityManager)
217212
*/
218213
void insert(Entity entity, T component)
219214
{
220-
if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity);
221-
222215
// Ensure m_sparse can hold this entity index.
223216
ensureSparseCapacity(entity);
224217

@@ -240,15 +233,11 @@ namespace nexo::ecs {
240233
*
241234
* @param entity The entity to add the component to
242235
* @param componentData Pointer to the raw component data
243-
* @throws OutOfRange if entity ID exceeds MAX_ENTITIES
244-
*
245-
* @pre The entity must be a valid entity ID
236+
* @pre The entity must be a valid entity ID (validated by EntityManager)
246237
* @pre componentData must point to valid memory of component's size
247238
*/
248239
void insertRaw(Entity entity, const void* componentData) override
249240
{
250-
if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity);
251-
252241
ensureSparseCapacity(entity);
253242

254243
if (hasComponent(entity)) {
@@ -278,14 +267,10 @@ namespace nexo::ecs {
278267
*
279268
* @param entity The entity to add the component to
280269
* @param constructor Pointer to the constructor function or functor
281-
* @throws OutOfRange if entity ID exceeds MAX_ENTITIES
282-
*
283-
* @pre The entity must be a valid entity ID
270+
* @pre The entity must be a valid entity ID (validated by EntityManager)
284271
*/
285272
void insertRawWithConstructor(Entity entity, void (*constructor)(void* memoryDst))
286273
{
287-
if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity);
288-
289274
ensureSparseCapacity(entity);
290275

291276
if (hasComponent(entity)) {
@@ -294,12 +279,18 @@ namespace nexo::ecs {
294279
}
295280

296281
const size_t newIndex = m_size;
297-
m_sparse[entity] = newIndex;
298-
m_dense.push_back(entity);
299282

300-
// allocate new component in the array
283+
// Allocate first, then construct, then commit bookkeeping
301284
m_componentArray.emplace_back();
302-
constructor(&m_componentArray[newIndex]);
285+
try {
286+
constructor(&m_componentArray[newIndex]);
287+
} catch (...) {
288+
m_componentArray.pop_back();
289+
throw;
290+
}
291+
292+
m_sparse[entity] = newIndex;
293+
m_dense.push_back(entity);
303294
++m_size;
304295
}
305296

@@ -668,6 +659,9 @@ namespace nexo::ecs {
668659
* This class allows you to create component arrays at runtime without knowing
669660
* the component type at compile time. You only need to specify the size of
670661
* each component.
662+
*
663+
* @warning Not thread-safe. The ECS assumes a single-threaded execution model.
664+
* All component array operations must be performed from the main ECS thread.
671665
*/
672666
class alignas(64) TypeErasedComponentArray final : public IComponentArray {
673667
public:
@@ -690,9 +684,7 @@ namespace nexo::ecs {
690684
*
691685
* @param entity The entity to add the component to
692686
* @param componentData Pointer to the raw component data
693-
* @throws OutOfRange if entity ID exceeds MAX_ENTITIES
694-
*
695-
* @pre The entity must be a valid entity ID
687+
* @pre The entity must be a valid entity ID (validated by EntityManager)
696688
* @pre componentData must point to valid memory of component's size
697689
*/
698690
void insertRaw(Entity entity, const void* componentData) override;
@@ -702,9 +694,7 @@ namespace nexo::ecs {
702694
*
703695
* @param entity The entity to add the component to
704696
* @param constructor Pointer to the constructor function or functor
705-
* @throws OutOfRange if entity ID exceeds MAX_ENTITIES
706-
*
707-
* @pre The entity must be a valid entity ID
697+
* @pre The entity must be a valid entity ID (validated by EntityManager)
708698
*/
709699
void insertRawWithConstructor(Entity entity, void (*constructor)(void* memoryDst)) override;
710700

@@ -816,6 +806,8 @@ namespace nexo::ecs {
816806
std::vector<size_t> m_sparse;
817807
// Dense storage for entity IDs
818808
std::vector<Entity> m_dense;
809+
// Reusable buffer for swapComponents() to avoid per-call heap allocation
810+
std::vector<std::byte> m_swapBuffer;
819811
// Size of each component in bytes
820812
size_t m_componentSize;
821813
// Initial capacity

engine/src/ecs/Components.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ namespace nexo::ecs {
2222

2323
void ComponentManager::entityDestroyed(const Entity entity, const Signature &entitySignature)
2424
{
25-
for (const auto &group : m_groupRegistry | std::views::values) {
26-
if ((entitySignature & group->allSignature()) == group->allSignature()) group->removeFromGroup(entity);
27-
}
25+
m_groupManager.onEntityDestroyed(entity, entitySignature);
2826
for (const auto &componentArray : m_componentArrays) {
2927
if (componentArray) componentArray->entityDestroyed(entity);
3028
}

0 commit comments

Comments
 (0)