Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
command_buffer.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdint>
5#include <type_traits>
6
7#include "gaia/cnt/darray_ext.h"
8#include "gaia/cnt/dbitset.h"
9#include "gaia/ecs/archetype.h"
10#include "gaia/ecs/command_buffer_fwd.h"
11#include "gaia/ecs/common.h"
12#include "gaia/ecs/component.h"
13#include "gaia/ecs/component_cache.h"
14#include "gaia/ecs/component_cache_item.h"
15#include "gaia/ecs/id.h"
16#include "gaia/ecs/world.h"
17#include "gaia/ser/ser_buffer_binary.h"
18
19namespace gaia {
20 namespace ecs {
22 void lock() {}
23 void unlock() {}
24 };
25
27 mt::SpinLock m_lock;
28
29 void lock() {
30 m_lock.lock();
31 }
32
33 void unlock() {
34 m_lock.unlock();
35 }
36 };
37
38 namespace detail {
44 template <typename AccessContext>
45 class CommandBuffer final {
46 enum class OpType : uint8_t {
47 NONE = 0,
48 ADD_ENTITY,
49 CPY_ENTITY,
50 DEL_ENTITY,
51 ADD_COMPONENT,
52 ADD_COMPONENT_DATA,
53 SET_COMPONENT,
54 DEL_COMPONENT,
55 };
56
57 struct Op {
59 OpType type;
61 uint32_t off;
63 Entity target;
65 Entity other;
66 };
67
69 ecs::World& m_world;
77 uint32_t m_nextTemp = 0;
79 bool m_needsSort = false;
80
81 bool m_haveReal = false;
82 bool m_haveTemp = false;
83 Entity m_lastRealTarget = EntityBad;
84 Entity m_lastTempTarget = EntityBad;
85
87 BinarySerializer m_data;
89 AccessContext m_acc;
90
91 public:
92 explicit CommandBuffer(World& world): m_world(world) {}
93 ~CommandBuffer() = default;
94
95 CommandBuffer(CommandBuffer&&) = delete;
96 CommandBuffer(const CommandBuffer&) = delete;
97 CommandBuffer& operator=(CommandBuffer&&) = delete;
98 CommandBuffer& operator=(const CommandBuffer&) = delete;
99
104 GAIA_NODISCARD Entity add(EntityKind kind = EntityKind::EK_Gen) {
105 core::lock_scope lock(m_acc);
106
107 Entity temp = add_temp(kind);
108 push_op({OpType::ADD_ENTITY, 0, temp, EntityBad});
109 return temp;
110 }
111
115 GAIA_NODISCARD Entity copy(Entity entityFrom) {
116 core::lock_scope lock(m_acc);
117
118 Entity temp = add_temp(entityFrom.kind());
119 push_op({OpType::CPY_ENTITY, 0, temp, entityFrom});
120 return temp;
121 }
122
126 template <typename T>
127 void add(Entity entity) {
128 verify_comp<T>();
129 core::lock_scope lock(m_acc);
130
131 // Make sure the component is registered
132 const auto& item = comp_cache_add<T>(m_world);
133
134 push_op({OpType::ADD_COMPONENT, 0, entity, item.entity});
135 }
136
140 void add(Entity entity, Entity other) {
141 core::lock_scope lock(m_acc);
142
143 push_op({OpType::ADD_COMPONENT, 0, entity, other});
144 }
145
152 template <typename T>
153 void add(Entity entity, T&& value) {
154 verify_comp<T>();
155 core::lock_scope lock(m_acc);
156
157 // Make sure the component is registered
158 const auto& item = comp_cache_add<T>(m_world);
159
160 const auto pos = m_data.tell();
161 item.save(&m_data, &value, 0, 1, 1);
162 push_op({OpType::ADD_COMPONENT_DATA, pos, entity, item.entity});
163 }
164
171 template <typename T>
172 void set(Entity entity, T&& value) {
173 verify_comp<T>();
174 core::lock_scope lock(m_acc);
175
176 // Make sure the component is registered
177 const auto& item = comp_cache(m_world).template get<T>();
178
179 const auto pos = m_data.tell();
180 item.save(&m_data, &value, 0, 1, 1);
181 push_op({OpType::SET_COMPONENT, pos, entity, item.entity});
182 }
183
186 void del(Entity entity) {
187 core::lock_scope lock(m_acc);
188
189 push_op({OpType::DEL_ENTITY, 0, entity, EntityBad});
190 }
191
197 template <typename T>
198 void del(Entity entity) {
199 verify_comp<T>();
200 core::lock_scope lock(m_acc);
201
202 // Make sure the component is registered
203 const auto& item = comp_cache(m_world).template get<T>();
204
205 push_op({OpType::DEL_COMPONENT, 0, entity, item.entity});
206 }
207
211 void del(Entity entity, Entity object) {
212 core::lock_scope lock(m_acc);
213
214 push_op({OpType::DEL_COMPONENT, 0, entity, object});
215 }
216
217 private:
219 GAIA_NODISCARD bool is_rel(OpType t) const {
220 return (uint32_t)t >= (uint32_t)OpType::ADD_COMPONENT;
221 }
222
224 GAIA_NODISCARD bool is_tmp(Entity e) const {
225 return e.data.tmp != 0;
226 }
227
229 GAIA_NODISCARD Entity resolve(Entity e) const {
230 if (!is_tmp(e))
231 return e;
232
233 const auto ti = e.id();
234 if (ti < m_temp2real.size())
235 return m_temp2real[ti];
236
237 return EntityBad;
238 }
239
242 GAIA_NODISCARD bool is_canceled_temp(uint32_t idx) const {
243 const uint32_t base = idx * 3;
244 if (base + 2 >= m_tmpFlags.size())
245 return false;
246
247 const bool destroy = m_tmpFlags.test(base);
248 const bool usedOther = m_tmpFlags.test(base + 2);
249
250 // If deleted in this batch and not referenced by others, cancel entirely
251 return destroy && !usedOther;
252 }
253
255 GAIA_NODISCARD Entity add_temp(EntityKind kind) {
256 m_temp2real.push_back(EntityBad);
257 Entity tmp(m_nextTemp++, 0, true, false, kind);
258 // Use the unused flag to mark temporary entities
259 tmp.data.tmp = 1;
260 return tmp;
261 }
262
263 GAIA_NODISCARD bool less_target(Entity a, Entity b) const {
264 const bool ta = is_tmp(a);
265 const bool tb = is_tmp(b);
266
267 // Real entities always come first in order. Temps come last.
268 if (ta != tb)
269 return !ta && tb;
270
271 // Within same domain, normal numeric order.
272 return a.id() < b.id();
273 }
274
276 void check_sort(const Op& op) {
277 if (is_tmp(op.target)) {
278 if (m_haveTemp) {
279 // Only compare temp indices against previous temp target
280 const uint32_t prev = m_lastTempTarget.id();
281 const uint32_t curr = op.target.id();
282 if (curr < prev)
283 m_needsSort = true;
284 }
285 m_lastTempTarget = op.target;
286 m_haveTemp = true;
287 } else {
288 if (m_haveReal) {
289 // Only compare real IDs against previous real target
290 if (op.target.id() < m_lastRealTarget.id())
291 m_needsSort = true;
292 }
293 m_lastRealTarget = op.target;
294 m_haveReal = true;
295 }
296 }
297
299 void push_op(Op&& op) {
300 check_sort(op);
301 m_ops.push_back(GAIA_MOV(op));
302 }
303
305 void clear() {
306 m_ops.clear();
307 m_temp2real.clear();
308 m_tmpFlags.reset();
309 m_nextTemp = 0;
310 m_data.reset();
311
312 m_needsSort = false;
313 m_haveReal = false;
314 m_haveTemp = false;
315 m_lastRealTarget = EntityBad;
316 m_lastTempTarget = EntityBad;
317 }
318
319 public:
321 void commit() {
322 core::lock_scope lock(m_acc);
323
324 if (m_ops.empty())
325 return;
326
327 GAIA_PROF_SCOPE(cmdbuf::commit);
328
329 // Build flags + allocate entities
330 if (m_nextTemp > 0) {
331 GAIA_PROF_SCOPE(cmdbuf::alloc);
332
333 // Bit layout for each temporary entity:
334 // bit 0 -> marked for destruction (DEL_ENTITY recorded)
335 // bit 1 -> used as a relation target (appeared as target in component ops)
336 // bit 2 -> used as a relation source / dependency (appeared as other in component ops)
337 m_tmpFlags.resize(m_nextTemp * 3);
338
339 // Pre-map surviving temps for ADD/CPY (avoid allocating canceled temps)
340 if (m_temp2real.size() < m_nextTemp) {
341 const auto from = m_temp2real.size();
342 m_temp2real.resize(m_nextTemp);
343 GAIA_FOR2(from, m_nextTemp) m_temp2real[i] = EntityBad;
344 }
345
346 // Build all flags
347 for (const Op& o: m_ops) {
348 // Set flag bits
349 if (is_tmp(o.target)) {
350 const uint32_t ti = o.target.id();
351
352 if (o.type == OpType::DEL_ENTITY)
353 m_tmpFlags.set((ti * 3) + 0, true);
354 else if (is_rel(o.type))
355 m_tmpFlags.set((ti * 3) + 1, true);
356 }
357
358 if (is_tmp(o.other) && o.other.id() < m_tmpFlags.size())
359 m_tmpFlags.set((o.other.id() * 3) + 2, true);
360 }
361
362 // Allocate real entities for the surviving temporaries
363 for (const Op& o: m_ops) {
364 if (!is_tmp(o.target))
365 continue;
366
367 const uint32_t ti = o.target.id();
368 if (is_canceled_temp(ti))
369 continue;
370
371 if (o.type == OpType::ADD_ENTITY) {
372 if (m_temp2real[ti] == EntityBad)
373 m_temp2real[ti] = m_world.add(o.target.kind());
374 } else if (o.type == OpType::CPY_ENTITY) {
375 if (m_temp2real[ti] == EntityBad) {
376 const Entity src = resolve(o.other);
377 if (src != EntityBad)
378 m_temp2real[ti] = m_world.copy(src);
379 }
380 }
381 }
382 }
383
384 // Sort by (target, other), reduce last-wins, apply relations.
385 // DEL_COMPONENT last per target.
386 if (m_needsSort) {
387 GAIA_PROF_SCOPE(cmdbuf::sort);
388
389 m_needsSort = false;
390 core::sort(m_ops.begin(), m_ops.end(), [](const Op& a, const Op& b) {
391 if (a.target != b.target)
392 return a.target < b.target;
393 if (a.other != b.other)
394 return a.other < b.other;
395 return false;
396 });
397 }
398
399 // Replay batched operations
400 Entity lastKey = EntityBad;
401 Entity lastResolved = EntityBad;
402 auto resolve_cached = [&](Entity e) {
403 if (e == lastKey)
404 return lastResolved;
405 lastKey = e;
406 return lastResolved = resolve(e);
407 };
408 for (uint32_t p = 0; p < m_ops.size();) {
409 GAIA_PROF_SCOPE(cmdbuf::merge);
410
411 const Entity tgtKey = m_ops[p].target;
412
413 const bool tgtIsTemp = is_tmp(tgtKey);
414 const uint32_t ti = tgtIsTemp ? tgtKey.id() : 0u;
415 const Entity tgtReal =
416 tgtIsTemp ? (ti < m_temp2real.size() ? m_temp2real[ti] : EntityBad) : resolve_cached(tgtKey);
417
418 // Range for this target
419 uint32_t q = p;
420 bool hasDelEntity = false;
421 while (q < m_ops.size() && m_ops[q].target == tgtKey) {
422 if (m_ops[q].type == OpType::DEL_ENTITY)
423 hasDelEntity = true;
424 ++q;
425 }
426
427 // Skip canceled or non-existent temporary entities
428 if (tgtReal == EntityBad) {
429 p = q;
430 continue;
431 }
432 if (tgtIsTemp && is_canceled_temp(ti)) {
433 p = q;
434 continue;
435 }
436
437 enum : uint8_t { F_ADD = 1 << 0, F_ADD_DATA = 1 << 1, F_SET = 1 << 2, F_DEL = 1 << 3 };
438
439 // Emit relation groups.
440 // Inside [p..q) range (same target), process groups by 'other'.
441 // We perform per-component reduction.
442 for (uint32_t i = p; i < q;) {
443 const Entity othKey = m_ops[i].other;
444 const Entity othReal = resolve_cached(othKey);
445
446 // Group ops with same (target, other)
447 uint32_t j = i + 1;
448 while (j < q && m_ops[j].other == othKey)
449 ++j;
450
451 if (tgtReal != EntityBad) {
452 const uint32_t groupSize = j - i;
453 // Fast path - single op
454 if (groupSize == 1) {
455 const Op& op = m_ops[i];
456 switch (op.type) {
457 case OpType::DEL_COMPONENT:
458 World::EntityBuilder(m_world, tgtReal).del(othReal);
459 break;
460 case OpType::ADD_COMPONENT:
461 World::EntityBuilder(m_world, tgtReal).add(othReal);
462 break;
463 case OpType::ADD_COMPONENT_DATA:
464 World::EntityBuilder(m_world, tgtReal).add(othReal);
465 GAIA_FALLTHROUGH;
466 case OpType::SET_COMPONENT: {
467 const auto& ec = m_world.m_recs.entities[tgtReal.id()];
468 const auto row = tgtReal.kind() == EntityKind::EK_Uni ? 0U : ec.row;
469 const auto compIdx = ec.pChunk->comp_idx(othReal);
470 auto* pComponentData = (void*)ec.pChunk->comp_ptr_mut(compIdx, 0);
471
472 // Component data
473 m_data.seek(op.off);
474 const auto& item = m_world.comp_cache().get(othReal);
475 item.load(&m_data, pComponentData, row, row + 1, ec.pChunk->capacity());
476 } break;
477 default:
478 break;
479 }
480 }
481 // Slow path: merge multiple ops
482 else {
483 uint8_t mask = 0;
484 uint32_t dataPos = 0;
485
486 for (uint32_t k = i; k < j; ++k) {
487 const Op& op = m_ops[k];
488 switch (op.type) {
489 case OpType::ADD_COMPONENT:
490 mask |= F_ADD;
491 break;
492 case OpType::ADD_COMPONENT_DATA:
493 mask |= F_ADD_DATA;
494 dataPos = op.off;
495 break;
496 case OpType::SET_COMPONENT:
497 mask |= F_SET;
498 dataPos = op.off;
499 break;
500 case OpType::DEL_COMPONENT:
501 mask |= F_DEL;
502 break;
503 default:
504 break;
505 }
506 }
507
508 const bool hasAdd = mask & F_ADD;
509 const bool hasAddData = mask & F_ADD_DATA;
510 const bool hasSet = mask & F_SET;
511 const bool hasDel = mask & F_DEL;
512
513 // 1) ADD(+DATA) + DEL = no-op
514 if (hasDel && (hasAdd || hasAddData)) {
515 }
516 // 2) DEL only
517 else if (hasDel) {
518 World::EntityBuilder(m_world, tgtReal).del(othReal);
519 }
520 // 3) ADD_WITH_DATA or ADD+SET = ADD_WITH_DATA
521 else if (hasAddData || (hasAdd && hasSet)) {
522 World::EntityBuilder(m_world, tgtReal).add(othReal);
523
524 const auto& ec = m_world.m_recs.entities[tgtReal.id()];
525 const auto row = tgtReal.kind() == EntityKind::EK_Uni ? 0U : ec.row;
526 const auto compIdx = ec.pChunk->comp_idx(othReal);
527 auto* pComponentData = (void*)ec.pChunk->comp_ptr_mut(compIdx, 0);
528
529 // Component data
530 m_data.seek(dataPos);
531 const auto& item = m_world.comp_cache().get(othReal);
532 item.load(&m_data, pComponentData, row, row + 1, ec.pChunk->capacity());
533 }
534 // 4) ADD only
535 else if (hasAdd) {
536 World::EntityBuilder(m_world, tgtReal).add(othReal);
537 }
538 // 5) SET only
539 else if (hasSet) {
540 const auto& ec = m_world.m_recs.entities[tgtReal.id()];
541 const auto row = tgtReal.kind() == EntityKind::EK_Uni ? 0U : ec.row;
542 const auto compIdx = ec.pChunk->comp_idx(othReal);
543 auto* pComponentData = (void*)ec.pChunk->comp_ptr_mut(compIdx, 0);
544
545 // Component data
546 m_data.seek(dataPos);
547 const auto& item = m_world.comp_cache().get(othReal);
548 item.load(&m_data, pComponentData, row, row + 1, ec.pChunk->capacity());
549 }
550 }
551 }
552
553 // Advance to next component group
554 i = j;
555 }
556
557 // Safely delete entity only if it was actually created
558 if (hasDelEntity)
559 m_world.del(tgtReal);
560
561 // Advance to next target group
562 p = q;
563 }
564
565 clear();
566 }
567 };
568 } // namespace detail
569
570 using CommandBufferST = detail::CommandBuffer<AccessContextST>;
571 using CommandBufferMT = detail::CommandBuffer<AccessContextMT>;
572
573 inline CommandBufferST* cmd_buffer_st_create(World& world) {
574 return new CommandBufferST(world);
575 }
576 inline void cmd_buffer_destroy(CommandBufferST& cmdBuffer) {
577 delete &cmdBuffer;
578 }
579 inline void cmd_buffer_commit(CommandBufferST& cmdBuffer) {
580 cmdBuffer.commit();
581 }
582
583 inline CommandBufferMT* cmd_buffer_mt_create(World& world) {
584 return new CommandBufferMT(world);
585 }
586 inline void cmd_buffer_destroy(CommandBufferMT& cmdBuffer) {
587 delete &cmdBuffer;
588 }
589 inline void cmd_buffer_commit(CommandBufferMT& cmdBuffer) {
590 cmdBuffer.commit();
591 }
592 } // namespace ecs
593} // namespace gaia
Array of elements of type.
Definition darray_ext_impl.h:28
Definition dbitset.h:15
void reset()
Unsets all bits.
Definition dbitset.h:347
GAIA_NODISCARD constexpr uint32_t size() const
Returns the number of bits the dbitset holds.
Definition dbitset.h:419
GAIA_NODISCARD bool test(uint32_t pos) const
Returns the value of the bit at the position.
Definition dbitset.h:359
void set()
Sets all bits.
Definition dbitset.h:265
Definition ser_binary.h:10
GAIA_NODISCARD const ComponentCacheItem & get(detail::ComponentDescId compDescId) const noexcept
Returns the component cache item given the compDescId.
Definition component_cache.h:156
Definition world.h:48
void del(Entity entity)
Removes an entity along with all data associated with it.
Definition world.h:1406
GAIA_NODISCARD Entity copy(Entity srcEntity)
Creates a new entity by cloning an already existing one.
Definition world.h:1228
GAIA_NODISCARD Entity add(EntityKind kind=EntityKind::EK_Gen)
Creates a new empty entity.
Definition world.h:1079
Buffer for deferred execution of some operations on entities.
Definition command_buffer.h:45
void add(Entity entity, Entity other)
Requests an entity other to be added to entity entity.
Definition command_buffer.h:140
void add(Entity entity)
Requests a component T to be added to entity.
Definition command_buffer.h:127
void del(Entity entity)
Requests an existing entity to be removed.
Definition command_buffer.h:186
void add(Entity entity, T &&value)
Requests a component T to be added to entity. Also sets its value.
Definition command_buffer.h:153
void commit()
Commits all queued changes.
Definition command_buffer.h:321
GAIA_NODISCARD Entity add(EntityKind kind=EntityKind::EK_Gen)
Requests a new entity to be created.
Definition command_buffer.h:104
GAIA_NODISCARD Entity copy(Entity entityFrom)
Requests a new entity to be created by cloning an already existing entity.
Definition command_buffer.h:115
void del(Entity entity)
Requests removal of component T from entity.
Definition command_buffer.h:198
void set(Entity entity, T &&value)
Requests component data to be set to given values for a given entity.
Definition command_buffer.h:172
void del(Entity entity, Entity object)
Requests removal of entity object from entity entity.
Definition command_buffer.h:211
Definition spinlock.h:8
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition utility.h:123
Definition command_buffer.h:26
Definition command_buffer.h:21
cnt::ilist< EntityContainer, Entity > entities
Implicit list of entities. Used for look-ups only when searching for entities in chunks + data valida...
Definition entity_container.h:157
IdentifierData tmp
0-real entity, 1-temporary entity
Definition id.h:247
Definition id.h:225
Definition world.h:238
EntityBuilder & add(Entity entity)
Prepares an archetype movement by following the "add" edge of the current archetype.
Definition world.h:390
EntityBuilder & del(Entity entity)
Prepares an archetype movement by following the "del" edge of the current archetype.
Definition world.h:463