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/cnt/sarray_ext.h"
10#include "gaia/ecs/archetype.h"
11#include "gaia/ecs/command_buffer_fwd.h"
12#include "gaia/ecs/common.h"
13#include "gaia/ecs/component.h"
14#include "gaia/ecs/component_cache.h"
15#include "gaia/ecs/component_cache_item.h"
16#include "gaia/ecs/id.h"
17#include "gaia/ecs/world.h"
18#include "gaia/ser/ser_buffer_binary.h"
19
20namespace gaia {
21 namespace ecs {
25 void lock() {}
27 void unlock() {}
28 };
29
34
36 void lock() {
37 m_lock.lock();
38 }
39
41 void unlock() {
42 m_lock.unlock();
43 }
44 };
45
46 namespace detail {
55 template <typename AccessContext, bool Optimize>
56 class CommandBuffer final {
58 enum class OpType : uint8_t {
59 NONE = 0,
60 ADD_ENTITY,
61 CPY_ENTITY,
62 INSTANTIATE_ENTITY,
63 DEL_ENTITY,
64 ADD_COMPONENT,
65 ADD_COMPONENT_DATA,
66 SET_COMPONENT,
67 DEL_COMPONENT,
68 };
69
71 enum : uint8_t {
72 F_ADD = 1 << 0, //
73 F_ADD_DATA = 1 << 1, //
74 F_SET = 1 << 2, //
75 F_DEL = 1 << 3 //
76 };
77
78 struct Op {
80 OpType type;
82 uint32_t off;
84 Entity target;
87 Entity other;
90 Entity pairTarget = EntityBad;
92 uint32_t order = 0;
93 };
94
96 ecs::World& m_world;
104 uint32_t m_nextTemp = 0;
106 bool m_needsSort = false;
107
109 ser::bin_stream m_data;
111 AccessContext m_acc;
113
114 public:
117 explicit CommandBuffer(World& world): m_world(world) {}
118 ~CommandBuffer() = default;
119
120 CommandBuffer(CommandBuffer&&) = delete;
121 CommandBuffer(const CommandBuffer&) = delete;
122 CommandBuffer& operator=(CommandBuffer&&) = delete;
123 CommandBuffer& operator=(const CommandBuffer&) = delete;
124
128 GAIA_NODISCARD Entity add() {
129 core::lock_scope lock(m_acc);
130
131 Entity temp = add_temp();
132 push_op({OpType::ADD_ENTITY, 0, temp, EntityBad});
133 return temp;
134 }
135
140 GAIA_NODISCARD Entity copy(Entity entityFrom) {
141 core::lock_scope lock(m_acc);
142
143 Entity temp = add_temp();
144 push_op({OpType::CPY_ENTITY, 0, temp, entityFrom});
145 return temp;
146 }
147
156 GAIA_NODISCARD Entity instantiate(Entity prefabEntity) {
157 return instantiate(prefabEntity, EntityBad);
158 }
159
170 GAIA_NODISCARD Entity instantiate(Entity prefabEntity, Entity parentInstance) {
171 core::lock_scope lock(m_acc);
172
173 Entity temp = add_temp();
174 push_op({OpType::INSTANTIATE_ENTITY, 0, temp, prefabEntity, parentInstance});
175 return temp;
176 }
177
182 void instantiate_n(Entity prefabEntity, uint32_t count) {
183 instantiate_n(prefabEntity, EntityBad, count, []([[maybe_unused]] Entity) {});
184 }
185
190 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count) {
191 instantiate_n(prefabEntity, parentInstance, count, []([[maybe_unused]] Entity) {});
192 }
193
201 template <typename Func>
202 void instantiate_n(Entity prefabEntity, uint32_t count, Func func) {
203 instantiate_n(prefabEntity, EntityBad, count, func);
204 }
205
215 template <typename Func>
216 void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count, Func func) {
217 static_assert(
218 std::is_invocable_v<Func, Entity>,
219 "Command-buffer instantiate_n callbacks receive temporary Entity handles");
220 if (count == 0U)
221 return;
222
223 GAIA_FOR(count) {
224 const Entity temp = instantiate(prefabEntity, parentInstance);
225 func(temp);
226 }
227 }
228
234 template <typename T>
235 void add(Entity entity) {
236 verify_comp<T>();
237 core::lock_scope lock(m_acc);
238
239 // Make sure the component is registered
240 const auto& item = comp_cache_add<T>(m_world);
241
242 push_op({OpType::ADD_COMPONENT, 0, entity, item.entity});
243 }
244
248 void add(Entity entity, Entity other) {
249 core::lock_scope lock(m_acc);
250
251 if (other.pair()) {
252 push_op({OpType::ADD_COMPONENT, 0, entity, m_world.get(other.id()), m_world.get(other.gen())});
253 return;
254 }
255
256 push_op({OpType::ADD_COMPONENT, 0, entity, other});
257 }
258
262 void add(Entity entity, const Pair& pair) {
263 core::lock_scope lock(m_acc);
264
265 push_op({OpType::ADD_COMPONENT, 0, entity, pair.first(), pair.second()});
266 }
267
276 template <typename T>
277 void add(Entity entity, Entity object, T&& value) {
278 static_assert(core::is_raw_v<T>);
279 verify_comp<T>();
280 core::lock_scope lock(m_acc);
281
282 Entity other = object;
283 Entity pairTarget = EntityBad;
284 if (object.pair()) {
285 other = m_world.get(object.id());
286 pairTarget = m_world.get(object.gen());
287 }
288
289 const auto& item = comp_cache_add<T>(m_world);
290 const auto pos = m_data.tell();
291 auto serializer = ser::make_serializer(m_data);
292 save_value(serializer, item, value);
293 push_op({OpType::ADD_COMPONENT_DATA, pos, entity, other, pairTarget});
294 }
295
304 template <typename T>
305 void add(Entity entity, const Pair& pair, T&& value) {
306 static_assert(core::is_raw_v<T>);
307 verify_comp<T>();
308 core::lock_scope lock(m_acc);
309
310 const auto& item = comp_cache_add<T>(m_world);
311 const auto pos = m_data.tell();
312 auto serializer = ser::make_serializer(m_data);
313 save_value(serializer, item, value);
314 push_op({OpType::ADD_COMPONENT_DATA, pos, entity, pair.first(), pair.second()});
315 }
316
324 template <typename T, std::enable_if_t<!is_pair<std::remove_cv_t<std::remove_reference_t<T>>>::value, int> = 0>
325 void add(Entity entity, T&& value) {
326 verify_comp<T>();
327 core::lock_scope lock(m_acc);
328
329 // Make sure the component is registered
330 const auto& item = comp_cache_add<T>(m_world);
331
332 const auto pos = m_data.tell();
333 auto serializer = ser::make_serializer(m_data);
334 save_value(serializer, item, value);
335 push_op({OpType::ADD_COMPONENT_DATA, pos, entity, item.entity});
336 }
337
344 template <typename T>
345 void set(Entity entity, T&& value) {
346 verify_comp<T>();
347 core::lock_scope lock(m_acc);
348
349 // Make sure the component is registered
350 const auto& item = comp_cache(m_world).template get<T>();
351
352 const auto pos = m_data.tell();
353 auto serializer = ser::make_serializer(m_data);
354 save_value(serializer, item, value);
355 push_op({OpType::SET_COMPONENT, pos, entity, item.entity});
356 }
357
365 template <typename T>
366 void set(Entity entity, Entity object, T&& value) {
367 static_assert(core::is_raw_v<T>);
368 verify_comp<T>();
369 core::lock_scope lock(m_acc);
370
371 Entity other = object;
372 Entity pairTarget = EntityBad;
373 if (object.pair()) {
374 other = m_world.get(object.id());
375 pairTarget = m_world.get(object.gen());
376 }
377
378 const auto& item = comp_cache(m_world).template get<T>();
379 const auto pos = m_data.tell();
380 auto serializer = ser::make_serializer(m_data);
381 save_value(serializer, item, value);
382 push_op({OpType::SET_COMPONENT, pos, entity, other, pairTarget});
383 }
384
392 template <typename T>
393 void set(Entity entity, const Pair& pair, T&& value) {
394 static_assert(core::is_raw_v<T>);
395 verify_comp<T>();
396 core::lock_scope lock(m_acc);
397
398 const auto& item = comp_cache(m_world).template get<T>();
399 const auto pos = m_data.tell();
400 auto serializer = ser::make_serializer(m_data);
401 save_value(serializer, item, value);
402 push_op({OpType::SET_COMPONENT, pos, entity, pair.first(), pair.second()});
403 }
404
407 void del(Entity entity) {
408 core::lock_scope lock(m_acc);
409
410 push_op({OpType::DEL_ENTITY, 0, entity, EntityBad});
411 }
412
418 template <typename T>
419 void del(Entity entity) {
420 verify_comp<T>();
421 core::lock_scope lock(m_acc);
422
423 // Make sure the component is registered
424 const auto& item = comp_cache(m_world).template get<T>();
425
426 push_op({OpType::DEL_COMPONENT, 0, entity, item.entity});
427 }
428
432 void del(Entity entity, Entity object) {
433 core::lock_scope lock(m_acc);
434
435 if (object.pair()) {
436 push_op({OpType::DEL_COMPONENT, 0, entity, m_world.get(object.id()), m_world.get(object.gen())});
437 return;
438 }
439
440 push_op({OpType::DEL_COMPONENT, 0, entity, object});
441 }
442
446 void del(Entity entity, const Pair& pair) {
447 core::lock_scope lock(m_acc);
448
449 push_op({OpType::DEL_COMPONENT, 0, entity, pair.first(), pair.second()});
450 }
451
452 private:
459 template <typename T>
460 static void save_value(ser::serializer& serializer, const ComponentCacheItem& item, const T& value) {
461 if constexpr (mem::is_soa_layout_v<T>) {
462#if GAIA_ASSERT_ENABLED
463 serializer.check(value);
464#endif
465 serializer.save(value);
466 } else {
467 item.save(serializer, &value, 0, 1, 1);
468 }
469 }
470
474 GAIA_NODISCARD bool is_rel(OpType t) const {
475 return (uint32_t)t >= (uint32_t)OpType::ADD_COMPONENT;
476 }
477
481 GAIA_NODISCARD bool is_tmp(Entity e) const {
482 return e.data.tmp != 0;
483 }
484
488 GAIA_NODISCARD Entity resolve(Entity e) const {
489 if (!is_tmp(e))
490 return e;
491
492 const auto ti = e.id();
493 if (ti < m_temp2real.size())
494 return m_temp2real[ti];
495
496 return EntityBad;
497 }
498
502 GAIA_NODISCARD Entity resolve_object(const Op& op) const {
503 if (op.pairTarget == EntityBad)
504 return resolve(op.other);
505
506 const auto relation = resolve(op.other);
507 const auto target = resolve(op.pairTarget);
508 if (relation == EntityBad || target == EntityBad)
509 return EntityBad;
510
511 return (Entity)Pair(relation, target);
512 }
513
518 void allocate_instantiate_group(uint32_t firstIdx) {
519 const Op& first = m_ops[firstIdx];
520 const Entity prefab = resolve(first.other);
521 if (prefab == EntityBad)
522 return;
523
524 Entity parent = EntityBad;
525 if (first.pairTarget != EntityBad) {
526 parent = resolve(first.pairTarget);
527 if (parent == EntityBad)
528 return;
529 }
530
531 cnt::darray_ext<uint32_t, 16> tempIds;
532 for (uint32_t j = firstIdx; j < m_ops.size(); ++j) {
533 const Op& op = m_ops[j];
534 if (op.type != OpType::INSTANTIATE_ENTITY || op.other != first.other || op.pairTarget != first.pairTarget)
535 continue;
536 if (!is_tmp(op.target))
537 continue;
538
539 const uint32_t ti = op.target.id();
540 if (ti >= m_temp2real.size() || m_temp2real[ti] != EntityBad || is_canceled_temp(ti))
541 continue;
542
543 tempIds.push_back(ti);
544 }
545
546 if (tempIds.empty())
547 return;
548
549 if (tempIds.size() == 1) {
550 m_temp2real[tempIds[0]] =
551 parent == EntityBad ? m_world.instantiate(prefab) : m_world.instantiate(prefab, parent);
552 return;
553 }
554
555 uint32_t mapped = 0;
556 auto mapRoot = [&](Entity instance) {
557 GAIA_ASSERT(mapped < tempIds.size());
558 m_temp2real[tempIds[mapped++]] = instance;
559 };
560 if (parent == EntityBad)
561 m_world.instantiate_n(prefab, tempIds.size(), mapRoot);
562 else
563 m_world.instantiate_n(prefab, parent, tempIds.size(), mapRoot);
564 GAIA_ASSERT(mapped == tempIds.size());
565 }
566
570 void replay_add(Entity target, Entity object) {
571 if (object.pair()) {
572 World::EntityBuilder(m_world, target).add(Pair(m_world.get(object.id()), m_world.get(object.gen())));
573 return;
574 }
575
576 const auto* pItem = m_world.comp_cache().find(object);
577 if (pItem != nullptr && pItem->comp.storage_type() == DataStorageType::Sparse) {
578 const auto mode = m_world.sparse_storage_mode(object);
579 GAIA_ASSERT(mode != World::SparseStorageMode::None);
580 auto& store = m_world.sparse_component_store_erased_mut(object, *pItem);
581 (void)store.func_add(store.pStore, target);
582 m_world.finish_sparse_component_add_inter(target, object, mode);
583 return;
584 }
585
586 World::EntityBuilder(m_world, target).add(object);
587 }
588
592 void replay_del(Entity target, Entity object) {
593 if (object.pair())
594 m_world.del(target, Pair(m_world.get(object.id()), m_world.get(object.gen())));
595 else
596 m_world.del(target, object);
597 }
598
605 template <bool Reset = false>
606 void replay_data(Entity target, Entity object, uint32_t dataPos, bool finishWrite = true) {
607 auto serializer = ser::make_serializer(m_data);
608 serializer.seek(dataPos);
609 const auto* pItem =
610 object.pair() ? m_world.comp_cache().find_pair_payload(object) : m_world.comp_cache().find(object);
611 GAIA_ASSERT(pItem != nullptr);
612 if (pItem == nullptr)
613 return;
614 const auto& item = *pItem;
615
616 if (m_world.component_uses_sparse_storage(object)) {
617 const auto payload = m_world.mut_raw(target, object);
618 GAIA_ASSERT(payload.valid());
619 if (payload.valid()) {
620 if constexpr (Reset) {
621 if (item.func_ctor != nullptr)
622 item.func_ctor(payload.data, 1);
623 }
624 item.load(serializer, payload.data, 0, 1, 1);
625 }
626 if (finishWrite)
627 m_world.finish_write(target, object);
628 return;
629 }
630
631 const auto& ec = target.pair() ? m_world.fetch(target) : m_world.m_recs.entities[target.id()];
632 const auto row = ec.row;
633 const auto compIdx = ec.pChunk->comp_idx(object);
634 if constexpr (Reset)
635 ec.pChunk->call_ctor(row, compIdx, item);
636 auto* pComponentData = (void*)ec.pChunk->comp_ptr_mut(compIdx, 0);
637 item.load(serializer, pComponentData, row, row + 1, ec.pChunk->capacity());
638 if (finishWrite)
639 m_world.finish_write(target, object);
640 }
641
646 void replay_add_data(Entity target, Entity object, uint32_t dataPos) {
647 const auto* pItem =
648 object.pair() ? m_world.comp_cache().find_pair_payload(object) : m_world.comp_cache().find(object);
649 GAIA_ASSERT(pItem != nullptr);
650 if (pItem == nullptr)
651 return;
652 const auto& item = *pItem;
653 if (gaia::ecs::component_uses_sparse_storage(item.comp)) {
654 const auto mode = m_world.sparse_storage_mode(object);
655 GAIA_ASSERT(mode != World::SparseStorageMode::None);
656 auto& store = m_world.sparse_component_store_erased_mut(object, item);
657 auto* pPayload = store.func_add(store.pStore, target);
658
659 auto serializer = ser::make_serializer(m_data);
660 serializer.seek(dataPos);
661 item.load(serializer, pPayload, 0, 1, 1);
662 m_world.finish_sparse_component_add_inter(target, object, mode);
663 return;
664 }
665
666 World::EntityBuilder builder(m_world, target);
667#if GAIA_OBSERVERS_ENABLED
668 auto addDiffCtx = m_world.m_observers.prepare_diff(
669 m_world, ObserverEvent::OnAdd, EntitySpan{&object, 1}, EntitySpan{&target, 1});
670#endif
671 builder.add_inter_init(object);
672 builder.commit();
673 replay_data(target, object, dataPos, false);
674 m_world.notify_add_single(target, object);
675#if GAIA_OBSERVERS_ENABLED
676 m_world.m_observers.finish_diff(m_world, GAIA_MOV(addDiffCtx));
677#endif
678 }
679
684 void replay_op(const Op& op, Entity target, Entity object) {
685 switch (op.type) {
686 case OpType::DEL_COMPONENT:
687 replay_del(target, object);
688 break;
689 case OpType::ADD_COMPONENT:
690 replay_add(target, object);
691 break;
692 case OpType::ADD_COMPONENT_DATA:
693 replay_add_data(target, object, op.off);
694 break;
695 case OpType::SET_COMPONENT:
696 replay_data(target, object, op.off);
697 break;
698 default:
699 break;
700 }
701 }
702
711 GAIA_NOINLINE void
712 replay_merged(uint32_t i, uint32_t j, Entity target, Entity object, uint8_t mask, uint32_t dataPos) {
713 // Reduce ordinary component lifetimes when intermediate mutations have no ECS side effects.
714 bool canReduce = !object.pair() && object.id() > GAIA_ID(LastCoreComponent).id() &&
715 !m_world.m_hasRequiresPolicy && !m_world.m_hasCantCombinePolicy &&
716 !m_world.has_direct(object, Requires);
717#if GAIA_OBSERVERS_ENABLED
718 canReduce = canReduce && !m_world.m_observers.has_on_add_observers() &&
719 !m_world.m_observers.has_on_del_observers() && !m_world.m_observers.has_on_set_observers(object);
720#endif
721 const bool initial = canReduce && m_world.has_direct(target, object);
722 const bool hasAdd = (mask & (F_ADD | F_ADD_DATA)) != 0;
723 const auto* pItem = canReduce ? m_world.comp_cache().find(object) : nullptr;
724 if (pItem != nullptr) {
725 // Tags with constructors need World's chunk-backed construction path.
726 canReduce = pItem->func_dtor == nullptr && (pItem->comp.size() != 0 || pItem->func_ctor == nullptr);
727#if GAIA_ENABLE_ADD_DEL_HOOKS
728 canReduce = canReduce && pItem->hooks().func_add == nullptr && pItem->hooks().func_del == nullptr;
729#endif
730#if GAIA_ENABLE_SET_HOOKS
731 canReduce = canReduce && pItem->hooks().func_set == nullptr;
732#endif
733 }
734 if (canReduce) {
735 const bool hasData = (mask & (F_ADD_DATA | F_SET)) != 0;
736 GAIA_ASSERT(!hasData || hasAdd);
737 if (!hasAdd) {
738 if (initial)
739 replay_del(target, object);
740 } else if (hasData) {
741 if (initial)
742 replay_data<true>(target, object, dataPos);
743 else
744 replay_add_data(target, object, dataPos);
745 } else if (initial) {
746 // Bare re-adds run their default constructor without moving the entity.
747 if (pItem != nullptr && pItem->func_ctor != nullptr) {
748 const auto payload = m_world.mut_raw(target, object);
749 GAIA_ASSERT(payload.valid());
750 pItem->func_ctor(payload.data, 1);
751 m_world.finish_write(target, object);
752 }
753 } else {
754 replay_add(target, object);
755 }
756 return;
757 }
758
759 for (uint32_t k = i; k < j; ++k) {
760 const Op& op = m_ops[k];
761 // Merged groups may contain redundant deletes of an absent component.
762 if (op.type != OpType::DEL_COMPONENT || m_world.has_direct(target, object))
763 replay_op(op, target, object);
764 }
765 }
766
773 GAIA_NOINLINE bool replay_batch(uint32_t& p, uint32_t q, Entity target) {
774 if (target.pair() || m_world.m_hasRequiresPolicy || m_world.m_hasCantCombinePolicy)
775 return false;
776#if GAIA_OBSERVERS_ENABLED
777 if (m_world.m_observers.has_on_add_observers() || m_world.m_observers.has_on_del_observers())
778 return false;
779#endif
780 struct Group {
781 Entity object;
782 uint32_t dataPos;
783 uint8_t mask;
784 bool initial;
785 };
786 cnt::sarray_ext<Group, ChunkHeader::MAX_COMPONENTS> groups;
787 bool hasStructural = false;
788 const Archetype* pArchetype = nullptr;
789 uint32_t end = p;
790 for (uint32_t i = p; i < q;) {
791 // Allocation records were already applied; entity deletion remains deferred.
792 if (m_ops[i].type < OpType::ADD_COMPONENT) {
793 ++i;
794 continue;
795 }
796 if (m_ops[i].pairTarget != EntityBad || groups.size() == groups.capacity())
797 break;
798 const Entity object = resolve_object(m_ops[i]);
799 if (object == EntityBad || object.pair() || object.id() <= GAIA_ID(LastCoreComponent).id())
800 break;
801 const auto* pItem = m_world.comp_cache().find(object);
802 if (pItem != nullptr) {
803 if (pItem->comp.storage_type() == DataStorageType::Sparse || pItem->func_dtor != nullptr ||
804 (pItem->comp.size() == 0 && pItem->func_ctor != nullptr))
805 break;
806#if GAIA_ENABLE_ADD_DEL_HOOKS
807 if (pItem->hooks().func_add != nullptr || pItem->hooks().func_del != nullptr)
808 break;
809#endif
810#if GAIA_ENABLE_SET_HOOKS
811 if (pItem->hooks().func_set != nullptr)
812 break;
813#endif
814 }
815#if GAIA_OBSERVERS_ENABLED
816 if (m_world.m_observers.has_on_set_observers(object))
817 break;
818#endif
819 if (m_world.component_is_non_fragmenting(object))
820 break;
821 if (pArchetype == nullptr)
822 pArchetype = m_world.fetch(target).pArchetype;
823 Group group{object, 0, 0, pArchetype->has(object)};
824 const Entity key = m_ops[i].other;
825 do {
826 const Op& op = m_ops[i];
827 switch (op.type) {
828 case OpType::ADD_COMPONENT:
829 group.mask |= F_ADD;
830 break;
831 case OpType::ADD_COMPONENT_DATA:
832 group.mask |= F_ADD_DATA;
833 group.dataPos = op.off;
834 break;
835 case OpType::SET_COMPONENT:
836 group.mask |= F_SET;
837 group.dataPos = op.off;
838 break;
839 case OpType::DEL_COMPONENT:
840 group.mask = F_DEL;
841 break;
842 default:
843 break;
844 }
845 ++i;
846 } while (i < q && m_ops[i].other == key && m_ops[i].pairTarget == EntityBad);
847 if ((group.mask & F_DEL) != 0 && m_world.has_direct(object, Requires))
848 break;
849 const bool hasAdd = (group.mask & (F_ADD | F_ADD_DATA)) != 0;
850 hasStructural |= hasAdd ? !group.initial : ((group.mask & F_DEL) != 0 && group.initial);
851 groups.push_back(group);
852 end = i;
853 }
854 if (groups.size() < 2)
855 return false;
856
857 if (hasStructural) {
858 World::EntityBuilder builder(m_world, target);
859 for (const auto& group: groups) {
860 const bool hasAdd = (group.mask & (F_ADD | F_ADD_DATA)) != 0;
861 if (hasAdd && !group.initial)
862 builder.add_inter_init(group.object);
863 else if (!hasAdd && (group.mask & F_DEL) != 0 && group.initial)
864 builder.del_inter(group.object);
865 }
866 builder.commit();
867 }
868
869 // Restore surviving payloads only after the destination storage is available.
870 for (const auto& group: groups) {
871 const bool hasAdd = (group.mask & (F_ADD | F_ADD_DATA)) != 0;
872 const bool hasDel = (group.mask & F_DEL) != 0;
873 if (hasDel && !hasAdd) {
874 GAIA_ASSERT((group.mask & F_SET) == 0);
875 continue;
876 }
877 if ((group.mask & (F_ADD_DATA | F_SET)) != 0) {
878 if (hasDel && group.initial)
879 replay_data<true>(target, group.object, group.dataPos);
880 else
881 replay_data(target, group.object, group.dataPos, !hasAdd);
882 } else if (hasDel && group.initial) {
883 const auto* pItem = m_world.comp_cache().find(group.object);
884 if (pItem != nullptr && pItem->func_ctor != nullptr) {
885 const auto& ec = m_world.fetch(target);
886 ec.pChunk->call_ctor(ec.row, ec.pChunk->comp_idx(group.object), *pItem);
887 m_world.finish_write(target, group.object);
888 }
889 }
890 }
891 p = end;
892 return true;
893 }
894
899 GAIA_NODISCARD bool is_canceled_temp(uint32_t idx) const {
900 const uint32_t base = idx * 3;
901 if (base + 2 >= m_tmpFlags.size())
902 return false;
903
904 const bool destroy = m_tmpFlags.test(base);
905 const bool usedOther = m_tmpFlags.test(base + 2);
906
907 // If deleted in this batch and not referenced by others, cancel entirely
908 return destroy && !usedOther;
909 }
910
913 GAIA_NODISCARD Entity add_temp() {
914 m_temp2real.push_back(EntityBad);
915 Entity tmp(m_nextTemp++, 0, true, false);
916 // Use the unused flag to mark temporary entities
917 tmp.data.tmp = 1;
918 return tmp;
919 }
920
925 GAIA_NODISCARD static bool less_op_key(const Op& a, const Op& b) {
926 if (a.target != b.target)
927 return a.target < b.target;
928
929 const bool aIsPair = a.pairTarget != EntityBad;
930 const bool bIsPair = b.pairTarget != EntityBad;
931 if (aIsPair != bIsPair)
932 return !aIsPair;
933 if (aIsPair)
934 return false;
935
936 if (a.other != b.other)
937 return a.other < b.other;
938 return false;
939 }
940
943 void check_sort(const Op& op) {
944 if (!m_ops.empty() && less_op_key(op, m_ops.back()))
945 m_needsSort = true;
946 }
947
950 void push_op(Op&& op) {
951 if constexpr (Optimize) {
952 op.order = m_ops.size();
953 check_sort(op);
954 }
955 m_ops.push_back(GAIA_MOV(op));
956 }
957
961 void replay_plain() {
962 for (const Op& op: m_ops) {
963 switch (op.type) {
964 case OpType::ADD_ENTITY:
965 m_temp2real[op.target.id()] = m_world.add();
966 break;
967 case OpType::CPY_ENTITY:
968 m_temp2real[op.target.id()] = m_world.copy(resolve(op.other));
969 break;
970 case OpType::INSTANTIATE_ENTITY:
971 m_temp2real[op.target.id()] = op.pairTarget == EntityBad ?
972 m_world.instantiate(resolve(op.other)) :
973 m_world.instantiate(resolve(op.other), resolve(op.pairTarget));
974 break;
975 case OpType::DEL_ENTITY:
976 m_world.del(resolve(op.target));
977 break;
978 default:
979 replay_op(op, resolve(op.target), resolve_object(op));
980 break;
981 }
982 }
983 }
984
986 void clear() {
987 m_ops.clear();
988 m_temp2real.clear();
989 m_tmpFlags.reset();
990 m_nextTemp = 0;
991 m_data.reset();
992
993 m_needsSort = false;
994 }
996
997 public:
1008 void commit() {
1009 core::lock_scope lock(m_acc);
1010
1011 if (m_ops.empty())
1012 return;
1013
1014 GAIA_PROF_SCOPE(cmdbuf::commit);
1015
1016 if constexpr (!Optimize)
1017 replay_plain();
1018 else
1019 commit_optimized();
1020 clear();
1021 }
1022
1023 private:
1026 void commit_optimized() {
1027 // Build flags + allocate entities
1028 if (m_nextTemp > 0) {
1029 GAIA_PROF_SCOPE(cmdbuf::alloc);
1030
1031 // Bit layout for each temporary entity:
1032 // bit 0 -> marked for destruction (DEL_ENTITY recorded)
1033 // bit 1 -> used as a relation target (appeared as target in component ops)
1034 // bit 2 -> used as a relation source / dependency (appeared as other in component ops)
1035 m_tmpFlags.resize(m_nextTemp * 3);
1036
1037 // Pre-map surviving temps for ADD/CPY/INSTANTIATE (avoid allocating canceled temps)
1038 if (m_temp2real.size() < m_nextTemp) {
1039 const auto from = m_temp2real.size();
1040 m_temp2real.resize(m_nextTemp);
1041 GAIA_FOR2(from, m_nextTemp) m_temp2real[i] = EntityBad;
1042 }
1043
1044 // Build all flags
1045 for (const Op& o: m_ops) {
1046 // Set flag bits
1047 if (is_tmp(o.target)) {
1048 const uint32_t ti = o.target.id();
1049
1050 if (o.type == OpType::DEL_ENTITY)
1051 m_tmpFlags.set((ti * 3) + 0, true);
1052 else if (is_rel(o.type))
1053 m_tmpFlags.set((ti * 3) + 1, true);
1054 }
1055
1056 if (is_tmp(o.other) && o.other.id() < m_tmpFlags.size())
1057 m_tmpFlags.set((o.other.id() * 3) + 2, true);
1058 if (is_tmp(o.pairTarget) && o.pairTarget.id() < m_tmpFlags.size())
1059 m_tmpFlags.set((o.pairTarget.id() * 3) + 2, true);
1060 }
1061
1062 // Allocate real entities for the surviving temporaries
1063 for (uint32_t i = 0; i < m_ops.size(); ++i) {
1064 const Op& o = m_ops[i];
1065 if (!is_tmp(o.target))
1066 continue;
1067
1068 const uint32_t ti = o.target.id();
1069 if (is_canceled_temp(ti) || m_temp2real[ti] != EntityBad)
1070 continue;
1071
1072 if (o.type == OpType::ADD_ENTITY) {
1073 m_temp2real[ti] = m_world.add();
1074 } else if (o.type == OpType::CPY_ENTITY) {
1075 const Entity src = resolve(o.other);
1076 if (src != EntityBad)
1077 m_temp2real[ti] = m_world.copy(src);
1078 } else if (o.type == OpType::INSTANTIATE_ENTITY) {
1079 allocate_instantiate_group(i);
1080 }
1081 }
1082 }
1083
1084 // Sort by (target, other), reduce each component group, apply relations.
1085 // Preserve recorded order within each component group and each target's pair operations.
1086 if (m_needsSort) {
1087 GAIA_PROF_SCOPE(cmdbuf::sort);
1088
1089 m_needsSort = false;
1090 core::sort(m_ops.begin(), m_ops.end(), [](const Op& a, const Op& b) {
1091 if (less_op_key(a, b))
1092 return true;
1093 if (less_op_key(b, a))
1094 return false;
1095 return a.order < b.order;
1096 });
1097 }
1098
1099 // Replay batched operations
1100 Entity lastKey = EntityBad;
1101 Entity lastResolved = EntityBad;
1102 auto resolve_cached = [&](Entity e) {
1103 if (e == lastKey)
1104 return lastResolved;
1105 lastKey = e;
1106 return lastResolved = resolve(e);
1107 };
1108
1109 cnt::darray_ext<Entity, 16> deleteTargets;
1110 {
1111 GAIA_PROF_SCOPE(cmdbuf::merges);
1112 for (uint32_t p = 0; p < m_ops.size();) {
1113 GAIA_PROF_SCOPE(cmdbuf::merge);
1114
1115 const Entity tgtKey = m_ops[p].target;
1116
1117 const bool tgtIsTemp = is_tmp(tgtKey);
1118 const uint32_t ti = tgtIsTemp ? tgtKey.id() : 0u;
1119 const Entity tgtReal =
1120 tgtIsTemp ? (ti < m_temp2real.size() ? m_temp2real[ti] : EntityBad) : resolve_cached(tgtKey);
1121
1122 // Range for this target
1123 uint32_t q = p;
1124 bool hasDelEntity = false;
1125 while (q < m_ops.size() && m_ops[q].target == tgtKey) {
1126 if (m_ops[q].type == OpType::DEL_ENTITY)
1127 hasDelEntity = true;
1128 ++q;
1129 }
1130
1131 // Skip canceled or non-existent temporary entities
1132 if (tgtReal == EntityBad) {
1133 p = q;
1134 continue;
1135 }
1136 if (tgtIsTemp && is_canceled_temp(ti)) {
1137 p = q;
1138 continue;
1139 }
1140
1141 if GAIA_UNLIKELY (m_ops[p].other != m_ops[q - 1].other &&
1142 (m_ops[p].type != OpType::SET_COMPONENT || m_ops[q - 1].type != OpType::SET_COMPONENT) &&
1143 replay_batch(p, q, tgtReal) && p == q) {
1144 if (hasDelEntity)
1145 deleteTargets.push_back(tgtReal);
1146 p = q;
1147 continue;
1148 }
1149
1150 // Emit relation groups.
1151 // Inside [p..q) range (same target), process groups by 'other'.
1152 // We perform per-component reduction.
1153 for (uint32_t i = p; i < q;) {
1154 const Entity othKey = m_ops[i].other;
1155 const Entity pairTargetKey = m_ops[i].pairTarget;
1156 const Entity othReal = resolve_object(m_ops[i]);
1157
1158 // Group ops with same (target, other)
1159 uint32_t j = i + 1;
1160 while (j < q && m_ops[j].other == othKey && m_ops[j].pairTarget == pairTargetKey)
1161 ++j;
1162
1163 if (tgtReal != EntityBad) {
1164 const uint32_t groupSize = j - i;
1165 // Fast path - single op
1166 if (groupSize == 1) {
1167 replay_op(m_ops[i], tgtReal, othReal);
1168 }
1169 // Slow path: merge multiple ops
1170 else {
1171 uint8_t mask = 0;
1172 uint32_t dataPos = 0;
1173
1174 for (uint32_t k = i; k < j; ++k) {
1175 const Op& op = m_ops[k];
1176 switch (op.type) {
1177 case OpType::ADD_COMPONENT:
1178 mask |= F_ADD;
1179 break;
1180 case OpType::ADD_COMPONENT_DATA:
1181 mask |= F_ADD_DATA;
1182 dataPos = op.off;
1183 break;
1184 case OpType::SET_COMPONENT:
1185 mask |= F_SET;
1186 dataPos = op.off;
1187 break;
1188 case OpType::DEL_COMPONENT:
1189 // A delete discards payloads from the preceding component lifetime.
1190 mask = F_DEL;
1191 break;
1192 default:
1193 break;
1194 }
1195 }
1196
1197 const bool hasAdd = mask & F_ADD;
1198 const bool hasAddData = mask & F_ADD_DATA;
1199 const bool hasSet = mask & F_SET;
1200 const bool hasDel = mask & F_DEL;
1201
1202 // 1) DEL present: reduce the surviving component lifetime.
1203 // Replay in order when World policies or observable side effects require it.
1204 if (hasDel) {
1205 replay_merged(i, j, tgtReal, othReal, mask, dataPos);
1206 }
1207 // 2) ADD_WITH_DATA or ADD+SET = ADD_WITH_DATA
1208 else if (hasAddData || (hasAdd && hasSet)) {
1209 replay_add_data(tgtReal, othReal, dataPos);
1210 }
1211 // 3) ADD only
1212 else if (hasAdd) {
1213 replay_add(tgtReal, othReal);
1214 }
1215 // 4) SET only
1216 else if (hasSet) {
1217 replay_data(tgtReal, othReal, dataPos);
1218 }
1219 }
1220 }
1221
1222 // Advance to next component group
1223 i = j;
1224 }
1225
1226 // Apply deletions after every relationship operation so sorting target groups cannot
1227 // invalidate an endpoint that a later group still needs to resolve.
1228 if (hasDelEntity)
1229 deleteTargets.push_back(tgtReal);
1230
1231 // Advance to next target group
1232 p = q;
1233 }
1234 }
1235 for (auto entity: deleteTargets) {
1236 if (m_world.valid(entity))
1237 m_world.del(entity);
1238 }
1239 }
1240 };
1241 } // namespace detail
1242
1244 using CommandBufferST = detail::CommandBuffer<AccessContextST>;
1246 using CommandBufferMT = detail::CommandBuffer<AccessContextMT>;
1248 using CommandBufferPlainST = detail::CommandBuffer<AccessContextST, false>;
1251 using CommandBufferPlainMT = detail::CommandBuffer<AccessContextMT, false>;
1252
1256 inline CommandBufferST* cmd_buffer_st_create(World& world) {
1257 return new CommandBufferST(world);
1258 }
1261 inline void cmd_buffer_destroy(CommandBufferST& cmdBuffer) {
1262 delete &cmdBuffer;
1263 }
1266 inline void cmd_buffer_commit(CommandBufferST& cmdBuffer) {
1267 cmdBuffer.commit();
1268 }
1269
1273 inline CommandBufferMT* cmd_buffer_mt_create(World& world) {
1274 return new CommandBufferMT(world);
1275 }
1278 inline void cmd_buffer_destroy(CommandBufferMT& cmdBuffer) {
1279 delete &cmdBuffer;
1280 }
1283 inline void cmd_buffer_commit(CommandBufferMT& cmdBuffer) {
1284 cmdBuffer.commit();
1285 }
1286 } // namespace ecs
1287} // namespace gaia
Array of elements of type.
Definition darray_ext_impl.h:31
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_ext_impl.h:561
GAIA_NODISCARD auto begin() noexcept
Returns an iterator to the first element.
Definition darray_ext_impl.h:613
void push_back(const T &arg)
Appends an element.
Definition darray_ext_impl.h:360
GAIA_NODISCARD auto end() noexcept
Returns an iterator one past the last element.
Definition darray_ext_impl.h:649
void resize(size_type count, ResizePolicy policy=ResizePolicy::Exact)
Changes the number of elements.
Definition darray_ext_impl.h:289
void clear() noexcept
Removes all elements.
Definition darray_ext_impl.h:518
GAIA_NODISCARD decltype(auto) back() noexcept
Accesses the last element.
Definition darray_ext_impl.h:599
GAIA_NODISCARD bool empty() const noexcept
Checks whether the container has no elements.
Definition darray_ext_impl.h:567
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:3841
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:1052
void del(Entity entity)
Removes an entity along with all data associated with it.
Definition world.h:6727
GAIA_NODISCARD SparseStorageMode sparse_storage_mode(Entity component) const
Returns the sparse storage mode used by a component.
Definition world.h:1340
GAIA_NODISCARD Entity get(EntityId id) const
Returns the entity located at the index id.
Definition world.h:4287
GAIA_NODISCARD bool valid(Entity entity) const
Checks if entity is valid.
Definition world.h:4276
GAIA_NODISCARD Entity copy(Entity srcEntity)
Creates a new ordinary entity by cloning an already existing one. Does not trigger observers....
Definition world.h:4773
GAIA_NODISCARD bool has_direct(Entity entity, Entity object) const
Checks if entity directly contains the entity object, without semantic inheritance expansion.
Definition world.h:7695
void instantiate_n(Entity prefabEntity, uint32_t count, Func func=func_void_with_entity)
Instantiates count copies of a prefab as normal root entities. Each instance copies the prefab's dire...
Definition world.h:6565
GAIA_NODISCARD SparseComponentStoreErased & sparse_component_store_erased_mut(Entity component, const ComponentCacheItem &item)
Returns the erased sparse store for component, creating runtime-sized storage when absent.
Definition world.h:1711
GAIA_NODISCARD Entity add()
Creates a new empty entity.
Definition world.h:4336
GAIA_NODISCARD bool component_uses_sparse_storage(Entity component) const
Returns whether component stores instance data in sparse storage instead of archetype chunks....
Definition world.h:1303
GAIA_NODISCARD ComponentRawMutView mut_raw(Entity entity, Entity component)
Returns raw mutable bytes for a directly owned AoS component or exact pair payload....
Definition world.h:7273
GAIA_NODISCARD Entity instantiate(Entity prefabEntity)
Instantiates a prefab as a normal entity. The instance copies the prefab's direct data,...
Definition world.h:6517
void finish_sparse_component_add_inter(Entity entity, Entity object, SparseStorageMode mode)
Finishes adding a sparse component after its payload has been created. Fragmenting components also ad...
Definition world.h:1730
GAIA_NODISCARD bool component_is_non_fragmenting(Entity component) const
Returns whether component is non-fragmenting. Non-fragmenting component ids do not participate in arc...
Definition world.h:1325
Buffer for deferred execution of some operations on entities.
Definition command_buffer.h:56
void del(Entity entity, const Pair &pair)
Requests removal of a relationship pair from entity entity.
Definition command_buffer.h:446
void instantiate_n(Entity prefabEntity, uint32_t count, Func func)
Requests count prefab instantiations and records per-instance commands. The callback runs immediately...
Definition command_buffer.h:202
void del(Entity entity)
Requests an existing entity to be removed.
Definition command_buffer.h:407
GAIA_NODISCARD Entity instantiate(Entity prefabEntity, Entity parentInstance)
Requests a prefab to be instantiated as a normal entity parented under parentInstance....
Definition command_buffer.h:170
void del(Entity entity, Entity object)
Requests removal of entity object from entity entity.
Definition command_buffer.h:432
GAIA_NODISCARD Entity copy(Entity entityFrom)
Requests a new entity to be created by cloning an already existing entity.
Definition command_buffer.h:140
void set(Entity entity, Entity object, T &&value)
Requests component data identified by object to be set on entity.
Definition command_buffer.h:366
void add(Entity entity, const Pair &pair)
Requests a relationship pair to be added to entity entity.
Definition command_buffer.h:262
void add(Entity entity)
Requests a component T to be added to entity.
Definition command_buffer.h:235
void set(Entity entity, const Pair &pair, T &&value)
Requests relationship payload identified by pair to be set on entity.
Definition command_buffer.h:393
void instantiate_n(Entity prefabEntity, uint32_t count)
Requests count prefab instantiations. Optimizing buffers group matching records through World::instan...
Definition command_buffer.h:182
GAIA_NODISCARD Entity instantiate(Entity prefabEntity)
Requests a prefab to be instantiated as a normal entity. Commit replays through World::instantiate,...
Definition command_buffer.h:156
CommandBuffer(World &world)
Creates a command buffer bound to world.
Definition command_buffer.h:117
void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count, Func func)
Requests count prefab instantiations parented under parentInstance and records per-instance commands....
Definition command_buffer.h:216
void add(Entity entity, Entity other)
Requests an entity other to be added to entity entity.
Definition command_buffer.h:248
void add(Entity entity, const Pair &pair, T &&value)
Requests a relationship pair to be added to entity. Also sets its payload.
Definition command_buffer.h:305
void add(Entity entity, T &&value)
Requests a component T to be added to entity. Also sets its value.
Definition command_buffer.h:325
void set(Entity entity, T &&value)
Requests component data to be set to given values for a given entity.
Definition command_buffer.h:345
void del(Entity entity)
Requests removal of component T from entity.
Definition command_buffer.h:419
GAIA_NODISCARD Entity add()
Requests a new entity to be created.
Definition command_buffer.h:128
void add(Entity entity, Entity object, T &&value)
Requests a component identified by object to be added to entity. Also sets its value.
Definition command_buffer.h:277
void instantiate_n(Entity prefabEntity, Entity parentInstance, uint32_t count)
Requests count prefab instantiations parented under parentInstance.
Definition command_buffer.h:190
void commit()
Commits all queued changes. Plain buffers replay every request in recorded order, including entity cr...
Definition command_buffer.h:1008
Wrapper for two Entities forming a relationship pair. Conversion to Entity stores only id() of each e...
Definition id.h:559
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:197
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:187
Multi-threaded command-buffer access guard backed by a spin lock.
Definition command_buffer.h:31
mt::SpinLock m_lock
Spin lock serializing command-buffer access across worker threads.
Definition command_buffer.h:33
void lock()
Acquires the access guard.
Definition command_buffer.h:36
void unlock()
Releases the access guard.
Definition command_buffer.h:41
Single-threaded command-buffer access guard. Locking is a no-op.
Definition command_buffer.h:23
void unlock()
Releases the access guard.
Definition command_buffer.h:27
void lock()
Acquires the access guard.
Definition command_buffer.h:25
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:231
GAIA_NODISCARD constexpr auto gen() const noexcept
Generation index of the entity.
Definition id.h:301
GAIA_NODISCARD constexpr bool pair() const noexcept
Whether this id refers to a relationship pair.
Definition id.h:313
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:295
GAIA_NODISCARD constexpr bool entity() const noexcept
Whether this id refers to an entity.
Definition id.h:307
Runtime serializer type-erased handle. Traversal logic is shared with compile-time serialization,...
Definition ser_rt.h:94
void save(const T &arg)
Serializes a value through generic traversal.
Definition ser_rt.h:115
void seek(uint32_t pos)
Moves the backend cursor.
Definition ser_rt.h:219