2#include "gaia/config/config.h"
8#include "gaia/cnt/darray.h"
9#include "gaia/cnt/sarray.h"
10#include "gaia/cnt/sarray_ext.h"
11#include "gaia/cnt/set.h"
12#include "gaia/config/profiler.h"
13#include "gaia/core/utility.h"
14#include "gaia/ecs/api.h"
15#include "gaia/ecs/archetype.h"
16#include "gaia/ecs/archetype_common.h"
17#include "gaia/ecs/id.h"
18#include "gaia/ecs/query_common.h"
19#include "gaia/ecs/query_mask.h"
20#include "gaia/ecs/query_match_stamps.h"
21#include "gaia/ser/ser_binary.h"
22#include "gaia/util/str.h"
26 using EntityToArchetypeMap = cnt::map<EntityLookupKey, ComponentIndexEntryArray>;
34 enum class MatchingStyle {
52 const EntityToArchetypeVersionMap*
pVersions =
nullptr;
58 GAIA_NODISCARD
bool empty()
const {
67 GAIA_NODISCARD std::span<const ComponentIndexEntry>
83 return it ==
pVersions->end() ? 0 : it->second;
141 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select(
145 GAIA_ASSERT(key != EntityBadLookupKey);
147 const auto it = map.find(key);
148 if (it == map.end() || it->second.empty())
151 return std::span(it->second.data(), it->second.size());
154 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select(
155 const EntityToArchetypeMap& map, std::span<const Archetype*> arr, Entity ent, Entity src) {
156 GAIA_ASSERT(src != EntityBad);
158 return fetch_archetypes_for_select(map, arr, ent, EntityLookupKey(src));
161 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select(
162 const SingleArchetypeLookup& map, std::span<const Archetype*> arr, Entity ent,
const EntityLookupKey& key) {
164 GAIA_ASSERT(key != EntityBadLookupKey);
166 const auto it = core::find_if(map, [&](
const auto& item) {
167 return item.matches(key);
169 if (it == map.end() || arr.empty())
172 return std::span(&it->entry, 1);
175 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select(
176 const SingleArchetypeLookup& map, std::span<const Archetype*> arr, Entity ent, Entity src) {
177 GAIA_ASSERT(src != EntityBad);
179 return fetch_archetypes_for_select(map, arr, ent, EntityLookupKey(src));
182 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select_from_map(
183 const void* pData, std::span<const Archetype*> arr, Entity ent,
const EntityLookupKey& key) {
184 return fetch_archetypes_for_select(*(
const EntityToArchetypeMap*)pData, arr, ent, key);
187 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select_from_single(
188 const void* pData, std::span<const Archetype*> arr, Entity ent,
const EntityLookupKey& key) {
189 return fetch_archetypes_for_select(*(
const SingleArchetypeLookup*)pData, arr, ent, key);
192 inline ArchetypeLookupView
193 make_archetype_lookup_view(
const EntityToArchetypeMap& map,
const EntityToArchetypeVersionMap& versions) {
194 return ArchetypeLookupView{&map, &versions, fetch_archetypes_for_select_from_map};
197 inline ArchetypeLookupView make_archetype_lookup_view(
const SingleArchetypeLookup& map) {
198 return ArchetypeLookupView{&map,
nullptr, fetch_archetypes_for_select_from_single};
204 enum class EOpcode : uint8_t {
237 Var_Term_All_Src_Bind,
243 Var_Search_SelectAll,
245 Var_Search_SelectOtherOr,
246 Var_Search_SelectOtherOrBind,
248 Var_Search_SelectAny,
249 Var_Search_MaybeFinalize,
251 Var_Final_Require_Or,
256 using VmLabel = uint16_t;
271 struct QueryCompileCtx {
272 struct SourceTermOp {
273 EOpcode opcode = EOpcode::Src_Never;
278 EOpcode sourceOpcode = EOpcode::Src_Never;
292 GAIA_NODISCARD
bool empty()
const {
297 struct VarSearchMeta {
298 uint16_t selectAllPc = (uint16_t)-1;
299 uint16_t selectOrPc = (uint16_t)-1;
300 uint16_t selectOtherOrPc = (uint16_t)-1;
301 uint16_t selectOtherOrBindPc = (uint16_t)-1;
302 uint16_t beginAnyPc = (uint16_t)-1;
303 uint16_t selectAnyPc = (uint16_t)-1;
304 uint16_t maybeFinalizePc = (uint16_t)-1;
305 uint16_t initialAllMask = 0;
306 uint16_t initialOrMask = 0;
307 uint16_t initialAnyMask = 0;
308 uint16_t allBegin = 0;
309 uint16_t allCheckBegin = 0;
310 uint16_t allCount = 0;
311 uint16_t orBegin = 0;
312 uint16_t orCheckBegin = 0;
313 uint16_t orCount = 0;
314 uint16_t anyBegin = 0;
315 uint16_t anyCheckBegin = 0;
316 uint16_t anyCount = 0;
317 uint16_t notBegin = 0;
318 uint16_t notCount = 0;
319 uint8_t orVarMask = 0;
322 struct VarProgramStep {
323 VarProgram program{};
324 VarSearchMeta search{};
327 cnt::darray<CompiledOp> ops;
328 uint16_t mainOpsCount = 0;
330 cnt::sarray_ext<Entity, MAX_ITEMS_IN_QUERY> ids_all;
332 cnt::sarray_ext<Entity, MAX_ITEMS_IN_QUERY> ids_or;
334 cnt::sarray_ext<Entity, MAX_ITEMS_IN_QUERY> ids_not;
336 cnt::sarray_ext<SourceTermOp, MAX_ITEMS_IN_QUERY> terms_all_src;
338 cnt::sarray_ext<SourceTermOp, MAX_ITEMS_IN_QUERY> terms_or_src;
340 cnt::sarray_ext<SourceTermOp, MAX_ITEMS_IN_QUERY> terms_not_src;
342 cnt::sarray_ext<VarTermOp, MAX_ITEMS_IN_QUERY> terms_all_var;
344 cnt::sarray_ext<VarTermOp, MAX_ITEMS_IN_QUERY> terms_or_var;
346 cnt::sarray_ext<VarTermOp, MAX_ITEMS_IN_QUERY> terms_not_var;
348 cnt::sarray_ext<VarTermOp, MAX_ITEMS_IN_QUERY> terms_any_var;
350 cnt::sarray_ext<VarProgramStep, MaxVarCnt> var_programs;
352 uint8_t varMaskAll = 0;
353 uint8_t varMaskOr = 0;
354 uint8_t varMaskNot = 0;
355 uint8_t varMaskAny = 0;
357 GAIA_NODISCARD
bool has_src_terms()
const {
358 return !terms_all_src.empty() || !terms_or_src.empty() || !terms_not_src.empty();
361 GAIA_NODISCARD
bool has_variable_terms()
const {
362 return !terms_all_var.empty() || !terms_or_var.empty() || !terms_not_var.empty() || !terms_any_var.empty();
365 GAIA_NODISCARD
bool has_id_terms()
const {
366 return !ids_all.empty() || !ids_or.empty() || !ids_not.empty();
370 enum class EVarProgramTermSet : uint8_t { None, All, Or, Any, Not };
372 struct VarProgramOpcodeMeta {
373 EVarProgramTermSet termSet;
376 static constexpr auto VarProgramOpcodeFirst = EOpcode::Var_Term_All_Check;
377 static constexpr auto VarProgramOpcodeLast = EOpcode::Var_Final_Success;
378 static constexpr VarProgramOpcodeMeta VarProgramOpcodeMetaTable[] = {
379 {EVarProgramTermSet::All},
380 {EVarProgramTermSet::All},
381 {EVarProgramTermSet::All},
382 {EVarProgramTermSet::Or},
383 {EVarProgramTermSet::Or},
384 {EVarProgramTermSet::Any},
385 {EVarProgramTermSet::Any},
386 {EVarProgramTermSet::Not},
387 {EVarProgramTermSet::None},
388 {EVarProgramTermSet::None},
389 {EVarProgramTermSet::None},
390 {EVarProgramTermSet::None},
391 {EVarProgramTermSet::None},
392 {EVarProgramTermSet::None},
393 {EVarProgramTermSet::None},
394 {EVarProgramTermSet::Not},
395 {EVarProgramTermSet::None},
396 {EVarProgramTermSet::Or},
397 {EVarProgramTermSet::None},
401 sizeof(VarProgramOpcodeMetaTable) /
sizeof(VarProgramOpcodeMetaTable[0]) ==
402 (uint32_t)VarProgramOpcodeLast - (uint32_t)VarProgramOpcodeFirst + 1u,
403 "VarProgramOpcodeMetaTable out of sync with EOpcode variable micro-op range.");
405 GAIA_NODISCARD
inline const VarProgramOpcodeMeta& var_program_opcode_meta(EOpcode opcode) {
406 GAIA_ASSERT((uint32_t)opcode >= (uint32_t)VarProgramOpcodeFirst);
407 GAIA_ASSERT((uint32_t)opcode <= (uint32_t)VarProgramOpcodeLast);
408 return VarProgramOpcodeMetaTable[(uint32_t)opcode - (uint32_t)VarProgramOpcodeFirst];
411 GAIA_NODISCARD
inline uint8_t src_term_cost(
const QueryCompileCtx::SourceTermOp& termOp) {
412 const bool depth1 = termOp.term.travDepth == 1;
413 switch (termOp.opcode) {
414 case EOpcode::Src_Never:
416 case EOpcode::Src_Self:
418 case EOpcode::Src_Up:
419 case EOpcode::Src_Down:
420 return depth1 ? 2 : 4;
421 case EOpcode::Src_UpDown:
422 return depth1 ? 3 : 5;
428 GAIA_NODISCARD
inline uint8_t bound_match_id_cost(Entity queryId) {
430 return (!is_variable(queryId) && queryId.id() != All.
id()) ? 1u : 3u;
433 cost += (!is_variable((EntityId)queryId.id()) && queryId.id() != All.
id()) ? 1u : 3u;
434 cost += (!is_variable((EntityId)queryId.gen()) && queryId.gen() != All.
id()) ? 1u : 3u;
438 GAIA_NODISCARD
inline uint8_t bound_term_cost(
const QueryCompileCtx::VarTermOp& termOp) {
439 uint8_t cost = bound_match_id_cost(termOp.term.id);
440 if (termOp.term.src != EntityBad)
441 cost = (uint8_t)(cost + src_term_cost({termOp.sourceOpcode, termOp.term}));
445 GAIA_NODISCARD
inline uint8_t search_term_cost(
const QueryCompileCtx::VarTermOp& termOp) {
446 uint8_t cost = bound_term_cost(termOp);
447 if (termOp.term.src != EntityBad) {
448 const bool srcIsVar = is_variable(EntityId(termOp.term.src.id()));
449 cost = (uint8_t)(cost + (srcIsVar ? 32u : 8u));
454 template <
typename ProgramOpsArray>
455 inline void sort_program_ops_by_cost(ProgramOpsArray& ops) {
456 const auto cnt = (uint32_t)ops.size();
460 for (uint32_t i = 1; i < cnt; ++i) {
461 const auto key = ops[i];
465 const auto prev = ops[j - 1];
466 if (prev.cost < key.cost)
468 if (prev.cost == key.cost && (uint8_t)prev.opcode < (uint8_t)key.opcode)
470 if (prev.cost == key.cost && prev.opcode == key.opcode && prev.arg <= key.arg)
480 template <
typename SourceTermsArray>
481 inline void sort_src_terms_by_cost(SourceTermsArray& terms) {
482 const auto cnt = (uint32_t)terms.size();
486 for (uint32_t i = 1; i < cnt; ++i) {
488 const auto keyCost = src_term_cost(key);
491 while (j > 0 && src_term_cost(terms[j - 1]) > keyCost) {
492 terms[j] = terms[j - 1];
500 GAIA_NODISCARD
inline std::span<const CompiledOp>
501 program_ops(
const QueryCompileCtx& comp,
const QueryCompileCtx::VarProgram& program) {
502 GAIA_ASSERT((uint32_t)program.begin + (uint32_t)program.count <= (uint32_t)comp.ops.size());
503 return {comp.ops.data() + program.begin, program.count};
506 inline uint32_t handle_last_archetype_match(
507 QueryArchetypeCacheIndexMap* pCont, EntityLookupKey entityKey, uint32_t srcArchetypeCnt,
508 uint32_t srcRevision) {
509 if (pCont ==
nullptr)
512 const auto cache_it = pCont->find(entityKey);
513 uint32_t lastMatchedIdx = 0;
514 if (cache_it == pCont->end())
515 pCont->emplace(entityKey, QueryArchetypeCacheCursor{srcArchetypeCnt, srcRevision});
517 auto& cursor = cache_it->second;
518 if (cursor.revision == srcRevision)
519 lastMatchedIdx = cursor.index;
520 cursor.index = srcArchetypeCnt;
521 cursor.revision = srcRevision;
523 return lastMatchedIdx;
528 static bool check_mask(
const QueryMask& maskArchetype,
const QueryMask& maskQuery) {
529 return match_entity_mask(maskArchetype, maskQuery);
531 static void restart([[maybe_unused]] uint32_t& idx) {}
532 static bool can_continue(
bool hasMatch) {
535 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
536 return expectedMatches == totalMatches;
538 static uint32_t handle_last_match(
539 MatchingCtx& ctx, EntityLookupKey entityKey, uint32_t srcArchetypeCnt, uint32_t srcRevision) {
540 return handle_last_archetype_match(
541 ctx.pLastMatchedArchetypeIdx_All, entityKey, srcArchetypeCnt, srcRevision);
546 static bool check_mask(
const QueryMask& maskArchetype,
const QueryMask& maskQuery) {
547 return match_entity_mask(maskArchetype, maskQuery);
549 static void restart(uint32_t& idx) {
553 static bool can_continue([[maybe_unused]]
bool hasMatch) {
556 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
557 (void)expectedMatches;
558 return totalMatches > 0;
560 static uint32_t handle_last_match(
561 MatchingCtx& ctx, EntityLookupKey entityKey, uint32_t srcArchetypeCnt, uint32_t srcRevision) {
562 return handle_last_archetype_match(
563 ctx.pLastMatchedArchetypeIdx_Or, entityKey, srcArchetypeCnt, srcRevision);
568 static bool check_mask(
const QueryMask& maskArchetype,
const QueryMask& maskQuery) {
569 return !match_entity_mask(maskArchetype, maskQuery);
571 static void restart(uint32_t& idx) {
574 static bool can_continue(
bool hasMatch) {
577 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
578 (void)expectedMatches;
579 return totalMatches == 0;
581 static uint32_t handle_last_match(
582 MatchingCtx& ctx, EntityLookupKey entityKey, uint32_t srcArchetypeCnt, uint32_t srcRevision) {
583 return handle_last_archetype_match(
584 ctx.pLastMatchedArchetypeIdx_Not, entityKey, srcArchetypeCnt, srcRevision);
588 GAIA_NODISCARD
inline bool is_archetype_marked(
const MatchingCtx& ctx,
const Archetype* pArchetype) {
589 GAIA_ASSERT(ctx.pMatchesStampByArchetypeId !=
nullptr);
591 const auto& stamps = *ctx.pMatchesStampByArchetypeId;
592 const auto sid = (uint32_t)pArchetype->id();
593 if (!stamps.has(sid))
596 return stamps.get(sid) == ctx.matchesVersion;
599 inline void mark_archetype_match(MatchingCtx& ctx,
const Archetype* pArchetype) {
600 GAIA_ASSERT(ctx.pMatchesStampByArchetypeId !=
nullptr);
602 auto& stamps = *ctx.pMatchesStampByArchetypeId;
603 const auto sid = (uint32_t)pArchetype->id();
604 stamps.set(sid, ctx.matchesVersion);
606 ctx.pMatchesArr->emplace_back(pArchetype);
609 inline void add_all_archetypes(MatchingCtx& ctx) {
610 for (
const auto* pArchetype: ctx.allArchetypes) {
611 if (is_archetype_marked(ctx, pArchetype))
614 mark_archetype_match(ctx, pArchetype);
618 template <
typename OpKind>
619 inline bool match_inter_eval_matches(uint32_t queryIdMarches, uint32_t& outMatches) {
620 const bool hadAnyMatches = queryIdMarches > 0;
624 if (!OpKind::can_continue(hadAnyMatches))
629 outMatches += (uint32_t)hadAnyMatches;
641 template <
typename OpKind,
typename CmpFunc>
642 GAIA_NODISCARD
inline bool match_inter(EntitySpan queryIds, EntitySpan archetypeIds, CmpFunc func) {
643 const auto archetypeIdsCnt = (uint32_t)archetypeIds.size();
644 const auto queryIdsCnt = (uint32_t)queryIds.size();
647 uint32_t indices[2]{};
648 uint32_t matches = 0;
675 while (indices[0] < queryIdsCnt) {
676 const auto idInQuery = queryIds[indices[0]];
679 if (idInQuery == All || idInQuery.id() == Is.
id())
682 uint32_t queryIdMatches = 0;
683 while (indices[1] < archetypeIdsCnt) {
684 const auto idInArchetype = archetypeIds[indices[1]];
687 const auto res = func(idInQuery, idInArchetype);
697 if (!match_inter_eval_matches<OpKind>(queryIdMatches, matches))
706 if (!match_inter_eval_matches<OpKind>(queryIdMatches, matches))
713 OpKind::restart(indices[1]);
719 return OpKind::eval(queryIdsCnt, matches);
726 GAIA_NODISCARD
inline IdCmpResult cmp_ids(Entity idInQuery, Entity idInArchetype) {
727 return {idInQuery == idInArchetype};
730 GAIA_NODISCARD
inline IdCmpResult cmp_ids_pairs(Entity idInQuery, Entity idInArchetype) {
731 if (idInQuery.pair()) {
733 if (idInQuery == Pair(All, All))
741 if (idInQuery.gen() == All.
id())
742 return {idInQuery.id() == idInArchetype.id()};
749 if (idInQuery.id() == All.
id())
750 return {idInQuery.gen() == idInArchetype.gen()};
754 return cmp_ids(idInQuery, idInArchetype);
757 GAIA_NODISCARD
inline IdCmpResult
758 cmp_ids_is(
const World& w,
const Archetype& archetype, Entity idInQuery, Entity idInArchetype) {
760 if (idInQuery.pair() && idInQuery.id() == Is.
id()) {
761 auto archetypeIds = archetype.ids_view();
763 idInQuery.gen() == idInArchetype.id() ||
764 as_relations_trav_if(w, idInQuery, [&](Entity relation) {
765 const auto idx = core::get_index(archetypeIds, relation);
767 return idx != BadIndex;
772 return cmp_ids(idInQuery, idInArchetype);
775 GAIA_NODISCARD
inline IdCmpResult
776 cmp_ids_is_pairs(
const World& w,
const Archetype& archetype, Entity idInQuery, Entity idInArchetype) {
777 if (idInQuery.pair()) {
779 if (idInQuery == Pair(All, All))
783 if (idInQuery.id() == Is.
id()) {
785 if (idInArchetype == idInQuery)
788 const auto eQ = pair_tgt(w, idInQuery);
789 if (eQ == idInArchetype)
794 if (idInArchetype.id() == Is.
id()) {
795 const auto eA = pair_tgt(w, idInArchetype);
799 return {as_relations_trav_if(w, eQ, [eA](Entity relation) {
800 return eA == relation;
805 auto archetypeIds = archetype.ids_view();
806 return {as_relations_trav_if(w, eQ, [&archetypeIds](Entity relation) {
809 const auto idx = core::get_index(archetypeIds, relation);
811 return idx != BadIndex;
820 if (idInQuery.id() == All.
id()) {
821 if (idInQuery.gen() == idInArchetype.gen())
825 if (archetype.pairs_is() > 0) {
826 auto archetypeIds = archetype.ids_view();
828 const auto e = pair_tgt(w, idInQuery);
829 return {as_relations_trav_if(w, e, [&](Entity relation) {
832 const auto idx = core::get_index(archetypeIds, relation);
834 return idx != BadIndex;
847 if (idInQuery.gen() == All.
id()) {
848 return {idInQuery.id() == idInArchetype.id()};
853 return cmp_ids(idInQuery, idInArchetype);
862 template <
typename OpKind>
863 GAIA_NODISCARD
inline bool match_res(
const Archetype& archetype, EntitySpan queryIds) {
866 if (archetype.pairs() == 0) {
867 return match_inter<OpKind>(
868 queryIds, archetype.ids_view(),
870 [](Entity idInQuery, Entity idInArchetype) {
871 return cmp_ids(idInQuery, idInArchetype);
876 return match_inter<OpKind>(
877 queryIds, archetype.ids_view(),
879 [](Entity idInQuery, Entity idInArchetype) {
880 return cmp_ids_pairs(idInQuery, idInArchetype);
890 template <
typename OpKind>
891 GAIA_NODISCARD
inline bool match_res_as(
const World& w,
const Archetype& archetype, EntitySpan queryIds) {
893 if (archetype.pairs() == 0) {
894 return match_inter<OpKind>(
895 queryIds, archetype.ids_view(),
897 [&](Entity idInQuery, Entity idInArchetype) {
898 return cmp_ids_is(w, archetype, idInQuery, idInArchetype);
902 return match_inter<OpKind>(
903 queryIds, archetype.ids_view(),
905 [&](Entity idInQuery, Entity idInArchetype) {
906 return cmp_ids_is_pairs(w, archetype, idInQuery, idInArchetype);
910 GAIA_NODISCARD
inline bool match_single_id_on_archetype(
const World& w,
const Archetype& archetype, Entity
id) {
911 const Entity ids[1] = {
id};
912 return match_res_as<OpOr>(w, archetype, EntitySpan{ids, 1});
915 GAIA_NODISCARD
inline bool match_single_id_on_archetype_exact(
const Archetype& archetype, Entity
id) {
916 const Entity ids[1] = {
id};
917 return match_res<OpOr>(archetype, EntitySpan{ids, 1});
920 GAIA_NODISCARD
inline EOpcode src_opcode_from_term(
const QueryTerm& term) {
921 const bool includeSelf = query_trav_has(term.travKind, QueryTravKind::Self);
922 const bool includeUp = query_trav_has(term.travKind, QueryTravKind::Up) && term.entTrav != EntityBad;
923 const bool includeDown = query_trav_has(term.travKind, QueryTravKind::Down) && term.entTrav != EntityBad;
924 if (!includeSelf && !includeUp && !includeDown)
925 return EOpcode::Src_Never;
926 if (includeSelf && !includeUp && !includeDown)
927 return EOpcode::Src_Self;
928 if (includeUp && includeDown)
929 return EOpcode::Src_UpDown;
931 return EOpcode::Src_Up;
932 return EOpcode::Src_Down;
935 struct SourceLookupCursor {
936 uint32_t queueIdx = 0;
937 uint32_t childIdx = 0;
938 uint32_t childLevel = 0;
939 uint32_t upDepth = 0;
941 bool initialized =
false;
942 bool selfEmitted =
false;
943 Entity upSource = EntityBad;
944 std::span<const Entity> cachedSources{};
945 cnt::darray<Entity> queue;
946 cnt::darray<uint32_t> levels;
947 cnt::darray<Entity> children;
948 cnt::set<EntityLookupKey> visited;
950 void reset_runtime_state() {
957 upSource = EntityBad;
966 GAIA_NODISCARD
inline bool next_lookup_src_cursor(
967 const World& w, EOpcode opcode,
const QueryTerm& term, Entity sourceEntity, SourceLookupCursor& cursor,
970 template <
typename Func>
971 GAIA_NODISCARD
inline bool
972 each_lookup_src(
const World& w, EOpcode opcode,
const QueryTerm& term, Entity sourceEntity, Func&& func) {
973 SourceLookupCursor cursor{};
974 Entity source = EntityBad;
975 while (next_lookup_src_cursor(w, opcode, term, sourceEntity, cursor, source)) {
983 template <
typename Func>
984 GAIA_NODISCARD
inline bool
985 each_lookup_src(
const World& w,
const QueryTerm& term, Entity sourceEntity, Func&& func) {
986 return each_lookup_src(w, src_opcode_from_term(term), term, sourceEntity, GAIA_FWD(func));
989 GAIA_NODISCARD
inline bool next_lookup_src_cursor_up(
990 const World& w,
const QueryTerm& term, Entity sourceEntity, SourceLookupCursor& cursor, Entity& outSource,
992 if (!valid(w, sourceEntity))
995 const uint32_t maxDepth =
998 if (!cursor.initialized) {
999 cursor.initialized =
true;
1000 cursor.upSource = sourceEntity;
1003 if (includeSelf && !cursor.selfEmitted) {
1004 cursor.selfEmitted =
true;
1005 outSource = sourceEntity;
1009 while (cursor.upDepth < maxDepth) {
1010 const auto next = target(w, cursor.upSource, term.entTrav);
1011 if (next == EntityBad || next == cursor.upSource)
1013 if (!world_entity_enabled(w, next))
1016 cursor.upSource = next;
1025 GAIA_NODISCARD
inline bool next_lookup_src_cursor_down(
1026 const World& w,
const QueryTerm& term, Entity sourceEntity, SourceLookupCursor& cursor, Entity& outSource,
1028 if (!valid(w, sourceEntity))
1031 const uint32_t maxDepth =
1034 if (!cursor.initialized) {
1035 cursor.initialized =
true;
1036 cursor.queue.push_back(sourceEntity);
1037 cursor.levels.push_back(0);
1038 cursor.visited.insert(EntityLookupKey(sourceEntity));
1041 if (includeSelf && !cursor.selfEmitted) {
1042 cursor.selfEmitted =
true;
1043 outSource = sourceEntity;
1048 if (cursor.childIdx < cursor.children.size()) {
1049 const auto child = cursor.children[cursor.childIdx++];
1050 cursor.queue.push_back(child);
1051 cursor.levels.push_back(cursor.childLevel);
1056 bool loadedChildren =
false;
1057 while (cursor.queueIdx < cursor.queue.size()) {
1058 const auto source = cursor.queue[cursor.queueIdx];
1059 const auto level = cursor.levels[cursor.queueIdx];
1061 if (level >= maxDepth)
1064 cursor.children.clear();
1065 cursor.childIdx = 0;
1066 cursor.childLevel = level + 1;
1067 sources(w, term.entTrav, source, [&](Entity next) {
1068 if (!world_entity_enabled(w, next))
1071 const auto key = EntityLookupKey(next);
1072 const auto ins = cursor.visited.insert(key);
1076 cursor.children.push_back(next);
1079 core::sort(cursor.children, [](Entity left, Entity right) {
1080 return left.id() < right.id();
1083 if (!cursor.children.empty()) {
1084 loadedChildren =
true;
1089 if (!loadedChildren)
1094 GAIA_NODISCARD
inline bool next_lookup_src_cursor(
1095 const World& w, EOpcode opcode,
const QueryTerm& term, Entity sourceEntity, SourceLookupCursor& cursor,
1096 Entity& outSource) {
1097 const bool includeSelf = query_trav_has(term.travKind, QueryTravKind::Self);
1098 const bool unlimitedTraversal =
1101 if (unlimitedTraversal && world_enabled_hierarchy_version(w) == 0 &&
1102 (opcode == EOpcode::Src_Up || opcode == EOpcode::Src_Down || opcode == EOpcode::Src_UpDown)) {
1103 if (!valid(w, sourceEntity))
1106 if (!cursor.initialized)
1107 cursor.initialized =
true;
1109 if (includeSelf && !cursor.selfEmitted) {
1110 cursor.selfEmitted =
true;
1111 outSource = sourceEntity;
1115 const auto adv_cached_src = [&](std::span<const Entity> cachedSources) {
1116 if (cursor.cachedSources.data() != cachedSources.data() ||
1117 cursor.cachedSources.size() != cachedSources.size()) {
1118 cursor.cachedSources = cachedSources;
1119 cursor.queueIdx = 0;
1122 if (cursor.queueIdx < cursor.cachedSources.size()) {
1123 outSource = cursor.cachedSources[cursor.queueIdx++];
1130 if (opcode == EOpcode::Src_Up) {
1131 return adv_cached_src(targets_trav_cache(w, term.entTrav, sourceEntity));
1134 if (opcode == EOpcode::Src_Down) {
1135 return adv_cached_src(sources_bfs_trav_cache(w, term.entTrav, sourceEntity));
1138 if (cursor.phase == 0) {
1139 if (adv_cached_src(targets_trav_cache(w, term.entTrav, sourceEntity)))
1143 cursor.cachedSources = {};
1144 cursor.queueIdx = 0;
1147 return adv_cached_src(sources_bfs_trav_cache(w, term.entTrav, sourceEntity));
1151 case EOpcode::Src_Never:
1153 case EOpcode::Src_Self:
1154 if (cursor.initialized || !valid(w, sourceEntity))
1156 cursor.initialized =
true;
1157 outSource = sourceEntity;
1159 case EOpcode::Src_Up:
1160 return next_lookup_src_cursor_up(w, term, sourceEntity, cursor, outSource, includeSelf);
1161 case EOpcode::Src_Down:
1162 return next_lookup_src_cursor_down(w, term, sourceEntity, cursor, outSource, includeSelf);
1163 case EOpcode::Src_UpDown:
1164 if (cursor.phase == 0) {
1165 if (next_lookup_src_cursor_up(w, term, sourceEntity, cursor, outSource, includeSelf))
1167 cursor.reset_runtime_state();
1170 return next_lookup_src_cursor_down(w, term, sourceEntity, cursor, outSource,
false);
1177 GAIA_NODISCARD
inline bool match_src_term(
const World& w,
const QueryTerm& term, EOpcode opcode) {
1178 auto match_src_entity = [&](Entity source) {
1179 if (!valid(w, source))
1182 auto* pArchetype = archetype_from_entity(w, source);
1183 if (pArchetype ==
nullptr)
1186 return match_single_id_on_archetype(w, *pArchetype, term.id);
1189 return each_lookup_src(w, opcode, term, term.src, match_src_entity);
1192 GAIA_NODISCARD
inline bool match_src_term(
const World& w,
const QueryTerm& term) {
1193 return match_src_term(w, term, src_opcode_from_term(term));
1196 struct VarBindings {
1197 cnt::sarray<Entity, MaxVarCnt> values{};
1201 struct VarTermMatchCursor {
1203 uint32_t sourceArchetypeIdx = 0;
1204 uint32_t sourceChunkIdx = 0;
1205 uint32_t sourceEntityIdx = 0;
1206 Entity source = EntityBad;
1207 SourceLookupCursor sourceCursor{};
1210 GAIA_NODISCARD
inline bool is_var_entity(Entity entity) {
1211 return is_variable(EntityId(entity.id()));
1214 GAIA_NODISCARD
inline uint32_t var_index(Entity varEntity) {
1215 GAIA_ASSERT(is_var_entity(varEntity));
1216 return (uint32_t)(varEntity.id() - Var0.
id());
1219 GAIA_NODISCARD
inline bool var_is_bound(
const VarBindings& vars, Entity varEntity) {
1220 const auto idx = var_index(varEntity);
1221 return (vars.mask & (uint8_t(1) << idx)) != 0;
1224 GAIA_NODISCARD
inline bool bind_var(VarBindings& vars, Entity varEntity, Entity value) {
1225 const auto idx = var_index(varEntity);
1226 const auto bit = (uint8_t(1) << idx);
1227 if ((vars.mask & bit) != 0)
1228 return vars.values[idx].id() == value.id();
1230 vars.values[idx] = value;
1235 GAIA_NODISCARD
inline bool match_token(VarBindings& vars, Entity token, Entity value,
bool pairSide) {
1236 if (pairSide && token.id() == All.
id())
1239 if (!is_var_entity(token))
1240 return token.id() == value.id();
1242 return bind_var(vars, token, value);
1245 struct ResolvedPairToken {
1246 Entity token = EntityBad;
1247 Entity matchValue = EntityBad;
1248 bool concrete =
false;
1249 bool needsBind =
false;
1252 struct RawMatchToken {
1253 uint32_t matchId = 0;
1254 uint8_t bindVarIdx = 0xff;
1255 bool concrete =
false;
1256 bool needsBind =
false;
1259 GAIA_NODISCARD
inline ResolvedPairToken resolve_pair_query_token(Entity queryToken,
const VarBindings& vars) {
1260 ResolvedPairToken out{};
1261 out.token = queryToken;
1263 if (queryToken == EntityBad)
1266 if (is_var_entity(queryToken)) {
1267 if (!var_is_bound(vars, queryToken)) {
1268 out.needsBind =
true;
1272 out.matchValue = vars.values[var_index(queryToken)];
1273 out.concrete = out.matchValue.id() != All.
id();
1277 if (queryToken.id() == All.
id())
1280 out.matchValue = queryToken;
1281 out.concrete =
true;
1287 GAIA_NODISCARD
inline RawMatchToken resolve_raw_pair_match_token(Entity queryToken,
const VarBindings& vars) {
1288 RawMatchToken out{};
1290 if (queryToken == EntityBad || queryToken.id() == All.
id())
1293 if (is_var_entity(queryToken)) {
1294 out.bindVarIdx = (uint8_t)var_index(queryToken);
1295 if ((vars.mask & (uint8_t(1) << out.bindVarIdx)) == 0) {
1296 out.needsBind =
true;
1300 out.matchId = vars.values[out.bindVarIdx].id();
1301 out.concrete = out.matchId != All.
id();
1305 out.matchId = queryToken.id();
1306 out.concrete =
true;
1310 GAIA_NODISCARD
inline uint32_t count_pair_id_matches_limited(
1311 const World& w,
const Archetype& archetype, Entity queryId,
const VarBindings& varsIn, uint32_t limit) {
1312 GAIA_ASSERT(limit > 0);
1313 GAIA_ASSERT(queryId.pair());
1315 const auto queryRel = pair_rel(w, queryId);
1316 const auto queryTgt = pair_tgt(w, queryId);
1317 if (queryRel == EntityBad || queryTgt == EntityBad)
1320 const auto rel = resolve_raw_pair_match_token(queryRel, varsIn);
1321 const auto tgt = resolve_raw_pair_match_token(queryTgt, varsIn);
1322 const bool sameUnboundVar = rel.needsBind && tgt.needsBind && rel.bindVarIdx == tgt.bindVarIdx;
1326 if (!rel.needsBind && !tgt.needsBind && !sameUnboundVar) {
1327 const auto matchPair = Pair(
1328 rel.concrete ? Entity((EntityId)rel.matchId, 0,
true,
false, EntityKind::EK_Gen) : All,
1329 tgt.concrete ? Entity((EntityId)tgt.matchId, 0, true, false, EntityKind::EK_Gen) : All);
1330 const auto count = archetype.pair_matches(matchPair);
1331 return count < limit ? count : limit;
1335 auto archetypeIds = archetype.ids_view();
1336 const auto cnt = (uint32_t)archetypeIds.size();
1338 const auto idInArchetype = archetypeIds[i];
1339 if (!idInArchetype.pair())
1341 if (rel.concrete && idInArchetype.id() != rel.matchId)
1343 if (tgt.concrete && idInArchetype.gen() != tgt.matchId)
1345 if (sameUnboundVar && idInArchetype.id() != idInArchetype.gen())
1356 template <
typename Func>
1357 GAIA_NODISCARD
inline bool each_term_match(
1358 const World& w,
const Archetype& candidateArchetype,
const QueryCompileCtx::VarTermOp& termOp,
1359 const VarBindings& varsIn, Func&& func);
1361 GAIA_NODISCARD
inline bool next_id_match_cursor(
1362 const World& w,
const Archetype& archetype, Entity queryId,
const VarBindings& varsIn, uint32_t& idIdx,
1363 VarBindings& outVars) {
1364 auto archetypeIds = archetype.ids_view();
1365 const auto cnt = (uint32_t)archetypeIds.size();
1367 if (!queryId.pair()) {
1368 for (uint32_t i = idIdx; i < cnt; ++i) {
1369 const auto idInArchetype = archetypeIds[i];
1370 if (idInArchetype.pair())
1373 const auto value = id_entity(w, idInArchetype);
1374 if (value == EntityBad)
1378 if (!match_token(vars, queryId, value,
false))
1389 const auto queryRel = pair_rel(w, queryId);
1390 const auto queryTgt = pair_tgt(w, queryId);
1391 if (queryRel == EntityBad || queryTgt == EntityBad)
1393 const auto rel = resolve_pair_query_token(queryRel, varsIn);
1394 const auto tgt = resolve_pair_query_token(queryTgt, varsIn);
1396 for (uint32_t i = idIdx; i < cnt; ++i) {
1397 const auto idInArchetype = archetypeIds[i];
1398 if (!idInArchetype.pair())
1401 if (rel.concrete && idInArchetype.id() != rel.matchValue.id())
1403 if (tgt.concrete && idInArchetype.gen() != tgt.matchValue.id())
1407 if (rel.needsBind) {
1408 const auto relValue = pair_rel(w, idInArchetype);
1409 if (relValue == EntityBad)
1411 if (!match_token(vars, rel.token, relValue,
true))
1414 if (tgt.needsBind) {
1415 const auto tgtValue = pair_tgt(w, idInArchetype);
1416 if (tgtValue == EntityBad)
1418 if (!match_token(vars, tgt.token, tgtValue,
true))
1430 GAIA_NODISCARD
inline bool next_term_match_cursor(
1431 const World& w,
const Archetype& archetype,
const QueryCompileCtx::VarTermOp& termOp,
1432 const VarBindings& varsIn, VarTermMatchCursor& cursor, VarBindings& outVars) {
1433 const auto& term = termOp.term;
1434 if (term.src == EntityBad)
1435 return next_id_match_cursor(w, archetype, term.id, varsIn, cursor.idIdx, outVars);
1437 auto sourceEntity = term.src;
1438 if (is_var_entity(sourceEntity)) {
1439 if (!var_is_bound(varsIn, sourceEntity))
1441 sourceEntity = varsIn.values[var_index(sourceEntity)];
1445 if (cursor.source != EntityBad) {
1446 auto* pSrcArchetype = archetype_from_entity(w, cursor.source);
1447 if (pSrcArchetype !=
nullptr &&
1448 next_id_match_cursor(w, *pSrcArchetype, term.id, varsIn, cursor.idIdx, outVars))
1452 cursor.source = EntityBad;
1455 Entity nextSource = EntityBad;
1456 if (!next_lookup_src_cursor(w, termOp.sourceOpcode, term, sourceEntity, cursor.sourceCursor, nextSource))
1459 cursor.source = nextSource;
1463 GAIA_NODISCARD
inline bool term_has_match_bound(
1464 const World& w,
const Archetype& candidateArchetype,
const QueryCompileCtx::VarTermOp& termOp,
1465 const VarBindings& vars);
1467 GAIA_NODISCARD
inline bool term_has_match(
1468 const World& w,
const Archetype& archetype,
const QueryCompileCtx::VarTermOp& termOp,
1469 const VarBindings& varsIn) {
1470 if ((uint8_t)(termOp.varMask & ~varsIn.mask) == 0)
1471 return term_has_match_bound(w, archetype, termOp, varsIn);
1473 return each_term_match(w, archetype, termOp, varsIn, [&](
const VarBindings&) {
1478 GAIA_NODISCARD
inline uint32_t count_term_matches_limited(
1479 const World& w,
const Archetype& archetype,
const QueryCompileCtx::VarTermOp& termOp,
1480 const VarBindings& varsIn, uint32_t limit) {
1481 GAIA_ASSERT(limit > 0);
1483 if ((uint8_t)(termOp.varMask & ~varsIn.mask) == 0)
1484 return term_has_match_bound(w, archetype, termOp, varsIn) ? 1u : 0u;
1486 if (termOp.term.src == EntityBad && termOp.term.id.pair())
1487 return count_pair_id_matches_limited(w, archetype, termOp.term.id, varsIn, limit);
1490 (void)each_term_match(w, archetype, termOp, varsIn, [&](
const VarBindings&) {
1492 return count >= limit;
1497 GAIA_NODISCARD
inline bool has_concrete_match_id(Entity queryId) {
1498 if (!queryId.pair())
1499 return !is_variable(queryId) && queryId.id() != All.
id();
1501 return !is_variable((EntityId)queryId.id()) && queryId.id() != All.
id() &&
1502 !is_variable((EntityId)queryId.gen()) && queryId.gen() != All.
id();
1505 GAIA_NODISCARD
inline bool
1506 match_id_bound(
const World& w,
const Archetype& archetype, Entity queryId,
const VarBindings& vars) {
1507 auto archetypeIds = archetype.ids_view();
1508 const auto cnt = (uint32_t)archetypeIds.size();
1510 if (!queryId.pair()) {
1511 Entity queryToken = queryId;
1512 if (is_var_entity(queryToken)) {
1513 if (!var_is_bound(vars, queryToken))
1515 queryToken = vars.values[var_index(queryToken)];
1519 const auto idInArchetype = archetypeIds[i];
1520 if (idInArchetype.pair())
1523 const auto value = id_entity(w, idInArchetype);
1524 if (value == EntityBad)
1526 if (queryToken.id() != value.id())
1535 auto queryRel = pair_rel(w, queryId);
1536 auto queryTgt = pair_tgt(w, queryId);
1537 if (queryRel == EntityBad || queryTgt == EntityBad)
1540 if (is_var_entity(queryRel)) {
1541 if (!var_is_bound(vars, queryRel))
1543 queryRel = vars.values[var_index(queryRel)];
1546 if (is_var_entity(queryTgt)) {
1547 if (!var_is_bound(vars, queryTgt))
1549 queryTgt = vars.values[var_index(queryTgt)];
1552 const bool relIsConcrete = queryRel.id() != All.
id();
1553 const bool tgtIsConcrete = queryTgt.id() != All.
id();
1555 if (relIsConcrete || tgtIsConcrete) {
1557 archetype.pair_matches(Pair(relIsConcrete ? queryRel : All, tgtIsConcrete ? queryTgt : All));
1561 if (relIsConcrete && tgtIsConcrete)
1566 const auto idInArchetype = archetypeIds[i];
1567 if (!idInArchetype.pair())
1570 if (relIsConcrete && idInArchetype.id() != queryRel.id())
1572 if (tgtIsConcrete && idInArchetype.gen() != queryTgt.id())
1575 if (!relIsConcrete) {
1576 const auto rel = pair_rel(w, idInArchetype);
1577 if (rel == EntityBad)
1580 if (!tgtIsConcrete) {
1581 const auto tgt = pair_tgt(w, idInArchetype);
1582 if (tgt == EntityBad)
1592 GAIA_NODISCARD
inline bool next_self_src_var_match_cursor(
1593 const MatchingCtx& ctx,
const QueryCompileCtx::VarTermOp& termOp,
const VarBindings& varsIn,
1594 VarTermMatchCursor& cursor, VarBindings& outVars) {
1595 GAIA_ASSERT(ctx.pWorld !=
nullptr);
1596 GAIA_ASSERT(is_var_entity(termOp.term.src));
1597 GAIA_ASSERT(!var_is_bound(varsIn, termOp.term.src));
1598 GAIA_ASSERT(termOp.sourceOpcode == EOpcode::Src_Self);
1600 const auto adv_matches = [&](std::span<const ComponentIndexEntry> sourceRecords,
bool idsPreFiltered) {
1601 for (; cursor.sourceArchetypeIdx < sourceRecords.size(); ++cursor.sourceArchetypeIdx) {
1602 const auto* pSrcArchetype = sourceRecords[cursor.sourceArchetypeIdx].pArchetype;
1603 if (pSrcArchetype ==
nullptr)
1605 if (!idsPreFiltered && !match_single_id_on_archetype(*ctx.pWorld, *pSrcArchetype, termOp.term.id))
1608 const auto& chunks = pSrcArchetype->chunks();
1609 for (; cursor.sourceChunkIdx < chunks.size(); ++cursor.sourceChunkIdx) {
1610 const auto* pChunk = chunks[cursor.sourceChunkIdx];
1611 if (pChunk ==
nullptr || pChunk->empty())
1614 const auto entities = pChunk->entity_view();
1615 for (; cursor.sourceEntityIdx < entities.size(); ++cursor.sourceEntityIdx) {
1616 const auto entity = entities[cursor.sourceEntityIdx];
1618 if (!bind_var(vars, termOp.term.src, entity))
1622 ++cursor.sourceEntityIdx;
1626 cursor.sourceEntityIdx = 0;
1629 cursor.sourceChunkIdx = 0;
1635 const auto adv_matches_all = [&](std::span<const Archetype*> sourceArchetypes) {
1636 for (; cursor.sourceArchetypeIdx < sourceArchetypes.size(); ++cursor.sourceArchetypeIdx) {
1637 const auto* pSrcArchetype = sourceArchetypes[cursor.sourceArchetypeIdx];
1638 if (pSrcArchetype ==
nullptr)
1640 if (!match_single_id_on_archetype(*ctx.pWorld, *pSrcArchetype, termOp.term.id))
1643 const auto& chunks = pSrcArchetype->chunks();
1644 for (; cursor.sourceChunkIdx < chunks.size(); ++cursor.sourceChunkIdx) {
1645 const auto* pChunk = chunks[cursor.sourceChunkIdx];
1646 if (pChunk ==
nullptr || pChunk->empty())
1649 const auto entities = pChunk->entity_view();
1650 for (; cursor.sourceEntityIdx < entities.size(); ++cursor.sourceEntityIdx) {
1651 const auto entity = entities[cursor.sourceEntityIdx];
1653 if (!bind_var(vars, termOp.term.src, entity))
1657 ++cursor.sourceEntityIdx;
1661 cursor.sourceEntityIdx = 0;
1664 cursor.sourceChunkIdx = 0;
1670 if (!ctx.archetypeLookup.empty()) {
1671 const auto sourceArchetypes =
1672 ctx.archetypeLookup.fetch(ctx.allArchetypes, termOp.term.id, EntityLookupKey(termOp.term.id));
1673 if (adv_matches(sourceArchetypes,
true))
1675 }
else if (adv_matches_all(ctx.allArchetypes))
1681 GAIA_NODISCARD
inline bool next_src_var_match_cursor_inverse(
1682 const MatchingCtx& ctx,
const QueryCompileCtx::VarTermOp& termOp,
const VarBindings& varsIn,
1683 VarTermMatchCursor& cursor, VarBindings& outVars, EOpcode inverseOpcode) {
1684 GAIA_ASSERT(ctx.pWorld !=
nullptr);
1685 GAIA_ASSERT(is_var_entity(termOp.term.src));
1686 GAIA_ASSERT(!var_is_bound(varsIn, termOp.term.src));
1688 inverseOpcode == EOpcode::Src_Up || inverseOpcode == EOpcode::Src_Down ||
1689 inverseOpcode == EOpcode::Src_UpDown);
1691 const auto adv_matches = [&](std::span<const ComponentIndexEntry> sourceRecords,
bool idsPreFiltered) {
1692 for (; cursor.sourceArchetypeIdx < sourceRecords.size(); ++cursor.sourceArchetypeIdx) {
1693 const auto* pSrcArchetype = sourceRecords[cursor.sourceArchetypeIdx].pArchetype;
1694 if (pSrcArchetype ==
nullptr)
1696 if (!idsPreFiltered && !match_single_id_on_archetype(*ctx.pWorld, *pSrcArchetype, termOp.term.id))
1699 const auto& chunks = pSrcArchetype->chunks();
1700 for (; cursor.sourceChunkIdx < chunks.size(); ++cursor.sourceChunkIdx) {
1701 const auto* pChunk = chunks[cursor.sourceChunkIdx];
1702 if (pChunk ==
nullptr || pChunk->empty())
1705 const auto entities = pChunk->entity_view();
1706 while (cursor.sourceEntityIdx < entities.size()) {
1707 if (cursor.source == EntityBad) {
1708 cursor.source = entities[cursor.sourceEntityIdx];
1709 cursor.sourceCursor = {};
1712 Entity candidate = EntityBad;
1713 if (next_lookup_src_cursor(
1714 *ctx.pWorld, inverseOpcode, termOp.term, cursor.source, cursor.sourceCursor, candidate)) {
1716 if (!bind_var(vars, termOp.term.src, candidate))
1723 cursor.source = EntityBad;
1724 ++cursor.sourceEntityIdx;
1727 cursor.sourceEntityIdx = 0;
1730 cursor.sourceChunkIdx = 0;
1736 const auto adv_matches_all = [&](std::span<const Archetype*> sourceArchetypes) {
1737 for (; cursor.sourceArchetypeIdx < sourceArchetypes.size(); ++cursor.sourceArchetypeIdx) {
1738 const auto* pSrcArchetype = sourceArchetypes[cursor.sourceArchetypeIdx];
1739 if (pSrcArchetype ==
nullptr)
1741 if (!match_single_id_on_archetype(*ctx.pWorld, *pSrcArchetype, termOp.term.id))
1744 const auto& chunks = pSrcArchetype->chunks();
1745 for (; cursor.sourceChunkIdx < chunks.size(); ++cursor.sourceChunkIdx) {
1746 const auto* pChunk = chunks[cursor.sourceChunkIdx];
1747 if (pChunk ==
nullptr || pChunk->empty())
1750 const auto entities = pChunk->entity_view();
1751 while (cursor.sourceEntityIdx < entities.size()) {
1752 if (cursor.source == EntityBad) {
1753 cursor.source = entities[cursor.sourceEntityIdx];
1754 cursor.sourceCursor = {};
1757 Entity candidate = EntityBad;
1758 if (next_lookup_src_cursor(
1759 *ctx.pWorld, inverseOpcode, termOp.term, cursor.source, cursor.sourceCursor, candidate)) {
1761 if (!bind_var(vars, termOp.term.src, candidate))
1768 cursor.source = EntityBad;
1769 ++cursor.sourceEntityIdx;
1772 cursor.sourceEntityIdx = 0;
1775 cursor.sourceChunkIdx = 0;
1781 if (!ctx.archetypeLookup.empty()) {
1782 const auto sourceArchetypes =
1783 ctx.archetypeLookup.fetch(ctx.allArchetypes, termOp.term.id, EntityLookupKey(termOp.term.id));
1784 if (adv_matches(sourceArchetypes,
true))
1786 }
else if (adv_matches_all(ctx.allArchetypes))
1792 GAIA_NODISCARD
inline bool next_up_src_var_match_cursor(
1793 const MatchingCtx& ctx,
const QueryCompileCtx::VarTermOp& termOp,
const VarBindings& varsIn,
1794 VarTermMatchCursor& cursor, VarBindings& outVars) {
1795 GAIA_ASSERT(ctx.pWorld !=
nullptr);
1796 GAIA_ASSERT(is_var_entity(termOp.term.src));
1797 GAIA_ASSERT(!var_is_bound(varsIn, termOp.term.src));
1798 GAIA_ASSERT(termOp.sourceOpcode == EOpcode::Src_Up);
1799 return next_src_var_match_cursor_inverse(ctx, termOp, varsIn, cursor, outVars, EOpcode::Src_Down);
1802 GAIA_NODISCARD
inline bool next_down_src_var_match_cursor(
1803 const MatchingCtx& ctx,
const QueryCompileCtx::VarTermOp& termOp,
const VarBindings& varsIn,
1804 VarTermMatchCursor& cursor, VarBindings& outVars) {
1805 GAIA_ASSERT(termOp.sourceOpcode == EOpcode::Src_Down);
1806 return next_src_var_match_cursor_inverse(ctx, termOp, varsIn, cursor, outVars, EOpcode::Src_Up);
1809 GAIA_NODISCARD
inline bool next_updown_src_var_match_cursor(
1810 const MatchingCtx& ctx,
const QueryCompileCtx::VarTermOp& termOp,
const VarBindings& varsIn,
1811 VarTermMatchCursor& cursor, VarBindings& outVars) {
1812 GAIA_ASSERT(termOp.sourceOpcode == EOpcode::Src_UpDown);
1813 return next_src_var_match_cursor_inverse(ctx, termOp, varsIn, cursor, outVars, EOpcode::Src_UpDown);
1816 GAIA_NODISCARD
inline bool next_term_match_cursor(
1817 const MatchingCtx& ctx,
const Archetype& archetype,
const QueryCompileCtx::VarTermOp& termOp,
1818 const VarBindings& varsIn, VarTermMatchCursor& cursor, VarBindings& outVars) {
1819 const auto& term = termOp.term;
1820 const bool hasUnboundVar =
1821 term.src != EntityBad && is_var_entity(term.src) && !var_is_bound(varsIn, term.src);
1822 if (hasUnboundVar && termOp.sourceOpcode == EOpcode::Src_Self) {
1823 return next_self_src_var_match_cursor(ctx, termOp, varsIn, cursor, outVars);
1825 if (hasUnboundVar && termOp.sourceOpcode == EOpcode::Src_Up) {
1826 return next_up_src_var_match_cursor(ctx, termOp, varsIn, cursor, outVars);
1828 if (hasUnboundVar && termOp.sourceOpcode == EOpcode::Src_Down) {
1829 return next_down_src_var_match_cursor(ctx, termOp, varsIn, cursor, outVars);
1831 if (hasUnboundVar && termOp.sourceOpcode == EOpcode::Src_UpDown) {
1832 return next_updown_src_var_match_cursor(ctx, termOp, varsIn, cursor, outVars);
1835 return next_term_match_cursor(*ctx.pWorld, archetype, termOp, varsIn, cursor, outVars);
1838 GAIA_NODISCARD
inline bool term_has_match_bound(
1839 const World& w,
const Archetype& candidateArchetype,
const QueryCompileCtx::VarTermOp& termOp,
1840 const VarBindings& vars) {
1841 const auto& term = termOp.term;
1842 auto match_on_archetype = [&](
const Archetype& archetype) {
1843 return match_id_bound(w, archetype, term.id, vars);
1846 if (term.src == EntityBad)
1847 return match_on_archetype(candidateArchetype);
1849 auto sourceEntity = term.src;
1850 if (is_var_entity(sourceEntity)) {
1851 if (!var_is_bound(vars, sourceEntity))
1853 sourceEntity = vars.values[var_index(sourceEntity)];
1856 return each_lookup_src(w, termOp.sourceOpcode, term, sourceEntity, [&](Entity source) {
1857 auto* pSrcArchetype = archetype_from_entity(w, source);
1858 if (pSrcArchetype == nullptr)
1860 if (!match_on_archetype(*pSrcArchetype))
1867 template <
typename Func>
1868 GAIA_NODISCARD
inline bool each_id_match(
1869 const World& w,
const Archetype& archetype, Entity queryId,
const VarBindings& varsIn, Func&& func) {
1870 auto archetypeIds = archetype.ids_view();
1871 const auto cnt = (uint32_t)archetypeIds.size();
1873 if (!queryId.pair()) {
1875 const auto idInArchetype = archetypeIds[i];
1876 if (idInArchetype.pair())
1879 const auto value = id_entity(w, idInArchetype);
1880 if (value == EntityBad)
1884 if (!match_token(vars, queryId, value,
false))
1893 const auto queryRel = pair_rel(w, queryId);
1894 const auto queryTgt = pair_tgt(w, queryId);
1895 if (queryRel == EntityBad || queryTgt == EntityBad)
1897 const auto rel = resolve_pair_query_token(queryRel, varsIn);
1898 const auto tgt = resolve_pair_query_token(queryTgt, varsIn);
1901 const auto idInArchetype = archetypeIds[i];
1902 if (!idInArchetype.pair())
1905 if (rel.concrete && idInArchetype.id() != rel.matchValue.id())
1907 if (tgt.concrete && idInArchetype.gen() != tgt.matchValue.id())
1910 if (!rel.needsBind && !tgt.needsBind) {
1917 if (rel.needsBind) {
1918 const auto relValue = pair_rel(w, idInArchetype);
1919 if (relValue == EntityBad)
1921 if (!match_token(vars, rel.token, relValue,
true))
1924 if (tgt.needsBind) {
1925 const auto tgtValue = pair_tgt(w, idInArchetype);
1926 if (tgtValue == EntityBad)
1928 if (!match_token(vars, tgt.token, tgtValue,
true))
1939 template <
typename Func>
1940 GAIA_NODISCARD
inline bool each_term_match(
1941 const World& w,
const Archetype& candidateArchetype,
const QueryCompileCtx::VarTermOp& termOp,
1942 const VarBindings& varsIn, Func&& func) {
1943 const auto& term = termOp.term;
1944 auto&& matchFunc = GAIA_FWD(func);
1945 auto each_on_src = [&](Entity sourceEntity,
const VarBindings& vars) {
1946 return each_lookup_src(w, termOp.sourceOpcode, term, sourceEntity, [&](Entity source) {
1947 auto* pSrcArchetype = archetype_from_entity(w, source);
1948 if (pSrcArchetype == nullptr)
1951 return each_id_match(w, *pSrcArchetype, term.id, vars, matchFunc);
1955 if (term.src == EntityBad)
1956 return each_id_match(w, candidateArchetype, term.id, varsIn, matchFunc);
1958 if (is_var_entity(term.src)) {
1959 if (!var_is_bound(varsIn, term.src))
1962 const auto source = varsIn.values[var_index(term.src)];
1963 return each_on_src(source, varsIn);
1966 return each_on_src(term.src, varsIn);
1969 template <
typename OpKind, MatchingStyle Style>
1970 inline void match_archetype_inter(MatchingCtx& ctx, std::span<const ComponentIndexEntry> records) {
1971 if constexpr (Style != MatchingStyle::Complex) {
1972 if (ctx.idsToMatch.size() == 1) {
1973 for (
const auto& entry: records) {
1974 const auto* pArchetype = entry.pArchetype;
1975 if (is_archetype_marked(ctx, pArchetype))
1977#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
1978 if constexpr (Style == MatchingStyle::Simple) {
1979 if (!OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask))
1983 mark_archetype_match(ctx, pArchetype);
1989 if constexpr (Style == MatchingStyle::Complex) {
1990 for (
const auto& record: records) {
1991 const auto* pArchetype = record.pArchetype;
1992 if (is_archetype_marked(ctx, pArchetype))
1995 if (!match_res_as<OpKind>(*ctx.pWorld, *pArchetype, ctx.idsToMatch))
1998 mark_archetype_match(ctx, pArchetype);
2001#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
2002 else if constexpr (Style == MatchingStyle::Simple) {
2003 for (
const auto& record: records) {
2004 const auto* pArchetype = record.pArchetype;
2005 if (is_archetype_marked(ctx, pArchetype))
2009 if (!OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask))
2012 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
2015 mark_archetype_match(ctx, pArchetype);
2020 for (
const auto& record: records) {
2021 const auto* pArchetype = record.pArchetype;
2022 if (is_archetype_marked(ctx, pArchetype))
2025 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
2028 mark_archetype_match(ctx, pArchetype);
2033 template <
typename OpKind, MatchingStyle Style>
2034 inline void match_archetype_inter(MatchingCtx& ctx, std::span<const Archetype*> archetypes) {
2035 if constexpr (Style == MatchingStyle::Complex) {
2036 for (
const auto* pArchetype: archetypes) {
2037 if (is_archetype_marked(ctx, pArchetype))
2040 if (!match_res_as<OpKind>(*ctx.pWorld, *pArchetype, ctx.idsToMatch))
2043 mark_archetype_match(ctx, pArchetype);
2046#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
2047 else if constexpr (Style == MatchingStyle::Simple) {
2048 for (
const auto* pArchetype: archetypes) {
2049 if (is_archetype_marked(ctx, pArchetype))
2052 if (!OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask))
2055 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
2058 mark_archetype_match(ctx, pArchetype);
2063 for (
const auto* pArchetype: archetypes) {
2064 if (is_archetype_marked(ctx, pArchetype))
2067 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
2070 mark_archetype_match(ctx, pArchetype);
2075 template <
typename OpKind, MatchingStyle Style>
2076 inline void match_archetype_inter(
2077 MatchingCtx& ctx, EntityLookupKey entityKey, std::span<const ComponentIndexEntry> records) {
2078 const uint32_t lookupRevision = ctx.archetypeLookup.revision(entityKey);
2079 uint32_t lastMatchedIdx = OpKind::handle_last_match(ctx, entityKey, (uint32_t)records.size(), lookupRevision);
2080 if (lastMatchedIdx >= records.size())
2083 auto recordsNew = std::span(&records[lastMatchedIdx], records.size() - lastMatchedIdx);
2084 match_archetype_inter<OpKind, Style>(ctx, recordsNew);
2087 template <
typename OpKind, MatchingStyle Style>
2089 match_archetype_inter(MatchingCtx& ctx, EntityLookupKey entityKey, std::span<const Archetype*> archetypes) {
2090 const uint32_t lookupRevision = ctx.archetypeLookup.revision(entityKey);
2091 uint32_t lastMatchedIdx =
2092 OpKind::handle_last_match(ctx, entityKey, (uint32_t)archetypes.size(), lookupRevision);
2093 if (lastMatchedIdx >= archetypes.size())
2096 auto archetypesNew = std::span(&archetypes[lastMatchedIdx], archetypes.size() - lastMatchedIdx);
2097 match_archetype_inter<OpKind, Style>(ctx, archetypesNew);
2100 template <MatchingStyle Style>
2101 inline void match_archetype_all(MatchingCtx& ctx) {
2102 if constexpr (Style == MatchingStyle::Complex) {
2105 if (ctx.ent.id() == Is.
id()) {
2106 ctx.ent = EntityBad;
2107 match_archetype_inter<OpAll, Style>(ctx, EntityBadLookupKey, ctx.allArchetypes);
2109 auto entityKey = EntityLookupKey(ctx.ent);
2111 auto archetypes = ctx.archetypeLookup.fetch(ctx.allArchetypes, ctx.ent, entityKey);
2112 if (archetypes.empty())
2115 match_archetype_inter<OpAll, Style>(ctx, entityKey, archetypes);
2118 auto entityKey = EntityLookupKey(ctx.ent);
2122 auto archetypes = ctx.archetypeLookup.fetch(ctx.allArchetypes, ctx.ent, entityKey);
2123 if (archetypes.empty())
2126 match_archetype_inter<OpAll, Style>(ctx, entityKey, archetypes);
2130 template <MatchingStyle Style>
2131 inline void match_archetype_or(MatchingCtx& ctx) {
2132 EntityLookupKey entityKey(ctx.ent);
2137 auto archetypes = ctx.archetypeLookup.fetch(ctx.allArchetypes, ctx.ent, entityKey);
2138 if (archetypes.empty())
2141 match_archetype_inter<OpOr, Style>(ctx, entityKey, archetypes);
2144 inline void match_archetype_or_as(MatchingCtx& ctx) {
2145 EntityLookupKey entityKey = EntityBadLookupKey;
2150 if (ctx.ent.id() == Is.
id()) {
2151 ctx.ent = EntityBad;
2152 match_archetype_inter<OpOr, MatchingStyle::Complex>(ctx, entityKey, ctx.allArchetypes);
2154 entityKey = EntityLookupKey(ctx.ent);
2156 auto archetypes = ctx.archetypeLookup.fetch(ctx.allArchetypes, ctx.ent, entityKey);
2157 if (archetypes.empty())
2160 match_archetype_inter<OpOr, MatchingStyle::Complex>(ctx, entityKey, archetypes);
2164 template <MatchingStyle Style>
2165 inline void match_archetype_no_2(MatchingCtx& ctx) {
2169 if constexpr (Style == MatchingStyle::Complex) {
2170 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2171 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2173 if (match_res_as<OpNo>(*ctx.pWorld, *pArchetype, ctx.idsToMatch)) {
2178 core::swap_erase(*ctx.pMatchesArr, i);
2181#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
2182 else if constexpr (Style == MatchingStyle::Simple) {
2183 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2184 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2187 if (OpNo::check_mask(pArchetype->queryMask(), ctx.queryMask))
2190 if (match_res<OpNo>(*pArchetype, ctx.idsToMatch)) {
2195 core::swap_erase(*ctx.pMatchesArr, i);
2200 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2201 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2203 if (match_res<OpNo>(*pArchetype, ctx.idsToMatch)) {
2208 core::swap_erase(*ctx.pMatchesArr, i);
2213 template <
typename OpKind, MatchingStyle Style,
bool WildcardWithAsFallback = false>
2214 inline void filter_current_matches(MatchingCtx& ctx, EntitySpan idsToMatch) {
2215 if constexpr (Style == MatchingStyle::Complex) {
2216 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2217 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2218 if (match_res_as<OpKind>(*ctx.pWorld, *pArchetype, idsToMatch)) {
2223 core::swap_erase(*ctx.pMatchesArr, i);
2226#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
2227 else if constexpr (Style == MatchingStyle::Simple) {
2228 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2229 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2230 if (OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask) &&
2231 match_res<OpKind>(*pArchetype, idsToMatch)) {
2236 core::swap_erase(*ctx.pMatchesArr, i);
2241 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2242 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2243 if (match_res<OpKind>(*pArchetype, idsToMatch) ||
2244 (WildcardWithAsFallback && match_res_as<OpKind>(*ctx.pWorld, *pArchetype, idsToMatch))) {
2249 core::swap_erase(*ctx.pMatchesArr, i);
2254 template <MatchingStyle Style>
2255 GAIA_NODISCARD
inline bool exec_not_impl(
const QueryCompileCtx& comp, MatchingCtx& ctx) {
2256 ctx.idsToMatch = std::span{comp.ids_not.data(), comp.ids_not.size()};
2258 if (ctx.targetEntities.empty()) {
2260 if (ctx.pMatchesArr->empty()) {
2263 match_archetype_inter<detail::OpNo, Style>(ctx, EntityBadLookupKey, ctx.allArchetypes);
2265 match_archetype_no_2<Style>(ctx);
2269 if (ctx.pMatchesArr->empty())
2270 match_archetype_inter<detail::OpNo, Style>(ctx, ctx.allArchetypes);
2272 match_archetype_no_2<Style>(ctx);
2278 template <MatchingStyle Style>
2279 GAIA_NODISCARD
inline bool exec_all_impl(
const QueryCompileCtx& comp, MatchingCtx& ctx) {
2280 ctx.ent = comp.ids_all[0];
2281 ctx.idsToMatch = std::span{comp.ids_all.data(), comp.ids_all.size()};
2283 if (ctx.targetEntities.empty())
2284 match_archetype_all<Style>(ctx);
2286 match_archetype_inter<OpAll, Style>(ctx, ctx.allArchetypes);
2289 return !ctx.pMatchesArr->empty();
2292 template <MatchingStyle Style>
2293 GAIA_NODISCARD
inline bool exec_or_noall_impl(
const QueryCompileCtx& comp, MatchingCtx& ctx) {
2297 const auto cnt = comp.ids_or.size();
2300 ctx.ent = comp.ids_or[i];
2301 const Entity idsToMatchData[1] = {ctx.ent};
2302 ctx.idsToMatch = EntitySpan{idsToMatchData, 1};
2304 if constexpr (Style == MatchingStyle::Complex)
2305 match_archetype_or_as(ctx);
2307 match_archetype_or<Style>(ctx);
2313 template <MatchingStyle Style>
2314 GAIA_NODISCARD
inline bool exec_or_withall_impl(
const QueryCompileCtx& comp, MatchingCtx& ctx) {
2318 ctx.idsToMatch = std::span{comp.ids_or.data(), comp.ids_or.size()};
2320 if constexpr (Style == MatchingStyle::Complex)
2321 filter_current_matches<OpOr, MatchingStyle::Complex>(ctx, ctx.idsToMatch);
2322 else if constexpr (Style == MatchingStyle::Simple)
2323 filter_current_matches<OpOr, MatchingStyle::Simple>(ctx, ctx.idsToMatch);
2325 filter_current_matches<OpOr, MatchingStyle::Wildcard, true>(ctx, ctx.idsToMatch);
2330 template <
typename SourceTermsArray>
2331 GAIA_NODISCARD
inline const QueryCompileCtx::SourceTermOp&
2332 get_src_term_op(
const QueryCompileCtx& comp,
const MatchingCtx& ctx,
const SourceTermsArray& terms) {
2333 const auto& stackItem = comp.ops[ctx.pc];
2334 GAIA_ASSERT(stackItem.arg < terms.size());
2335 return terms[stackItem.arg];
2344 static constexpr uint32_t OpcodeArgLimit = 256u;
2346 MAX_ITEMS_IN_QUERY <= OpcodeArgLimit,
2347 "CompiledOp::arg is uint8_t. Increase arg width if query term capacity grows above 256.");
2349 detail::QueryCompileCtx m_compCtx;
2352 static const char* opcode_name(detail::EOpcode opcode) {
2353 static const char* s_names[] = {
2378 "term_all_src_bind",
2387 "search_other_or_bind",
2390 "search_maybe_finalize",
2397 sizeof(s_names) /
sizeof(s_names[0]) == (uint32_t)detail::EOpcode::Var_Final_Success + 1u,
2398 "Opcode name table out of sync with EOpcode.");
2399 return s_names[(uint32_t)opcode];
2402 GAIA_NODISCARD
static bool opcode_has_arg(detail::EOpcode opcode) {
2403 return opcode == detail::EOpcode::Src_AllTerm ||
2404 opcode == detail::EOpcode::Src_NotTerm ||
2405 opcode == detail::EOpcode::Src_OrTerm;
2408 static void add_uint(
util::str& out, uint32_t value) {
2410 const auto len = GAIA_STRFMT(buffer,
sizeof(buffer),
"%u", value);
2411 GAIA_ASSERT(len >= 0);
2412 out.
append(buffer, (uint32_t)len);
2415 static void add_cstr(
util::str& out,
const char* value) {
2416 GAIA_ASSERT(value !=
nullptr);
2417 out.
append(value, (uint32_t)GAIA_STRLEN(value, 64));
2420 static void add_id_expr(
util::str& out,
const World& world, EntityId
id) {
2421 if (is_variable(
id)) {
2423 add_uint(out, (uint32_t)(
id - Var0.
id()));
2427 if (
id == All.
id()) {
2432 const auto entity = entity_from_id(world,
id);
2433 if (entity != EntityBad)
2434 add_entity_expr(out, world, entity);
2437 add_uint(out, (uint32_t)
id);
2442 if (entity == EntityBad) {
2447 if (entity.
pair()) {
2449 add_id_expr(out, world, (EntityId)entity.
id());
2451 add_id_expr(out, world, (EntityId)entity.
gen());
2456 if (is_variable(EntityId(entity.
id()))) {
2458 add_uint(out, (uint32_t)(entity.
id() - Var0.
id()));
2462 if (entity.
id() == All.
id()) {
2467 const auto name = entity_name(world, entity);
2468 if (!name.empty()) {
2469 out.
append(name.data(), name.size());
2473 add_uint(out, entity.
id());
2475 add_uint(out, entity.
gen());
2479 add_entity_expr(out, world, term.
id);
2481 if (term.
src == EntityBad)
2484 add_entity_expr(out, world, term.
src);
2487 if (term.
entTrav != EntityBad) {
2489 add_entity_expr(out, world, term.
entTrav);
2494 add_uint(out, (uint32_t)term.
travDepth);
2499 add_ids_section(
util::str& out,
const char* title, std::span<const Entity> ids,
const World& world) {
2503 add_cstr(out, title);
2505 add_uint(out, (uint32_t)ids.
size());
2508 const auto cnt = (uint32_t)ids.size();
2513 add_entity_expr(out, world, ids[i]);
2518 static void add_src_terms_section(
2521 const World& world) {
2525 add_cstr(out, title);
2527 add_uint(out, (uint32_t)terms.
size());
2530 const auto cnt = (uint32_t)terms.
size();
2535 add_cstr(out, opcode_name(terms[i].opcode));
2537 add_term_expr(out, world, terms[i].term);
2542 static void add_var_terms_section(
2548 add_cstr(out, title);
2550 add_uint(out, (uint32_t)terms.
size());
2553 const auto cnt = (uint32_t)terms.
size();
2558 add_cstr(out, opcode_name(terms[i].sourceOpcode));
2560 add_term_expr(out, world, terms[i].term);
2566 var_program_op_term(
const detail::QueryCompileCtx& comp,
const detail::CompiledOp& op) {
2567 switch (detail::var_program_opcode_meta(op.opcode).termSet) {
2568 case detail::EVarProgramTermSet::None:
2570 return comp.terms_all_var[0].term;
2571 case detail::EVarProgramTermSet::Or:
2572 return comp.terms_or_var[(uint32_t)op.arg].term;
2573 case detail::EVarProgramTermSet::Any:
2574 return comp.terms_any_var[(uint32_t)op.arg].term;
2575 case detail::EVarProgramTermSet::Not:
2576 return comp.terms_not_var[(uint32_t)op.arg].term;
2577 case detail::EVarProgramTermSet::All:
2579 return comp.terms_all_var[(uint32_t)op.arg].term;
2583 static void add_var_program_ops_section(
2584 util::str& out,
const char* title, std::span<const detail::CompiledOp> ops,
2585 const detail::QueryCompileCtx& comp,
const World& world) {
2589 add_cstr(out, title);
2591 add_uint(out, (uint32_t)ops.
size());
2594 const auto cnt = (uint32_t)ops.size();
2596 const auto& op = ops[i];
2600 add_cstr(out, opcode_name(op.opcode));
2601 if (detail::var_program_opcode_meta(op.opcode).termSet != detail::EVarProgramTermSet::None) {
2603 add_uint(out, (uint32_t)op.arg);
2605 add_uint(out, (uint32_t)op.cost);
2607 add_term_expr(out, world, var_program_op_term(comp, op));
2610 add_uint(out, (uint32_t)op.pc_ok);
2612 add_uint(out, (uint32_t)op.pc_fail);
2617 static void add_var_program_exec_section(
util::str& out,
const detail::QueryCompileCtx& comp) {
2618 if (comp.var_programs.empty())
2621 out.
append(
"var_exec: ");
2622 add_uint(out, (uint32_t)comp.var_programs.
size());
2625 const auto cnt = (uint32_t)comp.var_programs.size();
2634 static void add_var_program_sections(
util::str& out,
const detail::QueryCompileCtx& comp,
const World& world) {
2635 const auto cnt = (uint32_t)comp.var_programs.size();
2637 const auto& step = comp.var_programs[i];
2639 [[maybe_unused]]
const auto len = GAIA_STRFMT(title,
sizeof(title),
"varp%u", i);
2640 GAIA_ASSERT(len > 0);
2641 add_var_program_ops_section(out, title, detail::program_ops(comp, step.program), comp, world);
2646 GAIA_NODISCARD
static detail::VarBindings make_initial_var_bindings(
const MatchingCtx& ctx) {
2647 detail::VarBindings vars{};
2649 GAIA_FOR(MaxVarCnt) {
2650 const auto bit = (uint8_t(1) << i);
2651 if ((vars.mask & bit) == 0)
2658 GAIA_NODISCARD
static uint8_t
2659 term_unbound_var_mask(
const World& world,
const QueryTerm& term,
const detail::VarBindings& vars) {
2662 if (detail::is_var_entity(term.
src) && !detail::var_is_bound(vars, term.
src))
2663 mask |= (uint8_t(1) << detail::var_index(term.
src));
2666 const auto idEnt = id_entity(world, term.
id);
2667 if (detail::is_var_entity(idEnt) && !detail::var_is_bound(vars, idEnt))
2668 mask |= (uint8_t(1) << detail::var_index(idEnt));
2672 const auto relEnt = pair_rel(world, term.
id);
2673 if (detail::is_var_entity(relEnt) && !detail::var_is_bound(vars, relEnt))
2674 mask |= (uint8_t(1) << detail::var_index(relEnt));
2676 const auto tgtEnt = pair_tgt(world, term.
id);
2677 if (detail::is_var_entity(tgtEnt) && !detail::var_is_bound(vars, tgtEnt))
2678 mask |= (uint8_t(1) << detail::var_index(tgtEnt));
2683 GAIA_NODISCARD
bool eval_variable_terms_program_on_archetype(
2685 GAIA_ASSERT(m_compCtx.var_programs.size() == 1);
2686 const auto& programStep = m_compCtx.var_programs[0];
2687 return match_search_program_on_archetype(ctx, archetype, programStep, orAlreadySatisfied);
2690 GAIA_NODISCARD
const detail::QueryCompileCtx::VarTermOp&
2691 search_program_term_op(
const detail::CompiledOp& op)
const {
2692 switch (op.opcode) {
2693 case detail::EOpcode::Var_Term_Or_Check:
2694 case detail::EOpcode::Var_Term_Or_Bind:
2695 case detail::EOpcode::Var_Final_Or_Check:
2696 return m_compCtx.terms_or_var[(uint32_t)op.arg];
2697 case detail::EOpcode::Var_Term_Any_Check:
2698 case detail::EOpcode::Var_Term_Any_Bind:
2699 return m_compCtx.terms_any_var[(uint32_t)op.arg];
2700 case detail::EOpcode::Var_Term_Not:
2701 case detail::EOpcode::Var_Final_Not_Check:
2702 return m_compCtx.terms_not_var[(uint32_t)op.arg];
2703 case detail::EOpcode::Var_Term_All_Check:
2704 case detail::EOpcode::Var_Term_All_Bind:
2705 case detail::EOpcode::Var_Term_All_Src_Bind:
2706 return m_compCtx.terms_all_var[(uint32_t)op.arg];
2709 return m_compCtx.terms_all_var[0];
2713 GAIA_NODISCARD
bool select_next_pending_search_all_term(
2714 std::span<const detail::CompiledOp> programOps,
const detail::QueryCompileCtx::VarSearchMeta& search,
2715 uint16_t pendingMask,
const detail::VarBindings& vars, uint32_t& outLocalIdx, uint32_t& outPc,
2716 bool preferBoundTerms =
true)
const {
2717 outLocalIdx = (uint32_t)-1;
2718 outPc = (uint32_t)-1;
2719 uint32_t firstReadyLocalIdx = (uint32_t)-1;
2720 uint32_t firstReadyPc = (uint32_t)-1;
2722 for (uint32_t localIdx = 0; localIdx < search.allCount; ++localIdx) {
2723 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
2724 if ((pendingMask & bit) == 0)
2727 const auto bindPc = (uint32_t)search.allBegin + localIdx;
2728 const auto& bindOp = programOps[bindPc];
2729 const auto& termOp = search_program_term_op(bindOp);
2730 if (detail::is_var_entity(termOp.term.src) && !detail::var_is_bound(vars, termOp.term.src) &&
2731 bindOp.opcode != detail::EOpcode::Var_Term_All_Src_Bind)
2734 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~vars.mask) != 0;
2735 const auto pc = bindsNewVars ? bindPc : (uint32_t)search.allCheckBegin + localIdx;
2736 if (preferBoundTerms && !bindsNewVars) {
2737 outLocalIdx = localIdx;
2742 if (firstReadyLocalIdx == (uint32_t)-1) {
2743 firstReadyLocalIdx = localIdx;
2748 if (firstReadyLocalIdx == (uint32_t)-1)
2751 outLocalIdx = firstReadyLocalIdx;
2752 outPc = firstReadyPc;
2756 GAIA_NODISCARD
bool select_next_pending_search_or_term(
2757 std::span<const detail::CompiledOp> programOps,
const detail::QueryCompileCtx::VarSearchMeta& search,
2758 uint16_t pendingMask, uint16_t pendingCheckMask,
const detail::VarBindings& vars,
bool preferBoundTerms,
2759 bool requireNewBindings, uint32_t& outLocalIdx, uint32_t& outPc)
const {
2760 outLocalIdx = (uint32_t)-1;
2761 outPc = (uint32_t)-1;
2762 if (requireNewBindings && (uint8_t)(search.orVarMask & ~vars.mask) == 0)
2765 uint32_t firstReadyLocalIdx = (uint32_t)-1;
2766 uint32_t firstReadyPc = (uint32_t)-1;
2768 for (uint32_t localIdx = 0; localIdx < search.orCount; ++localIdx) {
2769 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
2770 if ((pendingMask & bit) == 0)
2773 const auto bindPc = (uint32_t)search.orBegin + localIdx;
2774 const auto& bindOp = programOps[bindPc];
2775 const auto& termOp = search_program_term_op(bindOp);
2776 if (detail::is_var_entity(termOp.term.src) && !detail::var_is_bound(vars, termOp.term.src))
2779 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~vars.mask) != 0;
2780 if (requireNewBindings) {
2783 outLocalIdx = localIdx;
2788 if (!bindsNewVars && (pendingCheckMask & bit) == 0)
2791 const auto pc = bindsNewVars ? bindPc : (uint32_t)search.orCheckBegin + localIdx;
2792 if (preferBoundTerms && !bindsNewVars) {
2793 outLocalIdx = localIdx;
2798 if (firstReadyLocalIdx == (uint32_t)-1) {
2799 firstReadyLocalIdx = localIdx;
2804 if (firstReadyLocalIdx == (uint32_t)-1)
2807 outLocalIdx = firstReadyLocalIdx;
2808 outPc = firstReadyPc;
2812 GAIA_NODISCARD
bool select_next_pending_search_any_term(
2813 std::span<const detail::CompiledOp> programOps,
const detail::QueryCompileCtx::VarSearchMeta& search,
2814 uint16_t pendingMask,
const detail::VarBindings& vars, uint32_t& outLocalIdx, uint32_t& outPc)
const {
2815 outLocalIdx = (uint32_t)-1;
2816 outPc = (uint32_t)-1;
2817 uint32_t firstReadyBindingLocalIdx = (uint32_t)-1;
2818 uint32_t firstReadyBindingPc = (uint32_t)-1;
2820 for (uint32_t localIdx = 0; localIdx < search.anyCount; ++localIdx) {
2821 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
2822 if ((pendingMask & bit) == 0)
2825 const auto bindPc = (uint32_t)search.anyBegin + localIdx;
2826 const auto& bindOp = programOps[bindPc];
2827 const auto& termOp = search_program_term_op(bindOp);
2828 if (detail::is_var_entity(termOp.term.src) && !detail::var_is_bound(vars, termOp.term.src))
2831 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~vars.mask) != 0;
2832 if (!bindsNewVars) {
2833 outLocalIdx = localIdx;
2834 outPc = (uint32_t)search.anyCheckBegin + localIdx;
2838 if (firstReadyBindingLocalIdx == (uint32_t)-1) {
2839 firstReadyBindingLocalIdx = localIdx;
2840 firstReadyBindingPc = bindPc;
2844 if (firstReadyBindingLocalIdx == (uint32_t)-1)
2847 outLocalIdx = firstReadyBindingLocalIdx;
2848 outPc = firstReadyBindingPc;
2852 GAIA_NODISCARD int32_t select_best_pending_search_term(
2853 const MatchingCtx& ctx,
const Archetype& archetype, std::span<const detail::CompiledOp> programOps,
2854 uint16_t begin, uint16_t count, uint16_t pendingMask,
const detail::VarBindings& vars,
2855 uint32_t& outBestIdx)
const {
2856 constexpr uint32_t MatchProbeLimit = 64;
2857 outBestIdx = (uint32_t)-1;
2858 uint32_t bestMatchCnt = MatchProbeLimit + 1;
2860 for (uint32_t localIdx = 0; localIdx < count; ++localIdx) {
2861 const auto i = (uint32_t)begin + localIdx;
2862 const auto bit = (uint16_t)(uint16_t(1) << i);
2863 if ((pendingMask & bit) == 0)
2866 const auto& termOp = search_program_term_op(programOps[i]);
2867 if (detail::is_var_entity(termOp.term.src) && !detail::var_is_bound(vars, termOp.term.src))
2870 const auto matchCnt =
2871 detail::count_term_matches_limited(*ctx.
pWorld, archetype, termOp, vars, bestMatchCnt);
2875 if (outBestIdx == (uint32_t)-1 || matchCnt < bestMatchCnt) {
2877 bestMatchCnt = matchCnt;
2878 if (bestMatchCnt == 1)
2883 return outBestIdx == (uint32_t)-1 ? 0 : 1;
2886 GAIA_NODISCARD
bool can_skip_pending_search_all(
2887 std::span<const detail::CompiledOp> programOps,
const detail::QueryCompileCtx::VarSearchMeta& search,
2888 uint16_t pendingAllMask,
const detail::VarBindings& vars)
const {
2889 const auto anyVarMask = m_compCtx.varMaskAny;
2890 for (uint32_t localIdx = 0; localIdx < search.allCount; ++localIdx) {
2891 const auto i = (uint32_t)search.allBegin + localIdx;
2892 const auto bit = (uint16_t(1) << i);
2893 if ((pendingAllMask & bit) == 0)
2896 const auto& termOp = search_program_term_op(programOps[i]);
2897 const auto missingMask = (uint8_t)(termOp.varMask & ~vars.mask);
2898 if (missingMask == 0)
2900 if ((missingMask & ~anyVarMask) != 0)
2907 static inline constexpr uint16_t BacktrackPC = (uint16_t)-1;
2909 GAIA_NODISCARD
bool match_search_program_on_archetype(
2911 const detail::QueryCompileCtx::VarProgramStep& programStep,
bool orAlreadySatisfied)
const {
2912 using namespace detail;
2914 struct SearchProgramState {
2916 uint16_t pendingAllMask = 0;
2917 uint16_t pendingOrMask = 0;
2918 uint16_t pendingFinalOrMask = 0;
2919 uint16_t pendingAnyMask = 0;
2920 uint16_t pc = BacktrackPC;
2921 uint8_t termOpIdx = 0xff;
2922 uint8_t bestOrIdx = 0xff;
2923 uint8_t scanIdx = 0;
2924 bool orMatched =
false;
2925 bool anyMatched =
false;
2926 bool currentAnyMatched =
false;
2929 struct SearchBacktrackFrame {
2930 SearchProgramState state{};
2931 VarBindings varsBase{};
2932 VarTermMatchCursor cursor{};
2935 const auto& program = programStep.program;
2936 const auto& search = programStep.search;
2937 const auto programOps = detail::program_ops(m_compCtx, program);
2938 if (programOps.empty())
2940 GAIA_ASSERT(search.selectAllPc != BacktrackPC);
2941 GAIA_ASSERT(search.selectOrPc != BacktrackPC);
2942 GAIA_ASSERT(search.selectOtherOrPc != BacktrackPC);
2943 GAIA_ASSERT(search.selectOtherOrBindPc != BacktrackPC);
2944 GAIA_ASSERT(search.beginAnyPc != BacktrackPC);
2945 GAIA_ASSERT(search.selectAnyPc != BacktrackPC);
2946 GAIA_ASSERT(search.maybeFinalizePc != BacktrackPC);
2948 const auto is_term_ready = [&](
const detail::CompiledOp& op,
const VarBindings& vars) {
2949 const auto& termOp = search_program_term_op(op);
2950 return !is_var_entity(termOp.term.src) || var_is_bound(vars, termOp.term.src) ||
2951 op.opcode == EOpcode::Var_Term_All_Src_Bind;
2954 const auto can_binding_satisfy_pending_or = [&](
const SearchProgramState& state,
2955 const VarBindings& nextVars) {
2956 if (state.orMatched || search.orCount == 0 || state.pendingOrMask == 0)
2959 bool hasDeferredOr =
false;
2960 for (uint32_t localIdx = 0; localIdx < search.orCount; ++localIdx) {
2961 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
2962 if ((state.pendingOrMask & bit) == 0)
2965 const auto bindPc = (uint32_t)search.orBegin + localIdx;
2966 const auto& bindOp = programOps[bindPc];
2967 const auto& termOp = search_program_term_op(bindOp);
2968 const auto missingMaskBefore = (uint8_t)(termOp.varMask & ~state.vars.mask);
2969 const auto missingMaskAfter = (uint8_t)(termOp.varMask & ~nextVars.mask);
2970 if (missingMaskAfter != 0) {
2971 hasDeferredOr =
true;
2975 if (missingMaskBefore == 0 && (state.pendingFinalOrMask & bit) == 0)
2978 if (term_has_match(*ctx.
pWorld, archetype, termOp, nextVars))
2982 return hasDeferredOr;
2985 const auto adv_after_search_term_success = [&](SearchProgramState& state,
const detail::CompiledOp& op,
2986 VarBindings nextVars) {
2987 const auto bit = (uint16_t)(uint16_t(1) << state.termOpIdx);
2988 state.vars = nextVars;
2989 switch (op.opcode) {
2990 case EOpcode::Var_Term_All_Check:
2991 case EOpcode::Var_Term_All_Bind:
2992 case EOpcode::Var_Term_All_Src_Bind:
2993 state.pendingAllMask = (uint16_t)(state.pendingAllMask & ~bit);
2994 state.pc = op.pc_ok;
2996 case EOpcode::Var_Term_Or_Check:
2997 case EOpcode::Var_Term_Or_Bind:
2998 state.pendingOrMask = (uint16_t)(state.pendingOrMask & ~(uint16_t(1) << state.termOpIdx));
2999 state.pendingFinalOrMask = (uint16_t)(state.pendingFinalOrMask & ~(uint16_t(1) << state.termOpIdx));
3000 state.orMatched =
true;
3001 state.pc = op.pc_ok;
3003 case EOpcode::Var_Term_Any_Check:
3004 case EOpcode::Var_Term_Any_Bind:
3005 state.pendingAnyMask = (uint16_t)(state.pendingAnyMask & ~(uint16_t(1) << state.termOpIdx));
3006 state.anyMatched =
true;
3007 state.currentAnyMatched =
true;
3008 state.pc = op.pc_ok;
3012 state.pc = BacktrackPC;
3017 const auto handle_search_term_exhausted = [&](SearchProgramState& state,
const detail::CompiledOp& op) {
3018 if (op.opcode == EOpcode::Var_Term_Any_Check || op.opcode == EOpcode::Var_Term_Any_Bind) {
3019 state.pendingAnyMask = (uint16_t)(state.pendingAnyMask & ~(uint16_t(1) << state.termOpIdx));
3021 state.pc = op.pc_fail;
3024 const auto try_enter_search_term = [&](SearchProgramState& state,
3026 const auto& op = programOps[state.pc];
3027 const auto& termOp = search_program_term_op(op);
3028 SearchBacktrackFrame frame{};
3029 frame.state = state;
3030 frame.varsBase = state.vars;
3032 VarBindings nextVars{};
3034 if (!detail::next_term_match_cursor(ctx, archetype, termOp, frame.varsBase, frame.cursor, nextVars))
3036 if (can_binding_satisfy_pending_or(state, nextVars))
3040 if (op.opcode == EOpcode::Var_Term_Any_Check || op.opcode == EOpcode::Var_Term_Any_Bind) {
3041 frame.state.anyMatched =
true;
3042 frame.state.currentAnyMatched =
true;
3045 stack.push_back(GAIA_MOV(frame));
3046 adv_after_search_term_success(state, op, nextVars);
3050 const auto backtrack = [&](SearchProgramState& state,
3052 while (!stack.empty()) {
3053 auto& frame = stack.back();
3054 const auto resumeState = frame.state;
3055 const auto& op = programOps[resumeState.pc];
3056 const auto& termOp = search_program_term_op(op);
3057 VarBindings nextVars{};
3059 if (detail::next_term_match_cursor(ctx, archetype, termOp, frame.varsBase, frame.cursor, nextVars)) {
3060 if (op.opcode == EOpcode::Var_Term_Any_Check || op.opcode == EOpcode::Var_Term_Any_Bind) {
3061 frame.state.anyMatched =
true;
3062 frame.state.currentAnyMatched =
true;
3065 state = frame.state;
3066 adv_after_search_term_success(state, op, nextVars);
3071 state = resumeState;
3072 handle_search_term_exhausted(state, op);
3073 if (state.pc != BacktrackPC)
3081 SearchProgramState state{};
3082 state.vars = make_initial_var_bindings(ctx);
3083 state.pendingAllMask = search.initialAllMask;
3084 state.pendingOrMask = search.initialOrMask;
3085 state.pendingFinalOrMask = search.initialOrMask;
3086 state.pendingAnyMask = search.initialAnyMask;
3087 state.pc = search.selectAllPc;
3090 if (state.pc == BacktrackPC) {
3091 if (!backtrack(state, stack))
3095 const auto& op = programOps[state.pc];
3096 switch (op.opcode) {
3097 case EOpcode::Var_Search_SelectAll: {
3098 if (state.pendingAllMask == 0) {
3099 state.pc = op.pc_fail;
3103 if (search.orCount == 0 && search.anyCount == 0) {
3104 uint32_t nextAllLocalIdx = (uint32_t)-1;
3105 uint32_t nextAllPc = (uint32_t)-1;
3106 if (select_next_pending_search_all_term(
3107 programOps, search, state.pendingAllMask, state.vars, nextAllLocalIdx, nextAllPc)) {
3108 const auto bindPc = (uint32_t)search.allBegin + nextAllLocalIdx;
3109 if (nextAllPc != bindPc) {
3110 state.termOpIdx = (uint8_t)nextAllLocalIdx;
3111 state.pc = (uint16_t)nextAllPc;
3116 uint32_t bestAllIdx = (uint32_t)-1;
3117 const auto allSel = select_best_pending_search_term(
3118 ctx, archetype, programOps, search.allBegin, search.allCount, state.pendingAllMask, state.vars,
3121 if (!backtrack(state, stack))
3127 state.termOpIdx = (uint8_t)bestAllIdx;
3128 state.pc = (uint16_t)bestAllIdx;
3132 uint32_t nextAllLocalIdx = (uint32_t)-1;
3133 uint32_t nextAllPc = (uint32_t)-1;
3134 if (select_next_pending_search_all_term(
3135 programOps, search, state.pendingAllMask, state.vars, nextAllLocalIdx, nextAllPc)) {
3136 state.termOpIdx = (uint8_t)nextAllLocalIdx;
3137 state.pc = (uint16_t)nextAllPc;
3142 state.pc = op.pc_fail;
3145 case EOpcode::Var_Search_SelectOr: {
3146 if (!state.orMatched && search.anyCount == 0 && (uint8_t)(search.orVarMask & ~state.vars.mask) == 0) {
3147 state.bestOrIdx = 0xff;
3149 state.pc = search.maybeFinalizePc;
3153 if (state.orMatched && (uint8_t)(search.orVarMask & ~state.vars.mask) == 0) {
3154 state.bestOrIdx = 0xff;
3156 state.pc = search.beginAnyPc;
3160 uint32_t nextOrLocalIdx = (uint32_t)-1;
3161 uint32_t nextOrPc = (uint32_t)-1;
3162 if (select_next_pending_search_or_term(
3163 programOps, search, state.pendingOrMask, state.pendingFinalOrMask, state.vars, !state.orMatched,
3164 state.orMatched, nextOrLocalIdx, nextOrPc)) {
3165 state.bestOrIdx = (uint8_t)nextOrLocalIdx;
3167 state.termOpIdx = state.bestOrIdx;
3168 state.pc = (uint16_t)nextOrPc;
3172 state.bestOrIdx = 0xff;
3174 state.pc = op.pc_fail;
3177 case EOpcode::Var_Search_SelectOtherOr: {
3178 if (state.orMatched && (uint8_t)(search.orVarMask & ~state.vars.mask) == 0) {
3180 state.pc = search.beginAnyPc;
3185 while (state.scanIdx < search.orCount) {
3186 const auto localIdx = (uint32_t)state.scanIdx++;
3187 if (localIdx == state.bestOrIdx)
3190 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
3191 if ((state.pendingOrMask & bit) == 0)
3193 const auto bindPc = (uint32_t)search.orBegin + localIdx;
3194 if (!is_term_ready(programOps[bindPc], state.vars))
3197 const bool bindsNewVars =
3198 (uint8_t)(search_program_term_op(programOps[bindPc]).varMask & ~state.vars.mask) != 0;
3199 if (state.orMatched) {
3203 if (!bindsNewVars && (state.pendingFinalOrMask & bit) == 0)
3209 state.termOpIdx = (uint8_t)localIdx;
3210 state.pc = (uint16_t)((uint32_t)search.orCheckBegin + localIdx);
3217 state.pc = op.pc_fail;
3221 case EOpcode::Var_Search_SelectOtherOrBind: {
3222 if (state.orMatched) {
3223 state.pc = op.pc_fail;
3228 for (uint32_t localIdx = state.scanIdx; localIdx < search.orCount; ++localIdx) {
3229 if (localIdx == state.bestOrIdx)
3232 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
3233 if ((state.pendingOrMask & bit) == 0)
3236 const auto bindPc = (uint32_t)search.orBegin + localIdx;
3237 if (!is_term_ready(programOps[bindPc], state.vars))
3240 const auto& termOp = search_program_term_op(programOps[bindPc]);
3241 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~state.vars.mask) != 0;
3245 state.scanIdx = (uint8_t)(localIdx + 1u);
3246 state.termOpIdx = (uint8_t)localIdx;
3247 state.pc = (uint16_t)bindPc;
3253 state.pc = op.pc_fail;
3256 case EOpcode::Var_Search_BeginAny:
3257 state.anyMatched =
false;
3259 state.currentAnyMatched =
false;
3260 state.pc = op.pc_ok;
3262 case EOpcode::Var_Search_SelectAny: {
3263 uint32_t nextAnyLocalIdx = (uint32_t)-1;
3264 uint32_t nextAnyPc = (uint32_t)-1;
3265 const bool found = select_next_pending_search_any_term(
3266 programOps, search, state.pendingAnyMask, state.vars, nextAnyLocalIdx, nextAnyPc);
3268 state.termOpIdx = (uint8_t)nextAnyLocalIdx;
3269 state.currentAnyMatched =
false;
3270 state.pc = (uint16_t)nextAnyPc;
3275 state.pc = op.pc_fail;
3278 case EOpcode::Var_Search_MaybeFinalize:
3279 if (!state.anyMatched &&
3280 can_skip_pending_search_all(programOps, search, state.pendingAllMask, state.vars))
3281 state.pc = op.pc_ok;
3282 else if (op.pc_fail != BacktrackPC)
3283 state.pc = op.pc_fail;
3284 else if (!backtrack(state, stack))
3287 case EOpcode::Var_Term_All_Check:
3288 if (term_has_match(*ctx.
pWorld, archetype, search_program_term_op(op), state.vars))
3289 adv_after_search_term_success(state, op, state.vars);
3291 handle_search_term_exhausted(state, op);
3292 if (state.pc == BacktrackPC && !backtrack(state, stack))
3296 case EOpcode::Var_Term_All_Bind:
3297 case EOpcode::Var_Term_All_Src_Bind:
3298 if (!try_enter_search_term(state, stack)) {
3299 handle_search_term_exhausted(state, op);
3300 if (state.pc == BacktrackPC && !backtrack(state, stack))
3304 case EOpcode::Var_Term_Or_Check:
3305 case EOpcode::Var_Term_Or_Bind:
3306 case EOpcode::Var_Term_Any_Check:
3307 case EOpcode::Var_Term_Any_Bind: {
3308 const auto& termOp = search_program_term_op(op);
3309 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~state.vars.mask) != 0;
3310 if (!bindsNewVars) {
3311 if (term_has_match(*ctx.
pWorld, archetype, termOp, state.vars))
3312 adv_after_search_term_success(state, op, state.vars);
3314 if (op.opcode == EOpcode::Var_Term_Or_Check || op.opcode == EOpcode::Var_Term_Or_Bind)
3315 state.pendingFinalOrMask =
3316 (uint16_t)(state.pendingFinalOrMask & ~(uint16_t(1) << state.termOpIdx));
3317 handle_search_term_exhausted(state, op);
3318 if (state.pc == BacktrackPC && !backtrack(state, stack))
3324 if (!try_enter_search_term(state, stack)) {
3325 handle_search_term_exhausted(state, op);
3326 if (state.pc == BacktrackPC && !backtrack(state, stack))
3331 case EOpcode::Var_Final_Not_Check:
3332 if (term_has_match(*ctx.
pWorld, archetype, search_program_term_op(op), state.vars)) {
3333 if (!backtrack(state, stack))
3336 state.pc = op.pc_ok;
3338 case EOpcode::Var_Final_Require_Or:
3339 if (orAlreadySatisfied || state.orMatched || search.orCount == 0)
3340 state.pc = op.pc_ok;
3341 else if (op.pc_fail != BacktrackPC)
3342 state.pc = op.pc_fail;
3343 else if (!backtrack(state, stack))
3346 case EOpcode::Var_Final_Or_Check:
3347 if ((state.pendingFinalOrMask & (uint16_t(1) << op.arg)) == 0)
3348 state.pc = op.pc_fail;
3349 else if (term_has_match(*ctx.
pWorld, archetype, search_program_term_op(op), state.vars))
3350 state.pc = op.pc_ok;
3352 state.pendingFinalOrMask = (uint16_t)(state.pendingFinalOrMask & ~(uint16_t(1) << op.arg));
3353 if (op.pc_fail != BacktrackPC)
3354 state.pc = op.pc_fail;
3355 else if (!backtrack(state, stack))
3359 case EOpcode::Var_Final_Success:
3369 void filter_variable_terms(
MatchingCtx& ctx, VarEvalFunc evalFunc)
const {
3370 if (!m_compCtx.has_variable_terms())
3373 const bool orAlreadySatisfied = !m_compCtx.ids_or.empty() || ctx.
skipOr;
3375 constexpr uint32_t FilterChunkSize = 64;
3377 uint32_t writeIdx = 0;
3379 const auto flush_filtered = [&]() {
3380 for (
const auto* pFiltered: filtered) {
3386 GAIA_FOR(sourceCnt) {
3388 const bool matched = (this->*evalFunc)(ctx, *pArchetype, orAlreadySatisfied);
3393 if (filtered.
size() != FilterChunkSize)
3399 if (!filtered.
empty())
3404 GAIA_NODISCARD detail::VmLabel add_op(detail::CompiledOp&& op) {
3405 const auto cnt = (detail::VmLabel)m_compCtx.ops.size();
3407 op.pc_fail = cnt - 1;
3408 m_compCtx.ops.push_back(GAIA_MOV(op));
3412 GAIA_NODISCARD detail::VmLabel add_gate_op(detail::CompiledOp&& op) {
3413 const auto cnt = add_op(GAIA_MOV(op));
3414 m_compCtx.ops[cnt].pc_fail = (detail::VmLabel)-1;
3418 GAIA_NODISCARD
static uint8_t opcode_arg(uint32_t idx) {
3419 GAIA_ASSERT(idx < OpcodeArgLimit);
3420 return (uint8_t)idx;
3423 template <
typename SourceTermsArray>
3424 void emit_src_gate_terms(
const SourceTermsArray& terms, detail::EOpcode opcode) {
3425 const auto cnt = (uint32_t)terms.
size();
3427 detail::CompiledOp op{};
3429 op.arg = opcode_arg(i);
3430 (void)add_gate_op(GAIA_MOV(op));
3434 void emit_src_or_terms(
bool hasOrFallback) {
3437 const auto srcOrCnt = (uint32_t)m_compCtx.terms_or_src.
size();
3438 GAIA_FOR(srcOrCnt) {
3439 detail::CompiledOp op{};
3440 op.opcode = detail::EOpcode::Src_OrTerm;
3441 op.arg = opcode_arg(i);
3442 orSrcOpLabels.
push_back(add_op(GAIA_MOV(op)));
3445 const auto orExitPc = (detail::VmLabel)m_compCtx.ops.
size();
3446 for (
const auto opLabel: orSrcOpLabels)
3447 m_compCtx.ops[opLabel].pc_ok = orExitPc;
3449 const auto lastIdx = (uint32_t)orSrcOpLabels.
size() - 1u;
3451 m_compCtx.ops[orSrcOpLabels[i]].pc_fail = orSrcOpLabels[i + 1];
3454 m_compCtx.ops[orSrcOpLabels[lastIdx]].pc_fail = hasOrFallback ? orExitPc : (detail::VmLabel)-1;
3457 GAIA_NODISCARD
static detail::EOpcode pick_all_opcode(
bool isSimple,
bool isAs) {
3459 return detail::EOpcode::All_Simple;
3461 return detail::EOpcode::All_Complex;
3462 return detail::EOpcode::All_Wildcard;
3465 GAIA_NODISCARD
static detail::EOpcode pick_or_opcode(
bool hasAllTerms,
bool isSimple,
bool isAs) {
3468 return detail::EOpcode::Or_NoAll_Simple;
3470 return detail::EOpcode::Or_NoAll_Complex;
3471 return detail::EOpcode::Or_NoAll_Wildcard;
3475 return detail::EOpcode::Or_WithAll_Simple;
3477 return detail::EOpcode::Or_WithAll_Complex;
3478 return detail::EOpcode::Or_WithAll_Wildcard;
3481 GAIA_NODISCARD
static detail::EOpcode pick_not_opcode(
bool isSimple,
bool isAs) {
3483 return detail::EOpcode::Not_Simple;
3485 return detail::EOpcode::Not_Complex;
3486 return detail::EOpcode::Not_Wildcard;
3491 GAIA_NODISCARD
bool op_all_simple(
MatchingCtx& ctx)
const {
3492 GAIA_PROF_SCOPE(vm::op_and_simple);
3493 return detail::exec_all_impl<MatchingStyle::Simple>(m_compCtx, ctx);
3496 GAIA_NODISCARD
bool op_all_wildcard(
MatchingCtx& ctx)
const {
3497 GAIA_PROF_SCOPE(vm::op_and_wildcard);
3498 return detail::exec_all_impl<MatchingStyle::Wildcard>(m_compCtx, ctx);
3501 GAIA_NODISCARD
bool op_all_complex(
MatchingCtx& ctx)
const {
3502 GAIA_PROF_SCOPE(vm::op_and_complex);
3503 return detail::exec_all_impl<MatchingStyle::Complex>(m_compCtx, ctx);
3506 GAIA_NODISCARD
bool op_or_noall_simple(
MatchingCtx& ctx)
const {
3507 GAIA_PROF_SCOPE(vm::op_or);
3508 return detail::exec_or_noall_impl<MatchingStyle::Simple>(m_compCtx, ctx);
3511 GAIA_NODISCARD
bool op_or_noall_wildcard(
MatchingCtx& ctx)
const {
3512 GAIA_PROF_SCOPE(vm::op_or);
3513 return detail::exec_or_noall_impl<MatchingStyle::Wildcard>(m_compCtx, ctx);
3516 GAIA_NODISCARD
bool op_or_noall_complex(
MatchingCtx& ctx)
const {
3517 GAIA_PROF_SCOPE(vm::op_or);
3518 return detail::exec_or_noall_impl<MatchingStyle::Complex>(m_compCtx, ctx);
3521 GAIA_NODISCARD
bool op_or_withall_simple(
MatchingCtx& ctx)
const {
3522 GAIA_PROF_SCOPE(vm::op_or);
3523 return detail::exec_or_withall_impl<MatchingStyle::Simple>(m_compCtx, ctx);
3526 GAIA_NODISCARD
bool op_or_withall_wildcard(
MatchingCtx& ctx)
const {
3527 GAIA_PROF_SCOPE(vm::op_or);
3528 return detail::exec_or_withall_impl<MatchingStyle::Wildcard>(m_compCtx, ctx);
3531 GAIA_NODISCARD
bool op_or_withall_complex(
MatchingCtx& ctx)
const {
3532 GAIA_PROF_SCOPE(vm::op_or);
3533 return detail::exec_or_withall_impl<MatchingStyle::Complex>(m_compCtx, ctx);
3536 GAIA_NODISCARD
bool op_not_simple(
MatchingCtx& ctx)
const {
3537 GAIA_PROF_SCOPE(vm::op_not);
3538 return detail::exec_not_impl<MatchingStyle::Simple>(m_compCtx, ctx);
3541 GAIA_NODISCARD
bool op_not_wildcard(
MatchingCtx& ctx)
const {
3542 GAIA_PROF_SCOPE(vm::op_not);
3543 return detail::exec_not_impl<MatchingStyle::Wildcard>(m_compCtx, ctx);
3546 GAIA_NODISCARD
bool op_not_complex(
MatchingCtx& ctx)
const {
3547 GAIA_PROF_SCOPE(vm::op_not);
3548 return detail::exec_not_impl<MatchingStyle::Complex>(m_compCtx, ctx);
3551 GAIA_NODISCARD
bool op_seed_all(
MatchingCtx& ctx)
const {
3552 GAIA_PROF_SCOPE(vm::op_seed_all);
3553 detail::add_all_archetypes(ctx);
3557 GAIA_NODISCARD
bool op_var_filter(
MatchingCtx& ctx)
const {
3558 GAIA_PROF_SCOPE(vm::op_var_filter);
3559 GAIA_ASSERT(!m_compCtx.var_programs.empty());
3560 filter_variable_terms(ctx, &VirtualMachine::eval_variable_terms_program_on_archetype);
3564 GAIA_NODISCARD
bool op_src_all_term(
MatchingCtx& ctx)
const {
3565 GAIA_PROF_SCOPE(vm::op_src_all);
3566 const auto& termOp = detail::get_src_term_op(m_compCtx, ctx, m_compCtx.terms_all_src);
3567 return detail::match_src_term(*ctx.
pWorld, termOp.term, termOp.opcode);
3570 GAIA_NODISCARD
bool op_src_not_term(
MatchingCtx& ctx)
const {
3571 GAIA_PROF_SCOPE(vm::op_src_not);
3572 const auto& termOp = detail::get_src_term_op(m_compCtx, ctx, m_compCtx.terms_not_src);
3573 return !detail::match_src_term(*ctx.
pWorld, termOp.term, termOp.opcode);
3576 GAIA_NODISCARD
bool op_src_or_term(
MatchingCtx& ctx)
const {
3577 GAIA_PROF_SCOPE(vm::op_src_or);
3578 const auto& termOp = detail::get_src_term_op(m_compCtx, ctx, m_compCtx.terms_or_src);
3579 const bool matched = detail::match_src_term(*ctx.
pWorld, termOp.term, termOp.opcode);
3584 if (m_compCtx.ids_all.empty())
3585 detail::add_all_archetypes(ctx);
3589 static constexpr OpcodeFunc OpcodeFuncs[] = {
3590 &VirtualMachine::op_all_simple,
3591 &VirtualMachine::op_all_wildcard,
3592 &VirtualMachine::op_all_complex,
3593 &VirtualMachine::op_or_noall_simple,
3594 &VirtualMachine::op_or_noall_wildcard,
3595 &VirtualMachine::op_or_noall_complex,
3596 &VirtualMachine::op_or_withall_simple,
3597 &VirtualMachine::op_or_withall_wildcard,
3598 &VirtualMachine::op_or_withall_complex,
3599 &VirtualMachine::op_not_simple,
3600 &VirtualMachine::op_not_wildcard,
3601 &VirtualMachine::op_not_complex,
3602 &VirtualMachine::op_seed_all,
3603 &VirtualMachine::op_var_filter,
3604 &VirtualMachine::op_src_all_term,
3605 &VirtualMachine::op_src_not_term,
3606 &VirtualMachine::op_src_or_term
3609 sizeof(OpcodeFuncs) /
sizeof(OpcodeFuncs[0]) == (uint32_t)detail::EOpcode::Src_Never,
3610 "OpcodeFuncs must contain all executable opcodes.");
3612 GAIA_NODISCARD
bool exec_opcode(
const detail::CompiledOp& stackItem,
MatchingCtx& ctx)
const {
3613 const auto opcodeIdx = (uint32_t)stackItem.opcode;
3614 GAIA_ASSERT(opcodeIdx < (uint32_t)detail::EOpcode::Src_Never);
3615 return (this->*OpcodeFuncs[opcodeIdx])(ctx);
3626 out.
append(
"main_ops: ");
3627 add_uint(out, (uint32_t)m_compCtx.mainOpsCount);
3630 const auto opsCnt = (uint32_t)m_compCtx.mainOpsCount;
3632 const auto& op = m_compCtx.ops[i];
3636 add_cstr(out, opcode_name(op.opcode));
3637 if (opcode_has_arg(op.opcode)) {
3639 add_uint(out, op.arg);
3642 add_uint(out, op.pc_ok);
3644 add_uint(out, op.pc_fail);
3649 out,
"ids_all", std::span<const Entity>{m_compCtx.ids_all.
data(), m_compCtx.ids_all.size()}, world);
3651 out,
"ids_or", std::span<const Entity>{m_compCtx.ids_or.
data(), m_compCtx.ids_or.size()}, world);
3653 out,
"ids_not", std::span<const Entity>{m_compCtx.ids_not.
data(), m_compCtx.ids_not.size()}, world);
3655 add_src_terms_section(out,
"src_all", m_compCtx.terms_all_src, world);
3656 add_src_terms_section(out,
"src_or", m_compCtx.terms_or_src, world);
3657 add_src_terms_section(out,
"src_not", m_compCtx.terms_not_src, world);
3659 add_var_terms_section(out,
"var_all", m_compCtx.terms_all_var, world);
3660 add_var_terms_section(out,
"var_or", m_compCtx.terms_or_var, world);
3661 add_var_terms_section(out,
"var_not", m_compCtx.terms_not_var, world);
3662 add_var_terms_section(out,
"var_any", m_compCtx.terms_any_var, world);
3663 add_var_program_exec_section(out, m_compCtx);
3664 add_var_program_sections(out, m_compCtx, world);
3676 GAIA_PROF_SCOPE(vm::compile);
3677 (void)entityToArchetypeMap;
3678 (void)allArchetypes;
3680 m_compCtx.ids_all.
clear();
3681 m_compCtx.ids_or.
clear();
3682 m_compCtx.ids_not.
clear();
3683 m_compCtx.terms_all_src.
clear();
3684 m_compCtx.terms_or_src.
clear();
3685 m_compCtx.terms_not_src.
clear();
3686 m_compCtx.terms_all_var.
clear();
3687 m_compCtx.terms_or_var.
clear();
3688 m_compCtx.terms_not_var.
clear();
3689 m_compCtx.terms_any_var.
clear();
3690 m_compCtx.varMaskAll = 0;
3691 m_compCtx.varMaskOr = 0;
3692 m_compCtx.varMaskNot = 0;
3693 m_compCtx.varMaskAny = 0;
3694 m_compCtx.var_programs.
clear();
3695 m_compCtx.mainOpsCount = 0;
3696 m_compCtx.ops.
clear();
3698 auto& data = queryCtx.
data;
3699 GAIA_ASSERT(queryCtx.
w !=
nullptr);
3700 const auto& world = *queryCtx.
w;
3702 auto isNonFragmentingDirectTerm = [&](
const QueryTerm& term) {
3703 if (term.
src != EntityBad || term.
entTrav != EntityBad || term_has_variables(term))
3706 const auto id = term.
id;
3707 return (
id.
pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world,
id))) ||
3708 (!
id.
pair() && world_component_is_non_fragmenting(world,
id));
3711 QueryTermSpan terms = data.terms_view_mut();
3712 QueryTermSpan terms_all = terms.subspan(0, data.firstOr);
3713 QueryTermSpan terms_or = terms.subspan(data.firstOr, data.firstNot - data.firstOr);
3714 QueryTermSpan terms_not = terms.subspan(data.firstNot, data.firstAny - data.firstNot);
3715 QueryTermSpan terms_any = terms.subspan(data.firstAny);
3718 if (!terms_all.empty()) {
3719 GAIA_PROF_SCOPE(vm::compile_all);
3721 const auto cnt = terms_all.size();
3723 auto& p = terms_all[i];
3724 if (isNonFragmentingDirectTerm(p))
3726 if (term_has_variables(p)) {
3727 const auto varMask = term_unbound_var_mask(world, p, detail::VarBindings{});
3728 m_compCtx.terms_all_var.push_back({detail::src_opcode_from_term(p), p, varMask});
3729 m_compCtx.varMaskAll |= varMask;
3733 if (p.src == EntityBad) {
3734 m_compCtx.ids_all.push_back(p.id);
3737 m_compCtx.terms_all_src.push_back({detail::src_opcode_from_term(p), p});
3742 if (!terms_or.empty()) {
3743 GAIA_PROF_SCOPE(vm::compile_or);
3745 const auto cnt = terms_or.size();
3747 auto& p = terms_or[i];
3748 if (p.src == EntityBad && hasEntityFilterTerms)
3750 if (term_has_variables(p)) {
3751 const auto varMask = term_unbound_var_mask(world, p, detail::VarBindings{});
3752 m_compCtx.terms_or_var.push_back({detail::src_opcode_from_term(p), p, varMask});
3753 m_compCtx.varMaskOr |= varMask;
3757 if (p.src == EntityBad)
3758 m_compCtx.ids_or.push_back(p.id);
3760 m_compCtx.terms_or_src.push_back({detail::src_opcode_from_term(p), p});
3765 if (!terms_not.empty()) {
3766 GAIA_PROF_SCOPE(vm::compile_not);
3768 const auto cnt = terms_not.size();
3770 auto& p = terms_not[i];
3771 if (isNonFragmentingDirectTerm(p))
3773 if (term_has_variables(p)) {
3774 const auto varMask = term_unbound_var_mask(world, p, detail::VarBindings{});
3775 m_compCtx.terms_not_var.push_back({detail::src_opcode_from_term(p), p, varMask});
3776 m_compCtx.varMaskNot |= varMask;
3780 if (p.src == EntityBad)
3781 m_compCtx.ids_not.push_back(p.id);
3783 m_compCtx.terms_not_src.push_back({detail::src_opcode_from_term(p), p});
3788 if (!terms_any.empty()) {
3789 GAIA_PROF_SCOPE(vm::compile_any);
3791 const auto cnt = terms_any.size();
3793 auto& p = terms_any[i];
3794 if (!term_has_variables(p))
3796 const auto varMask = term_unbound_var_mask(world, p, detail::VarBindings{});
3797 m_compCtx.terms_any_var.push_back({detail::src_opcode_from_term(p), p, varMask});
3798 m_compCtx.varMaskAny |= varMask;
3802 detail::sort_src_terms_by_cost(m_compCtx.terms_all_src);
3803 detail::sort_src_terms_by_cost(m_compCtx.terms_or_src);
3804 detail::sort_src_terms_by_cost(m_compCtx.terms_not_src);
3806 constexpr uint32_t VarSearchProgramOpCapacity = MAX_ITEMS_IN_QUERY * 3u + 8u;
3808 detail::QueryCompileCtx::VarSearchMeta varSearchMeta{};
3810 auto init_var_search_program = [&]() {
3811 varSearchProgramOps.
clear();
3822 const auto allVarCnt = (uint32_t)m_compCtx.terms_all_var.
size();
3823 GAIA_FOR(allVarCnt) {
3824 const auto cost = detail::search_term_cost(m_compCtx.terms_all_var[i]);
3825 const auto srcVarBit =
3826 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src)
3827 ? (uint8_t)(uint8_t(1) << detail::var_index(m_compCtx.terms_all_var[i].term.src))
3829 const auto canBindFromSelfSource =
3830 m_compCtx.terms_all_var[i].sourceOpcode == detail::EOpcode::Src_Self &&
3831 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src) &&
3832 m_compCtx.terms_all_var[i].varMask == srcVarBit &&
3833 (uint8_t)(m_compCtx.terms_all_var[i].varMask & m_compCtx.varMaskAny) == 0;
3834 const auto canBindFromUpSource =
3835 m_compCtx.terms_all_var[i].sourceOpcode == detail::EOpcode::Src_Up &&
3836 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src) &&
3837 m_compCtx.terms_all_var[i].varMask == srcVarBit &&
3838 (uint8_t)(m_compCtx.terms_all_var[i].varMask & m_compCtx.varMaskAny) == 0;
3839 const auto canBindFromDownSource =
3840 m_compCtx.terms_all_var[i].sourceOpcode == detail::EOpcode::Src_Down &&
3841 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src) &&
3842 m_compCtx.terms_all_var[i].varMask == srcVarBit &&
3843 (uint8_t)(m_compCtx.terms_all_var[i].varMask & m_compCtx.varMaskAny) == 0;
3844 const auto canBindFromUpDownSource =
3845 m_compCtx.terms_all_var[i].sourceOpcode == detail::EOpcode::Src_UpDown &&
3846 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src) &&
3847 m_compCtx.terms_all_var[i].varMask == srcVarBit &&
3848 (uint8_t)(m_compCtx.terms_all_var[i].varMask & m_compCtx.varMaskAny) == 0;
3850 canBindFromSelfSource || canBindFromUpSource || canBindFromDownSource || canBindFromUpDownSource
3851 ? detail::EOpcode::Var_Term_All_Src_Bind
3852 : detail::EOpcode::Var_Term_All_Bind;
3853 searchAllBindOps.
push_back({opcode, 0, 0, (uint8_t)i, cost});
3854 searchAllCheckOps.
push_back({detail::EOpcode::Var_Term_All_Check, 0, 0, (uint8_t)i, cost});
3856 detail::sort_program_ops_by_cost(searchAllBindOps);
3857 detail::sort_program_ops_by_cost(searchAllCheckOps);
3859 const auto orVarCnt = (uint32_t)m_compCtx.terms_or_var.size();
3860 GAIA_FOR(orVarCnt) {
3861 const auto cost = detail::search_term_cost(m_compCtx.terms_or_var[i]);
3862 searchOrBindOps.
push_back({detail::EOpcode::Var_Term_Or_Bind, 0, 0, (uint8_t)i, cost});
3863 searchOrCheckOps.
push_back({detail::EOpcode::Var_Term_Or_Check, 0, 0, (uint8_t)i, cost});
3864 finalOrCheckOps.
push_back({detail::EOpcode::Var_Final_Or_Check, 0, 0, (uint8_t)i, cost});
3865 varSearchMeta.orVarMask = (uint8_t)(varSearchMeta.orVarMask | m_compCtx.terms_or_var[i].varMask);
3867 detail::sort_program_ops_by_cost(searchOrBindOps);
3868 detail::sort_program_ops_by_cost(searchOrCheckOps);
3869 detail::sort_program_ops_by_cost(finalOrCheckOps);
3871 const auto anyVarCnt = (uint32_t)m_compCtx.terms_any_var.size();
3872 GAIA_FOR(anyVarCnt) {
3873 const auto cost = detail::search_term_cost(m_compCtx.terms_any_var[i]);
3874 searchAnyBindOps.
push_back({detail::EOpcode::Var_Term_Any_Bind, 0, 0, (uint8_t)i, cost});
3875 searchAnyCheckOps.
push_back({detail::EOpcode::Var_Term_Any_Check, 0, 0, (uint8_t)i, cost});
3877 detail::sort_program_ops_by_cost(searchAnyBindOps);
3878 detail::sort_program_ops_by_cost(searchAnyCheckOps);
3880 const auto notVarCnt = (uint32_t)m_compCtx.terms_not_var.size();
3881 GAIA_FOR(notVarCnt) {
3883 {detail::EOpcode::Var_Final_Not_Check, 0, 0, (uint8_t)i,
3884 detail::search_term_cost(m_compCtx.terms_not_var[i])});
3886 detail::sort_program_ops_by_cost(finalNotOps);
3888 for (
const auto& op: searchAllBindOps)
3890 for (
const auto& op: searchOrBindOps)
3892 for (
const auto& op: searchAnyBindOps)
3895 varSearchMeta.allBegin = 0;
3896 varSearchMeta.allCount = (uint16_t)searchAllBindOps.
size();
3897 varSearchMeta.orBegin = varSearchMeta.allCount;
3898 varSearchMeta.orCount = (uint16_t)searchOrBindOps.
size();
3899 varSearchMeta.anyBegin = (uint16_t)(varSearchMeta.orBegin + varSearchMeta.orCount);
3900 varSearchMeta.anyCount = (uint16_t)searchAnyBindOps.
size();
3901 varSearchMeta.notBegin = 0;
3902 varSearchMeta.notCount = 0;
3904 const auto termOpsCnt = (uint16_t)varSearchProgramOps.
size();
3905 const auto selectAllPc = termOpsCnt;
3906 const auto selectOrPc = (uint16_t)(termOpsCnt + 1u);
3907 const auto selectOtherOrPc = (uint16_t)(termOpsCnt + 2u);
3908 const auto selectOtherOrBindPc = (uint16_t)(termOpsCnt + 3u);
3909 const auto beginAnyPc = (uint16_t)(termOpsCnt + 4u);
3910 const auto selectAnyPc = (uint16_t)(termOpsCnt + 5u);
3911 const auto maybeFinalizePc = (uint16_t)(termOpsCnt + 6u);
3912 const auto allCheckBegin = (uint16_t)(termOpsCnt + 7u);
3913 const auto orCheckBegin = (uint16_t)(allCheckBegin + searchAllCheckOps.
size());
3914 const auto anyCheckBegin = (uint16_t)(orCheckBegin + searchOrCheckOps.
size());
3915 const auto finalNotBegin = (uint16_t)(anyCheckBegin + searchAnyCheckOps.
size());
3916 const auto finalRequireOrPc = (uint16_t)(finalNotBegin + finalNotOps.
size());
3917 const auto finalOrCheckBegin = (uint16_t)(finalRequireOrPc + 1u);
3918 const auto finalSuccessPc = (uint16_t)(finalOrCheckBegin + finalOrCheckOps.
size());
3919 const auto finalBegin = !finalNotOps.
empty() ? finalNotBegin : finalRequireOrPc;
3920 const auto backtrackPc = (detail::VmLabel)-1;
3922 for (
auto& op: varSearchProgramOps) {
3923 switch (op.opcode) {
3924 case detail::EOpcode::Var_Term_All_Bind:
3925 case detail::EOpcode::Var_Term_All_Src_Bind:
3926 op.pc_ok = selectAllPc;
3927 op.pc_fail = backtrackPc;
3929 case detail::EOpcode::Var_Term_Or_Bind:
3930 op.pc_ok = selectAllPc;
3931 op.pc_fail = selectOtherOrBindPc;
3933 case detail::EOpcode::Var_Term_Any_Bind:
3934 op.pc_ok = selectAllPc;
3935 op.pc_fail = maybeFinalizePc;
3942 varSearchProgramOps.
push_back({detail::EOpcode::Var_Search_SelectAll, selectAllPc, selectOrPc, 0, 0});
3943 varSearchProgramOps.
push_back({detail::EOpcode::Var_Search_SelectOr, selectOrPc, selectOtherOrPc, 0, 0});
3945 {detail::EOpcode::Var_Search_SelectOtherOr, selectOtherOrPc, selectOtherOrBindPc, 0, 0});
3947 {detail::EOpcode::Var_Search_SelectOtherOrBind, selectOtherOrBindPc, beginAnyPc, 0, 0});
3948 varSearchProgramOps.
push_back({detail::EOpcode::Var_Search_BeginAny, selectAnyPc, backtrackPc, 0, 0});
3949 varSearchProgramOps.
push_back({detail::EOpcode::Var_Search_SelectAny, selectAnyPc, maybeFinalizePc, 0, 0});
3950 varSearchProgramOps.
push_back({detail::EOpcode::Var_Search_MaybeFinalize, finalBegin, backtrackPc, 0, 0});
3951 for (
auto op: searchAllCheckOps) {
3952 op.pc_ok = selectAllPc;
3953 op.pc_fail = backtrackPc;
3956 for (
auto op: searchOrCheckOps) {
3957 op.pc_ok = selectAllPc;
3958 op.pc_fail = selectOtherOrBindPc;
3961 for (
auto op: searchAnyCheckOps) {
3962 op.pc_ok = selectAllPc;
3963 op.pc_fail = maybeFinalizePc;
3966 for (uint32_t i = 0; i < finalNotOps.
size(); ++i) {
3967 auto op = finalNotOps[i];
3968 op.pc_ok = (i + 1u < finalNotOps.
size()) ? (uint16_t)(finalNotBegin + i + 1u) : finalRequireOrPc;
3969 op.pc_fail = backtrackPc;
3973 {detail::EOpcode::Var_Final_Require_Or, finalSuccessPc,
3974 searchOrCheckOps.
empty() ? backtrackPc : finalOrCheckBegin, 0, 0});
3975 for (uint32_t i = 0; i < finalOrCheckOps.
size(); ++i) {
3976 auto op = finalOrCheckOps[i];
3977 op.pc_ok = finalSuccessPc;
3978 op.pc_fail = (i + 1u < finalOrCheckOps.
size()) ? (uint16_t)(finalOrCheckBegin + i + 1u) : backtrackPc;
3981 varSearchProgramOps.
push_back({detail::EOpcode::Var_Final_Success, finalSuccessPc, backtrackPc, 0, 0});
3983 varSearchMeta.selectAllPc = selectAllPc;
3984 varSearchMeta.selectOrPc = selectOrPc;
3985 varSearchMeta.selectOtherOrPc = selectOtherOrPc;
3986 varSearchMeta.selectOtherOrBindPc = selectOtherOrBindPc;
3987 varSearchMeta.beginAnyPc = beginAnyPc;
3988 varSearchMeta.selectAnyPc = selectAnyPc;
3989 varSearchMeta.maybeFinalizePc = maybeFinalizePc;
3990 varSearchMeta.allCheckBegin = allCheckBegin;
3991 varSearchMeta.orCheckBegin = orCheckBegin;
3992 varSearchMeta.anyCheckBegin = anyCheckBegin;
3993 varSearchMeta.notBegin = finalNotBegin;
3994 varSearchMeta.notCount = (uint16_t)finalNotOps.
size();
3996 auto init_mask = [](uint16_t begin, uint16_t count) {
3998 for (uint16_t i = 0; i < count; ++i)
3999 mask = (uint16_t)(mask | (uint16_t(1) << (begin + i)));
4003 varSearchMeta.initialAllMask = init_mask(varSearchMeta.allBegin, varSearchMeta.allCount);
4004 varSearchMeta.initialOrMask = init_mask(0, varSearchMeta.orCount);
4005 varSearchMeta.initialAnyMask = init_mask(0, varSearchMeta.anyCount);
4008 create_opcodes(queryCtx);
4010 init_var_search_program();
4012 auto emit_flat_program = [&](std::span<const detail::CompiledOp> ops) {
4013 detail::QueryCompileCtx::VarProgram program{};
4018 GAIA_ASSERT(m_compCtx.ops.size() <= UINT16_MAX);
4019 program.begin = (uint16_t)m_compCtx.ops.size();
4020 program.count = (uint16_t)ops.size();
4021 for (
const auto& op: ops)
4022 m_compCtx.ops.push_back(op);
4026 m_compCtx.var_programs.clear();
4027 if (m_compCtx.has_variable_terms()) {
4028 const auto program = emit_flat_program(
4029 std::span<const detail::CompiledOp>{varSearchProgramOps.
data(), varSearchProgramOps.
size()});
4030 if (!program.empty())
4031 m_compCtx.var_programs.push_back({program, varSearchMeta});
4036 void create_opcodes(
QueryCtx& queryCtx) {
4040 uint16_t preservedProgramBase = 0;
4042 preservedProgramOffsets.
clear();
4044 if (!m_compCtx.var_programs.empty()) {
4045 preservedProgramBase = m_compCtx.var_programs[0].program.
begin;
4046 GAIA_ASSERT(preservedProgramBase <= m_compCtx.ops.size());
4047 const auto preservedCnt = (uint32_t)m_compCtx.ops.size() - (uint32_t)preservedProgramBase;
4048 preservedVarOps.
reserve(preservedCnt);
4049 for (uint32_t i = 0; i < preservedCnt; ++i)
4050 preservedVarOps.
push_back(m_compCtx.ops[(uint32_t)preservedProgramBase + i]);
4052 for (
const auto& step: m_compCtx.var_programs) {
4053 GAIA_ASSERT(step.program.begin >= preservedProgramBase);
4054 preservedProgramOffsets.
push_back((uint16_t)(step.program.begin - preservedProgramBase));
4058 m_compCtx.ops.
clear();
4061 if (!m_compCtx.terms_all_src.empty())
4062 emit_src_gate_terms(m_compCtx.terms_all_src, detail::EOpcode::Src_AllTerm);
4065 if (!m_compCtx.terms_not_src.empty())
4066 emit_src_gate_terms(m_compCtx.terms_not_src, detail::EOpcode::Src_NotTerm);
4069 if (!m_compCtx.terms_or_src.empty()) {
4070 const bool hasOrFallback = !m_compCtx.ids_or.empty() || !m_compCtx.terms_or_var.empty();
4071 emit_src_or_terms(hasOrFallback);
4075 if (!m_compCtx.has_id_terms() &&
4076 (m_compCtx.has_src_terms() || m_compCtx.has_variable_terms() ||
4078 detail::CompiledOp op{};
4079 op.opcode = detail::EOpcode::Seed_All;
4080 (void)add_op(GAIA_MOV(op));
4084 if (!m_compCtx.ids_all.empty()) {
4085 detail::CompiledOp op{};
4086 op.opcode = pick_all_opcode(isSimple, isAs);
4087 (void)add_gate_op(GAIA_MOV(op));
4091 if (!m_compCtx.ids_or.empty()) {
4092 detail::CompiledOp op{};
4093 op.opcode = pick_or_opcode(!m_compCtx.ids_all.empty(), isSimple, isAs);
4094 (void)add_op(GAIA_MOV(op));
4098 if (!m_compCtx.ids_not.empty()) {
4099 detail::CompiledOp op{};
4100 op.opcode = pick_not_opcode(isSimple, isAs);
4101 (void)add_op(GAIA_MOV(op));
4105 if (m_compCtx.has_variable_terms()) {
4106 detail::CompiledOp op{};
4107 op.opcode = detail::EOpcode::Var_Filter;
4108 (void)add_gate_op(GAIA_MOV(op));
4111 m_compCtx.mainOpsCount = (uint16_t)m_compCtx.ops.size();
4113 if (!preservedVarOps.
empty()) {
4114 const auto newProgramBase = (uint16_t)m_compCtx.ops.size();
4115 for (
const auto& op: preservedVarOps)
4116 m_compCtx.ops.push_back(op);
4118 GAIA_ASSERT(preservedProgramOffsets.
size() == m_compCtx.var_programs.size());
4119 const auto programCnt = (uint32_t)m_compCtx.var_programs.size();
4120 GAIA_FOR(programCnt)
4121 m_compCtx.var_programs[i].program.begin = (uint16_t)(newProgramBase + preservedProgramOffsets[i]);
4125 queryCtx.
data.
flags &= ~QueryCtx::QueryFlags::Recompile;
4132 return !m_compCtx.ops.empty();
4138 return (uint32_t)m_compCtx.ops.size();
4144 uint64_t hash = 1469598103934665603ull;
4145 for (
const auto& op: m_compCtx.ops) {
4146 const uint64_t packed =
4147 (uint64_t)(uint8_t)op.opcode |
4148 ((uint64_t)op.pc_ok << 8u) |
4149 ((uint64_t)op.pc_fail << 24u) |
4150 ((uint64_t)op.arg << 40u) |
4151 ((uint64_t)op.cost << 48u);
4153 hash *= 1099511628211ull;
4161 GAIA_PROF_SCOPE(vm::exec);
4163 if (m_compCtx.mainOpsCount == 0)
4170 auto& stackItem = m_compCtx.ops[ctx.
pc];
4171 GAIA_ASSERT((uint32_t)stackItem.opcode < (uint32_t)detail::EOpcode::Src_Never);
4172 const bool ret = exec_opcode(stackItem, ctx);
4173 ctx.
pc = ret ? stackItem.pc_ok : stackItem.pc_fail;
4174 }
while (ctx.
pc < m_compCtx.mainOpsCount);
Array with variable size of elements of type.
Definition darray_impl.h:27
void reserve(size_type cap)
Ensures storage for at least the requested number of elements.
Definition darray_impl.h:223
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
GAIA_NODISCARD pointer data() noexcept
Returns a pointer to the element storage.
Definition darray_impl.h:193
void push_back(const T &arg)
Appends an element.
Definition darray_impl.h:309
Fixed-shape group of chunks storing entities that share the same component layout....
Definition archetype.h:97
Owns entities, components, archetypes, queries, observers, and systems.
Definition world.h:80
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:262
Compiles query terms into matching bytecode and evaluates that bytecode against archetypes....
Definition vm.h:2343
GAIA_NODISCARD bool is_compiled() const
Returns whether this VM contains compiled executable opcodes.
Definition vm.h:4131
void exec(MatchingCtx &ctx)
Executes compiled query-matching opcodes.
Definition vm.h:4160
GAIA_NODISCARD uint32_t op_count() const
Returns the total number of compiled opcodes.
Definition vm.h:4137
GAIA_NODISCARD uint64_t op_signature() const
Computes a stable signature of the compiled opcode stream.
Definition vm.h:4143
void compile(const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes, QueryCtx &queryCtx)
Transforms inputs into virtual machine opcodes.
Definition vm.h:3673
GAIA_NODISCARD util::str bytecode(const World &world) const
Formats the compiled query bytecode for diagnostics.
Definition vm.h:3622
Hashmap lookup structure used for Entity.
Definition id.h:543
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:296
GAIA_NODISCARD constexpr auto gen() const noexcept
Generation index of the entity.
Definition id.h:365
GAIA_NODISCARD constexpr bool pair() const noexcept
Whether this id refers to a relationship pair.
Definition id.h:377
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:359
GAIA_NODISCARD bool has_dep_flag(DependencyFlags dependency) const
Tests whether a dependency fact was recorded.
Definition query_common.h:1065
uint32_t as_mask_0
Mask for items with Is relationship pair. If the id is a pair, the first part (id) is written here.
Definition query_common.h:1138
uint32_t as_mask_1
Mask for items with Is relationship pair. If the id is a pair, the second part (gen) is written here.
Definition query_common.h:1141
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:1170
uint16_t flags
Query flags.
Definition query_common.h:1154
Authored and compiled state defining query identity and execution behavior.
Definition query_common.h:885
struct gaia::ecs::QueryCtx::Data data
Compiled query payload.
const World * w
World against which the query is compiled and executed.
Definition query_common.h:887
@ Complex
Query requires the general matching path.
Definition query_common.h:904
@ DependencyHasEntityFilterTerms
At least one term requires per-entity filtering.
Definition query_common.h:990
static constexpr uint8_t TravDepthUnlimited
Traversal-depth value selecting the internally bounded unlimited mode.
Definition query_common.h:518
Internal representation of QueryInput.
Definition query_common.h:732
Entity id
Queried id.
Definition query_common.h:734
uint8_t travDepth
Maximum number of traversal steps.
Definition query_common.h:742
Entity entTrav
Optional traversal relation for source lookups.
Definition query_common.h:738
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:736
Type-erased view over component-to-archetype lookup storage.
Definition vm.h:44
GAIA_NODISCARD uint32_t revision(const EntityLookupKey &key) const
Returns the current revision of a lookup bucket.
Definition vm.h:78
FetchByKeyFn fetchByKey
Lookup function, or null for an empty view.
Definition vm.h:54
GAIA_NODISCARD bool empty() const
Returns whether the lookup view is empty.
Definition vm.h:58
std::span< const ComponentIndexEntry >(*)(const void *, std::span< const Archetype * >, Entity, const EntityLookupKey &) FetchByKeyFn
Function used to fetch archetypes for an entity lookup key.
Definition vm.h:47
GAIA_NODISCARD std::span< const ComponentIndexEntry > fetch(std::span< const Archetype * > arr, Entity ent, const EntityLookupKey &key) const
Fetches archetype entries for an entity lookup key.
Definition vm.h:68
const void * pData
Lookup implementation data.
Definition vm.h:50
const EntityToArchetypeVersionMap * pVersions
Optional lookup-bucket revision table used to validate cached incremental cursors.
Definition vm.h:52
Mutable execution context used while matching query bytecode.
Definition vm.h:88
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_Not
Idx of the last matched archetype against the NOT opcode.
Definition vm.h:111
EntitySpan idsToMatch
List of entity ids in a query to consider.
Definition vm.h:135
Entity ent
Entity to match.
Definition vm.h:133
cnt::sarray< Entity, MaxVarCnt > varBindings
Runtime variable bindings (Var0..Var7) provided by the query.
Definition vm.h:123
ArchetypeMatchStamps * pMatchesStampByArchetypeId
Per-archetype stamp table for O(1) dedup in hot loops.
Definition vm.h:103
std::span< const Archetype * > allArchetypes
Array of all archetypes in the world.
Definition vm.h:99
uint32_t matchesVersion
Current dedup version used with pMatchesStampByArchetypeId.
Definition vm.h:105
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_All
Idx of the last matched archetype against the ALL opcode.
Definition vm.h:107
bool skipOr
OR group was already satisfied by source terms.
Definition vm.h:127
uint32_t pc
Current stack position (program counter)
Definition vm.h:137
EntitySpan targetEntities
Entities for which we run the VM. If empty, we run against all of them.
Definition vm.h:95
QueryMask queryMask
Mask for speeding up simple query matching.
Definition vm.h:113
cnt::darr< const Archetype * > * pMatchesArr
Array of already matches archetypes. Reset before each exec().
Definition vm.h:101
uint32_t as_mask_0
Mask for items with Is relationship pair. If the id is a pair, the first part (id) is written here.
Definition vm.h:116
const World * pWorld
World.
Definition vm.h:93
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_Or
Idx of the last matched archetype against the OR opcode.
Definition vm.h:109
ArchetypeLookupView archetypeLookup
entity -> archetypes lookup used to seed structural candidate archetypes
Definition vm.h:97
uint16_t flags
Flags copied over from QueryCtx::Data.
Definition vm.h:121
uint8_t varBindingMask
Bitmask of bindings set in varBindings.
Definition vm.h:125
uint32_t as_mask_1
Mask for items with Is relationship pair. If the id is a pair, the second part (gen) is written here.
Definition vm.h:119
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:332
GAIA_NODISCARD uint32_t size() const
Returns number of characters stored in the string.
Definition str.h:432
void append(const char *data, uint32_t size)
Appends size characters from data.
Definition str.h:390
void reserve(uint32_t len)
Reserves capacity for at least len characters.
Definition str.h:359
GAIA_NODISCARD const char * data() const
Returns read-only pointer to internal data.
Definition str.h:420