58 enum class OpType : uint8_t {
90 Entity pairTarget = EntityBad;
104 uint32_t m_nextTemp = 0;
106 bool m_needsSort =
false;
132 push_op({OpType::ADD_ENTITY, 0, temp, EntityBad});
144 push_op({OpType::CPY_ENTITY, 0, temp, entityFrom});
174 push_op({OpType::INSTANTIATE_ENTITY, 0, temp, prefabEntity, parentInstance});
201 template <
typename Func>
215 template <
typename Func>
218 std::is_invocable_v<Func, Entity>,
219 "Command-buffer instantiate_n callbacks receive temporary Entity handles");
234 template <
typename T>
240 const auto& item = comp_cache_add<T>(m_world);
242 push_op({OpType::ADD_COMPONENT, 0, entity, item.
entity});
252 push_op({OpType::ADD_COMPONENT, 0, entity, m_world.
get(other.
id()), m_world.
get(other.
gen())});
256 push_op({OpType::ADD_COMPONENT, 0, entity, other});
265 push_op({OpType::ADD_COMPONENT, 0, entity,
pair.first(),
pair.second()});
276 template <
typename T>
278 static_assert(core::is_raw_v<T>);
283 Entity pairTarget = EntityBad;
285 other = m_world.
get(
object.
id());
286 pairTarget = m_world.
get(
object.gen());
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});
304 template <
typename T>
306 static_assert(core::is_raw_v<T>);
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()});
324 template <
typename T, std::enable_if_t<!is_pair<std::remove_cv_t<std::remove_reference_t<T>>>::value,
int> = 0>
330 const auto& item = comp_cache_add<T>(m_world);
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});
344 template <
typename T>
350 const auto& item = comp_cache(m_world).template get<T>();
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});
365 template <
typename T>
367 static_assert(core::is_raw_v<T>);
372 Entity pairTarget = EntityBad;
374 other = m_world.
get(
object.
id());
375 pairTarget = m_world.
get(
object.gen());
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});
392 template <
typename T>
394 static_assert(core::is_raw_v<T>);
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()});
410 push_op({OpType::DEL_ENTITY, 0, entity, EntityBad});
418 template <
typename T>
424 const auto& item = comp_cache(m_world).template get<T>();
426 push_op({OpType::DEL_COMPONENT, 0, entity, item.
entity});
436 push_op({OpType::DEL_COMPONENT, 0, entity, m_world.
get(
object.
id()), m_world.
get(
object.gen())});
440 push_op({OpType::DEL_COMPONENT, 0, entity,
object});
449 push_op({OpType::DEL_COMPONENT, 0, entity,
pair.first(),
pair.second()});
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);
465 serializer.
save(value);
467 item.save(serializer, &value, 0, 1, 1);
474 GAIA_NODISCARD
bool is_rel(OpType t)
const {
475 return (uint32_t)t >= (uint32_t)OpType::ADD_COMPONENT;
481 GAIA_NODISCARD
bool is_tmp(Entity e)
const {
482 return e.data.tmp != 0;
488 GAIA_NODISCARD Entity resolve(Entity e)
const {
492 const auto ti = e.id();
493 if (ti < m_temp2real.
size())
494 return m_temp2real[ti];
502 GAIA_NODISCARD Entity resolve_object(
const Op& op)
const {
503 if (op.pairTarget == EntityBad)
504 return resolve(op.other);
506 const auto relation = resolve(op.other);
507 const auto target = resolve(op.pairTarget);
508 if (relation == EntityBad || target == EntityBad)
511 return (Entity)Pair(relation, target);
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)
524 Entity parent = EntityBad;
525 if (first.pairTarget != EntityBad) {
526 parent = resolve(first.pairTarget);
527 if (parent == EntityBad)
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)
536 if (!is_tmp(op.target))
539 const uint32_t ti = op.target.id();
540 if (ti >= m_temp2real.
size() || m_temp2real[ti] != EntityBad || is_canceled_temp(ti))
543 tempIds.push_back(ti);
549 if (tempIds.size() == 1) {
550 m_temp2real[tempIds[0]] =
556 auto mapRoot = [&](Entity instance) {
557 GAIA_ASSERT(mapped < tempIds.size());
558 m_temp2real[tempIds[mapped++]] = instance;
560 if (parent == EntityBad)
563 m_world.
instantiate_n(prefab, parent, tempIds.size(), mapRoot);
564 GAIA_ASSERT(mapped == tempIds.size());
570 void replay_add(Entity target, Entity
object) {
572 World::EntityBuilder(m_world, target).add(Pair(m_world.
get(
object.id()), m_world.
get(
object.gen())));
576 const auto* pItem = m_world.
comp_cache().find(
object);
577 if (pItem !=
nullptr && pItem->comp.storage_type() == DataStorageType::Sparse) {
579 GAIA_ASSERT(mode != World::SparseStorageMode::None);
581 (void)store.func_add(store.pStore, target);
586 World::EntityBuilder(m_world, target).add(
object);
592 void replay_del(Entity target, Entity
object) {
594 m_world.
del(target, Pair(m_world.
get(
object.id()), m_world.
get(
object.gen())));
596 m_world.
del(target,
object);
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);
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)
614 const auto& item = *pItem;
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);
624 item.load(serializer, payload.data, 0, 1, 1);
627 m_world.finish_write(target,
object);
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);
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());
639 m_world.finish_write(target,
object);
646 void replay_add_data(Entity target, Entity
object, uint32_t dataPos) {
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)
652 const auto& item = *pItem;
653 if (gaia::ecs::component_uses_sparse_storage(item.comp)) {
655 GAIA_ASSERT(mode != World::SparseStorageMode::None);
657 auto* pPayload = store.func_add(store.pStore, target);
659 auto serializer = ser::make_serializer(m_data);
660 serializer.
seek(dataPos);
661 item.load(serializer, pPayload, 0, 1, 1);
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});
671 builder.add_inter_init(
object);
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));
684 void replay_op(
const Op& op, Entity target, Entity
object) {
686 case OpType::DEL_COMPONENT:
687 replay_del(target,
object);
689 case OpType::ADD_COMPONENT:
690 replay_add(target,
object);
692 case OpType::ADD_COMPONENT_DATA:
693 replay_add_data(target,
object, op.off);
695 case OpType::SET_COMPONENT:
696 replay_data(target,
object, op.off);
712 replay_merged(uint32_t i, uint32_t j, Entity target, Entity
object, uint8_t mask, uint32_t dataPos) {
714 bool canReduce = !
object.pair() &&
object.id() > GAIA_ID(LastCoreComponent).id() &&
715 !m_world.m_hasRequiresPolicy && !m_world.m_hasCantCombinePolicy &&
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);
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) {
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;
730#if GAIA_ENABLE_SET_HOOKS
731 canReduce = canReduce && pItem->hooks().func_set ==
nullptr;
735 const bool hasData = (mask & (F_ADD_DATA | F_SET)) != 0;
736 GAIA_ASSERT(!hasData || hasAdd);
739 replay_del(target,
object);
740 }
else if (hasData) {
742 replay_data<true>(target,
object, dataPos);
744 replay_add_data(target,
object, dataPos);
745 }
else if (initial) {
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);
754 replay_add(target,
object);
759 for (uint32_t k = i; k < j; ++k) {
760 const Op& op = m_ops[k];
762 if (op.type != OpType::DEL_COMPONENT || m_world.
has_direct(target,
object))
763 replay_op(op, target,
object);
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)
776#if GAIA_OBSERVERS_ENABLED
777 if (m_world.m_observers.has_on_add_observers() || m_world.m_observers.has_on_del_observers())
786 cnt::sarray_ext<Group, ChunkHeader::MAX_COMPONENTS> groups;
787 bool hasStructural =
false;
788 const Archetype* pArchetype =
nullptr;
790 for (uint32_t i = p; i < q;) {
792 if (m_ops[i].type < OpType::ADD_COMPONENT) {
796 if (m_ops[i].pairTarget != EntityBad || groups.size() == groups.capacity())
798 const Entity
object = resolve_object(m_ops[i]);
799 if (
object == EntityBad ||
object.pair() ||
object.
id() <= GAIA_ID(LastCoreComponent).
id())
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))
806#if GAIA_ENABLE_ADD_DEL_HOOKS
807 if (pItem->hooks().func_add !=
nullptr || pItem->hooks().func_del !=
nullptr)
810#if GAIA_ENABLE_SET_HOOKS
811 if (pItem->hooks().func_set !=
nullptr)
815#if GAIA_OBSERVERS_ENABLED
816 if (m_world.m_observers.has_on_set_observers(
object))
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;
826 const Op& op = m_ops[i];
828 case OpType::ADD_COMPONENT:
831 case OpType::ADD_COMPONENT_DATA:
832 group.mask |= F_ADD_DATA;
833 group.dataPos = op.off;
835 case OpType::SET_COMPONENT:
837 group.dataPos = op.off;
839 case OpType::DEL_COMPONENT:
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))
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);
854 if (groups.size() < 2)
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);
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);
877 if ((group.mask & (F_ADD_DATA | F_SET)) != 0) {
878 if (hasDel && group.initial)
879 replay_data<true>(target, group.object, group.dataPos);
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);
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())
904 const bool destroy = m_tmpFlags.test(base);
905 const bool usedOther = m_tmpFlags.test(base + 2);
908 return destroy && !usedOther;
913 GAIA_NODISCARD Entity add_temp() {
915 Entity tmp(m_nextTemp++, 0,
true,
false);
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;
929 const bool aIsPair = a.pairTarget != EntityBad;
930 const bool bIsPair = b.pairTarget != EntityBad;
931 if (aIsPair != bIsPair)
936 if (a.other != b.other)
937 return a.other < b.other;
943 void check_sort(
const Op& op) {
944 if (!m_ops.
empty() && less_op_key(op, m_ops.
back()))
950 void push_op(Op&& op) {
951 if constexpr (Optimize) {
952 op.order = m_ops.
size();
961 void replay_plain() {
962 for (
const Op& op: m_ops) {
964 case OpType::ADD_ENTITY:
965 m_temp2real[op.target.id()] = m_world.
add();
967 case OpType::CPY_ENTITY:
968 m_temp2real[op.target.id()] = m_world.
copy(resolve(op.other));
970 case OpType::INSTANTIATE_ENTITY:
971 m_temp2real[op.target.id()] = op.pairTarget == EntityBad ?
973 m_world.
instantiate(resolve(op.other), resolve(op.pairTarget));
975 case OpType::DEL_ENTITY:
976 m_world.
del(resolve(op.target));
979 replay_op(op, resolve(op.target), resolve_object(op));
1014 GAIA_PROF_SCOPE(cmdbuf::commit);
1016 if constexpr (!Optimize)
1026 void commit_optimized() {
1028 if (m_nextTemp > 0) {
1029 GAIA_PROF_SCOPE(cmdbuf::alloc);
1035 m_tmpFlags.
resize(m_nextTemp * 3);
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;
1045 for (
const Op& o: m_ops) {
1047 if (is_tmp(o.target)) {
1048 const uint32_t ti = o.target.id();
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);
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);
1063 for (uint32_t i = 0; i < m_ops.
size(); ++i) {
1064 const Op& o = m_ops[i];
1065 if (!is_tmp(o.target))
1068 const uint32_t ti = o.target.id();
1069 if (is_canceled_temp(ti) || m_temp2real[ti] != EntityBad)
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);
1087 GAIA_PROF_SCOPE(cmdbuf::sort);
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))
1093 if (less_op_key(b, a))
1095 return a.order < b.order;
1100 Entity lastKey = EntityBad;
1101 Entity lastResolved = EntityBad;
1102 auto resolve_cached = [&](Entity e) {
1104 return lastResolved;
1106 return lastResolved = resolve(e);
1109 cnt::darray_ext<Entity, 16> deleteTargets;
1111 GAIA_PROF_SCOPE(cmdbuf::merges);
1112 for (uint32_t p = 0; p < m_ops.
size();) {
1113 GAIA_PROF_SCOPE(cmdbuf::merge);
1115 const Entity tgtKey = m_ops[p].target;
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);
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;
1132 if (tgtReal == EntityBad) {
1136 if (tgtIsTemp && is_canceled_temp(ti)) {
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) {
1145 deleteTargets.push_back(tgtReal);
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]);
1160 while (j < q && m_ops[j].other == othKey && m_ops[j].pairTarget == pairTargetKey)
1163 if (tgtReal != EntityBad) {
1164 const uint32_t groupSize = j - i;
1166 if (groupSize == 1) {
1167 replay_op(m_ops[i], tgtReal, othReal);
1172 uint32_t dataPos = 0;
1174 for (uint32_t k = i; k < j; ++k) {
1175 const Op& op = m_ops[k];
1177 case OpType::ADD_COMPONENT:
1180 case OpType::ADD_COMPONENT_DATA:
1184 case OpType::SET_COMPONENT:
1188 case OpType::DEL_COMPONENT:
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;
1205 replay_merged(i, j, tgtReal, othReal, mask, dataPos);
1208 else if (hasAddData || (hasAdd && hasSet)) {
1209 replay_add_data(tgtReal, othReal, dataPos);
1213 replay_add(tgtReal, othReal);
1217 replay_data(tgtReal, othReal, dataPos);
1229 deleteTargets.push_back(tgtReal);
1235 for (
auto entity: deleteTargets) {
1236 if (m_world.
valid(entity))
1237 m_world.
del(entity);