Gaia-ECS v1.0.0
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 {
24 void lock() {}
26 void unlock() {}
27 };
28
33
35 void lock() {
36 m_lock.lock();
37 }
38
40 void unlock() {
41 m_lock.unlock();
42 }
43 };
44
45 namespace detail {
51 template <typename AccessContext>
52 class CommandBuffer final {
53 enum class OpType : uint8_t {
54 NONE = 0,
55 ADD_ENTITY,
56 CPY_ENTITY,
57 DEL_ENTITY,
58 ADD_COMPONENT,
59 ADD_COMPONENT_DATA,
60 SET_COMPONENT,
61 DEL_COMPONENT,
62 };
63
64 struct Op {
66 OpType type;
68 uint32_t off;
70 Entity target;
72 Entity other;
73 };
74
76 ecs::World& m_world;
84 uint32_t m_nextTemp = 0;
86 bool m_needsSort = false;
87
88 bool m_haveReal = false;
89 bool m_haveTemp = false;
90 Entity m_lastRealTarget = EntityBad;
91 Entity m_lastTempTarget = EntityBad;
92
94 ser::bin_stream m_data;
96 AccessContext m_acc;
97
98 public:
99 explicit CommandBuffer(World& world): m_world(world) {}
100 ~CommandBuffer() = default;
101
102 CommandBuffer(CommandBuffer&&) = delete;
103 CommandBuffer(const CommandBuffer&) = delete;
104 CommandBuffer& operator=(CommandBuffer&&) = delete;
105 CommandBuffer& operator=(const CommandBuffer&) = delete;
106
111 GAIA_NODISCARD Entity add(EntityKind kind = EntityKind::EK_Gen) {
112 core::lock_scope lock(m_acc);
113
114 Entity temp = add_temp(kind);
115 push_op({OpType::ADD_ENTITY, 0, temp, EntityBad});
116 return temp;
117 }
118
122 GAIA_NODISCARD Entity copy(Entity entityFrom) {
123 core::lock_scope lock(m_acc);
124
125 Entity temp = add_temp(entityFrom.kind());
126 push_op({OpType::CPY_ENTITY, 0, temp, entityFrom});
127 return temp;
128 }
129
135 template <typename T>
136 void add(Entity entity) {
137 verify_comp<T>();
138 core::lock_scope lock(m_acc);
139
140 // Make sure the component is registered
141 const auto& item = comp_cache_add<T>(m_world);
142
143 push_op({OpType::ADD_COMPONENT, 0, entity, item.entity});
144 }
145
149 void add(Entity entity, Entity other) {
150 core::lock_scope lock(m_acc);
151
152 push_op({OpType::ADD_COMPONENT, 0, entity, other});
153 }
154
158 void add(Entity entity, const Pair& pair) {
159 core::lock_scope lock(m_acc);
160
161 push_op({OpType::ADD_COMPONENT, 0, entity, (Entity)pair});
162 }
163
171 template <typename T, std::enable_if_t<!is_pair<std::remove_cv_t<std::remove_reference_t<T>>>::value, int> = 0>
172 void add(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_add<T>(m_world);
178
179 const auto pos = m_data.tell();
180 auto serializer = ser::make_serializer(m_data);
181 item.save(serializer, &value, 0, 1, 1);
182 push_op({OpType::ADD_COMPONENT_DATA, pos, entity, item.entity});
183 }
184
191 template <typename T>
192 void set(Entity entity, T&& value) {
193 verify_comp<T>();
194 core::lock_scope lock(m_acc);
195
196 // Make sure the component is registered
197 const auto& item = comp_cache(m_world).template get<T>();
198
199 const auto pos = m_data.tell();
200 auto serializer = ser::make_serializer(m_data);
201 item.save(serializer, &value, 0, 1, 1);
202 push_op({OpType::SET_COMPONENT, pos, entity, item.entity});
203 }
204
207 void del(Entity entity) {
208 core::lock_scope lock(m_acc);
209
210 push_op({OpType::DEL_ENTITY, 0, entity, EntityBad});
211 }
212
218 template <typename T>
219 void del(Entity entity) {
220 verify_comp<T>();
221 core::lock_scope lock(m_acc);
222
223 // Make sure the component is registered
224 const auto& item = comp_cache(m_world).template get<T>();
225
226 push_op({OpType::DEL_COMPONENT, 0, entity, item.entity});
227 }
228
232 void del(Entity entity, Entity object) {
233 core::lock_scope lock(m_acc);
234
235 push_op({OpType::DEL_COMPONENT, 0, entity, object});
236 }
237
241 void del(Entity entity, const Pair& pair) {
242 core::lock_scope lock(m_acc);
243
244 push_op({OpType::DEL_COMPONENT, 0, entity, (Entity)pair});
245 }
246
247 private:
249 GAIA_NODISCARD bool is_rel(OpType t) const {
250 return (uint32_t)t >= (uint32_t)OpType::ADD_COMPONENT;
251 }
252
254 GAIA_NODISCARD bool is_tmp(Entity e) const {
255 return e.data.tmp != 0;
256 }
257
259 GAIA_NODISCARD Entity resolve(Entity e) const {
260 if (!is_tmp(e))
261 return e;
262
263 const auto ti = e.id();
264 if (ti < m_temp2real.size())
265 return m_temp2real[ti];
266
267 return EntityBad;
268 }
269
271 GAIA_NODISCARD Pair decode_pair(Entity pair) const {
272 GAIA_ASSERT(pair.pair());
273 return Pair(m_world.get(pair.id()), m_world.get(pair.gen()));
274 }
275
277 void replay_add(Entity target, Entity object) {
278 if (object.pair())
279 World::EntityBuilder(m_world, target).add(decode_pair(object));
280 else
281 World::EntityBuilder(m_world, target).add(object);
282 }
283
285 void replay_del(Entity target, Entity object) {
286 if (object.pair())
287 World::EntityBuilder(m_world, target).del(decode_pair(object));
288 else
289 World::EntityBuilder(m_world, target).del(object);
290 }
291
294 GAIA_NODISCARD bool is_canceled_temp(uint32_t idx) const {
295 const uint32_t base = idx * 3;
296 if (base + 2 >= m_tmpFlags.size())
297 return false;
298
299 const bool destroy = m_tmpFlags.test(base);
300 const bool usedOther = m_tmpFlags.test(base + 2);
301
302 // If deleted in this batch and not referenced by others, cancel entirely
303 return destroy && !usedOther;
304 }
305
307 GAIA_NODISCARD Entity add_temp(EntityKind kind) {
308 m_temp2real.push_back(EntityBad);
309 Entity tmp(m_nextTemp++, 0, true, false, kind);
310 // Use the unused flag to mark temporary entities
311 tmp.data.tmp = 1;
312 return tmp;
313 }
314
315 GAIA_NODISCARD bool less_target(Entity a, Entity b) const {
316 const bool ta = is_tmp(a);
317 const bool tb = is_tmp(b);
318
319 // Real entities always come first in order. Temps come last.
320 if (ta != tb)
321 return !ta && tb;
322
323 // Within same domain, normal numeric order.
324 return a.id() < b.id();
325 }
326
328 void check_sort(const Op& op) {
329 if (is_tmp(op.target)) {
330 if (m_haveTemp) {
331 // Only compare temp indices against previous temp target
332 const uint32_t prev = m_lastTempTarget.id();
333 const uint32_t curr = op.target.id();
334 if (curr < prev)
335 m_needsSort = true;
336 }
337 m_lastTempTarget = op.target;
338 m_haveTemp = true;
339 } else {
340 if (m_haveReal) {
341 // Only compare real IDs against previous real target
342 if (op.target.id() < m_lastRealTarget.id())
343 m_needsSort = true;
344 }
345 m_lastRealTarget = op.target;
346 m_haveReal = true;
347 }
348 }
349
351 void push_op(Op&& op) {
352 check_sort(op);
353 m_ops.push_back(GAIA_MOV(op));
354 }
355
357 void clear() {
358 m_ops.clear();
359 m_temp2real.clear();
360 m_tmpFlags.reset();
361 m_nextTemp = 0;
362 m_data.reset();
363
364 m_needsSort = false;
365 m_haveReal = false;
366 m_haveTemp = false;
367 m_lastRealTarget = EntityBad;
368 m_lastTempTarget = EntityBad;
369 }
370
371 public:
373 void commit() {
374 core::lock_scope lock(m_acc);
375
376 if (m_ops.empty())
377 return;
378
379 GAIA_PROF_SCOPE(cmdbuf::commit);
380
381 // Build flags + allocate entities
382 if (m_nextTemp > 0) {
383 GAIA_PROF_SCOPE(cmdbuf::alloc);
384
385 // Bit layout for each temporary entity:
386 // bit 0 -> marked for destruction (DEL_ENTITY recorded)
387 // bit 1 -> used as a relation target (appeared as target in component ops)
388 // bit 2 -> used as a relation source / dependency (appeared as other in component ops)
389 m_tmpFlags.resize(m_nextTemp * 3);
390
391 // Pre-map surviving temps for ADD/CPY (avoid allocating canceled temps)
392 if (m_temp2real.size() < m_nextTemp) {
393 const auto from = m_temp2real.size();
394 m_temp2real.resize(m_nextTemp);
395 GAIA_FOR2(from, m_nextTemp) m_temp2real[i] = EntityBad;
396 }
397
398 // Build all flags
399 for (const Op& o: m_ops) {
400 // Set flag bits
401 if (is_tmp(o.target)) {
402 const uint32_t ti = o.target.id();
403
404 if (o.type == OpType::DEL_ENTITY)
405 m_tmpFlags.set((ti * 3) + 0, true);
406 else if (is_rel(o.type))
407 m_tmpFlags.set((ti * 3) + 1, true);
408 }
409
410 if (is_tmp(o.other) && o.other.id() < m_tmpFlags.size())
411 m_tmpFlags.set((o.other.id() * 3) + 2, true);
412 }
413
414 // Allocate real entities for the surviving temporaries
415 for (const Op& o: m_ops) {
416 if (!is_tmp(o.target))
417 continue;
418
419 const uint32_t ti = o.target.id();
420 if (is_canceled_temp(ti))
421 continue;
422
423 if (o.type == OpType::ADD_ENTITY) {
424 if (m_temp2real[ti] == EntityBad)
425 m_temp2real[ti] = m_world.add(o.target.kind());
426 } else if (o.type == OpType::CPY_ENTITY) {
427 if (m_temp2real[ti] == EntityBad) {
428 const Entity src = resolve(o.other);
429 if (src != EntityBad)
430 m_temp2real[ti] = m_world.copy(src);
431 }
432 }
433 }
434 }
435
436 // Sort by (target, other), reduce last-wins, apply relations.
437 // DEL_COMPONENT last per target.
438 if (m_needsSort) {
439 GAIA_PROF_SCOPE(cmdbuf::sort);
440
441 m_needsSort = false;
442 core::sort(m_ops.begin(), m_ops.end(), [](const Op& a, const Op& b) {
443 if (a.target != b.target)
444 return a.target < b.target;
445 if (a.other != b.other)
446 return a.other < b.other;
447 return false;
448 });
449 }
450
451 // Replay batched operations
452 Entity lastKey = EntityBad;
453 Entity lastResolved = EntityBad;
454 auto resolve_cached = [&](Entity e) {
455 if (e == lastKey)
456 return lastResolved;
457 lastKey = e;
458 return lastResolved = resolve(e);
459 };
460
461 {
462 GAIA_PROF_SCOPE(cmdbuf::merges);
463 for (uint32_t p = 0; p < m_ops.size();) {
464 GAIA_PROF_SCOPE(cmdbuf::merge);
465
466 const Entity tgtKey = m_ops[p].target;
467
468 const bool tgtIsTemp = is_tmp(tgtKey);
469 const uint32_t ti = tgtIsTemp ? tgtKey.id() : 0u;
470 const Entity tgtReal =
471 tgtIsTemp ? (ti < m_temp2real.size() ? m_temp2real[ti] : EntityBad) : resolve_cached(tgtKey);
472
473 // Range for this target
474 uint32_t q = p;
475 bool hasDelEntity = false;
476 while (q < m_ops.size() && m_ops[q].target == tgtKey) {
477 if (m_ops[q].type == OpType::DEL_ENTITY)
478 hasDelEntity = true;
479 ++q;
480 }
481
482 // Skip canceled or non-existent temporary entities
483 if (tgtReal == EntityBad) {
484 p = q;
485 continue;
486 }
487 if (tgtIsTemp && is_canceled_temp(ti)) {
488 p = q;
489 continue;
490 }
491
492 enum : uint8_t { F_ADD = 1 << 0, F_ADD_DATA = 1 << 1, F_SET = 1 << 2, F_DEL = 1 << 3 };
493
494 // Emit relation groups.
495 // Inside [p..q) range (same target), process groups by 'other'.
496 // We perform per-component reduction.
497 for (uint32_t i = p; i < q;) {
498 const Entity othKey = m_ops[i].other;
499 const Entity othReal = resolve_cached(othKey);
500
501 // Group ops with same (target, other)
502 uint32_t j = i + 1;
503 while (j < q && m_ops[j].other == othKey)
504 ++j;
505
506 if (tgtReal != EntityBad) {
507 const uint32_t groupSize = j - i;
508 // Fast path - single op
509 if (groupSize == 1) {
510 const Op& op = m_ops[i];
511 switch (op.type) {
512 case OpType::DEL_COMPONENT:
513 replay_del(tgtReal, othReal);
514 break;
515 case OpType::ADD_COMPONENT:
516 replay_add(tgtReal, othReal);
517 break;
518 case OpType::ADD_COMPONENT_DATA:
519 replay_add(tgtReal, othReal);
520 GAIA_FALLTHROUGH;
521 case OpType::SET_COMPONENT: {
522 const auto& ec = m_world.m_recs.entities[tgtReal.id()];
523 const auto row = tgtReal.kind() == EntityKind::EK_Uni ? 0U : ec.row;
524 const auto compIdx = ec.pChunk->comp_idx(othReal);
525 auto* pComponentData = (void*)ec.pChunk->comp_ptr_mut(compIdx, 0);
526
527 // Component data
528 auto serializer = ser::make_serializer(m_data);
529 serializer.seek(op.off);
530 const auto& item = m_world.comp_cache().get(othReal);
531 item.load(serializer, pComponentData, row, row + 1, ec.pChunk->capacity());
532 } break;
533 default:
534 break;
535 }
536 }
537 // Slow path: merge multiple ops
538 else {
539 uint8_t mask = 0;
540 uint32_t dataPos = 0;
541
542 for (uint32_t k = i; k < j; ++k) {
543 const Op& op = m_ops[k];
544 switch (op.type) {
545 case OpType::ADD_COMPONENT:
546 mask |= F_ADD;
547 break;
548 case OpType::ADD_COMPONENT_DATA:
549 mask |= F_ADD_DATA;
550 dataPos = op.off;
551 break;
552 case OpType::SET_COMPONENT:
553 mask |= F_SET;
554 dataPos = op.off;
555 break;
556 case OpType::DEL_COMPONENT:
557 mask |= F_DEL;
558 break;
559 default:
560 break;
561 }
562 }
563
564 const bool hasAdd = mask & F_ADD;
565 const bool hasAddData = mask & F_ADD_DATA;
566 const bool hasSet = mask & F_SET;
567 const bool hasDel = mask & F_DEL;
568
569 // 1) ADD(+DATA) + DEL = no-op
570 if (hasDel && (hasAdd || hasAddData)) {
571 }
572 // 2) DEL only
573 else if (hasDel) {
574 replay_del(tgtReal, othReal);
575 }
576 // 3) ADD_WITH_DATA or ADD+SET = ADD_WITH_DATA
577 else if (hasAddData || (hasAdd && hasSet)) {
578 replay_add(tgtReal, othReal);
579
580 const auto& ec = m_world.m_recs.entities[tgtReal.id()];
581 const auto row = tgtReal.kind() == EntityKind::EK_Uni ? 0U : ec.row;
582 const auto compIdx = ec.pChunk->comp_idx(othReal);
583 auto* pComponentData = (void*)ec.pChunk->comp_ptr_mut(compIdx, 0);
584
585 // Component data
586 auto serializer = ser::make_serializer(m_data);
587 serializer.seek(dataPos);
588 const auto& item = m_world.comp_cache().get(othReal);
589 item.load(serializer, pComponentData, row, row + 1, ec.pChunk->capacity());
590 }
591 // 4) ADD only
592 else if (hasAdd) {
593 replay_add(tgtReal, othReal);
594 }
595 // 5) SET only
596 else if (hasSet) {
597 const auto& ec = m_world.m_recs.entities[tgtReal.id()];
598 const auto row = tgtReal.kind() == EntityKind::EK_Uni ? 0U : ec.row;
599 const auto compIdx = ec.pChunk->comp_idx(othReal);
600 auto* pComponentData = (void*)ec.pChunk->comp_ptr_mut(compIdx, 0);
601
602 // Component data
603 auto serializer = ser::make_serializer(m_data);
604 serializer.seek(dataPos);
605 const auto& item = m_world.comp_cache().get(othReal);
606 item.load(serializer, pComponentData, row, row + 1, ec.pChunk->capacity());
607 }
608 }
609 }
610
611 // Advance to next component group
612 i = j;
613 }
614
615 // Safely delete entity only if it was actually created
616 if (hasDelEntity)
617 m_world.del(tgtReal);
618
619 // Advance to next target group
620 p = q;
621 }
622 }
623
624 clear();
625 }
626 };
627 } // namespace detail
628
629 using CommandBufferST = detail::CommandBuffer<AccessContextST>;
630 using CommandBufferMT = detail::CommandBuffer<AccessContextMT>;
631
632 inline CommandBufferST* cmd_buffer_st_create(World& world) {
633 return new CommandBufferST(world);
634 }
635 inline void cmd_buffer_destroy(CommandBufferST& cmdBuffer) {
636 delete &cmdBuffer;
637 }
638 inline void cmd_buffer_commit(CommandBufferST& cmdBuffer) {
639 cmdBuffer.commit();
640 }
641
642 inline CommandBufferMT* cmd_buffer_mt_create(World& world) {
643 return new CommandBufferMT(world);
644 }
645 inline void cmd_buffer_destroy(CommandBufferMT& cmdBuffer) {
646 delete &cmdBuffer;
647 }
648 inline void cmd_buffer_commit(CommandBufferMT& cmdBuffer) {
649 cmdBuffer.commit();
650 }
651 } // namespace ecs
652} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:27
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_impl.h:504
void clear() noexcept
Removes all elements.
Definition darray_impl.h:449
GAIA_NODISCARD auto begin() noexcept
Returns an iterator to the first element.
Definition darray_impl.h:556
void resize(size_type count)
Changes the number of elements.
Definition darray_impl.h:240
GAIA_NODISCARD bool empty() const noexcept
Checks whether the container has no elements.
Definition darray_impl.h:510
void push_back(const T &arg)
Appends an element.
Definition darray_impl.h:309
GAIA_NODISCARD auto end() noexcept
Returns an iterator one past the last element.
Definition darray_impl.h:592
Owns entities, components, archetypes, queries, observers, and systems.
Definition world.h:80
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:3321
void del(Entity entity)
Removes an entity along with all data associated with it.
Definition world.h:5791
GAIA_NODISCARD Entity get(EntityId id) const
Returns the entity located at the index id.
Definition world.h:3767
GAIA_NODISCARD Entity copy(Entity srcEntity)
Creates a new entity by cloning an already existing one. Does not trigger observers.
Definition world.h:4146
GAIA_NODISCARD Entity add(EntityKind kind=EntityKind::EK_Gen)
Creates a new empty entity.
Definition world.h:3817
Buffer for deferred execution of some operations on entities.
Definition command_buffer.h:52
void add(Entity entity, Entity other)
Requests an entity other to be added to entity entity.
Definition command_buffer.h:149
void add(Entity entity)
Requests a component T to be added to entity.
Definition command_buffer.h:136
void del(Entity entity)
Requests an existing entity to be removed.
Definition command_buffer.h:207
void add(Entity entity, const Pair &pair)
Requests a relationship pair to be added to entity entity.
Definition command_buffer.h:158
void commit()
Commits all queued changes.
Definition command_buffer.h:373
GAIA_NODISCARD Entity add(EntityKind kind=EntityKind::EK_Gen)
Requests a new entity to be created.
Definition command_buffer.h:111
void add(Entity entity, T &&value)
Requests a component T to be added to entity. Also sets its value.
Definition command_buffer.h:172
GAIA_NODISCARD Entity copy(Entity entityFrom)
Requests a new entity to be created by cloning an already existing entity.
Definition command_buffer.h:122
void del(Entity entity)
Requests removal of component T from entity.
Definition command_buffer.h:219
void set(Entity entity, T &&value)
Requests component data to be set to given values for a given entity.
Definition command_buffer.h:192
void del(Entity entity, Entity object)
Requests removal of entity object from entity entity.
Definition command_buffer.h:232
void del(Entity entity, const Pair &pair)
Requests removal of a relationship pair from entity entity.
Definition command_buffer.h:241
Wrapper for two Entities forming a relationship pair.
Definition id.h:614
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:262
Non-recursive spin lock backed by an atomic flag.
Definition spinlock.h:9
void lock()
Spins until the lock is acquired.
Definition spinlock.h:26
void unlock()
Releases the lock.
Definition spinlock.h:39
Default in-memory binary backend used by ECS world/runtime serialization. Provides aligned raw read/w...
Definition ser_binary.h:12
uint32_t tell() const
Returns current stream cursor position in bytes.
Definition ser_binary.h:48
void reset()
Clears buffered data and resets stream position.
Definition ser_binary.h:43
RAII helper that calls lock() on construction and unlock() on destruction.
Definition utility.h:188
Multi-threaded command-buffer access guard backed by a spin lock.
Definition command_buffer.h:30
mt::SpinLock m_lock
Spin lock serializing command-buffer access across worker threads.
Definition command_buffer.h:32
void lock()
Acquires the access guard.
Definition command_buffer.h:35
void unlock()
Releases the access guard.
Definition command_buffer.h:40
Single-threaded command-buffer access guard. Locking is a no-op.
Definition command_buffer.h:22
void unlock()
Releases the access guard.
Definition command_buffer.h:26
void lock()
Acquires the access guard.
Definition command_buffer.h:24
IdentifierData tmp
0-real entity, 1-temporary entity
Definition id.h:320
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:296
InternalData data
Structured view of the packed value.
Definition id.h:328
GAIA_NODISCARD constexpr auto kind() const noexcept
Entity kind of this id.
Definition id.h:389
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:359
GAIA_NODISCARD constexpr bool entity() const noexcept
Whether this id refers to an entity.
Definition id.h:371