Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
vm.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdint>
5#include <cstdio>
6#include <type_traits>
7
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"
23
24namespace gaia {
25 namespace ecs {
26 using EntityToArchetypeMap = cnt::map<EntityLookupKey, ComponentIndexEntryArray>;
27
28 } // namespace ecs
29
30 namespace ecs {
31 namespace vm {
32
33 enum class MatchingStyle { Simple, Wildcard, Complex };
34
36 using FetchByKeyFn = std::span<const ComponentIndexEntry> (*)(
38
39 const void* pData = nullptr;
42 FetchByKeyFn fetchByKey = nullptr;
43
44 GAIA_NODISCARD bool empty() const {
45 return fetchByKey == nullptr;
46 }
47
49 fetch(std::span<const Archetype*> arr, Entity ent, const EntityLookupKey& key) const {
50 if (empty())
51 return {};
52
53 return fetchByKey(pData, arr, ent, key);
54 }
55
56 GAIA_NODISCARD uint32_t revision(const EntityLookupKey& key) const {
57 if (pVersions == nullptr || pVersions->empty())
58 return 0;
59
60 const auto it = pVersions->find(key);
61 return it == pVersions->end() ? 0 : it->second;
62 }
63 };
64
116
117 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select(
119 (void)arr;
120 (void)ent;
121 GAIA_ASSERT(key != EntityBadLookupKey);
122
123 const auto it = map.find(key);
124 if (it == map.end() || it->second.empty())
125 return {};
126
127 return std::span(it->second.data(), it->second.size());
128 }
129
130 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select(
131 const EntityToArchetypeMap& map, std::span<const Archetype*> arr, Entity ent, Entity src) {
132 GAIA_ASSERT(src != EntityBad);
133
134 return fetch_archetypes_for_select(map, arr, ent, EntityLookupKey(src));
135 }
136
137 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select(
138 const SingleArchetypeLookup& map, std::span<const Archetype*> arr, Entity ent, const EntityLookupKey& key) {
139 (void)ent;
140 GAIA_ASSERT(key != EntityBadLookupKey);
141
142 const auto it = core::find_if(map, [&](const auto& item) {
143 return item.matches(key);
144 });
145 if (it == map.end() || arr.empty())
146 return {};
147
148 return std::span(&it->entry, 1);
149 }
150
151 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select(
152 const SingleArchetypeLookup& map, std::span<const Archetype*> arr, Entity ent, Entity src) {
153 GAIA_ASSERT(src != EntityBad);
154
155 return fetch_archetypes_for_select(map, arr, ent, EntityLookupKey(src));
156 }
157
158 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select_from_map(
159 const void* pData, std::span<const Archetype*> arr, Entity ent, const EntityLookupKey& key) {
160 return fetch_archetypes_for_select(*(const EntityToArchetypeMap*)pData, arr, ent, key);
161 }
162
163 inline std::span<const ComponentIndexEntry> fetch_archetypes_for_select_from_single(
164 const void* pData, std::span<const Archetype*> arr, Entity ent, const EntityLookupKey& key) {
165 return fetch_archetypes_for_select(*(const SingleArchetypeLookup*)pData, arr, ent, key);
166 }
167
168 inline ArchetypeLookupView
169 make_archetype_lookup_view(const EntityToArchetypeMap& map, const EntityToArchetypeVersionMap& versions) {
170 return ArchetypeLookupView{&map, &versions, fetch_archetypes_for_select_from_map};
171 }
172
173 inline ArchetypeLookupView make_archetype_lookup_view(const SingleArchetypeLookup& map) {
174 return ArchetypeLookupView{&map, nullptr, fetch_archetypes_for_select_from_single};
175 }
176
177 namespace detail {
178 enum class EOpcode : uint8_t { //
180 All_Simple,
181 All_Wildcard,
182 All_Complex,
184 Or_NoAll_Simple,
185 Or_NoAll_Wildcard,
186 Or_NoAll_Complex,
187 Or_WithAll_Simple,
188 Or_WithAll_Wildcard,
189 Or_WithAll_Complex,
191 Not_Simple,
192 Not_Wildcard,
193 Not_Complex,
195 Seed_All,
197 Var_Filter,
199 Src_AllTerm,
200 Src_NotTerm,
201 Src_OrTerm,
203 Src_Never,
204 Src_Self,
205 Src_Up,
206 Src_Down,
207 Src_UpDown,
209 Var_Term_All_Check,
210 Var_Term_All_Bind,
211 Var_Term_All_Src_Bind,
212 Var_Term_Or_Check,
213 Var_Term_Or_Bind,
214 Var_Term_Any_Check,
215 Var_Term_Any_Bind,
216 Var_Term_Not,
217 Var_Search_SelectAll,
218 Var_Search_SelectOr,
219 Var_Search_SelectOtherOr,
220 Var_Search_SelectOtherOrBind,
221 Var_Search_BeginAny,
222 Var_Search_SelectAny,
223 Var_Search_MaybeFinalize,
224 Var_Final_Not_Check,
225 Var_Final_Require_Or,
226 Var_Final_Or_Check,
227 Var_Final_Success
228 };
229
230 using VmLabel = uint16_t;
231
232 struct CompiledOp {
234 EOpcode opcode;
236 VmLabel pc_ok;
238 VmLabel pc_fail;
240 uint8_t arg = 0;
242 uint8_t cost = 0;
243 };
244
247 EOpcode opcode = EOpcode::Src_Never;
248 QueryTerm term{};
249 };
250
251 struct VarTermOp {
252 EOpcode sourceOpcode = EOpcode::Src_Never;
253 QueryTerm term{};
254 uint8_t varMask = 0;
255 };
256
257 struct VarProgram {
258 uint16_t begin = 0;
259 uint16_t count = 0;
260
261 void clear() {
262 begin = 0;
263 count = 0;
264 }
265
266 GAIA_NODISCARD bool empty() const {
267 return count == 0;
268 }
269 };
270
272 uint16_t selectAllPc = (uint16_t)-1;
273 uint16_t selectOrPc = (uint16_t)-1;
274 uint16_t selectOtherOrPc = (uint16_t)-1;
275 uint16_t selectOtherOrBindPc = (uint16_t)-1;
276 uint16_t beginAnyPc = (uint16_t)-1;
277 uint16_t selectAnyPc = (uint16_t)-1;
278 uint16_t maybeFinalizePc = (uint16_t)-1;
279 uint16_t initialAllMask = 0;
280 uint16_t initialOrMask = 0;
281 uint16_t initialAnyMask = 0;
282 uint16_t allBegin = 0;
283 uint16_t allCheckBegin = 0;
284 uint16_t allCount = 0;
285 uint16_t orBegin = 0;
286 uint16_t orCheckBegin = 0;
287 uint16_t orCount = 0;
288 uint16_t anyBegin = 0;
289 uint16_t anyCheckBegin = 0;
290 uint16_t anyCount = 0;
291 uint16_t notBegin = 0;
292 uint16_t notCount = 0;
293 uint8_t orVarMask = 0;
294 };
295
297 VarProgram program{};
298 VarSearchMeta search{};
299 };
300
302 uint16_t mainOpsCount = 0;
326 uint8_t varMaskAll = 0;
327 uint8_t varMaskOr = 0;
328 uint8_t varMaskNot = 0;
329 uint8_t varMaskAny = 0;
330
331 GAIA_NODISCARD bool has_src_terms() const {
332 return !terms_all_src.empty() || !terms_or_src.empty() || !terms_not_src.empty();
333 }
334
335 GAIA_NODISCARD bool has_variable_terms() const {
336 return !terms_all_var.empty() || !terms_or_var.empty() || !terms_not_var.empty() || !terms_any_var.empty();
337 }
338
339 GAIA_NODISCARD bool has_id_terms() const {
340 return !ids_all.empty() || !ids_or.empty() || !ids_not.empty();
341 }
342 };
343
344 enum class EVarProgramTermSet : uint8_t { None, All, Or, Any, Not };
345
347 EVarProgramTermSet termSet;
348 };
349
350 static constexpr auto VarProgramOpcodeFirst = EOpcode::Var_Term_All_Check;
351 static constexpr auto VarProgramOpcodeLast = EOpcode::Var_Final_Success;
352 static constexpr VarProgramOpcodeMeta VarProgramOpcodeMetaTable[] = {
353 {EVarProgramTermSet::All}, //
354 {EVarProgramTermSet::All}, //
355 {EVarProgramTermSet::All}, //
356 {EVarProgramTermSet::Or}, //
357 {EVarProgramTermSet::Or}, //
358 {EVarProgramTermSet::Any}, //
359 {EVarProgramTermSet::Any}, //
360 {EVarProgramTermSet::Not}, //
361 {EVarProgramTermSet::None}, //
362 {EVarProgramTermSet::None}, //
363 {EVarProgramTermSet::None}, //
364 {EVarProgramTermSet::None}, //
365 {EVarProgramTermSet::None}, //
366 {EVarProgramTermSet::None}, //
367 {EVarProgramTermSet::None}, //
368 {EVarProgramTermSet::Not}, //
369 {EVarProgramTermSet::None}, //
370 {EVarProgramTermSet::Or}, //
371 {EVarProgramTermSet::None}, //
372 };
373
374 static_assert(
375 sizeof(VarProgramOpcodeMetaTable) / sizeof(VarProgramOpcodeMetaTable[0]) ==
376 (uint32_t)VarProgramOpcodeLast - (uint32_t)VarProgramOpcodeFirst + 1u,
377 "VarProgramOpcodeMetaTable out of sync with EOpcode variable micro-op range.");
378
379 GAIA_NODISCARD inline const VarProgramOpcodeMeta& var_program_opcode_meta(EOpcode opcode) {
380 GAIA_ASSERT((uint32_t)opcode >= (uint32_t)VarProgramOpcodeFirst);
381 GAIA_ASSERT((uint32_t)opcode <= (uint32_t)VarProgramOpcodeLast);
382 return VarProgramOpcodeMetaTable[(uint32_t)opcode - (uint32_t)VarProgramOpcodeFirst];
383 }
384
385 GAIA_NODISCARD inline uint8_t src_term_cost(const QueryCompileCtx::SourceTermOp& termOp) {
386 const bool depth1 = termOp.term.travDepth == 1;
387 switch (termOp.opcode) {
388 case EOpcode::Src_Never:
389 return 0;
390 case EOpcode::Src_Self:
391 return 1;
392 case EOpcode::Src_Up:
393 case EOpcode::Src_Down:
394 return depth1 ? 2 : 4;
395 case EOpcode::Src_UpDown:
396 return depth1 ? 3 : 5;
397 default:
398 return 6;
399 }
400 }
401
402 GAIA_NODISCARD inline uint8_t bound_match_id_cost(Entity queryId) {
403 if (!queryId.pair())
404 return (!is_variable(queryId) && queryId.id() != All.id()) ? 1u : 3u;
405
406 uint8_t cost = 0;
407 cost += (!is_variable((EntityId)queryId.id()) && queryId.id() != All.id()) ? 1u : 3u;
408 cost += (!is_variable((EntityId)queryId.gen()) && queryId.gen() != All.id()) ? 1u : 3u;
409 return cost;
410 }
411
412 GAIA_NODISCARD inline uint8_t bound_term_cost(const QueryCompileCtx::VarTermOp& termOp) {
413 uint8_t cost = bound_match_id_cost(termOp.term.id);
414 if (termOp.term.src != EntityBad)
415 cost = (uint8_t)(cost + src_term_cost({termOp.sourceOpcode, termOp.term}));
416 return cost;
417 }
418
419 GAIA_NODISCARD inline uint8_t search_term_cost(const QueryCompileCtx::VarTermOp& termOp) {
420 uint8_t cost = bound_term_cost(termOp);
421 if (termOp.term.src != EntityBad) {
422 const bool srcIsVar = is_variable(EntityId(termOp.term.src.id()));
423 cost = (uint8_t)(cost + (srcIsVar ? 32u : 8u));
424 }
425 return cost;
426 }
427
428 template <typename ProgramOpsArray>
429 inline void sort_program_ops_by_cost(ProgramOpsArray& ops) {
430 const auto cnt = (uint32_t)ops.size();
431 if (cnt < 2)
432 return;
433
434 for (uint32_t i = 1; i < cnt; ++i) {
435 const auto key = ops[i];
436
437 uint32_t j = i;
438 while (j > 0) {
439 const auto prev = ops[j - 1];
440 if (prev.cost < key.cost)
441 break;
442 if (prev.cost == key.cost && (uint8_t)prev.opcode < (uint8_t)key.opcode)
443 break;
444 if (prev.cost == key.cost && prev.opcode == key.opcode && prev.arg <= key.arg)
445 break;
446 ops[j] = prev;
447 --j;
448 }
449
450 ops[j] = key;
451 }
452 }
453
454 template <typename SourceTermsArray>
455 inline void sort_src_terms_by_cost(SourceTermsArray& terms) {
456 const auto cnt = (uint32_t)terms.size();
457 if (cnt < 2)
458 return;
459
460 for (uint32_t i = 1; i < cnt; ++i) {
461 auto key = terms[i];
462 const auto keyCost = src_term_cost(key);
463
464 uint32_t j = i;
465 while (j > 0 && src_term_cost(terms[j - 1]) > keyCost) {
466 terms[j] = terms[j - 1];
467 --j;
468 }
469
470 terms[j] = key;
471 }
472 }
473
474 GAIA_NODISCARD inline std::span<const CompiledOp>
475 program_ops(const QueryCompileCtx& comp, const QueryCompileCtx::VarProgram& program) {
476 GAIA_ASSERT((uint32_t)program.begin + (uint32_t)program.count <= (uint32_t)comp.ops.size());
477 return {comp.ops.data() + program.begin, program.count};
478 }
479
480 inline uint32_t handle_last_archetype_match(
481 QueryArchetypeCacheIndexMap* pCont, EntityLookupKey entityKey, uint32_t srcArchetypeCnt,
482 uint32_t srcRevision) {
483 if (pCont == nullptr)
484 return 0;
485
486 const auto cache_it = pCont->find(entityKey);
487 uint32_t lastMatchedIdx = 0;
488 if (cache_it == pCont->end())
489 pCont->emplace(entityKey, QueryArchetypeCacheCursor{srcArchetypeCnt, srcRevision});
490 else {
491 auto& cursor = cache_it->second;
492 if (cursor.revision == srcRevision)
493 lastMatchedIdx = cursor.index;
494 cursor.index = srcArchetypeCnt;
495 cursor.revision = srcRevision;
496 }
497 return lastMatchedIdx;
498 }
499
500 // Operator ALL (used by query::all)
501 struct OpAll {
502 static bool check_mask(const QueryMask& maskArchetype, const QueryMask& maskQuery) {
503 return match_entity_mask(maskArchetype, maskQuery);
504 }
505 static void restart([[maybe_unused]] uint32_t& idx) {}
506 static bool can_continue(bool hasMatch) {
507 return hasMatch;
508 }
509 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
510 return expectedMatches == totalMatches;
511 }
512 static uint32_t handle_last_match(
513 MatchingCtx& ctx, EntityLookupKey entityKey, uint32_t srcArchetypeCnt, uint32_t srcRevision) {
514 return handle_last_archetype_match(
515 ctx.pLastMatchedArchetypeIdx_All, entityKey, srcArchetypeCnt, srcRevision);
516 }
517 };
518 // Operator OR (used by query::or_)
519 struct OpOr {
520 static bool check_mask(const QueryMask& maskArchetype, const QueryMask& maskQuery) {
521 return match_entity_mask(maskArchetype, maskQuery);
522 }
523 static void restart(uint32_t& idx) {
524 // OR terms are evaluated independently.
525 idx = 0;
526 }
527 static bool can_continue([[maybe_unused]] bool hasMatch) {
528 return true;
529 }
530 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
531 (void)expectedMatches;
532 return totalMatches > 0;
533 }
534 static uint32_t handle_last_match(
535 MatchingCtx& ctx, EntityLookupKey entityKey, uint32_t srcArchetypeCnt, uint32_t srcRevision) {
536 return handle_last_archetype_match(
537 ctx.pLastMatchedArchetypeIdx_Or, entityKey, srcArchetypeCnt, srcRevision);
538 }
539 };
540 // Operator NOT (used by query::no)
541 struct OpNo {
542 static bool check_mask(const QueryMask& maskArchetype, const QueryMask& maskQuery) {
543 return !match_entity_mask(maskArchetype, maskQuery);
544 }
545 static void restart(uint32_t& idx) {
546 idx = 0;
547 }
548 static bool can_continue(bool hasMatch) {
549 return !hasMatch;
550 }
551 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
552 (void)expectedMatches;
553 return totalMatches == 0;
554 }
555 static uint32_t handle_last_match(
556 MatchingCtx& ctx, EntityLookupKey entityKey, uint32_t srcArchetypeCnt, uint32_t srcRevision) {
557 return handle_last_archetype_match(
558 ctx.pLastMatchedArchetypeIdx_Not, entityKey, srcArchetypeCnt, srcRevision);
559 }
560 };
561
562 GAIA_NODISCARD inline bool is_archetype_marked(const MatchingCtx& ctx, const Archetype* pArchetype) {
563 GAIA_ASSERT(ctx.pMatchesStampByArchetypeId != nullptr);
564
565 const auto& stamps = *ctx.pMatchesStampByArchetypeId;
566 const auto sid = (uint32_t)pArchetype->id();
567 if (!stamps.has(sid))
568 return false;
569
570 return stamps.get(sid) == ctx.matchesVersion;
571 }
572
573 inline void mark_archetype_match(MatchingCtx& ctx, const Archetype* pArchetype) {
574 GAIA_ASSERT(ctx.pMatchesStampByArchetypeId != nullptr);
575
576 auto& stamps = *ctx.pMatchesStampByArchetypeId;
577 const auto sid = (uint32_t)pArchetype->id();
578 stamps.set(sid, ctx.matchesVersion);
579
580 ctx.pMatchesArr->emplace_back(pArchetype);
581 }
582
583 inline void add_all_archetypes(MatchingCtx& ctx) {
584 for (const auto* pArchetype: ctx.allArchetypes) {
585 if (is_archetype_marked(ctx, pArchetype))
586 continue;
587
588 mark_archetype_match(ctx, pArchetype);
589 }
590 }
591
592 template <typename OpKind>
593 inline bool match_inter_eval_matches(uint32_t queryIdMarches, uint32_t& outMatches) {
594 const bool hadAnyMatches = queryIdMarches > 0;
595
596 // We finished checking matches with an id from query.
597 // We need to check if we have sufficient amount of results in the run.
598 if (!OpKind::can_continue(hadAnyMatches))
599 return false;
600
601 // No matter the amount of matches we only care if at least one
602 // match happened with the id from query.
603 outMatches += (uint32_t)hadAnyMatches;
604 return true;
605 }
606
615 template <typename OpKind, typename CmpFunc>
616 GAIA_NODISCARD inline bool match_inter(EntitySpan queryIds, EntitySpan archetypeIds, CmpFunc func) {
617 const auto archetypeIdsCnt = (uint32_t)archetypeIds.size();
618 const auto queryIdsCnt = (uint32_t)queryIds.size();
619
620 // Arrays are sorted so we can do linear intersection lookup
621 uint32_t indices[2]{}; // 0 for query ids, 1 for archetype ids
622 uint32_t matches = 0;
623
624 // Ids in query and archetype are sorted.
625 // Therefore, to match any two ids we perform a linear intersection forward loop.
626 // The only exception are transitive ids in which case we need to start searching
627 // form the start.
628 // Finding just one match for any id in the query is enough to start checking
629 // the next it. We only have 3 different operations - ALL, OR, NOT.
630 //
631 // Example:
632 // - query #1 ------------------------
633 // queryIds : 5, 10
634 // archetypeIds: 1, 3, 5, 6, 7, 10
635 // - query #2 ------------------------
636 // queryIds : 1, 10, 11
637 // archetypeIds: 3, 5, 6, 7, 10, 15
638 // -----------------------------------
639 // indices[0] : 0, 1, 2
640 // indices[1] : 0, 1, 2, 3, 4, 5
641 //
642 // For query #1:
643 // We start matching 5 in the query with 1 in the archetype. They do not match.
644 // We continue with 3 in the archetype. No match.
645 // We continue with 5 in the archetype. Match.
646 // We try to match 10 in the query with 6 in the archetype. No match.
647 // ... etc.
648
649 while (indices[0] < queryIdsCnt) {
650 const auto idInQuery = queryIds[indices[0]];
651
652 // For * and transitive ids we have to search from the start.
653 if (idInQuery == All || idInQuery.id() == Is.id())
654 indices[1] = 0;
655
656 uint32_t queryIdMatches = 0;
657 while (indices[1] < archetypeIdsCnt) {
658 const auto idInArchetype = archetypeIds[indices[1]];
659
660 // See if we have a match
661 const auto res = func(idInQuery, idInArchetype);
662
663 // Once a match is found we start matching with the next id in query.
664 if (res.matched) {
665 ++indices[0];
666 ++indices[1];
667 ++queryIdMatches;
668
669 // Only continue with the next iteration unless the given Op determines it is
670 // no longer needed.
671 if (!match_inter_eval_matches<OpKind>(queryIdMatches, matches))
672 return false;
673
674 goto next_query_id;
675 } else {
676 ++indices[1];
677 }
678 }
679
680 if (!match_inter_eval_matches<OpKind>(queryIdMatches, matches))
681 return false;
682
683 ++indices[0];
684 // Make sure to continue from the right index on the archetype array.
685 // Some operators can keep moving forward (AND, OR), but NOT needs to start
686 // matching from the beginning again if the previous query operator didn't find a match.
687 OpKind::restart(indices[1]);
688
689 next_query_id:
690 continue;
691 }
692
693 return OpKind::eval(queryIdsCnt, matches);
694 }
695
696 struct IdCmpResult {
697 bool matched;
698 };
699
700 GAIA_NODISCARD inline IdCmpResult cmp_ids(Entity idInQuery, Entity idInArchetype) {
701 return {idInQuery == idInArchetype};
702 }
703
704 GAIA_NODISCARD inline IdCmpResult cmp_ids_pairs(Entity idInQuery, Entity idInArchetype) {
705 if (idInQuery.pair()) {
706 // all(Pair<All, All>) aka "any pair"
707 if (idInQuery == Pair(All, All))
708 return {true};
709
710 // all(Pair<X, All>):
711 // X, AAA
712 // X, BBB
713 // ...
714 // X, ZZZ
715 if (idInQuery.gen() == All.id())
716 return {idInQuery.id() == idInArchetype.id()};
717
718 // all(Pair<All, X>):
719 // AAA, X
720 // BBB, X
721 // ...
722 // ZZZ, X
723 if (idInQuery.id() == All.id())
724 return {idInQuery.gen() == idInArchetype.gen()};
725 }
726
727 // 1:1 match needed for non-pairs
728 return cmp_ids(idInQuery, idInArchetype);
729 }
730
731 GAIA_NODISCARD inline IdCmpResult
732 cmp_ids_is(const World& w, const Archetype& archetype, Entity idInQuery, Entity idInArchetype) {
733 // all(Pair<Is, X>)
734 if (idInQuery.pair() && idInQuery.id() == Is.id()) {
735 auto archetypeIds = archetype.ids_view();
736 return {
737 idInQuery.gen() == idInArchetype.id() || // X vs Id
738 as_relations_trav_if(w, idInQuery, [&](Entity relation) {
739 const auto idx = core::get_index(archetypeIds, relation);
740 // Stop at the first match
741 return idx != BadIndex;
742 })};
743 }
744
745 // 1:1 match needed for non-pairs
746 return cmp_ids(idInQuery, idInArchetype);
747 }
748
749 GAIA_NODISCARD inline IdCmpResult
750 cmp_ids_is_pairs(const World& w, const Archetype& archetype, Entity idInQuery, Entity idInArchetype) {
751 if (idInQuery.pair()) {
752 // all(Pair<All, All>) aka "any pair"
753 if (idInQuery == Pair(All, All))
754 return {true};
755
756 // all(Pair<Is, X>)
757 if (idInQuery.id() == Is.id()) {
758 // (Is, X) in archetype == (Is, X) in query
759 if (idInArchetype == idInQuery)
760 return {true};
761
762 const auto eQ = pair_tgt(w, idInQuery);
763 if (eQ == idInArchetype)
764 return {true};
765
766 // If the archetype entity is an (Is, X) pair treat Is as X and try matching it with
767 // entities inheriting from e.
768 if (idInArchetype.id() == Is.id()) {
769 const auto eA = pair_tgt(w, idInArchetype);
770 if (eA == eQ)
771 return {true};
772
773 return {as_relations_trav_if(w, eQ, [eA](Entity relation) {
774 return eA == relation;
775 })};
776 }
777
778 // Archetype entity is generic, try matching it with entities inheriting from e.
779 auto archetypeIds = archetype.ids_view();
780 return {as_relations_trav_if(w, eQ, [&archetypeIds](Entity relation) {
781 // Relation does not necessary match the sorted order of components in the archetype
782 // so we need to search through all of its ids.
783 const auto idx = core::get_index(archetypeIds, relation);
784 // Stop at the first match
785 return idx != BadIndex;
786 })};
787 }
788
789 // all(Pair<All, X>):
790 // AAA, X
791 // BBB, X
792 // ...
793 // ZZZ, X
794 if (idInQuery.id() == All.id()) {
795 if (idInQuery.gen() == idInArchetype.gen())
796 return {true};
797
798 // If there are any Is pairs on the archetype we need to check if we match them
799 if (archetype.pairs_is() > 0) {
800 auto archetypeIds = archetype.ids_view();
801
802 const auto e = pair_tgt(w, idInQuery);
803 return {as_relations_trav_if(w, e, [&](Entity relation) {
804 // Relation does not necessary match the sorted order of components in the archetype
805 // so we need to search through all of its ids.
806 const auto idx = core::get_index(archetypeIds, relation);
807 // Stop at the first match
808 return idx != BadIndex;
809 })};
810 }
811
812 // No match found
813 return {false};
814 }
815
816 // all(Pair<X, All>):
817 // X, AAA
818 // X, BBB
819 // ...
820 // X, ZZZ
821 if (idInQuery.gen() == All.id()) {
822 return {idInQuery.id() == idInArchetype.id()};
823 }
824 }
825
826 // 1:1 match needed for non-pairs
827 return cmp_ids(idInQuery, idInArchetype);
828 }
829
836 template <typename OpKind>
837 GAIA_NODISCARD inline bool match_res(const Archetype& archetype, EntitySpan queryIds) {
838 // Archetype has no pairs we can compare ids directly.
839 // This has better performance.
840 if (archetype.pairs() == 0) {
841 return match_inter<OpKind>(
842 queryIds, archetype.ids_view(),
843 // Cmp func
844 [](Entity idInQuery, Entity idInArchetype) {
845 return cmp_ids(idInQuery, idInArchetype);
846 });
847 }
848
849 // Pairs are present, we have to evaluate.
850 return match_inter<OpKind>(
851 queryIds, archetype.ids_view(),
852 // Cmp func
853 [](Entity idInQuery, Entity idInArchetype) {
854 return cmp_ids_pairs(idInQuery, idInArchetype);
855 });
856 }
857
864 template <typename OpKind>
865 GAIA_NODISCARD inline bool match_res_as(const World& w, const Archetype& archetype, EntitySpan queryIds) {
866 // Archetype has no pairs we can compare ids directly
867 if (archetype.pairs() == 0) {
868 return match_inter<OpKind>(
869 queryIds, archetype.ids_view(),
870 // cmp func
871 [&](Entity idInQuery, Entity idInArchetype) {
872 return cmp_ids_is(w, archetype, idInQuery, idInArchetype);
873 });
874 }
875
876 return match_inter<OpKind>(
877 queryIds, archetype.ids_view(),
878 // cmp func
879 [&](Entity idInQuery, Entity idInArchetype) {
880 return cmp_ids_is_pairs(w, archetype, idInQuery, idInArchetype);
881 });
882 }
883
884 GAIA_NODISCARD inline bool match_single_id_on_archetype(const World& w, const Archetype& archetype, Entity id) {
885 const Entity ids[1] = {id};
886 return match_res_as<OpOr>(w, archetype, EntitySpan{ids, 1});
887 }
888
889 GAIA_NODISCARD inline bool match_single_id_on_archetype_exact(const Archetype& archetype, Entity id) {
890 const Entity ids[1] = {id};
891 return match_res<OpOr>(archetype, EntitySpan{ids, 1});
892 }
893
894 GAIA_NODISCARD inline EOpcode src_opcode_from_term(const QueryTerm& term) {
895 const bool includeSelf = query_trav_has(term.travKind, QueryTravKind::Self);
896 const bool includeUp = query_trav_has(term.travKind, QueryTravKind::Up) && term.entTrav != EntityBad;
897 const bool includeDown = query_trav_has(term.travKind, QueryTravKind::Down) && term.entTrav != EntityBad;
898 if (!includeSelf && !includeUp && !includeDown)
899 return EOpcode::Src_Never;
900 if (includeSelf && !includeUp && !includeDown)
901 return EOpcode::Src_Self;
902 if (includeUp && includeDown)
903 return EOpcode::Src_UpDown;
904 if (includeUp)
905 return EOpcode::Src_Up;
906 return EOpcode::Src_Down;
907 }
908
910 uint32_t queueIdx = 0;
911 uint32_t childIdx = 0;
912 uint32_t childLevel = 0;
913 uint32_t upDepth = 0;
914 uint8_t phase = 0;
915 bool initialized = false;
916 bool selfEmitted = false;
917 Entity upSource = EntityBad;
918 std::span<const Entity> cachedSources{};
921 cnt::darray<Entity> children;
923
924 void reset_runtime_state() {
925 queueIdx = 0;
926 childIdx = 0;
927 childLevel = 0;
928 upDepth = 0;
929 initialized = false;
930 selfEmitted = false;
931 upSource = EntityBad;
932 cachedSources = {};
933 queue.clear();
934 levels.clear();
935 children.clear();
936 visited.clear();
937 }
938 };
939
940 GAIA_NODISCARD inline bool next_lookup_src_cursor(
941 const World& w, EOpcode opcode, const QueryTerm& term, Entity sourceEntity, SourceLookupCursor& cursor,
942 Entity& outSource);
943
944 template <typename Func>
945 GAIA_NODISCARD inline bool
946 each_lookup_src(const World& w, EOpcode opcode, const QueryTerm& term, Entity sourceEntity, Func&& func) {
947 SourceLookupCursor cursor{};
948 Entity source = EntityBad;
949 while (next_lookup_src_cursor(w, opcode, term, sourceEntity, cursor, source)) {
950 if (func(source))
951 return true;
952 }
953
954 return false;
955 }
956
957 template <typename Func>
958 GAIA_NODISCARD inline bool
959 each_lookup_src(const World& w, const QueryTerm& term, Entity sourceEntity, Func&& func) {
960 return each_lookup_src(w, src_opcode_from_term(term), term, sourceEntity, GAIA_FWD(func));
961 }
962
963 GAIA_NODISCARD inline bool next_lookup_src_cursor_up(
964 const World& w, const QueryTerm& term, Entity sourceEntity, SourceLookupCursor& cursor, Entity& outSource,
965 bool includeSelf) {
966 if (!valid(w, sourceEntity))
967 return false;
968
969 const uint32_t maxDepth =
970 term.travDepth == QueryTermOptions::TravDepthUnlimited ? MAX_TRAV_DEPTH : (uint32_t)term.travDepth;
971
972 if (!cursor.initialized) {
973 cursor.initialized = true;
974 cursor.upSource = sourceEntity;
975 }
976
977 if (includeSelf && !cursor.selfEmitted) {
978 cursor.selfEmitted = true;
979 outSource = sourceEntity;
980 return true;
981 }
982
983 while (cursor.upDepth < maxDepth) {
984 const auto next = target(w, cursor.upSource, term.entTrav);
985 if (next == EntityBad || next == cursor.upSource)
986 return false;
987
988 cursor.upSource = next;
989 ++cursor.upDepth;
990 outSource = next;
991 return true;
992 }
993
994 return false;
995 }
996
997 GAIA_NODISCARD inline bool next_lookup_src_cursor_down(
998 const World& w, const QueryTerm& term, Entity sourceEntity, SourceLookupCursor& cursor, Entity& outSource,
999 bool includeSelf) {
1000 if (!valid(w, sourceEntity))
1001 return false;
1002
1003 const uint32_t maxDepth =
1004 term.travDepth == QueryTermOptions::TravDepthUnlimited ? MAX_TRAV_DEPTH : (uint32_t)term.travDepth;
1005
1006 if (!cursor.initialized) {
1007 cursor.initialized = true;
1008 cursor.queue.push_back(sourceEntity);
1009 cursor.levels.push_back(0);
1010 cursor.visited.insert(EntityLookupKey(sourceEntity));
1011 }
1012
1013 if (includeSelf && !cursor.selfEmitted) {
1014 cursor.selfEmitted = true;
1015 outSource = sourceEntity;
1016 return true;
1017 }
1018
1019 for (;;) {
1020 if (cursor.childIdx < cursor.children.size()) {
1021 const auto child = cursor.children[cursor.childIdx++];
1022 cursor.queue.push_back(child);
1023 cursor.levels.push_back(cursor.childLevel);
1024 outSource = child;
1025 return true;
1026 }
1027
1028 bool loadedChildren = false;
1029 while (cursor.queueIdx < cursor.queue.size()) {
1030 const auto source = cursor.queue[cursor.queueIdx];
1031 const auto level = cursor.levels[cursor.queueIdx];
1032 ++cursor.queueIdx;
1033 if (level >= maxDepth)
1034 continue;
1035
1036 cursor.children.clear();
1037 cursor.childIdx = 0;
1038 cursor.childLevel = level + 1;
1039 sources(w, term.entTrav, source, [&](Entity next) {
1040 const auto key = EntityLookupKey(next);
1041 const auto ins = cursor.visited.insert(key);
1042 if (!ins.second)
1043 return;
1044
1045 cursor.children.push_back(next);
1046 });
1047
1048 core::sort(cursor.children, [](Entity left, Entity right) {
1049 return left.id() < right.id();
1050 });
1051
1052 if (!cursor.children.empty()) {
1053 loadedChildren = true;
1054 break;
1055 }
1056 }
1057
1058 if (!loadedChildren)
1059 return false;
1060 }
1061 }
1062
1063 GAIA_NODISCARD inline bool next_lookup_src_cursor(
1064 const World& w, EOpcode opcode, const QueryTerm& term, Entity sourceEntity, SourceLookupCursor& cursor,
1065 Entity& outSource) {
1066 const bool includeSelf = query_trav_has(term.travKind, QueryTravKind::Self);
1067 const bool unlimitedTraversal =
1068 term.travDepth == QueryTermOptions::TravDepthUnlimited && term.entTrav != EntityBad;
1069
1070 if (unlimitedTraversal &&
1071 (opcode == EOpcode::Src_Up || opcode == EOpcode::Src_Down || opcode == EOpcode::Src_UpDown)) {
1072 if (!valid(w, sourceEntity))
1073 return false;
1074
1075 if (!cursor.initialized)
1076 cursor.initialized = true;
1077
1078 if (includeSelf && !cursor.selfEmitted) {
1079 cursor.selfEmitted = true;
1080 outSource = sourceEntity;
1081 return true;
1082 }
1083
1084 const auto adv_cached_src = [&](std::span<const Entity> cachedSources) {
1085 if (cursor.cachedSources.data() != cachedSources.data() ||
1086 cursor.cachedSources.size() != cachedSources.size()) {
1087 cursor.cachedSources = cachedSources;
1088 cursor.queueIdx = 0;
1089 }
1090
1091 if (cursor.queueIdx < cursor.cachedSources.size()) {
1092 outSource = cursor.cachedSources[cursor.queueIdx++];
1093 return true;
1094 }
1095
1096 return false;
1097 };
1098
1099 if (opcode == EOpcode::Src_Up) {
1100 return adv_cached_src(targets_trav_cache(w, term.entTrav, sourceEntity));
1101 }
1102
1103 if (opcode == EOpcode::Src_Down) {
1104 return adv_cached_src(sources_bfs_trav_cache(w, term.entTrav, sourceEntity));
1105 }
1106
1107 if (cursor.phase == 0) {
1108 if (adv_cached_src(targets_trav_cache(w, term.entTrav, sourceEntity)))
1109 return true;
1110
1111 cursor.phase = 1;
1112 cursor.cachedSources = {};
1113 cursor.queueIdx = 0;
1114 }
1115
1116 return adv_cached_src(sources_bfs_trav_cache(w, term.entTrav, sourceEntity));
1117 }
1118
1119 switch (opcode) {
1120 case EOpcode::Src_Never:
1121 return false;
1122 case EOpcode::Src_Self:
1123 if (cursor.initialized || !valid(w, sourceEntity))
1124 return false;
1125 cursor.initialized = true;
1126 outSource = sourceEntity;
1127 return true;
1128 case EOpcode::Src_Up:
1129 return next_lookup_src_cursor_up(w, term, sourceEntity, cursor, outSource, includeSelf);
1130 case EOpcode::Src_Down:
1131 return next_lookup_src_cursor_down(w, term, sourceEntity, cursor, outSource, includeSelf);
1132 case EOpcode::Src_UpDown:
1133 if (cursor.phase == 0) {
1134 if (next_lookup_src_cursor_up(w, term, sourceEntity, cursor, outSource, includeSelf))
1135 return true;
1136 cursor.reset_runtime_state();
1137 cursor.phase = 1;
1138 }
1139 return next_lookup_src_cursor_down(w, term, sourceEntity, cursor, outSource, false);
1140 default:
1141 GAIA_ASSERT(false);
1142 return false;
1143 }
1144 }
1145
1146 GAIA_NODISCARD inline bool match_src_term(const World& w, const QueryTerm& term, EOpcode opcode) {
1147 auto match_src_entity = [&](Entity source) {
1148 if (!valid(w, source))
1149 return false;
1150
1151 auto* pArchetype = archetype_from_entity(w, source);
1152 if (pArchetype == nullptr)
1153 return false;
1154
1155 return match_single_id_on_archetype(w, *pArchetype, term.id);
1156 };
1157
1158 return each_lookup_src(w, opcode, term, term.src, match_src_entity);
1159 }
1160
1161 GAIA_NODISCARD inline bool match_src_term(const World& w, const QueryTerm& term) {
1162 return match_src_term(w, term, src_opcode_from_term(term));
1163 }
1164
1167 uint8_t mask = 0;
1168 };
1169
1171 uint32_t idIdx = 0;
1172 uint32_t sourceArchetypeIdx = 0;
1173 uint32_t sourceChunkIdx = 0;
1174 uint32_t sourceEntityIdx = 0;
1175 Entity source = EntityBad;
1176 SourceLookupCursor sourceCursor{};
1177 };
1178
1179 GAIA_NODISCARD inline bool is_var_entity(Entity entity) {
1180 return is_variable(EntityId(entity.id()));
1181 }
1182
1183 GAIA_NODISCARD inline uint32_t var_index(Entity varEntity) {
1184 GAIA_ASSERT(is_var_entity(varEntity));
1185 return (uint32_t)(varEntity.id() - Var0.id());
1186 }
1187
1188 GAIA_NODISCARD inline bool var_is_bound(const VarBindings& vars, Entity varEntity) {
1189 const auto idx = var_index(varEntity);
1190 return (vars.mask & (uint8_t(1) << idx)) != 0;
1191 }
1192
1193 GAIA_NODISCARD inline bool bind_var(VarBindings& vars, Entity varEntity, Entity value) {
1194 const auto idx = var_index(varEntity);
1195 const auto bit = (uint8_t(1) << idx);
1196 if ((vars.mask & bit) != 0)
1197 return vars.values[idx].id() == value.id();
1198
1199 vars.values[idx] = value;
1200 vars.mask |= bit;
1201 return true;
1202 }
1203
1204 GAIA_NODISCARD inline bool match_token(VarBindings& vars, Entity token, Entity value, bool pairSide) {
1205 if (pairSide && token.id() == All.id())
1206 return true;
1207
1208 if (!is_var_entity(token))
1209 return token.id() == value.id();
1210
1211 return bind_var(vars, token, value);
1212 }
1213
1215 Entity token = EntityBad;
1216 Entity matchValue = EntityBad;
1217 bool concrete = false;
1218 bool needsBind = false;
1219 };
1220
1222 uint32_t matchId = 0;
1223 uint8_t bindVarIdx = 0xff;
1224 bool concrete = false;
1225 bool needsBind = false;
1226 };
1227
1228 GAIA_NODISCARD inline ResolvedPairToken resolve_pair_query_token(Entity queryToken, const VarBindings& vars) {
1229 ResolvedPairToken out{};
1230 out.token = queryToken;
1231
1232 if (queryToken == EntityBad)
1233 return out;
1234
1235 if (is_var_entity(queryToken)) {
1236 if (!var_is_bound(vars, queryToken)) {
1237 out.needsBind = true;
1238 return out;
1239 }
1240
1241 out.matchValue = vars.values[var_index(queryToken)];
1242 out.concrete = out.matchValue.id() != All.id();
1243 return out;
1244 }
1245
1246 if (queryToken.id() == All.id())
1247 return out;
1248
1249 out.matchValue = queryToken;
1250 out.concrete = true;
1251 return out;
1252 }
1253
1256 GAIA_NODISCARD inline RawMatchToken resolve_raw_pair_match_token(Entity queryToken, const VarBindings& vars) {
1257 RawMatchToken out{};
1258
1259 if (queryToken == EntityBad || queryToken.id() == All.id())
1260 return out;
1261
1262 if (is_var_entity(queryToken)) {
1263 out.bindVarIdx = (uint8_t)var_index(queryToken);
1264 if ((vars.mask & (uint8_t(1) << out.bindVarIdx)) == 0) {
1265 out.needsBind = true;
1266 return out;
1267 }
1268
1269 out.matchId = vars.values[out.bindVarIdx].id();
1270 out.concrete = out.matchId != All.id();
1271 return out;
1272 }
1273
1274 out.matchId = queryToken.id();
1275 out.concrete = true;
1276 return out;
1277 }
1278
1279 GAIA_NODISCARD inline uint32_t count_pair_id_matches_limited(
1280 const World& w, const Archetype& archetype, Entity queryId, const VarBindings& varsIn, uint32_t limit) {
1281 GAIA_ASSERT(limit > 0);
1282 GAIA_ASSERT(queryId.pair());
1283
1284 const auto queryRel = pair_rel(w, queryId);
1285 const auto queryTgt = pair_tgt(w, queryId);
1286 if (queryRel == EntityBad || queryTgt == EntityBad)
1287 return 0;
1288
1289 const auto rel = resolve_raw_pair_match_token(queryRel, varsIn);
1290 const auto tgt = resolve_raw_pair_match_token(queryTgt, varsIn);
1291 const bool sameUnboundVar = rel.needsBind && tgt.needsBind && rel.bindVarIdx == tgt.bindVarIdx;
1292
1293 // Candidate-local pair cardinalities let us answer the common concrete/wildcard
1294 // cases in O(1) without rescanning all pair ids on the archetype.
1295 if (!rel.needsBind && !tgt.needsBind && !sameUnboundVar) {
1296 const auto matchPair = Pair(
1297 rel.concrete ? Entity((EntityId)rel.matchId, 0, true, false, EntityKind::EK_Gen) : All,
1298 tgt.concrete ? Entity((EntityId)tgt.matchId, 0, true, false, EntityKind::EK_Gen) : All);
1299 const auto count = archetype.pair_matches(matchPair);
1300 return count < limit ? count : limit;
1301 }
1302
1303 uint32_t count = 0;
1304 auto archetypeIds = archetype.ids_view();
1305 const auto cnt = (uint32_t)archetypeIds.size();
1306 GAIA_FOR(cnt) {
1307 const auto idInArchetype = archetypeIds[i];
1308 if (!idInArchetype.pair())
1309 continue;
1310 if (rel.concrete && idInArchetype.id() != rel.matchId)
1311 continue;
1312 if (tgt.concrete && idInArchetype.gen() != tgt.matchId)
1313 continue;
1314 if (sameUnboundVar && idInArchetype.id() != idInArchetype.gen())
1315 continue;
1316
1317 ++count;
1318 if (count >= limit)
1319 break;
1320 }
1321
1322 return count;
1323 }
1324
1325 template <typename Func>
1326 GAIA_NODISCARD inline bool each_term_match(
1327 const World& w, const Archetype& candidateArchetype, const QueryCompileCtx::VarTermOp& termOp,
1328 const VarBindings& varsIn, Func&& func);
1329
1330 GAIA_NODISCARD inline bool next_id_match_cursor(
1331 const World& w, const Archetype& archetype, Entity queryId, const VarBindings& varsIn, uint32_t& idIdx,
1332 VarBindings& outVars) {
1333 auto archetypeIds = archetype.ids_view();
1334 const auto cnt = (uint32_t)archetypeIds.size();
1335
1336 if (!queryId.pair()) {
1337 for (uint32_t i = idIdx; i < cnt; ++i) {
1338 const auto idInArchetype = archetypeIds[i];
1339 if (idInArchetype.pair())
1340 continue;
1341
1342 const auto value = id_entity(w, idInArchetype);
1343 if (value == EntityBad)
1344 continue;
1345
1346 auto vars = varsIn;
1347 if (!match_token(vars, queryId, value, false))
1348 continue;
1349
1350 outVars = vars;
1351 idIdx = i + 1;
1352 return true;
1353 }
1354
1355 return false;
1356 }
1357
1358 const auto queryRel = pair_rel(w, queryId);
1359 const auto queryTgt = pair_tgt(w, queryId);
1360 if (queryRel == EntityBad || queryTgt == EntityBad)
1361 return false;
1362 const auto rel = resolve_pair_query_token(queryRel, varsIn);
1363 const auto tgt = resolve_pair_query_token(queryTgt, varsIn);
1364
1365 for (uint32_t i = idIdx; i < cnt; ++i) {
1366 const auto idInArchetype = archetypeIds[i];
1367 if (!idInArchetype.pair())
1368 continue;
1369
1370 if (rel.concrete && idInArchetype.id() != rel.matchValue.id())
1371 continue;
1372 if (tgt.concrete && idInArchetype.gen() != tgt.matchValue.id())
1373 continue;
1374
1375 auto vars = varsIn;
1376 if (rel.needsBind) {
1377 const auto relValue = pair_rel(w, idInArchetype);
1378 if (relValue == EntityBad)
1379 continue;
1380 if (!match_token(vars, rel.token, relValue, true))
1381 continue;
1382 }
1383 if (tgt.needsBind) {
1384 const auto tgtValue = pair_tgt(w, idInArchetype);
1385 if (tgtValue == EntityBad)
1386 continue;
1387 if (!match_token(vars, tgt.token, tgtValue, true))
1388 continue;
1389 }
1390
1391 outVars = vars;
1392 idIdx = i + 1;
1393 return true;
1394 }
1395
1396 return false;
1397 }
1398
1399 GAIA_NODISCARD inline bool next_term_match_cursor(
1400 const World& w, const Archetype& archetype, const QueryCompileCtx::VarTermOp& termOp,
1401 const VarBindings& varsIn, VarTermMatchCursor& cursor, VarBindings& outVars) {
1402 const auto& term = termOp.term;
1403 if (term.src == EntityBad)
1404 return next_id_match_cursor(w, archetype, term.id, varsIn, cursor.idIdx, outVars);
1405
1406 auto sourceEntity = term.src;
1407 if (is_var_entity(sourceEntity)) {
1408 if (!var_is_bound(varsIn, sourceEntity))
1409 return false;
1410 sourceEntity = varsIn.values[var_index(sourceEntity)];
1411 }
1412
1413 for (;;) {
1414 if (cursor.source != EntityBad) {
1415 auto* pSrcArchetype = archetype_from_entity(w, cursor.source);
1416 if (pSrcArchetype != nullptr &&
1417 next_id_match_cursor(w, *pSrcArchetype, term.id, varsIn, cursor.idIdx, outVars))
1418 return true;
1419
1420 cursor.idIdx = 0;
1421 cursor.source = EntityBad;
1422 }
1423
1424 Entity nextSource = EntityBad;
1425 if (!next_lookup_src_cursor(w, termOp.sourceOpcode, term, sourceEntity, cursor.sourceCursor, nextSource))
1426 return false;
1427
1428 cursor.source = nextSource;
1429 }
1430 }
1431
1432 GAIA_NODISCARD inline bool term_has_match_bound(
1433 const World& w, const Archetype& candidateArchetype, const QueryCompileCtx::VarTermOp& termOp,
1434 const VarBindings& vars);
1435
1436 GAIA_NODISCARD inline bool term_has_match(
1437 const World& w, const Archetype& archetype, const QueryCompileCtx::VarTermOp& termOp,
1438 const VarBindings& varsIn) {
1439 if ((uint8_t)(termOp.varMask & ~varsIn.mask) == 0)
1440 return term_has_match_bound(w, archetype, termOp, varsIn);
1441
1442 return each_term_match(w, archetype, termOp, varsIn, [&](const VarBindings&) {
1443 return true;
1444 });
1445 }
1446
1447 GAIA_NODISCARD inline uint32_t count_term_matches_limited(
1448 const World& w, const Archetype& archetype, const QueryCompileCtx::VarTermOp& termOp,
1449 const VarBindings& varsIn, uint32_t limit) {
1450 GAIA_ASSERT(limit > 0);
1451
1452 if ((uint8_t)(termOp.varMask & ~varsIn.mask) == 0)
1453 return term_has_match_bound(w, archetype, termOp, varsIn) ? 1u : 0u;
1454
1455 if (termOp.term.src == EntityBad && termOp.term.id.pair())
1456 return count_pair_id_matches_limited(w, archetype, termOp.term.id, varsIn, limit);
1457
1458 uint32_t count = 0;
1459 (void)each_term_match(w, archetype, termOp, varsIn, [&](const VarBindings&) {
1460 ++count;
1461 return count >= limit;
1462 });
1463 return count;
1464 }
1465
1466 GAIA_NODISCARD inline bool has_concrete_match_id(Entity queryId) {
1467 if (!queryId.pair())
1468 return !is_variable(queryId) && queryId.id() != All.id();
1469
1470 return !is_variable((EntityId)queryId.id()) && queryId.id() != All.id() &&
1471 !is_variable((EntityId)queryId.gen()) && queryId.gen() != All.id();
1472 }
1473
1474 GAIA_NODISCARD inline bool
1475 match_id_bound(const World& w, const Archetype& archetype, Entity queryId, const VarBindings& vars) {
1476 auto archetypeIds = archetype.ids_view();
1477 const auto cnt = (uint32_t)archetypeIds.size();
1478
1479 if (!queryId.pair()) {
1480 Entity queryToken = queryId;
1481 if (is_var_entity(queryToken)) {
1482 if (!var_is_bound(vars, queryToken))
1483 return false;
1484 queryToken = vars.values[var_index(queryToken)];
1485 }
1486
1487 GAIA_FOR(cnt) {
1488 const auto idInArchetype = archetypeIds[i];
1489 if (idInArchetype.pair())
1490 continue;
1491
1492 const auto value = id_entity(w, idInArchetype);
1493 if (value == EntityBad)
1494 continue;
1495 if (queryToken.id() != value.id())
1496 continue;
1497
1498 return true;
1499 }
1500
1501 return false;
1502 }
1503
1504 auto queryRel = pair_rel(w, queryId);
1505 auto queryTgt = pair_tgt(w, queryId);
1506 if (queryRel == EntityBad || queryTgt == EntityBad)
1507 return false;
1508
1509 if (is_var_entity(queryRel)) {
1510 if (!var_is_bound(vars, queryRel))
1511 return false;
1512 queryRel = vars.values[var_index(queryRel)];
1513 }
1514
1515 if (is_var_entity(queryTgt)) {
1516 if (!var_is_bound(vars, queryTgt))
1517 return false;
1518 queryTgt = vars.values[var_index(queryTgt)];
1519 }
1520
1521 const bool relIsConcrete = queryRel.id() != All.id();
1522 const bool tgtIsConcrete = queryTgt.id() != All.id();
1523
1524 if (relIsConcrete || tgtIsConcrete) {
1525 const auto count =
1526 archetype.pair_matches(Pair(relIsConcrete ? queryRel : All, tgtIsConcrete ? queryTgt : All));
1527 if (count != 0)
1528 return true;
1529
1530 if (relIsConcrete && tgtIsConcrete)
1531 return false;
1532 }
1533
1534 GAIA_FOR(cnt) {
1535 const auto idInArchetype = archetypeIds[i];
1536 if (!idInArchetype.pair())
1537 continue;
1538
1539 if (relIsConcrete && idInArchetype.id() != queryRel.id())
1540 continue;
1541 if (tgtIsConcrete && idInArchetype.gen() != queryTgt.id())
1542 continue;
1543
1544 if (!relIsConcrete) {
1545 const auto rel = pair_rel(w, idInArchetype);
1546 if (rel == EntityBad)
1547 continue;
1548 }
1549 if (!tgtIsConcrete) {
1550 const auto tgt = pair_tgt(w, idInArchetype);
1551 if (tgt == EntityBad)
1552 continue;
1553 }
1554
1555 return true;
1556 }
1557
1558 return false;
1559 }
1560
1561 GAIA_NODISCARD inline bool next_self_src_var_match_cursor(
1562 const MatchingCtx& ctx, const QueryCompileCtx::VarTermOp& termOp, const VarBindings& varsIn,
1563 VarTermMatchCursor& cursor, VarBindings& outVars) {
1564 GAIA_ASSERT(ctx.pWorld != nullptr);
1565 GAIA_ASSERT(is_var_entity(termOp.term.src));
1566 GAIA_ASSERT(!var_is_bound(varsIn, termOp.term.src));
1567 GAIA_ASSERT(termOp.sourceOpcode == EOpcode::Src_Self);
1568
1569 const auto adv_matches = [&](std::span<const ComponentIndexEntry> sourceRecords, bool idsPreFiltered) {
1570 for (; cursor.sourceArchetypeIdx < sourceRecords.size(); ++cursor.sourceArchetypeIdx) {
1571 const auto* pSrcArchetype = sourceRecords[cursor.sourceArchetypeIdx].pArchetype;
1572 if (pSrcArchetype == nullptr)
1573 continue;
1574 if (!idsPreFiltered && !match_single_id_on_archetype(*ctx.pWorld, *pSrcArchetype, termOp.term.id))
1575 continue;
1576
1577 const auto& chunks = pSrcArchetype->chunks();
1578 for (; cursor.sourceChunkIdx < chunks.size(); ++cursor.sourceChunkIdx) {
1579 const auto* pChunk = chunks[cursor.sourceChunkIdx];
1580 if (pChunk == nullptr || pChunk->empty())
1581 continue;
1582
1583 const auto entities = pChunk->entity_view();
1584 for (; cursor.sourceEntityIdx < entities.size(); ++cursor.sourceEntityIdx) {
1585 const auto entity = entities[cursor.sourceEntityIdx];
1586 auto vars = varsIn;
1587 if (!bind_var(vars, termOp.term.src, entity))
1588 continue;
1589
1590 outVars = vars;
1591 ++cursor.sourceEntityIdx;
1592 return true;
1593 }
1594
1595 cursor.sourceEntityIdx = 0;
1596 }
1597
1598 cursor.sourceChunkIdx = 0;
1599 }
1600
1601 return false;
1602 };
1603
1604 const auto adv_matches_all = [&](std::span<const Archetype*> sourceArchetypes) {
1605 for (; cursor.sourceArchetypeIdx < sourceArchetypes.size(); ++cursor.sourceArchetypeIdx) {
1606 const auto* pSrcArchetype = sourceArchetypes[cursor.sourceArchetypeIdx];
1607 if (pSrcArchetype == nullptr)
1608 continue;
1609 if (!match_single_id_on_archetype(*ctx.pWorld, *pSrcArchetype, termOp.term.id))
1610 continue;
1611
1612 const auto& chunks = pSrcArchetype->chunks();
1613 for (; cursor.sourceChunkIdx < chunks.size(); ++cursor.sourceChunkIdx) {
1614 const auto* pChunk = chunks[cursor.sourceChunkIdx];
1615 if (pChunk == nullptr || pChunk->empty())
1616 continue;
1617
1618 const auto entities = pChunk->entity_view();
1619 for (; cursor.sourceEntityIdx < entities.size(); ++cursor.sourceEntityIdx) {
1620 const auto entity = entities[cursor.sourceEntityIdx];
1621 auto vars = varsIn;
1622 if (!bind_var(vars, termOp.term.src, entity))
1623 continue;
1624
1625 outVars = vars;
1626 ++cursor.sourceEntityIdx;
1627 return true;
1628 }
1629
1630 cursor.sourceEntityIdx = 0;
1631 }
1632
1633 cursor.sourceChunkIdx = 0;
1634 }
1635
1636 return false;
1637 };
1638
1639 if (!ctx.archetypeLookup.empty()) {
1640 const auto sourceArchetypes =
1641 ctx.archetypeLookup.fetch(ctx.allArchetypes, termOp.term.id, EntityLookupKey(termOp.term.id));
1642 if (adv_matches(sourceArchetypes, true))
1643 return true;
1644 } else if (adv_matches_all(ctx.allArchetypes))
1645 return true;
1646
1647 return false;
1648 }
1649
1650 GAIA_NODISCARD inline bool next_src_var_match_cursor_inverse(
1651 const MatchingCtx& ctx, const QueryCompileCtx::VarTermOp& termOp, const VarBindings& varsIn,
1652 VarTermMatchCursor& cursor, VarBindings& outVars, EOpcode inverseOpcode) {
1653 GAIA_ASSERT(ctx.pWorld != nullptr);
1654 GAIA_ASSERT(is_var_entity(termOp.term.src));
1655 GAIA_ASSERT(!var_is_bound(varsIn, termOp.term.src));
1656 GAIA_ASSERT(
1657 inverseOpcode == EOpcode::Src_Up || inverseOpcode == EOpcode::Src_Down ||
1658 inverseOpcode == EOpcode::Src_UpDown);
1659
1660 const auto adv_matches = [&](std::span<const ComponentIndexEntry> sourceRecords, bool idsPreFiltered) {
1661 for (; cursor.sourceArchetypeIdx < sourceRecords.size(); ++cursor.sourceArchetypeIdx) {
1662 const auto* pSrcArchetype = sourceRecords[cursor.sourceArchetypeIdx].pArchetype;
1663 if (pSrcArchetype == nullptr)
1664 continue;
1665 if (!idsPreFiltered && !match_single_id_on_archetype(*ctx.pWorld, *pSrcArchetype, termOp.term.id))
1666 continue;
1667
1668 const auto& chunks = pSrcArchetype->chunks();
1669 for (; cursor.sourceChunkIdx < chunks.size(); ++cursor.sourceChunkIdx) {
1670 const auto* pChunk = chunks[cursor.sourceChunkIdx];
1671 if (pChunk == nullptr || pChunk->empty())
1672 continue;
1673
1674 const auto entities = pChunk->entity_view();
1675 while (cursor.sourceEntityIdx < entities.size()) {
1676 if (cursor.source == EntityBad) {
1677 cursor.source = entities[cursor.sourceEntityIdx];
1678 cursor.sourceCursor = {};
1679 }
1680
1681 Entity candidate = EntityBad;
1682 if (next_lookup_src_cursor(
1683 *ctx.pWorld, inverseOpcode, termOp.term, cursor.source, cursor.sourceCursor, candidate)) {
1684 auto vars = varsIn;
1685 if (!bind_var(vars, termOp.term.src, candidate))
1686 continue;
1687
1688 outVars = vars;
1689 return true;
1690 }
1691
1692 cursor.source = EntityBad;
1693 ++cursor.sourceEntityIdx;
1694 }
1695
1696 cursor.sourceEntityIdx = 0;
1697 }
1698
1699 cursor.sourceChunkIdx = 0;
1700 }
1701
1702 return false;
1703 };
1704
1705 const auto adv_matches_all = [&](std::span<const Archetype*> sourceArchetypes) {
1706 for (; cursor.sourceArchetypeIdx < sourceArchetypes.size(); ++cursor.sourceArchetypeIdx) {
1707 const auto* pSrcArchetype = sourceArchetypes[cursor.sourceArchetypeIdx];
1708 if (pSrcArchetype == nullptr)
1709 continue;
1710 if (!match_single_id_on_archetype(*ctx.pWorld, *pSrcArchetype, termOp.term.id))
1711 continue;
1712
1713 const auto& chunks = pSrcArchetype->chunks();
1714 for (; cursor.sourceChunkIdx < chunks.size(); ++cursor.sourceChunkIdx) {
1715 const auto* pChunk = chunks[cursor.sourceChunkIdx];
1716 if (pChunk == nullptr || pChunk->empty())
1717 continue;
1718
1719 const auto entities = pChunk->entity_view();
1720 while (cursor.sourceEntityIdx < entities.size()) {
1721 if (cursor.source == EntityBad) {
1722 cursor.source = entities[cursor.sourceEntityIdx];
1723 cursor.sourceCursor = {};
1724 }
1725
1726 Entity candidate = EntityBad;
1727 if (next_lookup_src_cursor(
1728 *ctx.pWorld, inverseOpcode, termOp.term, cursor.source, cursor.sourceCursor, candidate)) {
1729 auto vars = varsIn;
1730 if (!bind_var(vars, termOp.term.src, candidate))
1731 continue;
1732
1733 outVars = vars;
1734 return true;
1735 }
1736
1737 cursor.source = EntityBad;
1738 ++cursor.sourceEntityIdx;
1739 }
1740
1741 cursor.sourceEntityIdx = 0;
1742 }
1743
1744 cursor.sourceChunkIdx = 0;
1745 }
1746
1747 return false;
1748 };
1749
1750 if (!ctx.archetypeLookup.empty()) {
1751 const auto sourceArchetypes =
1752 ctx.archetypeLookup.fetch(ctx.allArchetypes, termOp.term.id, EntityLookupKey(termOp.term.id));
1753 if (adv_matches(sourceArchetypes, true))
1754 return true;
1755 } else if (adv_matches_all(ctx.allArchetypes))
1756 return true;
1757
1758 return false;
1759 }
1760
1761 GAIA_NODISCARD inline bool next_up_src_var_match_cursor(
1762 const MatchingCtx& ctx, const QueryCompileCtx::VarTermOp& termOp, const VarBindings& varsIn,
1763 VarTermMatchCursor& cursor, VarBindings& outVars) {
1764 GAIA_ASSERT(ctx.pWorld != nullptr);
1765 GAIA_ASSERT(is_var_entity(termOp.term.src));
1766 GAIA_ASSERT(!var_is_bound(varsIn, termOp.term.src));
1767 GAIA_ASSERT(termOp.sourceOpcode == EOpcode::Src_Up);
1768 return next_src_var_match_cursor_inverse(ctx, termOp, varsIn, cursor, outVars, EOpcode::Src_Down);
1769 }
1770
1771 GAIA_NODISCARD inline bool next_down_src_var_match_cursor(
1772 const MatchingCtx& ctx, const QueryCompileCtx::VarTermOp& termOp, const VarBindings& varsIn,
1773 VarTermMatchCursor& cursor, VarBindings& outVars) {
1774 GAIA_ASSERT(termOp.sourceOpcode == EOpcode::Src_Down);
1775 return next_src_var_match_cursor_inverse(ctx, termOp, varsIn, cursor, outVars, EOpcode::Src_Up);
1776 }
1777
1778 GAIA_NODISCARD inline bool next_updown_src_var_match_cursor(
1779 const MatchingCtx& ctx, const QueryCompileCtx::VarTermOp& termOp, const VarBindings& varsIn,
1780 VarTermMatchCursor& cursor, VarBindings& outVars) {
1781 GAIA_ASSERT(termOp.sourceOpcode == EOpcode::Src_UpDown);
1782 return next_src_var_match_cursor_inverse(ctx, termOp, varsIn, cursor, outVars, EOpcode::Src_UpDown);
1783 }
1784
1785 GAIA_NODISCARD inline bool next_term_match_cursor(
1786 const MatchingCtx& ctx, const Archetype& archetype, const QueryCompileCtx::VarTermOp& termOp,
1787 const VarBindings& varsIn, VarTermMatchCursor& cursor, VarBindings& outVars) {
1788 const auto& term = termOp.term;
1789 const bool hasUnboundVar =
1790 term.src != EntityBad && is_var_entity(term.src) && !var_is_bound(varsIn, term.src);
1791 if (hasUnboundVar && termOp.sourceOpcode == EOpcode::Src_Self) {
1792 return next_self_src_var_match_cursor(ctx, termOp, varsIn, cursor, outVars);
1793 }
1794 if (hasUnboundVar && termOp.sourceOpcode == EOpcode::Src_Up) {
1795 return next_up_src_var_match_cursor(ctx, termOp, varsIn, cursor, outVars);
1796 }
1797 if (hasUnboundVar && termOp.sourceOpcode == EOpcode::Src_Down) {
1798 return next_down_src_var_match_cursor(ctx, termOp, varsIn, cursor, outVars);
1799 }
1800 if (hasUnboundVar && termOp.sourceOpcode == EOpcode::Src_UpDown) {
1801 return next_updown_src_var_match_cursor(ctx, termOp, varsIn, cursor, outVars);
1802 }
1803
1804 return next_term_match_cursor(*ctx.pWorld, archetype, termOp, varsIn, cursor, outVars);
1805 }
1806
1807 GAIA_NODISCARD inline bool term_has_match_bound(
1808 const World& w, const Archetype& candidateArchetype, const QueryCompileCtx::VarTermOp& termOp,
1809 const VarBindings& vars) {
1810 const auto& term = termOp.term;
1811 auto match_on_archetype = [&](const Archetype& archetype) {
1812 return match_id_bound(w, archetype, term.id, vars);
1813 };
1814
1815 if (term.src == EntityBad)
1816 return match_on_archetype(candidateArchetype);
1817
1818 auto sourceEntity = term.src;
1819 if (is_var_entity(sourceEntity)) {
1820 if (!var_is_bound(vars, sourceEntity))
1821 return false;
1822 sourceEntity = vars.values[var_index(sourceEntity)];
1823 }
1824
1825 return each_lookup_src(w, termOp.sourceOpcode, term, sourceEntity, [&](Entity source) {
1826 auto* pSrcArchetype = archetype_from_entity(w, source);
1827 if (pSrcArchetype == nullptr)
1828 return false;
1829 if (!match_on_archetype(*pSrcArchetype))
1830 return false;
1831
1832 return true;
1833 });
1834 }
1835
1836 template <typename Func>
1837 GAIA_NODISCARD inline bool each_id_match(
1838 const World& w, const Archetype& archetype, Entity queryId, const VarBindings& varsIn, Func&& func) {
1839 auto archetypeIds = archetype.ids_view();
1840 const auto cnt = (uint32_t)archetypeIds.size();
1841
1842 if (!queryId.pair()) {
1843 GAIA_FOR(cnt) {
1844 const auto idInArchetype = archetypeIds[i];
1845 if (idInArchetype.pair())
1846 continue;
1847
1848 const auto value = id_entity(w, idInArchetype);
1849 if (value == EntityBad)
1850 continue;
1851
1852 auto vars = varsIn;
1853 if (!match_token(vars, queryId, value, false))
1854 continue;
1855
1856 if (func(vars))
1857 return true;
1858 }
1859 return false;
1860 }
1861
1862 const auto queryRel = pair_rel(w, queryId);
1863 const auto queryTgt = pair_tgt(w, queryId);
1864 if (queryRel == EntityBad || queryTgt == EntityBad)
1865 return false;
1866 const auto rel = resolve_pair_query_token(queryRel, varsIn);
1867 const auto tgt = resolve_pair_query_token(queryTgt, varsIn);
1868
1869 GAIA_FOR(cnt) {
1870 const auto idInArchetype = archetypeIds[i];
1871 if (!idInArchetype.pair())
1872 continue;
1873
1874 if (rel.concrete && idInArchetype.id() != rel.matchValue.id())
1875 continue;
1876 if (tgt.concrete && idInArchetype.gen() != tgt.matchValue.id())
1877 continue;
1878
1879 if (!rel.needsBind && !tgt.needsBind) {
1880 if (func(varsIn))
1881 return true;
1882 continue;
1883 }
1884
1885 auto vars = varsIn;
1886 if (rel.needsBind) {
1887 const auto relValue = pair_rel(w, idInArchetype);
1888 if (relValue == EntityBad)
1889 continue;
1890 if (!match_token(vars, rel.token, relValue, true))
1891 continue;
1892 }
1893 if (tgt.needsBind) {
1894 const auto tgtValue = pair_tgt(w, idInArchetype);
1895 if (tgtValue == EntityBad)
1896 continue;
1897 if (!match_token(vars, tgt.token, tgtValue, true))
1898 continue;
1899 }
1900
1901 if (func(vars))
1902 return true;
1903 }
1904
1905 return false;
1906 }
1907
1908 template <typename Func>
1909 GAIA_NODISCARD inline bool each_term_match(
1910 const World& w, const Archetype& candidateArchetype, const QueryCompileCtx::VarTermOp& termOp,
1911 const VarBindings& varsIn, Func&& func) {
1912 const auto& term = termOp.term;
1913 auto&& matchFunc = GAIA_FWD(func);
1914 auto each_on_src = [&](Entity sourceEntity, const VarBindings& vars) {
1915 return each_lookup_src(w, termOp.sourceOpcode, term, sourceEntity, [&](Entity source) {
1916 auto* pSrcArchetype = archetype_from_entity(w, source);
1917 if (pSrcArchetype == nullptr)
1918 return false;
1919
1920 return each_id_match(w, *pSrcArchetype, term.id, vars, matchFunc);
1921 });
1922 };
1923
1924 if (term.src == EntityBad)
1925 return each_id_match(w, candidateArchetype, term.id, varsIn, matchFunc);
1926
1927 if (is_var_entity(term.src)) {
1928 if (!var_is_bound(varsIn, term.src))
1929 return false;
1930
1931 const auto source = varsIn.values[var_index(term.src)];
1932 return each_on_src(source, varsIn);
1933 }
1934
1935 return each_on_src(term.src, varsIn);
1936 }
1937
1938 template <typename OpKind, MatchingStyle Style>
1939 inline void match_archetype_inter(MatchingCtx& ctx, std::span<const ComponentIndexEntry> records) {
1940 if constexpr (Style != MatchingStyle::Complex) {
1941 if (ctx.idsToMatch.size() == 1) {
1942 for (const auto& entry: records) {
1943 const auto* pArchetype = entry.pArchetype;
1944 if (is_archetype_marked(ctx, pArchetype))
1945 continue;
1946#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
1947 if constexpr (Style == MatchingStyle::Simple) {
1948 if (!OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask))
1949 continue;
1950 }
1951#endif
1952 mark_archetype_match(ctx, pArchetype);
1953 }
1954 return;
1955 }
1956 }
1957
1958 if constexpr (Style == MatchingStyle::Complex) {
1959 for (const auto& record: records) {
1960 const auto* pArchetype = record.pArchetype;
1961 if (is_archetype_marked(ctx, pArchetype))
1962 continue;
1963
1964 if (!match_res_as<OpKind>(*ctx.pWorld, *pArchetype, ctx.idsToMatch))
1965 continue;
1966
1967 mark_archetype_match(ctx, pArchetype);
1968 }
1969 }
1970#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
1971 else if constexpr (Style == MatchingStyle::Simple) {
1972 for (const auto& record: records) {
1973 const auto* pArchetype = record.pArchetype;
1974 if (is_archetype_marked(ctx, pArchetype))
1975 continue;
1976
1977 // Try early exit
1978 if (!OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask))
1979 continue;
1980
1981 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
1982 continue;
1983
1984 mark_archetype_match(ctx, pArchetype);
1985 }
1986 }
1987#endif
1988 else {
1989 for (const auto& record: records) {
1990 const auto* pArchetype = record.pArchetype;
1991 if (is_archetype_marked(ctx, pArchetype))
1992 continue;
1993
1994 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
1995 continue;
1996
1997 mark_archetype_match(ctx, pArchetype);
1998 }
1999 }
2000 }
2001
2002 template <typename OpKind, MatchingStyle Style>
2003 inline void match_archetype_inter(MatchingCtx& ctx, std::span<const Archetype*> archetypes) {
2004 if constexpr (Style == MatchingStyle::Complex) {
2005 for (const auto* pArchetype: archetypes) {
2006 if (is_archetype_marked(ctx, pArchetype))
2007 continue;
2008
2009 if (!match_res_as<OpKind>(*ctx.pWorld, *pArchetype, ctx.idsToMatch))
2010 continue;
2011
2012 mark_archetype_match(ctx, pArchetype);
2013 }
2014 }
2015#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
2016 else if constexpr (Style == MatchingStyle::Simple) {
2017 for (const auto* pArchetype: archetypes) {
2018 if (is_archetype_marked(ctx, pArchetype))
2019 continue;
2020
2021 if (!OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask))
2022 continue;
2023
2024 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
2025 continue;
2026
2027 mark_archetype_match(ctx, pArchetype);
2028 }
2029 }
2030#endif
2031 else {
2032 for (const auto* pArchetype: archetypes) {
2033 if (is_archetype_marked(ctx, pArchetype))
2034 continue;
2035
2036 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
2037 continue;
2038
2039 mark_archetype_match(ctx, pArchetype);
2040 }
2041 }
2042 }
2043
2044 template <typename OpKind, MatchingStyle Style>
2045 inline void match_archetype_inter(
2046 MatchingCtx& ctx, EntityLookupKey entityKey, std::span<const ComponentIndexEntry> records) {
2047 const uint32_t lookupRevision = ctx.archetypeLookup.revision(entityKey);
2048 uint32_t lastMatchedIdx = OpKind::handle_last_match(ctx, entityKey, (uint32_t)records.size(), lookupRevision);
2049 if (lastMatchedIdx >= records.size())
2050 return;
2051
2052 auto recordsNew = std::span(&records[lastMatchedIdx], records.size() - lastMatchedIdx);
2053 match_archetype_inter<OpKind, Style>(ctx, recordsNew);
2054 }
2055
2056 template <typename OpKind, MatchingStyle Style>
2057 inline void
2058 match_archetype_inter(MatchingCtx& ctx, EntityLookupKey entityKey, std::span<const Archetype*> archetypes) {
2059 const uint32_t lookupRevision = ctx.archetypeLookup.revision(entityKey);
2060 uint32_t lastMatchedIdx =
2061 OpKind::handle_last_match(ctx, entityKey, (uint32_t)archetypes.size(), lookupRevision);
2062 if (lastMatchedIdx >= archetypes.size())
2063 return;
2064
2065 auto archetypesNew = std::span(&archetypes[lastMatchedIdx], archetypes.size() - lastMatchedIdx);
2066 match_archetype_inter<OpKind, Style>(ctx, archetypesNew);
2067 }
2068
2069 template <MatchingStyle Style>
2070 inline void match_archetype_all(MatchingCtx& ctx) {
2071 if constexpr (Style == MatchingStyle::Complex) {
2072 // For ALL we need all the archetypes to match. We start by checking
2073 // if the first one is registered in the world at all.
2074 if (ctx.ent.id() == Is.id()) {
2075 ctx.ent = EntityBad;
2076 match_archetype_inter<OpAll, Style>(ctx, EntityBadLookupKey, ctx.allArchetypes);
2077 } else {
2078 auto entityKey = EntityLookupKey(ctx.ent);
2079
2080 auto archetypes = ctx.archetypeLookup.fetch(ctx.allArchetypes, ctx.ent, entityKey);
2081 if (archetypes.empty())
2082 return;
2083
2084 match_archetype_inter<OpAll, Style>(ctx, entityKey, archetypes);
2085 }
2086 } else {
2087 auto entityKey = EntityLookupKey(ctx.ent);
2088
2089 // For ALL we need all the archetypes to match. We start by checking
2090 // if the first one is registered in the world at all.
2091 auto archetypes = ctx.archetypeLookup.fetch(ctx.allArchetypes, ctx.ent, entityKey);
2092 if (archetypes.empty())
2093 return;
2094
2095 match_archetype_inter<OpAll, Style>(ctx, entityKey, archetypes);
2096 }
2097 }
2098
2099 template <MatchingStyle Style>
2100 inline void match_archetype_or(MatchingCtx& ctx) {
2101 EntityLookupKey entityKey(ctx.ent);
2102
2103 // For OR we need at least one archetype to match.
2104 // However, because any of them can match, we need to check them all.
2105 // Iterating all of them is caller's responsibility.
2106 auto archetypes = ctx.archetypeLookup.fetch(ctx.allArchetypes, ctx.ent, entityKey);
2107 if (archetypes.empty())
2108 return;
2109
2110 match_archetype_inter<OpOr, Style>(ctx, entityKey, archetypes);
2111 }
2112
2113 inline void match_archetype_or_as(MatchingCtx& ctx) {
2114 EntityLookupKey entityKey = EntityBadLookupKey;
2115
2116 // For OR we need at least one archetype to match.
2117 // However, because any of them can match, we need to check them all.
2118 // Iterating all of them is caller's responsibility.
2119 if (ctx.ent.id() == Is.id()) {
2120 ctx.ent = EntityBad;
2121 match_archetype_inter<OpOr, MatchingStyle::Complex>(ctx, entityKey, ctx.allArchetypes);
2122 } else {
2123 entityKey = EntityLookupKey(ctx.ent);
2124
2125 auto archetypes = ctx.archetypeLookup.fetch(ctx.allArchetypes, ctx.ent, entityKey);
2126 if (archetypes.empty())
2127 return;
2128
2129 match_archetype_inter<OpOr, MatchingStyle::Complex>(ctx, entityKey, archetypes);
2130 }
2131 }
2132
2133 template <MatchingStyle Style>
2134 inline void match_archetype_no_2(MatchingCtx& ctx) {
2135 // We had some matches already (with ALL or OR). We need to remove those
2136 // that match with the NO list.
2137
2138 if constexpr (Style == MatchingStyle::Complex) {
2139 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2140 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2141
2142 if (match_res_as<OpNo>(*ctx.pWorld, *pArchetype, ctx.idsToMatch)) {
2143 ++i;
2144 continue;
2145 }
2146
2147 core::swap_erase(*ctx.pMatchesArr, i);
2148 }
2149 }
2150#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
2151 else if constexpr (Style == MatchingStyle::Simple) {
2152 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2153 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2154
2155 // Try early exit
2156 if (OpNo::check_mask(pArchetype->queryMask(), ctx.queryMask))
2157 continue;
2158
2159 if (match_res<OpNo>(*pArchetype, ctx.idsToMatch)) {
2160 ++i;
2161 continue;
2162 }
2163
2164 core::swap_erase(*ctx.pMatchesArr, i);
2165 }
2166 }
2167#endif
2168 else {
2169 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2170 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2171
2172 if (match_res<OpNo>(*pArchetype, ctx.idsToMatch)) {
2173 ++i;
2174 continue;
2175 }
2176
2177 core::swap_erase(*ctx.pMatchesArr, i);
2178 }
2179 }
2180 }
2181
2182 template <typename OpKind, MatchingStyle Style, bool WildcardWithAsFallback = false>
2183 inline void filter_current_matches(MatchingCtx& ctx, EntitySpan idsToMatch) {
2184 if constexpr (Style == MatchingStyle::Complex) {
2185 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2186 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2187 if (match_res_as<OpKind>(*ctx.pWorld, *pArchetype, idsToMatch)) {
2188 ++i;
2189 continue;
2190 }
2191
2192 core::swap_erase(*ctx.pMatchesArr, i);
2193 }
2194 }
2195#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
2196 else if constexpr (Style == MatchingStyle::Simple) {
2197 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2198 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2199 if (OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask) &&
2200 match_res<OpKind>(*pArchetype, idsToMatch)) {
2201 ++i;
2202 continue;
2203 }
2204
2205 core::swap_erase(*ctx.pMatchesArr, i);
2206 }
2207 }
2208#endif
2209 else {
2210 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
2211 const auto* pArchetype = (*ctx.pMatchesArr)[i];
2212 if (match_res<OpKind>(*pArchetype, idsToMatch) ||
2213 (WildcardWithAsFallback && match_res_as<OpKind>(*ctx.pWorld, *pArchetype, idsToMatch))) {
2214 ++i;
2215 continue;
2216 }
2217
2218 core::swap_erase(*ctx.pMatchesArr, i);
2219 }
2220 }
2221 }
2222
2223 template <MatchingStyle Style>
2224 GAIA_NODISCARD inline bool exec_not_impl(const QueryCompileCtx& comp, MatchingCtx& ctx) {
2225 ctx.idsToMatch = std::span{comp.ids_not.data(), comp.ids_not.size()};
2226
2227 if (ctx.targetEntities.empty()) {
2228 // We searched for nothing more than NOT matches
2229 if (ctx.pMatchesArr->empty()) {
2230 // If there are no previous matches (no ALL or OR matches),
2231 // we need to search among all archetypes.
2232 match_archetype_inter<detail::OpNo, Style>(ctx, EntityBadLookupKey, ctx.allArchetypes);
2233 } else {
2234 match_archetype_no_2<Style>(ctx);
2235 }
2236 } else {
2237 // We searched for nothing more than NOT matches
2238 if (ctx.pMatchesArr->empty())
2239 match_archetype_inter<detail::OpNo, Style>(ctx, ctx.allArchetypes);
2240 else
2241 match_archetype_no_2<Style>(ctx);
2242 }
2243
2244 return true;
2245 }
2246
2247 template <MatchingStyle Style>
2248 GAIA_NODISCARD inline bool exec_all_impl(const QueryCompileCtx& comp, MatchingCtx& ctx) {
2249 ctx.ent = comp.ids_all[0];
2250 ctx.idsToMatch = std::span{comp.ids_all.data(), comp.ids_all.size()};
2251
2252 if (ctx.targetEntities.empty())
2253 match_archetype_all<Style>(ctx);
2254 else
2255 match_archetype_inter<OpAll, Style>(ctx, ctx.allArchetypes);
2256
2257 // If no ALL matches were found, we can quit right away.
2258 return !ctx.pMatchesArr->empty();
2259 }
2260
2261 template <MatchingStyle Style>
2262 GAIA_NODISCARD inline bool exec_or_noall_impl(const QueryCompileCtx& comp, MatchingCtx& ctx) {
2263 if (ctx.skipOr)
2264 return true;
2265
2266 const auto cnt = comp.ids_or.size();
2267 // Try find matches with OR components.
2268 GAIA_FOR(cnt) {
2269 ctx.ent = comp.ids_or[i];
2270 const Entity idsToMatchData[1] = {ctx.ent};
2271 ctx.idsToMatch = EntitySpan{idsToMatchData, 1};
2272
2273 if constexpr (Style == MatchingStyle::Complex)
2274 match_archetype_or_as(ctx);
2275 else
2276 match_archetype_or<Style>(ctx);
2277 }
2278
2279 return true;
2280 }
2281
2282 template <MatchingStyle Style>
2283 GAIA_NODISCARD inline bool exec_or_withall_impl(const QueryCompileCtx& comp, MatchingCtx& ctx) {
2284 if (ctx.skipOr)
2285 return true;
2286
2287 ctx.idsToMatch = std::span{comp.ids_or.data(), comp.ids_or.size()};
2288
2289 if constexpr (Style == MatchingStyle::Complex)
2290 filter_current_matches<OpOr, MatchingStyle::Complex>(ctx, ctx.idsToMatch);
2291 else if constexpr (Style == MatchingStyle::Simple)
2292 filter_current_matches<OpOr, MatchingStyle::Simple>(ctx, ctx.idsToMatch);
2293 else
2294 filter_current_matches<OpOr, MatchingStyle::Wildcard, true>(ctx, ctx.idsToMatch);
2295
2296 return true;
2297 }
2298
2299 template <typename SourceTermsArray>
2300 GAIA_NODISCARD inline const QueryCompileCtx::SourceTermOp&
2301 get_src_term_op(const QueryCompileCtx& comp, const MatchingCtx& ctx, const SourceTermsArray& terms) {
2302 const auto& stackItem = comp.ops[ctx.pc];
2303 GAIA_ASSERT(stackItem.arg < terms.size());
2304 return terms[stackItem.arg];
2305 }
2306 } // namespace detail
2307
2312 static constexpr uint32_t OpcodeArgLimit = 256u;
2313 static_assert(
2314 MAX_ITEMS_IN_QUERY <= OpcodeArgLimit,
2315 "CompiledOp::arg is uint8_t. Increase arg width if query term capacity grows above 256.");
2316
2317 detail::QueryCompileCtx m_compCtx;
2318
2319 private:
2320 static const char* opcode_name(detail::EOpcode opcode) {
2321 static const char* s_names[] = {
2322 "all", //
2323 "allw", //
2324 "allc", //
2325 "or", //
2326 "orw", //
2327 "orc", //
2328 "ora", //
2329 "oraw", //
2330 "orac", //
2331 "not", //
2332 "notw", //
2333 "notc", //
2334 "seed", //
2335 "varf", //
2336 "src_all_t", //
2337 "src_not_t", //
2338 "src_or_t", //
2339 "nev", //
2340 "self", //
2341 "up", //
2342 "down", //
2343 "updown", //
2344 "term_all_check", //
2345 "term_all_bind", //
2346 "term_all_src_bind", //
2347 "term_or_check", //
2348 "term_or_bind", //
2349 "term_any_check", //
2350 "term_any_bind", //
2351 "term_not", //
2352 "search_all", //
2353 "search_or", //
2354 "search_other_or", //
2355 "search_other_or_bind", //
2356 "search_begin_any", //
2357 "search_any", //
2358 "search_maybe_finalize", //
2359 "final_not_check", //
2360 "final_require_or", //
2361 "final_or_check", //
2362 "final_success", //
2363 };
2364 static_assert(
2365 sizeof(s_names) / sizeof(s_names[0]) == (uint32_t)detail::EOpcode::Var_Final_Success + 1u,
2366 "Opcode name table out of sync with EOpcode.");
2367 return s_names[(uint32_t)opcode];
2368 }
2369
2370 GAIA_NODISCARD static bool opcode_has_arg(detail::EOpcode opcode) {
2371 return opcode == detail::EOpcode::Src_AllTerm || //
2372 opcode == detail::EOpcode::Src_NotTerm || //
2373 opcode == detail::EOpcode::Src_OrTerm;
2374 }
2375
2376 static void add_uint(util::str& out, uint32_t value) {
2377 char buffer[32];
2378 const auto len = GAIA_STRFMT(buffer, sizeof(buffer), "%u", value);
2379 GAIA_ASSERT(len >= 0);
2380 out.append(buffer, (uint32_t)len);
2381 }
2382
2383 static void add_cstr(util::str& out, const char* value) {
2384 GAIA_ASSERT(value != nullptr);
2385 out.append(value, (uint32_t)GAIA_STRLEN(value, 64));
2386 }
2387
2388 static void add_id_expr(util::str& out, const World& world, EntityId id) {
2389 if (is_variable(id)) {
2390 out.append('$');
2391 add_uint(out, (uint32_t)(id - Var0.id()));
2392 return;
2393 }
2394
2395 if (id == All.id()) {
2396 out.append('*');
2397 return;
2398 }
2399
2400 const auto entity = entity_from_id(world, id);
2401 if (entity != EntityBad)
2402 add_entity_expr(out, world, entity);
2403 else {
2404 out.append('#');
2405 add_uint(out, (uint32_t)id);
2406 }
2407 }
2408
2409 static void add_entity_expr(util::str& out, const World& world, Entity entity) {
2410 if (entity == EntityBad) {
2411 out.append("EntityBad");
2412 return;
2413 }
2414
2415 if (entity.pair()) {
2416 out.append('(');
2417 add_id_expr(out, world, (EntityId)entity.id());
2418 out.append(',');
2419 add_id_expr(out, world, (EntityId)entity.gen());
2420 out.append(')');
2421 return;
2422 }
2423
2424 if (is_variable(EntityId(entity.id()))) {
2425 out.append('$');
2426 add_uint(out, (uint32_t)(entity.id() - Var0.id()));
2427 return;
2428 }
2429
2430 if (entity.id() == All.id()) {
2431 out.append('*');
2432 return;
2433 }
2434
2435 const auto name = entity_name(world, entity);
2436 if (!name.empty()) {
2437 out.append(name.data(), name.size());
2438 return;
2439 }
2440
2441 add_uint(out, entity.id());
2442 out.append('.');
2443 add_uint(out, entity.gen());
2444 }
2445
2446 static void add_term_expr(util::str& out, const World& world, const QueryTerm& term) {
2447 add_entity_expr(out, world, term.id);
2448 out.append('(');
2449 if (term.src == EntityBad)
2450 out.append("$this");
2451 else
2452 add_entity_expr(out, world, term.src);
2453 out.append(')');
2454
2455 if (term.entTrav != EntityBad) {
2456 out.append(" trav=");
2457 add_entity_expr(out, world, term.entTrav);
2458 out.append(" depth=");
2459 if (term.travDepth == QueryTermOptions::TravDepthUnlimited)
2460 out.append('*');
2461 else
2462 add_uint(out, (uint32_t)term.travDepth);
2463 }
2464 }
2465
2466 static void
2467 add_ids_section(util::str& out, const char* title, std::span<const Entity> ids, const World& world) {
2468 if (ids.empty())
2469 return;
2470
2471 add_cstr(out, title);
2472 out.append(": ");
2473 add_uint(out, (uint32_t)ids.size());
2474 out.append('\n');
2475
2476 const auto cnt = (uint32_t)ids.size();
2477 GAIA_FOR(cnt) {
2478 out.append(" [");
2479 add_uint(out, i);
2480 out.append("] ");
2481 add_entity_expr(out, world, ids[i]);
2482 out.append('\n');
2483 }
2484 }
2485
2486 static void add_src_terms_section(
2487 util::str& out, const char* title,
2489 const World& world) {
2490 if (terms.empty())
2491 return;
2492
2493 add_cstr(out, title);
2494 out.append(": ");
2495 add_uint(out, (uint32_t)terms.size());
2496 out.append('\n');
2497
2498 const auto cnt = (uint32_t)terms.size();
2499 GAIA_FOR(cnt) {
2500 out.append(" [");
2501 add_uint(out, i);
2502 out.append("] ");
2503 add_cstr(out, opcode_name(terms[i].opcode));
2504 out.append(" id=");
2505 add_term_expr(out, world, terms[i].term);
2506 out.append('\n');
2507 }
2508 }
2509
2510 static void add_var_terms_section(
2511 util::str& out, const char* title,
2513 if (terms.empty())
2514 return;
2515
2516 add_cstr(out, title);
2517 out.append(": ");
2518 add_uint(out, (uint32_t)terms.size());
2519 out.append('\n');
2520
2521 const auto cnt = (uint32_t)terms.size();
2522 GAIA_FOR(cnt) {
2523 out.append(" [");
2524 add_uint(out, i);
2525 out.append("] ");
2526 add_cstr(out, opcode_name(terms[i].sourceOpcode));
2527 out.append(" id=");
2528 add_term_expr(out, world, terms[i].term);
2529 out.append('\n');
2530 }
2531 }
2532
2533 GAIA_NODISCARD static const QueryTerm&
2534 var_program_op_term(const detail::QueryCompileCtx& comp, const detail::CompiledOp& op) {
2535 switch (detail::var_program_opcode_meta(op.opcode).termSet) {
2536 case detail::EVarProgramTermSet::None:
2537 GAIA_ASSERT(false);
2538 return comp.terms_all_var[0].term;
2539 case detail::EVarProgramTermSet::Or:
2540 return comp.terms_or_var[(uint32_t)op.arg].term;
2541 case detail::EVarProgramTermSet::Any:
2542 return comp.terms_any_var[(uint32_t)op.arg].term;
2543 case detail::EVarProgramTermSet::Not:
2544 return comp.terms_not_var[(uint32_t)op.arg].term;
2545 case detail::EVarProgramTermSet::All:
2546 default:
2547 return comp.terms_all_var[(uint32_t)op.arg].term;
2548 }
2549 }
2550
2551 static void add_var_program_ops_section(
2552 util::str& out, const char* title, std::span<const detail::CompiledOp> ops,
2553 const detail::QueryCompileCtx& comp, const World& world) {
2554 if (ops.empty())
2555 return;
2556
2557 add_cstr(out, title);
2558 out.append(": ");
2559 add_uint(out, (uint32_t)ops.size());
2560 out.append('\n');
2561
2562 const auto cnt = (uint32_t)ops.size();
2563 GAIA_FOR(cnt) {
2564 const auto& op = ops[i];
2565 out.append(" [");
2566 add_uint(out, i);
2567 out.append("] ");
2568 add_cstr(out, opcode_name(op.opcode));
2569 if (detail::var_program_opcode_meta(op.opcode).termSet != detail::EVarProgramTermSet::None) {
2570 out.append(" term=");
2571 add_uint(out, (uint32_t)op.arg);
2572 out.append(" cost=");
2573 add_uint(out, (uint32_t)op.cost);
2574 out.append(" id=");
2575 add_term_expr(out, world, var_program_op_term(comp, op));
2576 }
2577 out.append(" ok=");
2578 add_uint(out, (uint32_t)op.pc_ok);
2579 out.append(" fail=");
2580 add_uint(out, (uint32_t)op.pc_fail);
2581 out.append('\n');
2582 }
2583 }
2584
2585 static void add_var_program_exec_section(util::str& out, const detail::QueryCompileCtx& comp) {
2586 if (comp.var_programs.empty())
2587 return;
2588
2589 out.append("var_exec: ");
2590 add_uint(out, (uint32_t)comp.var_programs.size());
2591 out.append('\n');
2592
2593 const auto cnt = (uint32_t)comp.var_programs.size();
2594 GAIA_FOR(cnt) {
2595 out.append(" [");
2596 add_uint(out, i);
2597 out.append("] search");
2598 out.append('\n');
2599 }
2600 }
2601
2602 static void add_var_program_sections(util::str& out, const detail::QueryCompileCtx& comp, const World& world) {
2603 const auto cnt = (uint32_t)comp.var_programs.size();
2604 GAIA_FOR(cnt) {
2605 const auto& step = comp.var_programs[i];
2606 char title[32];
2607 [[maybe_unused]] const auto len = GAIA_STRFMT(title, sizeof(title), "varp%u", i);
2608 GAIA_ASSERT(len > 0);
2609 add_var_program_ops_section(out, title, detail::program_ops(comp, step.program), comp, world);
2610 }
2611 }
2612
2613 private:
2614 GAIA_NODISCARD static detail::VarBindings make_initial_var_bindings(const MatchingCtx& ctx) {
2615 detail::VarBindings vars{};
2616 vars.mask = ctx.varBindingMask;
2617 GAIA_FOR(MaxVarCnt) {
2618 const auto bit = (uint8_t(1) << i);
2619 if ((vars.mask & bit) == 0)
2620 continue;
2621 vars.values[i] = ctx.varBindings[i];
2622 }
2623 return vars;
2624 }
2625
2626 GAIA_NODISCARD static uint8_t
2627 term_unbound_var_mask(const World& world, const QueryTerm& term, const detail::VarBindings& vars) {
2628 uint8_t mask = 0;
2629
2630 if (detail::is_var_entity(term.src) && !detail::var_is_bound(vars, term.src))
2631 mask |= (uint8_t(1) << detail::var_index(term.src));
2632
2633 if (!term.id.pair()) {
2634 const auto idEnt = id_entity(world, term.id);
2635 if (detail::is_var_entity(idEnt) && !detail::var_is_bound(vars, idEnt))
2636 mask |= (uint8_t(1) << detail::var_index(idEnt));
2637 return mask;
2638 }
2639
2640 const auto relEnt = pair_rel(world, term.id);
2641 if (detail::is_var_entity(relEnt) && !detail::var_is_bound(vars, relEnt))
2642 mask |= (uint8_t(1) << detail::var_index(relEnt));
2643
2644 const auto tgtEnt = pair_tgt(world, term.id);
2645 if (detail::is_var_entity(tgtEnt) && !detail::var_is_bound(vars, tgtEnt))
2646 mask |= (uint8_t(1) << detail::var_index(tgtEnt));
2647
2648 return mask;
2649 }
2650
2651 GAIA_NODISCARD bool eval_variable_terms_program_on_archetype(
2652 const MatchingCtx& ctx, const Archetype& archetype, bool orAlreadySatisfied) const {
2653 GAIA_ASSERT(m_compCtx.var_programs.size() == 1);
2654 const auto& programStep = m_compCtx.var_programs[0];
2655 return match_search_program_on_archetype(ctx, archetype, programStep, orAlreadySatisfied);
2656 }
2657
2658 GAIA_NODISCARD const detail::QueryCompileCtx::VarTermOp&
2659 search_program_term_op(const detail::CompiledOp& op) const {
2660 switch (op.opcode) {
2661 case detail::EOpcode::Var_Term_Or_Check:
2662 case detail::EOpcode::Var_Term_Or_Bind:
2663 case detail::EOpcode::Var_Final_Or_Check:
2664 return m_compCtx.terms_or_var[(uint32_t)op.arg];
2665 case detail::EOpcode::Var_Term_Any_Check:
2666 case detail::EOpcode::Var_Term_Any_Bind:
2667 return m_compCtx.terms_any_var[(uint32_t)op.arg];
2668 case detail::EOpcode::Var_Term_Not:
2669 case detail::EOpcode::Var_Final_Not_Check:
2670 return m_compCtx.terms_not_var[(uint32_t)op.arg];
2671 case detail::EOpcode::Var_Term_All_Check:
2672 case detail::EOpcode::Var_Term_All_Bind:
2673 case detail::EOpcode::Var_Term_All_Src_Bind:
2674 return m_compCtx.terms_all_var[(uint32_t)op.arg];
2675 default:
2676 GAIA_ASSERT(false);
2677 return m_compCtx.terms_all_var[0];
2678 }
2679 }
2680
2681 GAIA_NODISCARD bool select_next_pending_search_all_term(
2683 uint16_t pendingMask, const detail::VarBindings& vars, uint32_t& outLocalIdx, uint32_t& outPc,
2684 bool preferBoundTerms = true) const {
2685 outLocalIdx = (uint32_t)-1;
2686 outPc = (uint32_t)-1;
2687 uint32_t firstReadyLocalIdx = (uint32_t)-1;
2688 uint32_t firstReadyPc = (uint32_t)-1;
2689
2690 for (uint32_t localIdx = 0; localIdx < search.allCount; ++localIdx) {
2691 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
2692 if ((pendingMask & bit) == 0)
2693 continue;
2694
2695 const auto bindPc = (uint32_t)search.allBegin + localIdx;
2696 const auto& bindOp = programOps[bindPc];
2697 const auto& termOp = search_program_term_op(bindOp);
2698 if (detail::is_var_entity(termOp.term.src) && !detail::var_is_bound(vars, termOp.term.src) &&
2699 bindOp.opcode != detail::EOpcode::Var_Term_All_Src_Bind)
2700 continue;
2701
2702 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~vars.mask) != 0;
2703 const auto pc = bindsNewVars ? bindPc : (uint32_t)search.allCheckBegin + localIdx;
2704 if (preferBoundTerms && !bindsNewVars) {
2705 outLocalIdx = localIdx;
2706 outPc = pc;
2707 return true;
2708 }
2709
2710 if (firstReadyLocalIdx == (uint32_t)-1) {
2711 firstReadyLocalIdx = localIdx;
2712 firstReadyPc = pc;
2713 }
2714 }
2715
2716 if (firstReadyLocalIdx == (uint32_t)-1)
2717 return false;
2718
2719 outLocalIdx = firstReadyLocalIdx;
2720 outPc = firstReadyPc;
2721 return true;
2722 }
2723
2724 GAIA_NODISCARD bool select_next_pending_search_or_term(
2726 uint16_t pendingMask, uint16_t pendingCheckMask, const detail::VarBindings& vars, bool preferBoundTerms,
2727 bool requireNewBindings, uint32_t& outLocalIdx, uint32_t& outPc) const {
2728 outLocalIdx = (uint32_t)-1;
2729 outPc = (uint32_t)-1;
2730 if (requireNewBindings && (uint8_t)(search.orVarMask & ~vars.mask) == 0)
2731 return false;
2732
2733 uint32_t firstReadyLocalIdx = (uint32_t)-1;
2734 uint32_t firstReadyPc = (uint32_t)-1;
2735
2736 for (uint32_t localIdx = 0; localIdx < search.orCount; ++localIdx) {
2737 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
2738 if ((pendingMask & bit) == 0)
2739 continue;
2740
2741 const auto bindPc = (uint32_t)search.orBegin + localIdx;
2742 const auto& bindOp = programOps[bindPc];
2743 const auto& termOp = search_program_term_op(bindOp);
2744 if (detail::is_var_entity(termOp.term.src) && !detail::var_is_bound(vars, termOp.term.src))
2745 continue;
2746
2747 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~vars.mask) != 0;
2748 if (requireNewBindings) {
2749 if (!bindsNewVars)
2750 continue;
2751 outLocalIdx = localIdx;
2752 outPc = bindPc;
2753 return true;
2754 }
2755
2756 if (!bindsNewVars && (pendingCheckMask & bit) == 0)
2757 continue;
2758
2759 const auto pc = bindsNewVars ? bindPc : (uint32_t)search.orCheckBegin + localIdx;
2760 if (preferBoundTerms && !bindsNewVars) {
2761 outLocalIdx = localIdx;
2762 outPc = pc;
2763 return true;
2764 }
2765
2766 if (firstReadyLocalIdx == (uint32_t)-1) {
2767 firstReadyLocalIdx = localIdx;
2768 firstReadyPc = pc;
2769 }
2770 }
2771
2772 if (firstReadyLocalIdx == (uint32_t)-1)
2773 return false;
2774
2775 outLocalIdx = firstReadyLocalIdx;
2776 outPc = firstReadyPc;
2777 return true;
2778 }
2779
2780 GAIA_NODISCARD bool select_next_pending_search_any_term(
2782 uint16_t pendingMask, const detail::VarBindings& vars, uint32_t& outLocalIdx, uint32_t& outPc) const {
2783 outLocalIdx = (uint32_t)-1;
2784 outPc = (uint32_t)-1;
2785 uint32_t firstReadyBindingLocalIdx = (uint32_t)-1;
2786 uint32_t firstReadyBindingPc = (uint32_t)-1;
2787
2788 for (uint32_t localIdx = 0; localIdx < search.anyCount; ++localIdx) {
2789 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
2790 if ((pendingMask & bit) == 0)
2791 continue;
2792
2793 const auto bindPc = (uint32_t)search.anyBegin + localIdx;
2794 const auto& bindOp = programOps[bindPc];
2795 const auto& termOp = search_program_term_op(bindOp);
2796 if (detail::is_var_entity(termOp.term.src) && !detail::var_is_bound(vars, termOp.term.src))
2797 continue;
2798
2799 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~vars.mask) != 0;
2800 if (!bindsNewVars) {
2801 outLocalIdx = localIdx;
2802 outPc = (uint32_t)search.anyCheckBegin + localIdx;
2803 return true;
2804 }
2805
2806 if (firstReadyBindingLocalIdx == (uint32_t)-1) {
2807 firstReadyBindingLocalIdx = localIdx;
2808 firstReadyBindingPc = bindPc;
2809 }
2810 }
2811
2812 if (firstReadyBindingLocalIdx == (uint32_t)-1)
2813 return false;
2814
2815 outLocalIdx = firstReadyBindingLocalIdx;
2816 outPc = firstReadyBindingPc;
2817 return true;
2818 }
2819
2820 GAIA_NODISCARD int32_t select_best_pending_search_term(
2821 const MatchingCtx& ctx, const Archetype& archetype, std::span<const detail::CompiledOp> programOps,
2822 uint16_t begin, uint16_t count, uint16_t pendingMask, const detail::VarBindings& vars,
2823 uint32_t& outBestIdx) const {
2824 constexpr uint32_t MatchProbeLimit = 64;
2825 outBestIdx = (uint32_t)-1;
2826 uint32_t bestMatchCnt = MatchProbeLimit + 1;
2827
2828 for (uint32_t localIdx = 0; localIdx < count; ++localIdx) {
2829 const auto i = (uint32_t)begin + localIdx;
2830 const auto bit = (uint16_t)(uint16_t(1) << i);
2831 if ((pendingMask & bit) == 0)
2832 continue;
2833
2834 const auto& termOp = search_program_term_op(programOps[i]);
2835 if (detail::is_var_entity(termOp.term.src) && !detail::var_is_bound(vars, termOp.term.src))
2836 continue;
2837
2838 const auto matchCnt =
2839 detail::count_term_matches_limited(*ctx.pWorld, archetype, termOp, vars, bestMatchCnt);
2840 if (matchCnt == 0)
2841 return -1;
2842
2843 if (outBestIdx == (uint32_t)-1 || matchCnt < bestMatchCnt) {
2844 outBestIdx = i;
2845 bestMatchCnt = matchCnt;
2846 if (bestMatchCnt == 1)
2847 break;
2848 }
2849 }
2850
2851 return outBestIdx == (uint32_t)-1 ? 0 : 1;
2852 }
2853
2854 GAIA_NODISCARD bool can_skip_pending_search_all(
2856 uint16_t pendingAllMask, const detail::VarBindings& vars) const {
2857 const auto anyVarMask = m_compCtx.varMaskAny;
2858 for (uint32_t localIdx = 0; localIdx < search.allCount; ++localIdx) {
2859 const auto i = (uint32_t)search.allBegin + localIdx;
2860 const auto bit = (uint16_t(1) << i);
2861 if ((pendingAllMask & bit) == 0)
2862 continue;
2863
2864 const auto& termOp = search_program_term_op(programOps[i]);
2865 const auto missingMask = (uint8_t)(termOp.varMask & ~vars.mask);
2866 if (missingMask == 0)
2867 return false;
2868 if ((missingMask & ~anyVarMask) != 0)
2869 return false;
2870 }
2871
2872 return true;
2873 }
2874
2875 GAIA_NODISCARD bool match_search_program_on_archetype(
2876 const MatchingCtx& ctx, const Archetype& archetype,
2877 const detail::QueryCompileCtx::VarProgramStep& programStep, bool orAlreadySatisfied) const {
2878 using namespace detail;
2879
2880 static constexpr uint16_t BacktrackPC = (uint16_t)-1;
2881
2882 struct SearchProgramState {
2883 VarBindings vars{};
2884 uint16_t pendingAllMask = 0;
2885 uint16_t pendingOrMask = 0;
2886 uint16_t pendingFinalOrMask = 0;
2887 uint16_t pendingAnyMask = 0;
2888 uint16_t pc = BacktrackPC;
2889 uint8_t termOpIdx = 0xff;
2890 uint8_t bestOrIdx = 0xff;
2891 uint8_t scanIdx = 0;
2892 bool orMatched = false;
2893 bool anyMatched = false;
2894 bool currentAnyMatched = false;
2895 };
2896
2897 struct SearchBacktrackFrame {
2898 SearchProgramState state{};
2899 VarBindings varsBase{};
2900 VarTermMatchCursor cursor{};
2901 };
2902
2903 const auto& program = programStep.program;
2904 const auto& search = programStep.search;
2905 const auto programOps = detail::program_ops(m_compCtx, program);
2906 if (programOps.empty())
2907 return true;
2908 GAIA_ASSERT(search.selectAllPc != BacktrackPC);
2909 GAIA_ASSERT(search.selectOrPc != BacktrackPC);
2910 GAIA_ASSERT(search.selectOtherOrPc != BacktrackPC);
2911 GAIA_ASSERT(search.selectOtherOrBindPc != BacktrackPC);
2912 GAIA_ASSERT(search.beginAnyPc != BacktrackPC);
2913 GAIA_ASSERT(search.selectAnyPc != BacktrackPC);
2914 GAIA_ASSERT(search.maybeFinalizePc != BacktrackPC);
2915
2916 const auto is_term_ready = [&](const detail::CompiledOp& op, const VarBindings& vars) {
2917 const auto& termOp = search_program_term_op(op);
2918 return !is_var_entity(termOp.term.src) || var_is_bound(vars, termOp.term.src) ||
2919 op.opcode == EOpcode::Var_Term_All_Src_Bind;
2920 };
2921
2922 const auto can_binding_satisfy_pending_or = [&](const SearchProgramState& state,
2923 const VarBindings& nextVars) {
2924 if (state.orMatched || search.orCount == 0 || state.pendingOrMask == 0)
2925 return true;
2926
2927 bool hasDeferredOr = false;
2928 for (uint32_t localIdx = 0; localIdx < search.orCount; ++localIdx) {
2929 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
2930 if ((state.pendingOrMask & bit) == 0)
2931 continue;
2932
2933 const auto bindPc = (uint32_t)search.orBegin + localIdx;
2934 const auto& bindOp = programOps[bindPc];
2935 const auto& termOp = search_program_term_op(bindOp);
2936 const auto missingMaskBefore = (uint8_t)(termOp.varMask & ~state.vars.mask);
2937 const auto missingMaskAfter = (uint8_t)(termOp.varMask & ~nextVars.mask);
2938 if (missingMaskAfter != 0) {
2939 hasDeferredOr = true;
2940 continue;
2941 }
2942
2943 if (missingMaskBefore == 0 && (state.pendingFinalOrMask & bit) == 0)
2944 continue;
2945
2946 if (term_has_match(*ctx.pWorld, archetype, termOp, nextVars))
2947 return true;
2948 }
2949
2950 return hasDeferredOr;
2951 };
2952
2953 const auto adv_after_search_term_success = [&](SearchProgramState& state, const detail::CompiledOp& op,
2954 VarBindings nextVars) {
2955 const auto bit = (uint16_t)(uint16_t(1) << state.termOpIdx);
2956 state.vars = nextVars;
2957 switch (op.opcode) {
2958 case EOpcode::Var_Term_All_Check:
2959 case EOpcode::Var_Term_All_Bind:
2960 case EOpcode::Var_Term_All_Src_Bind:
2961 state.pendingAllMask = (uint16_t)(state.pendingAllMask & ~bit);
2962 state.pc = op.pc_ok;
2963 break;
2964 case EOpcode::Var_Term_Or_Check:
2965 case EOpcode::Var_Term_Or_Bind:
2966 state.pendingOrMask = (uint16_t)(state.pendingOrMask & ~(uint16_t(1) << state.termOpIdx));
2967 state.pendingFinalOrMask = (uint16_t)(state.pendingFinalOrMask & ~(uint16_t(1) << state.termOpIdx));
2968 state.orMatched = true;
2969 state.pc = op.pc_ok;
2970 break;
2971 case EOpcode::Var_Term_Any_Check:
2972 case EOpcode::Var_Term_Any_Bind:
2973 state.pendingAnyMask = (uint16_t)(state.pendingAnyMask & ~(uint16_t(1) << state.termOpIdx));
2974 state.anyMatched = true;
2975 state.currentAnyMatched = true;
2976 state.pc = op.pc_ok;
2977 break;
2978 default:
2979 GAIA_ASSERT(false);
2980 state.pc = BacktrackPC;
2981 break;
2982 }
2983 };
2984
2985 const auto handle_search_term_exhausted = [&](SearchProgramState& state, const detail::CompiledOp& op) {
2986 if (op.opcode == EOpcode::Var_Term_Any_Check || op.opcode == EOpcode::Var_Term_Any_Bind) {
2987 state.pendingAnyMask = (uint16_t)(state.pendingAnyMask & ~(uint16_t(1) << state.termOpIdx));
2988 }
2989 state.pc = op.pc_fail;
2990 };
2991
2992 const auto try_enter_search_term = [&](SearchProgramState& state,
2994 const auto& op = programOps[state.pc];
2995 const auto& termOp = search_program_term_op(op);
2996 SearchBacktrackFrame frame{};
2997 frame.state = state;
2998 frame.varsBase = state.vars;
2999
3000 VarBindings nextVars{};
3001 for (;;) {
3002 if (!detail::next_term_match_cursor(ctx, archetype, termOp, frame.varsBase, frame.cursor, nextVars))
3003 return false;
3004 if (can_binding_satisfy_pending_or(state, nextVars))
3005 break;
3006 }
3007
3008 if (op.opcode == EOpcode::Var_Term_Any_Check || op.opcode == EOpcode::Var_Term_Any_Bind) {
3009 frame.state.anyMatched = true;
3010 frame.state.currentAnyMatched = true;
3011 }
3012
3013 stack.push_back(GAIA_MOV(frame));
3014 adv_after_search_term_success(state, op, nextVars);
3015 return true;
3016 };
3017
3018 const auto backtrack = [&](SearchProgramState& state,
3020 while (!stack.empty()) {
3021 auto& frame = stack.back();
3022 const auto resumeState = frame.state;
3023 const auto& op = programOps[resumeState.pc];
3024 const auto& termOp = search_program_term_op(op);
3025 VarBindings nextVars{};
3026
3027 if (detail::next_term_match_cursor(ctx, archetype, termOp, frame.varsBase, frame.cursor, nextVars)) {
3028 if (op.opcode == EOpcode::Var_Term_Any_Check || op.opcode == EOpcode::Var_Term_Any_Bind) {
3029 frame.state.anyMatched = true;
3030 frame.state.currentAnyMatched = true;
3031 }
3032
3033 state = frame.state;
3034 adv_after_search_term_success(state, op, nextVars);
3035 return true;
3036 }
3037
3038 stack.pop_back();
3039 state = resumeState;
3040 handle_search_term_exhausted(state, op);
3041 if (state.pc != BacktrackPC)
3042 return true;
3043 }
3044
3045 return false;
3046 };
3047
3049 SearchProgramState state{};
3050 state.vars = make_initial_var_bindings(ctx);
3051 state.pendingAllMask = search.initialAllMask;
3052 state.pendingOrMask = search.initialOrMask;
3053 state.pendingFinalOrMask = search.initialOrMask;
3054 state.pendingAnyMask = search.initialAnyMask;
3055 state.pc = search.selectAllPc;
3056
3057 for (;;) {
3058 if (state.pc == BacktrackPC) {
3059 if (!backtrack(state, stack))
3060 return false;
3061 continue;
3062 }
3063 const auto& op = programOps[state.pc];
3064 switch (op.opcode) {
3065 case EOpcode::Var_Search_SelectAll: {
3066 if (state.pendingAllMask == 0) {
3067 state.pc = op.pc_fail;
3068 break;
3069 }
3070
3071 if (search.orCount == 0 && search.anyCount == 0) {
3072 uint32_t nextAllLocalIdx = (uint32_t)-1;
3073 uint32_t nextAllPc = (uint32_t)-1;
3074 if (select_next_pending_search_all_term(
3075 programOps, search, state.pendingAllMask, state.vars, nextAllLocalIdx, nextAllPc)) {
3076 const auto bindPc = (uint32_t)search.allBegin + nextAllLocalIdx;
3077 if (nextAllPc != bindPc) {
3078 state.termOpIdx = (uint8_t)nextAllLocalIdx;
3079 state.pc = (uint16_t)nextAllPc;
3080 break;
3081 }
3082 }
3083
3084 uint32_t bestAllIdx = (uint32_t)-1;
3085 const auto allSel = select_best_pending_search_term(
3086 ctx, archetype, programOps, search.allBegin, search.allCount, state.pendingAllMask, state.vars,
3087 bestAllIdx);
3088 if (allSel < 0) {
3089 if (!backtrack(state, stack))
3090 return false;
3091 break;
3092 }
3093
3094 if (allSel > 0) {
3095 state.termOpIdx = (uint8_t)bestAllIdx;
3096 state.pc = (uint16_t)bestAllIdx;
3097 break;
3098 }
3099 } else {
3100 uint32_t nextAllLocalIdx = (uint32_t)-1;
3101 uint32_t nextAllPc = (uint32_t)-1;
3102 if (select_next_pending_search_all_term(
3103 programOps, search, state.pendingAllMask, state.vars, nextAllLocalIdx, nextAllPc)) {
3104 state.termOpIdx = (uint8_t)nextAllLocalIdx;
3105 state.pc = (uint16_t)nextAllPc;
3106 break;
3107 }
3108 }
3109
3110 state.pc = op.pc_fail;
3111 break;
3112 }
3113 case EOpcode::Var_Search_SelectOr: {
3114 if (!state.orMatched && search.anyCount == 0 && (uint8_t)(search.orVarMask & ~state.vars.mask) == 0) {
3115 state.bestOrIdx = 0xff;
3116 state.scanIdx = 0;
3117 state.pc = search.maybeFinalizePc;
3118 break;
3119 }
3120
3121 if (state.orMatched && (uint8_t)(search.orVarMask & ~state.vars.mask) == 0) {
3122 state.bestOrIdx = 0xff;
3123 state.scanIdx = 0;
3124 state.pc = search.beginAnyPc;
3125 break;
3126 }
3127
3128 uint32_t nextOrLocalIdx = (uint32_t)-1;
3129 uint32_t nextOrPc = (uint32_t)-1;
3130 if (select_next_pending_search_or_term(
3131 programOps, search, state.pendingOrMask, state.pendingFinalOrMask, state.vars, !state.orMatched,
3132 state.orMatched, nextOrLocalIdx, nextOrPc)) {
3133 state.bestOrIdx = (uint8_t)nextOrLocalIdx;
3134 state.scanIdx = 0;
3135 state.termOpIdx = state.bestOrIdx;
3136 state.pc = (uint16_t)nextOrPc;
3137 break;
3138 }
3139
3140 state.bestOrIdx = 0xff;
3141 state.scanIdx = 0;
3142 state.pc = op.pc_fail;
3143 break;
3144 }
3145 case EOpcode::Var_Search_SelectOtherOr: {
3146 if (state.orMatched && (uint8_t)(search.orVarMask & ~state.vars.mask) == 0) {
3147 state.scanIdx = 0;
3148 state.pc = search.beginAnyPc;
3149 break;
3150 }
3151
3152 bool found = false;
3153 while (state.scanIdx < search.orCount) {
3154 const auto localIdx = (uint32_t)state.scanIdx++;
3155 if (localIdx == state.bestOrIdx)
3156 continue;
3157
3158 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
3159 if ((state.pendingOrMask & bit) == 0)
3160 continue;
3161 const auto bindPc = (uint32_t)search.orBegin + localIdx;
3162 if (!is_term_ready(programOps[bindPc], state.vars))
3163 continue;
3164
3165 const bool bindsNewVars =
3166 (uint8_t)(search_program_term_op(programOps[bindPc]).varMask & ~state.vars.mask) != 0;
3167 if (state.orMatched) {
3168 if (!bindsNewVars)
3169 continue;
3170 } else {
3171 if (!bindsNewVars && (state.pendingFinalOrMask & bit) == 0)
3172 continue;
3173 if (bindsNewVars)
3174 continue;
3175 }
3176
3177 state.termOpIdx = (uint8_t)localIdx;
3178 state.pc = (uint16_t)((uint32_t)search.orCheckBegin + localIdx);
3179 found = true;
3180 break;
3181 }
3182
3183 if (!found) {
3184 state.scanIdx = 0;
3185 state.pc = op.pc_fail;
3186 }
3187 break;
3188 }
3189 case EOpcode::Var_Search_SelectOtherOrBind: {
3190 if (state.orMatched) {
3191 state.pc = op.pc_fail;
3192 break;
3193 }
3194
3195 bool found = false;
3196 for (uint32_t localIdx = state.scanIdx; localIdx < search.orCount; ++localIdx) {
3197 if (localIdx == state.bestOrIdx)
3198 continue;
3199
3200 const auto bit = (uint16_t)(uint16_t(1) << localIdx);
3201 if ((state.pendingOrMask & bit) == 0)
3202 continue;
3203
3204 const auto bindPc = (uint32_t)search.orBegin + localIdx;
3205 if (!is_term_ready(programOps[bindPc], state.vars))
3206 continue;
3207
3208 const auto& termOp = search_program_term_op(programOps[bindPc]);
3209 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~state.vars.mask) != 0;
3210 if (!bindsNewVars)
3211 continue;
3212
3213 state.scanIdx = (uint8_t)(localIdx + 1u);
3214 state.termOpIdx = (uint8_t)localIdx;
3215 state.pc = (uint16_t)bindPc;
3216 found = true;
3217 break;
3218 }
3219
3220 if (!found)
3221 state.pc = op.pc_fail;
3222 break;
3223 }
3224 case EOpcode::Var_Search_BeginAny:
3225 state.anyMatched = false;
3226 state.scanIdx = 0;
3227 state.currentAnyMatched = false;
3228 state.pc = op.pc_ok;
3229 break;
3230 case EOpcode::Var_Search_SelectAny: {
3231 uint32_t nextAnyLocalIdx = (uint32_t)-1;
3232 uint32_t nextAnyPc = (uint32_t)-1;
3233 const bool found = select_next_pending_search_any_term(
3234 programOps, search, state.pendingAnyMask, state.vars, nextAnyLocalIdx, nextAnyPc);
3235 if (found) {
3236 state.termOpIdx = (uint8_t)nextAnyLocalIdx;
3237 state.currentAnyMatched = false;
3238 state.pc = (uint16_t)nextAnyPc;
3239 }
3240
3241 if (found)
3242 break;
3243 state.pc = op.pc_fail;
3244 break;
3245 }
3246 case EOpcode::Var_Search_MaybeFinalize:
3247 if (!state.anyMatched &&
3248 can_skip_pending_search_all(programOps, search, state.pendingAllMask, state.vars))
3249 state.pc = op.pc_ok;
3250 else if (op.pc_fail != BacktrackPC)
3251 state.pc = op.pc_fail;
3252 else if (!backtrack(state, stack))
3253 return false;
3254 break;
3255 case EOpcode::Var_Term_All_Check:
3256 if (term_has_match(*ctx.pWorld, archetype, search_program_term_op(op), state.vars))
3257 adv_after_search_term_success(state, op, state.vars);
3258 else {
3259 handle_search_term_exhausted(state, op);
3260 if (state.pc == BacktrackPC && !backtrack(state, stack))
3261 return false;
3262 }
3263 break;
3264 case EOpcode::Var_Term_All_Bind:
3265 case EOpcode::Var_Term_All_Src_Bind:
3266 if (!try_enter_search_term(state, stack)) {
3267 handle_search_term_exhausted(state, op);
3268 if (state.pc == BacktrackPC && !backtrack(state, stack))
3269 return false;
3270 }
3271 break;
3272 case EOpcode::Var_Term_Or_Check:
3273 case EOpcode::Var_Term_Or_Bind:
3274 case EOpcode::Var_Term_Any_Check:
3275 case EOpcode::Var_Term_Any_Bind: {
3276 const auto& termOp = search_program_term_op(op);
3277 const bool bindsNewVars = (uint8_t)(termOp.varMask & ~state.vars.mask) != 0;
3278 if (!bindsNewVars) {
3279 if (term_has_match(*ctx.pWorld, archetype, termOp, state.vars))
3280 adv_after_search_term_success(state, op, state.vars);
3281 else {
3282 if (op.opcode == EOpcode::Var_Term_Or_Check || op.opcode == EOpcode::Var_Term_Or_Bind)
3283 state.pendingFinalOrMask =
3284 (uint16_t)(state.pendingFinalOrMask & ~(uint16_t(1) << state.termOpIdx));
3285 handle_search_term_exhausted(state, op);
3286 if (state.pc == BacktrackPC && !backtrack(state, stack))
3287 return false;
3288 }
3289 break;
3290 }
3291
3292 if (!try_enter_search_term(state, stack)) {
3293 handle_search_term_exhausted(state, op);
3294 if (state.pc == BacktrackPC && !backtrack(state, stack))
3295 return false;
3296 }
3297 break;
3298 }
3299 case EOpcode::Var_Final_Not_Check:
3300 if (term_has_match(*ctx.pWorld, archetype, search_program_term_op(op), state.vars)) {
3301 if (!backtrack(state, stack))
3302 return false;
3303 } else
3304 state.pc = op.pc_ok;
3305 break;
3306 case EOpcode::Var_Final_Require_Or:
3307 if (orAlreadySatisfied || state.orMatched || search.orCount == 0)
3308 state.pc = op.pc_ok;
3309 else if (op.pc_fail != BacktrackPC)
3310 state.pc = op.pc_fail;
3311 else if (!backtrack(state, stack))
3312 return false;
3313 break;
3314 case EOpcode::Var_Final_Or_Check:
3315 if ((state.pendingFinalOrMask & (uint16_t(1) << op.arg)) == 0)
3316 state.pc = op.pc_fail;
3317 else if (term_has_match(*ctx.pWorld, archetype, search_program_term_op(op), state.vars))
3318 state.pc = op.pc_ok;
3319 else {
3320 state.pendingFinalOrMask = (uint16_t)(state.pendingFinalOrMask & ~(uint16_t(1) << op.arg));
3321 if (op.pc_fail != BacktrackPC)
3322 state.pc = op.pc_fail;
3323 else if (!backtrack(state, stack))
3324 return false;
3325 }
3326 break;
3327 case EOpcode::Var_Final_Success:
3328 return true;
3329 default:
3330 GAIA_ASSERT(false);
3331 return false;
3332 }
3333 }
3334 }
3335 using VarEvalFunc = bool (VirtualMachine::*)(const MatchingCtx&, const Archetype&, bool) const;
3336
3337 void filter_variable_terms(MatchingCtx& ctx, VarEvalFunc evalFunc) const {
3338 if (!m_compCtx.has_variable_terms())
3339 return;
3340
3341 const bool orAlreadySatisfied = !m_compCtx.ids_or.empty() || ctx.skipOr;
3342 const auto sourceCnt = ctx.pMatchesArr->size();
3343 constexpr uint32_t FilterChunkSize = 64;
3345 uint32_t writeIdx = 0;
3346
3347 const auto flush_filtered = [&]() {
3348 for (const auto* pFiltered: filtered) {
3349 (*ctx.pMatchesArr)[writeIdx++] = pFiltered;
3350 }
3351 filtered.clear();
3352 };
3353
3354 GAIA_FOR(sourceCnt) {
3355 const auto* pArchetype = (*ctx.pMatchesArr)[i];
3356 const bool matched = (this->*evalFunc)(ctx, *pArchetype, orAlreadySatisfied);
3357 if (!matched)
3358 continue;
3359
3360 filtered.push_back(pArchetype);
3361 if (filtered.size() != FilterChunkSize)
3362 continue;
3363
3364 flush_filtered();
3365 }
3366
3367 if (!filtered.empty())
3368 flush_filtered();
3369
3370 ctx.pMatchesArr->resize(writeIdx);
3371 }
3372 GAIA_NODISCARD detail::VmLabel add_op(detail::CompiledOp&& op) {
3373 const auto cnt = (detail::VmLabel)m_compCtx.ops.size();
3374 op.pc_ok = cnt + 1;
3375 op.pc_fail = cnt - 1;
3376 m_compCtx.ops.push_back(GAIA_MOV(op));
3377 return cnt;
3378 }
3379
3380 GAIA_NODISCARD detail::VmLabel add_gate_op(detail::CompiledOp&& op) {
3381 const auto cnt = add_op(GAIA_MOV(op));
3382 m_compCtx.ops[cnt].pc_fail = (detail::VmLabel)-1;
3383 return cnt;
3384 }
3385
3386 GAIA_NODISCARD static uint8_t opcode_arg(uint32_t idx) {
3387 GAIA_ASSERT(idx < OpcodeArgLimit);
3388 return (uint8_t)idx;
3389 }
3390
3391 template <typename SourceTermsArray>
3392 void emit_src_gate_terms(const SourceTermsArray& terms, detail::EOpcode opcode) {
3393 const auto cnt = (uint32_t)terms.size();
3394 GAIA_FOR(cnt) {
3395 detail::CompiledOp op{};
3396 op.opcode = opcode;
3397 op.arg = opcode_arg(i);
3398 (void)add_gate_op(GAIA_MOV(op));
3399 }
3400 }
3401
3402 void emit_src_or_terms(bool hasOrFallback) {
3404
3405 const auto srcOrCnt = (uint32_t)m_compCtx.terms_or_src.size();
3406 GAIA_FOR(srcOrCnt) {
3407 detail::CompiledOp op{};
3408 op.opcode = detail::EOpcode::Src_OrTerm;
3409 op.arg = opcode_arg(i);
3410 orSrcOpLabels.push_back(add_op(GAIA_MOV(op)));
3411 }
3412
3413 const auto orExitPc = (detail::VmLabel)m_compCtx.ops.size();
3414 for (const auto opLabel: orSrcOpLabels)
3415 m_compCtx.ops[opLabel].pc_ok = orExitPc;
3416
3417 const auto lastIdx = (uint32_t)orSrcOpLabels.size() - 1u;
3418 GAIA_FOR(lastIdx) {
3419 m_compCtx.ops[orSrcOpLabels[i]].pc_fail = orSrcOpLabels[i + 1];
3420 }
3421
3422 m_compCtx.ops[orSrcOpLabels[lastIdx]].pc_fail = hasOrFallback ? orExitPc : (detail::VmLabel)-1;
3423 }
3424
3425 GAIA_NODISCARD static detail::EOpcode pick_all_opcode(bool isSimple, bool isAs) {
3426 if (isSimple)
3427 return detail::EOpcode::All_Simple;
3428 if (isAs)
3429 return detail::EOpcode::All_Complex;
3430 return detail::EOpcode::All_Wildcard;
3431 }
3432
3433 GAIA_NODISCARD static detail::EOpcode pick_or_opcode(bool hasAllTerms, bool isSimple, bool isAs) {
3434 if (!hasAllTerms) {
3435 if (isSimple)
3436 return detail::EOpcode::Or_NoAll_Simple;
3437 if (isAs)
3438 return detail::EOpcode::Or_NoAll_Complex;
3439 return detail::EOpcode::Or_NoAll_Wildcard;
3440 }
3441
3442 if (isSimple)
3443 return detail::EOpcode::Or_WithAll_Simple;
3444 if (isAs)
3445 return detail::EOpcode::Or_WithAll_Complex;
3446 return detail::EOpcode::Or_WithAll_Wildcard;
3447 }
3448
3449 GAIA_NODISCARD static detail::EOpcode pick_not_opcode(bool isSimple, bool isAs) {
3450 if (isSimple)
3451 return detail::EOpcode::Not_Simple;
3452 if (isAs)
3453 return detail::EOpcode::Not_Complex;
3454 return detail::EOpcode::Not_Wildcard;
3455 }
3456
3457 using OpcodeFunc = bool (VirtualMachine::*)(MatchingCtx&) const;
3458
3459 GAIA_NODISCARD bool op_all_simple(MatchingCtx& ctx) const {
3460 GAIA_PROF_SCOPE(vm::op_and_simple);
3461 return detail::exec_all_impl<MatchingStyle::Simple>(m_compCtx, ctx);
3462 }
3463
3464 GAIA_NODISCARD bool op_all_wildcard(MatchingCtx& ctx) const {
3465 GAIA_PROF_SCOPE(vm::op_and_wildcard);
3466 return detail::exec_all_impl<MatchingStyle::Wildcard>(m_compCtx, ctx);
3467 }
3468
3469 GAIA_NODISCARD bool op_all_complex(MatchingCtx& ctx) const {
3470 GAIA_PROF_SCOPE(vm::op_and_complex);
3471 return detail::exec_all_impl<MatchingStyle::Complex>(m_compCtx, ctx);
3472 }
3473
3474 GAIA_NODISCARD bool op_or_noall_simple(MatchingCtx& ctx) const {
3475 GAIA_PROF_SCOPE(vm::op_or);
3476 return detail::exec_or_noall_impl<MatchingStyle::Simple>(m_compCtx, ctx);
3477 }
3478
3479 GAIA_NODISCARD bool op_or_noall_wildcard(MatchingCtx& ctx) const {
3480 GAIA_PROF_SCOPE(vm::op_or);
3481 return detail::exec_or_noall_impl<MatchingStyle::Wildcard>(m_compCtx, ctx);
3482 }
3483
3484 GAIA_NODISCARD bool op_or_noall_complex(MatchingCtx& ctx) const {
3485 GAIA_PROF_SCOPE(vm::op_or);
3486 return detail::exec_or_noall_impl<MatchingStyle::Complex>(m_compCtx, ctx);
3487 }
3488
3489 GAIA_NODISCARD bool op_or_withall_simple(MatchingCtx& ctx) const {
3490 GAIA_PROF_SCOPE(vm::op_or);
3491 return detail::exec_or_withall_impl<MatchingStyle::Simple>(m_compCtx, ctx);
3492 }
3493
3494 GAIA_NODISCARD bool op_or_withall_wildcard(MatchingCtx& ctx) const {
3495 GAIA_PROF_SCOPE(vm::op_or);
3496 return detail::exec_or_withall_impl<MatchingStyle::Wildcard>(m_compCtx, ctx);
3497 }
3498
3499 GAIA_NODISCARD bool op_or_withall_complex(MatchingCtx& ctx) const {
3500 GAIA_PROF_SCOPE(vm::op_or);
3501 return detail::exec_or_withall_impl<MatchingStyle::Complex>(m_compCtx, ctx);
3502 }
3503
3504 GAIA_NODISCARD bool op_not_simple(MatchingCtx& ctx) const {
3505 GAIA_PROF_SCOPE(vm::op_not);
3506 return detail::exec_not_impl<MatchingStyle::Simple>(m_compCtx, ctx);
3507 }
3508
3509 GAIA_NODISCARD bool op_not_wildcard(MatchingCtx& ctx) const {
3510 GAIA_PROF_SCOPE(vm::op_not);
3511 return detail::exec_not_impl<MatchingStyle::Wildcard>(m_compCtx, ctx);
3512 }
3513
3514 GAIA_NODISCARD bool op_not_complex(MatchingCtx& ctx) const {
3515 GAIA_PROF_SCOPE(vm::op_not);
3516 return detail::exec_not_impl<MatchingStyle::Complex>(m_compCtx, ctx);
3517 }
3518
3519 GAIA_NODISCARD bool op_seed_all(MatchingCtx& ctx) const {
3520 GAIA_PROF_SCOPE(vm::op_seed_all);
3521 detail::add_all_archetypes(ctx);
3522 return true;
3523 }
3524
3525 GAIA_NODISCARD bool op_var_filter(MatchingCtx& ctx) const {
3526 GAIA_PROF_SCOPE(vm::op_var_filter);
3527 GAIA_ASSERT(!m_compCtx.var_programs.empty());
3528 filter_variable_terms(ctx, &VirtualMachine::eval_variable_terms_program_on_archetype);
3529 return true;
3530 }
3531
3532 GAIA_NODISCARD bool op_src_all_term(MatchingCtx& ctx) const {
3533 GAIA_PROF_SCOPE(vm::op_src_all);
3534 const auto& termOp = detail::get_src_term_op(m_compCtx, ctx, m_compCtx.terms_all_src);
3535 return detail::match_src_term(*ctx.pWorld, termOp.term, termOp.opcode);
3536 }
3537
3538 GAIA_NODISCARD bool op_src_not_term(MatchingCtx& ctx) const {
3539 GAIA_PROF_SCOPE(vm::op_src_not);
3540 const auto& termOp = detail::get_src_term_op(m_compCtx, ctx, m_compCtx.terms_not_src);
3541 return !detail::match_src_term(*ctx.pWorld, termOp.term, termOp.opcode);
3542 }
3543
3544 GAIA_NODISCARD bool op_src_or_term(MatchingCtx& ctx) const {
3545 GAIA_PROF_SCOPE(vm::op_src_or);
3546 const auto& termOp = detail::get_src_term_op(m_compCtx, ctx, m_compCtx.terms_or_src);
3547 const bool matched = detail::match_src_term(*ctx.pWorld, termOp.term, termOp.opcode);
3548 if (!matched)
3549 return false;
3550
3551 ctx.skipOr = true;
3552 if (m_compCtx.ids_all.empty())
3553 detail::add_all_archetypes(ctx);
3554 return true;
3555 }
3556
3557 static constexpr OpcodeFunc OpcodeFuncs[] = {
3558 &VirtualMachine::op_all_simple, //
3559 &VirtualMachine::op_all_wildcard, //
3560 &VirtualMachine::op_all_complex, //
3561 &VirtualMachine::op_or_noall_simple, //
3562 &VirtualMachine::op_or_noall_wildcard, //
3563 &VirtualMachine::op_or_noall_complex, //
3564 &VirtualMachine::op_or_withall_simple, //
3565 &VirtualMachine::op_or_withall_wildcard, //
3566 &VirtualMachine::op_or_withall_complex, //
3567 &VirtualMachine::op_not_simple, //
3568 &VirtualMachine::op_not_wildcard, //
3569 &VirtualMachine::op_not_complex, //
3570 &VirtualMachine::op_seed_all, //
3571 &VirtualMachine::op_var_filter, //
3572 &VirtualMachine::op_src_all_term, //
3573 &VirtualMachine::op_src_not_term, //
3574 &VirtualMachine::op_src_or_term //
3575 };
3576 static_assert(
3577 sizeof(OpcodeFuncs) / sizeof(OpcodeFuncs[0]) == (uint32_t)detail::EOpcode::Src_Never,
3578 "OpcodeFuncs must contain all executable opcodes.");
3579
3580 GAIA_NODISCARD bool exec_opcode(const detail::CompiledOp& stackItem, MatchingCtx& ctx) const {
3581 const auto opcodeIdx = (uint32_t)stackItem.opcode;
3582 GAIA_ASSERT(opcodeIdx < (uint32_t)detail::EOpcode::Src_Never);
3583 return (this->*OpcodeFuncs[opcodeIdx])(ctx);
3584 }
3585
3586 public:
3587 GAIA_NODISCARD util::str bytecode(const World& world) const {
3588 util::str out;
3589 out.reserve(2048);
3590
3591 out.append("main_ops: ");
3592 add_uint(out, (uint32_t)m_compCtx.mainOpsCount);
3593 out.append('\n');
3594
3595 const auto opsCnt = (uint32_t)m_compCtx.mainOpsCount;
3596 GAIA_FOR(opsCnt) {
3597 const auto& op = m_compCtx.ops[i];
3598 out.append(" [");
3599 add_uint(out, i);
3600 out.append("] ");
3601 add_cstr(out, opcode_name(op.opcode));
3602 if (opcode_has_arg(op.opcode)) {
3603 out.append(" arg=");
3604 add_uint(out, op.arg);
3605 }
3606 out.append(" ok=");
3607 add_uint(out, op.pc_ok);
3608 out.append(" fail=");
3609 add_uint(out, op.pc_fail);
3610 out.append('\n');
3611 }
3612
3613 add_ids_section(
3614 out, "ids_all", std::span<const Entity>{m_compCtx.ids_all.data(), m_compCtx.ids_all.size()}, world);
3615 add_ids_section(
3616 out, "ids_or", std::span<const Entity>{m_compCtx.ids_or.data(), m_compCtx.ids_or.size()}, world);
3617 add_ids_section(
3618 out, "ids_not", std::span<const Entity>{m_compCtx.ids_not.data(), m_compCtx.ids_not.size()}, world);
3619
3620 add_src_terms_section(out, "src_all", m_compCtx.terms_all_src, world);
3621 add_src_terms_section(out, "src_or", m_compCtx.terms_or_src, world);
3622 add_src_terms_section(out, "src_not", m_compCtx.terms_not_src, world);
3623
3624 add_var_terms_section(out, "var_all", m_compCtx.terms_all_var, world);
3625 add_var_terms_section(out, "var_or", m_compCtx.terms_or_var, world);
3626 add_var_terms_section(out, "var_not", m_compCtx.terms_not_var, world);
3627 add_var_terms_section(out, "var_any", m_compCtx.terms_any_var, world);
3628 add_var_program_exec_section(out, m_compCtx);
3629 add_var_program_sections(out, m_compCtx, world);
3630
3631 return out;
3632 }
3633
3636 const EntityToArchetypeMap& entityToArchetypeMap, std::span<const Archetype*> allArchetypes,
3637 QueryCtx& queryCtx) {
3638 GAIA_PROF_SCOPE(vm::compile);
3639 (void)entityToArchetypeMap;
3640 (void)allArchetypes;
3641
3642 m_compCtx.ids_all.clear();
3643 m_compCtx.ids_or.clear();
3644 m_compCtx.ids_not.clear();
3645 m_compCtx.terms_all_src.clear();
3646 m_compCtx.terms_or_src.clear();
3647 m_compCtx.terms_not_src.clear();
3648 m_compCtx.terms_all_var.clear();
3649 m_compCtx.terms_or_var.clear();
3650 m_compCtx.terms_not_var.clear();
3651 m_compCtx.terms_any_var.clear();
3652 m_compCtx.varMaskAll = 0;
3653 m_compCtx.varMaskOr = 0;
3654 m_compCtx.varMaskNot = 0;
3655 m_compCtx.varMaskAny = 0;
3656 m_compCtx.var_programs.clear();
3657 m_compCtx.mainOpsCount = 0;
3658 m_compCtx.ops.clear();
3659
3660 auto& data = queryCtx.data;
3661 GAIA_ASSERT(queryCtx.w != nullptr);
3662 const auto& world = *queryCtx.w;
3663 const bool hasEntityFilterTerms = data.deps.has_dep_flag(QueryCtx::DependencyHasEntityFilterTerms);
3664 auto isAdjunctDirectTerm = [&](const QueryTerm& term) {
3665 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
3666 return false;
3667
3668 const auto id = term.id;
3669 return (id.pair() && world_is_exclusive_dont_fragment_relation(world, pair_rel(world, id))) ||
3670 (!id.pair() && world_is_non_fragmenting_out_of_line_component(world, id));
3671 };
3672
3673 QueryTermSpan terms = data.terms_view_mut();
3674 QueryTermSpan terms_all = terms.subspan(0, data.firstOr);
3675 QueryTermSpan terms_or = terms.subspan(data.firstOr, data.firstNot - data.firstOr);
3676 QueryTermSpan terms_not = terms.subspan(data.firstNot, data.firstAny - data.firstNot);
3677 QueryTermSpan terms_any = terms.subspan(data.firstAny);
3678
3679 // ALL
3680 if (!terms_all.empty()) {
3681 GAIA_PROF_SCOPE(vm::compile_all);
3682
3683 const auto cnt = terms_all.size();
3684 GAIA_FOR(cnt) {
3685 auto& p = terms_all[i];
3686 if (isAdjunctDirectTerm(p))
3687 continue;
3688 if (term_has_variables(p)) {
3689 const auto varMask = term_unbound_var_mask(world, p, detail::VarBindings{});
3690 m_compCtx.terms_all_var.push_back({detail::src_opcode_from_term(p), p, varMask});
3691 m_compCtx.varMaskAll |= varMask;
3692 continue;
3693 }
3694
3695 if (p.src == EntityBad) {
3696 m_compCtx.ids_all.push_back(p.id);
3697 continue;
3698 }
3699 m_compCtx.terms_all_src.push_back({detail::src_opcode_from_term(p), p});
3700 }
3701 }
3702
3703 // OR
3704 if (!terms_or.empty()) {
3705 GAIA_PROF_SCOPE(vm::compile_or);
3706
3707 const auto cnt = terms_or.size();
3708 GAIA_FOR(cnt) {
3709 auto& p = terms_or[i];
3710 if (p.src == EntityBad && hasEntityFilterTerms)
3711 continue;
3712 if (term_has_variables(p)) {
3713 const auto varMask = term_unbound_var_mask(world, p, detail::VarBindings{});
3714 m_compCtx.terms_or_var.push_back({detail::src_opcode_from_term(p), p, varMask});
3715 m_compCtx.varMaskOr |= varMask;
3716 continue;
3717 }
3718
3719 if (p.src == EntityBad)
3720 m_compCtx.ids_or.push_back(p.id);
3721 else
3722 m_compCtx.terms_or_src.push_back({detail::src_opcode_from_term(p), p});
3723 }
3724 }
3725
3726 // NOT
3727 if (!terms_not.empty()) {
3728 GAIA_PROF_SCOPE(vm::compile_not);
3729
3730 const auto cnt = terms_not.size();
3731 GAIA_FOR(cnt) {
3732 auto& p = terms_not[i];
3733 if (isAdjunctDirectTerm(p))
3734 continue;
3735 if (term_has_variables(p)) {
3736 const auto varMask = term_unbound_var_mask(world, p, detail::VarBindings{});
3737 m_compCtx.terms_not_var.push_back({detail::src_opcode_from_term(p), p, varMask});
3738 m_compCtx.varMaskNot |= varMask;
3739 continue;
3740 }
3741
3742 if (p.src == EntityBad)
3743 m_compCtx.ids_not.push_back(p.id);
3744 else
3745 m_compCtx.terms_not_src.push_back({detail::src_opcode_from_term(p), p});
3746 }
3747 }
3748
3749 // ANY
3750 if (!terms_any.empty()) {
3751 GAIA_PROF_SCOPE(vm::compile_any);
3752
3753 const auto cnt = terms_any.size();
3754 GAIA_FOR(cnt) {
3755 auto& p = terms_any[i];
3756 if (!term_has_variables(p))
3757 continue;
3758 const auto varMask = term_unbound_var_mask(world, p, detail::VarBindings{});
3759 m_compCtx.terms_any_var.push_back({detail::src_opcode_from_term(p), p, varMask});
3760 m_compCtx.varMaskAny |= varMask;
3761 }
3762 }
3763
3764 detail::sort_src_terms_by_cost(m_compCtx.terms_all_src);
3765 detail::sort_src_terms_by_cost(m_compCtx.terms_or_src);
3766 detail::sort_src_terms_by_cost(m_compCtx.terms_not_src);
3767
3768 constexpr uint32_t VarSearchProgramOpCapacity = MAX_ITEMS_IN_QUERY * 3u + 8u;
3771
3772 auto init_var_search_program = [&]() {
3773 varSearchProgramOps.clear();
3774 varSearchMeta = {};
3783
3784 const auto allVarCnt = (uint32_t)m_compCtx.terms_all_var.size();
3785 GAIA_FOR(allVarCnt) {
3786 const auto cost = detail::search_term_cost(m_compCtx.terms_all_var[i]);
3787 const auto srcVarBit =
3788 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src)
3789 ? (uint8_t)(uint8_t(1) << detail::var_index(m_compCtx.terms_all_var[i].term.src))
3790 : 0;
3791 const auto canBindFromSelfSource =
3792 m_compCtx.terms_all_var[i].sourceOpcode == detail::EOpcode::Src_Self &&
3793 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src) &&
3794 m_compCtx.terms_all_var[i].varMask == srcVarBit &&
3795 (uint8_t)(m_compCtx.terms_all_var[i].varMask & m_compCtx.varMaskAny) == 0;
3796 const auto canBindFromUpSource =
3797 m_compCtx.terms_all_var[i].sourceOpcode == detail::EOpcode::Src_Up &&
3798 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src) &&
3799 m_compCtx.terms_all_var[i].varMask == srcVarBit &&
3800 (uint8_t)(m_compCtx.terms_all_var[i].varMask & m_compCtx.varMaskAny) == 0;
3801 const auto canBindFromDownSource =
3802 m_compCtx.terms_all_var[i].sourceOpcode == detail::EOpcode::Src_Down &&
3803 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src) &&
3804 m_compCtx.terms_all_var[i].varMask == srcVarBit &&
3805 (uint8_t)(m_compCtx.terms_all_var[i].varMask & m_compCtx.varMaskAny) == 0;
3806 const auto canBindFromUpDownSource =
3807 m_compCtx.terms_all_var[i].sourceOpcode == detail::EOpcode::Src_UpDown &&
3808 detail::is_var_entity(m_compCtx.terms_all_var[i].term.src) &&
3809 m_compCtx.terms_all_var[i].varMask == srcVarBit &&
3810 (uint8_t)(m_compCtx.terms_all_var[i].varMask & m_compCtx.varMaskAny) == 0;
3811 const auto opcode =
3812 canBindFromSelfSource || canBindFromUpSource || canBindFromDownSource || canBindFromUpDownSource
3813 ? detail::EOpcode::Var_Term_All_Src_Bind
3814 : detail::EOpcode::Var_Term_All_Bind;
3815 searchAllBindOps.push_back({opcode, 0, 0, (uint8_t)i, cost});
3816 searchAllCheckOps.push_back({detail::EOpcode::Var_Term_All_Check, 0, 0, (uint8_t)i, cost});
3817 }
3818 detail::sort_program_ops_by_cost(searchAllBindOps);
3819 detail::sort_program_ops_by_cost(searchAllCheckOps);
3820
3821 const auto orVarCnt = (uint32_t)m_compCtx.terms_or_var.size();
3822 GAIA_FOR(orVarCnt) {
3823 const auto cost = detail::search_term_cost(m_compCtx.terms_or_var[i]);
3824 searchOrBindOps.push_back({detail::EOpcode::Var_Term_Or_Bind, 0, 0, (uint8_t)i, cost});
3825 searchOrCheckOps.push_back({detail::EOpcode::Var_Term_Or_Check, 0, 0, (uint8_t)i, cost});
3826 finalOrCheckOps.push_back({detail::EOpcode::Var_Final_Or_Check, 0, 0, (uint8_t)i, cost});
3827 varSearchMeta.orVarMask = (uint8_t)(varSearchMeta.orVarMask | m_compCtx.terms_or_var[i].varMask);
3828 }
3829 detail::sort_program_ops_by_cost(searchOrBindOps);
3830 detail::sort_program_ops_by_cost(searchOrCheckOps);
3831 detail::sort_program_ops_by_cost(finalOrCheckOps);
3832
3833 const auto anyVarCnt = (uint32_t)m_compCtx.terms_any_var.size();
3834 GAIA_FOR(anyVarCnt) {
3835 const auto cost = detail::search_term_cost(m_compCtx.terms_any_var[i]);
3836 searchAnyBindOps.push_back({detail::EOpcode::Var_Term_Any_Bind, 0, 0, (uint8_t)i, cost});
3837 searchAnyCheckOps.push_back({detail::EOpcode::Var_Term_Any_Check, 0, 0, (uint8_t)i, cost});
3838 }
3839 detail::sort_program_ops_by_cost(searchAnyBindOps);
3840 detail::sort_program_ops_by_cost(searchAnyCheckOps);
3841
3842 const auto notVarCnt = (uint32_t)m_compCtx.terms_not_var.size();
3843 GAIA_FOR(notVarCnt) {
3844 finalNotOps.push_back(
3845 {detail::EOpcode::Var_Final_Not_Check, 0, 0, (uint8_t)i,
3846 detail::search_term_cost(m_compCtx.terms_not_var[i])});
3847 }
3848 detail::sort_program_ops_by_cost(finalNotOps);
3849
3850 for (const auto& op: searchAllBindOps)
3851 varSearchProgramOps.push_back(op);
3852 for (const auto& op: searchOrBindOps)
3853 varSearchProgramOps.push_back(op);
3854 for (const auto& op: searchAnyBindOps)
3855 varSearchProgramOps.push_back(op);
3856
3857 varSearchMeta.allBegin = 0;
3858 varSearchMeta.allCount = (uint16_t)searchAllBindOps.size();
3859 varSearchMeta.orBegin = varSearchMeta.allCount;
3860 varSearchMeta.orCount = (uint16_t)searchOrBindOps.size();
3861 varSearchMeta.anyBegin = (uint16_t)(varSearchMeta.orBegin + varSearchMeta.orCount);
3862 varSearchMeta.anyCount = (uint16_t)searchAnyBindOps.size();
3863 varSearchMeta.notBegin = 0;
3864 varSearchMeta.notCount = 0;
3865
3866 const auto termOpsCnt = (uint16_t)varSearchProgramOps.size();
3867 const auto selectAllPc = termOpsCnt;
3868 const auto selectOrPc = (uint16_t)(termOpsCnt + 1u);
3869 const auto selectOtherOrPc = (uint16_t)(termOpsCnt + 2u);
3870 const auto selectOtherOrBindPc = (uint16_t)(termOpsCnt + 3u);
3871 const auto beginAnyPc = (uint16_t)(termOpsCnt + 4u);
3872 const auto selectAnyPc = (uint16_t)(termOpsCnt + 5u);
3873 const auto maybeFinalizePc = (uint16_t)(termOpsCnt + 6u);
3874 const auto allCheckBegin = (uint16_t)(termOpsCnt + 7u);
3875 const auto orCheckBegin = (uint16_t)(allCheckBegin + searchAllCheckOps.size());
3876 const auto anyCheckBegin = (uint16_t)(orCheckBegin + searchOrCheckOps.size());
3877 const auto finalNotBegin = (uint16_t)(anyCheckBegin + searchAnyCheckOps.size());
3878 const auto finalRequireOrPc = (uint16_t)(finalNotBegin + finalNotOps.size());
3879 const auto finalOrCheckBegin = (uint16_t)(finalRequireOrPc + 1u);
3880 const auto finalSuccessPc = (uint16_t)(finalOrCheckBegin + finalOrCheckOps.size());
3881 const auto finalBegin = !finalNotOps.empty() ? finalNotBegin : finalRequireOrPc;
3882 const auto backtrackPc = (detail::VmLabel)-1;
3883
3884 for (auto& op: varSearchProgramOps) {
3885 switch (op.opcode) {
3886 case detail::EOpcode::Var_Term_All_Bind:
3887 case detail::EOpcode::Var_Term_All_Src_Bind:
3888 op.pc_ok = selectAllPc;
3889 op.pc_fail = backtrackPc;
3890 break;
3891 case detail::EOpcode::Var_Term_Or_Bind:
3892 op.pc_ok = selectAllPc;
3893 op.pc_fail = selectOtherOrBindPc;
3894 break;
3895 case detail::EOpcode::Var_Term_Any_Bind:
3896 op.pc_ok = selectAllPc;
3897 op.pc_fail = maybeFinalizePc;
3898 break;
3899 default:
3900 break;
3901 }
3902 }
3903
3904 varSearchProgramOps.push_back({detail::EOpcode::Var_Search_SelectAll, selectAllPc, selectOrPc, 0, 0});
3905 varSearchProgramOps.push_back({detail::EOpcode::Var_Search_SelectOr, selectOrPc, selectOtherOrPc, 0, 0});
3906 varSearchProgramOps.push_back(
3907 {detail::EOpcode::Var_Search_SelectOtherOr, selectOtherOrPc, selectOtherOrBindPc, 0, 0});
3908 varSearchProgramOps.push_back(
3909 {detail::EOpcode::Var_Search_SelectOtherOrBind, selectOtherOrBindPc, beginAnyPc, 0, 0});
3910 varSearchProgramOps.push_back({detail::EOpcode::Var_Search_BeginAny, selectAnyPc, backtrackPc, 0, 0});
3911 varSearchProgramOps.push_back({detail::EOpcode::Var_Search_SelectAny, selectAnyPc, maybeFinalizePc, 0, 0});
3912 varSearchProgramOps.push_back({detail::EOpcode::Var_Search_MaybeFinalize, finalBegin, backtrackPc, 0, 0});
3913 for (auto op: searchAllCheckOps) {
3914 op.pc_ok = selectAllPc;
3915 op.pc_fail = backtrackPc;
3916 varSearchProgramOps.push_back(op);
3917 }
3918 for (auto op: searchOrCheckOps) {
3919 op.pc_ok = selectAllPc;
3920 op.pc_fail = selectOtherOrBindPc;
3921 varSearchProgramOps.push_back(op);
3922 }
3923 for (auto op: searchAnyCheckOps) {
3924 op.pc_ok = selectAllPc;
3925 op.pc_fail = maybeFinalizePc;
3926 varSearchProgramOps.push_back(op);
3927 }
3928 for (uint32_t i = 0; i < finalNotOps.size(); ++i) {
3929 auto op = finalNotOps[i];
3930 op.pc_ok = (i + 1u < finalNotOps.size()) ? (uint16_t)(finalNotBegin + i + 1u) : finalRequireOrPc;
3931 op.pc_fail = backtrackPc;
3932 varSearchProgramOps.push_back(op);
3933 }
3934 varSearchProgramOps.push_back(
3935 {detail::EOpcode::Var_Final_Require_Or, finalSuccessPc,
3936 searchOrCheckOps.empty() ? backtrackPc : finalOrCheckBegin, 0, 0});
3937 for (uint32_t i = 0; i < finalOrCheckOps.size(); ++i) {
3938 auto op = finalOrCheckOps[i];
3939 op.pc_ok = finalSuccessPc;
3940 op.pc_fail = (i + 1u < finalOrCheckOps.size()) ? (uint16_t)(finalOrCheckBegin + i + 1u) : backtrackPc;
3941 varSearchProgramOps.push_back(op);
3942 }
3943 varSearchProgramOps.push_back({detail::EOpcode::Var_Final_Success, finalSuccessPc, backtrackPc, 0, 0});
3944
3945 varSearchMeta.selectAllPc = selectAllPc;
3946 varSearchMeta.selectOrPc = selectOrPc;
3947 varSearchMeta.selectOtherOrPc = selectOtherOrPc;
3948 varSearchMeta.selectOtherOrBindPc = selectOtherOrBindPc;
3949 varSearchMeta.beginAnyPc = beginAnyPc;
3950 varSearchMeta.selectAnyPc = selectAnyPc;
3951 varSearchMeta.maybeFinalizePc = maybeFinalizePc;
3952 varSearchMeta.allCheckBegin = allCheckBegin;
3953 varSearchMeta.orCheckBegin = orCheckBegin;
3954 varSearchMeta.anyCheckBegin = anyCheckBegin;
3955 varSearchMeta.notBegin = finalNotBegin;
3956 varSearchMeta.notCount = (uint16_t)finalNotOps.size();
3957
3958 auto init_mask = [](uint16_t begin, uint16_t count) {
3959 uint16_t mask = 0;
3960 for (uint16_t i = 0; i < count; ++i)
3961 mask = (uint16_t)(mask | (uint16_t(1) << (begin + i)));
3962 return mask;
3963 };
3964
3965 varSearchMeta.initialAllMask = init_mask(varSearchMeta.allBegin, varSearchMeta.allCount);
3966 varSearchMeta.initialOrMask = init_mask(0, varSearchMeta.orCount);
3967 varSearchMeta.initialAnyMask = init_mask(0, varSearchMeta.anyCount);
3968 };
3969
3970 create_opcodes(queryCtx);
3971
3972 init_var_search_program();
3973
3974 auto emit_flat_program = [&](std::span<const detail::CompiledOp> ops) {
3976 program.clear();
3977 if (ops.empty())
3978 return program;
3979
3980 GAIA_ASSERT(m_compCtx.ops.size() <= UINT16_MAX);
3981 program.begin = (uint16_t)m_compCtx.ops.size();
3982 program.count = (uint16_t)ops.size();
3983 for (const auto& op: ops)
3984 m_compCtx.ops.push_back(op);
3985 return program;
3986 };
3987
3988 m_compCtx.var_programs.clear();
3989 if (m_compCtx.has_variable_terms()) {
3990 const auto program = emit_flat_program(
3991 std::span<const detail::CompiledOp>{varSearchProgramOps.data(), varSearchProgramOps.size()});
3992 if (!program.empty())
3993 m_compCtx.var_programs.push_back({program, varSearchMeta});
3994 }
3995 }
3996
3997 void create_opcodes(QueryCtx& queryCtx) {
3998 const bool isSimple = (queryCtx.data.flags & QueryCtx::QueryFlags::Complex) == 0U;
3999 const bool isAs = (queryCtx.data.as_mask_0 + queryCtx.data.as_mask_1) != 0U;
4001 uint16_t preservedProgramBase = 0;
4002 cnt::sarray_ext<uint16_t, MaxVarCnt> preservedProgramOffsets;
4003 preservedProgramOffsets.clear();
4004
4005 if (!m_compCtx.var_programs.empty()) {
4006 preservedProgramBase = m_compCtx.var_programs[0].program.begin;
4007 GAIA_ASSERT(preservedProgramBase <= m_compCtx.ops.size());
4008 const auto preservedCnt = (uint32_t)m_compCtx.ops.size() - (uint32_t)preservedProgramBase;
4009 preservedVarOps.reserve(preservedCnt);
4010 for (uint32_t i = 0; i < preservedCnt; ++i)
4011 preservedVarOps.push_back(m_compCtx.ops[(uint32_t)preservedProgramBase + i]);
4012
4013 for (const auto& step: m_compCtx.var_programs) {
4014 GAIA_ASSERT(step.program.begin >= preservedProgramBase);
4015 preservedProgramOffsets.push_back((uint16_t)(step.program.begin - preservedProgramBase));
4016 }
4017 }
4018
4019 m_compCtx.ops.clear();
4020
4021 // Source ALL terms: all must match, each is a dedicated gate opcode.
4022 if (!m_compCtx.terms_all_src.empty())
4023 emit_src_gate_terms(m_compCtx.terms_all_src, detail::EOpcode::Src_AllTerm);
4024
4025 // Source NOT terms: none can match, each is a dedicated gate opcode.
4026 if (!m_compCtx.terms_not_src.empty())
4027 emit_src_gate_terms(m_compCtx.terms_not_src, detail::EOpcode::Src_NotTerm);
4028
4029 // Source OR terms: emit a fallback chain that backtracks across alternatives.
4030 if (!m_compCtx.terms_or_src.empty()) {
4031 const bool hasOrFallback = !m_compCtx.ids_or.empty() || !m_compCtx.terms_or_var.empty();
4032 emit_src_or_terms(hasOrFallback);
4033 }
4034
4035 // Queries without direct id terms seed from all archetypes via explicit bytecode.
4036 if (!m_compCtx.has_id_terms() &&
4037 (m_compCtx.has_src_terms() || m_compCtx.has_variable_terms() ||
4038 queryCtx.data.deps.has_dep_flag(QueryCtx::DependencyHasEntityFilterTerms))) {
4039 detail::CompiledOp op{};
4040 op.opcode = detail::EOpcode::Seed_All;
4041 (void)add_op(GAIA_MOV(op));
4042 }
4043
4044 // ALL
4045 if (!m_compCtx.ids_all.empty()) {
4046 detail::CompiledOp op{};
4047 op.opcode = pick_all_opcode(isSimple, isAs);
4048 (void)add_gate_op(GAIA_MOV(op));
4049 }
4050
4051 // OR
4052 if (!m_compCtx.ids_or.empty()) {
4053 detail::CompiledOp op{};
4054 op.opcode = pick_or_opcode(!m_compCtx.ids_all.empty(), isSimple, isAs);
4055 (void)add_op(GAIA_MOV(op));
4056 }
4057
4058 // NOT
4059 if (!m_compCtx.ids_not.empty()) {
4060 detail::CompiledOp op{};
4061 op.opcode = pick_not_opcode(isSimple, isAs);
4062 (void)add_op(GAIA_MOV(op));
4063 }
4064
4065 // Variable term evaluation is part of the VM stream.
4066 if (m_compCtx.has_variable_terms()) {
4067 detail::CompiledOp op{};
4068 op.opcode = detail::EOpcode::Var_Filter;
4069 (void)add_gate_op(GAIA_MOV(op));
4070 }
4071
4072 m_compCtx.mainOpsCount = (uint16_t)m_compCtx.ops.size();
4073
4074 if (!preservedVarOps.empty()) {
4075 const auto newProgramBase = (uint16_t)m_compCtx.ops.size();
4076 for (const auto& op: preservedVarOps)
4077 m_compCtx.ops.push_back(op);
4078
4079 GAIA_ASSERT(preservedProgramOffsets.size() == m_compCtx.var_programs.size());
4080 const auto programCnt = (uint32_t)m_compCtx.var_programs.size();
4081 GAIA_FOR(programCnt)
4082 m_compCtx.var_programs[i].program.begin = (uint16_t)(newProgramBase + preservedProgramOffsets[i]);
4083 }
4084
4085 // Mark as compiled
4086 queryCtx.data.flags &= ~QueryCtx::QueryFlags::Recompile;
4087 }
4088
4089 GAIA_NODISCARD bool is_compiled() const {
4090 return !m_compCtx.ops.empty();
4091 }
4092
4093 GAIA_NODISCARD uint32_t op_count() const {
4094 return (uint32_t)m_compCtx.ops.size();
4095 }
4096
4097 GAIA_NODISCARD uint64_t op_signature() const {
4098 uint64_t hash = 1469598103934665603ull;
4099 for (const auto& op: m_compCtx.ops) {
4100 const uint64_t packed = //
4101 (uint64_t)(uint8_t)op.opcode | //
4102 ((uint64_t)op.pc_ok << 8u) | //
4103 ((uint64_t)op.pc_fail << 24u) | //
4104 ((uint64_t)op.arg << 40u) | //
4105 ((uint64_t)op.cost << 48u);
4106 hash ^= packed;
4107 hash *= 1099511628211ull;
4108 }
4109 return hash;
4110 }
4111
4113 void exec(MatchingCtx& ctx) {
4114 GAIA_PROF_SCOPE(vm::exec);
4115 ctx.skipOr = false;
4116 if (m_compCtx.mainOpsCount == 0)
4117 return;
4118
4119 ctx.pc = 0;
4120
4121 // Extract data from the buffer
4122 do {
4123 auto& stackItem = m_compCtx.ops[ctx.pc];
4124 GAIA_ASSERT((uint32_t)stackItem.opcode < (uint32_t)detail::EOpcode::Src_Never);
4125 const bool ret = exec_opcode(stackItem, ctx);
4126 ctx.pc = ret ? stackItem.pc_ok : stackItem.pc_fail;
4127 } while (ctx.pc < m_compCtx.mainOpsCount); // (uint32_t)-1 falls in this category as well
4128 }
4129 };
4130
4131 } // namespace vm
4132 } // namespace ecs
4133
4134} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Definition span_impl.h:99
Definition archetype.h:83
Definition world.h:174
Wrapper for two Entities forming a relationship pair.
Definition id.h:529
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:224
Compiles query terms into matching bytecode and evaluates that bytecode against archetypes....
Definition vm.h:2311
void exec(MatchingCtx &ctx)
Executes compiled opcodes.
Definition vm.h:4113
void compile(const EntityToArchetypeMap &entityToArchetypeMap, std::span< const Archetype * > allArchetypes, QueryCtx &queryCtx)
Transforms inputs into virtual machine opcodes.
Definition vm.h:3635
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
constexpr uint32_t BadIndex
Sentinel index value returned by helpers when a lookup fails.
Definition utility.h:20
Definition query_match_stamps.h:13
Hashmap lookup structure used for Entity.
Definition id.h:468
Definition id.h:247
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:756
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:759
Dependencies deps
Explicit dependency metadata derived from query shape.
Definition query_common.h:788
uint16_t flags
Query flags.
Definition query_common.h:772
Definition query_common.h:564
Internal representation of QueryInput.
Definition query_common.h:445
Entity id
Queried id.
Definition query_common.h:447
uint8_t travDepth
Maximum number of traversal steps.
Definition query_common.h:455
Entity entTrav
Optional traversal relation for source lookups.
Definition query_common.h:451
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:449
const EntityToArchetypeVersionMap * pVersions
Optional lookup-bucket revision table used to validate cached incremental cursors.
Definition vm.h:41
Definition vm.h:65
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_Not
Idx of the last matched archetype against the NOT opcode.
Definition vm.h:88
EntitySpan idsToMatch
List of entity ids in a query to consider.
Definition vm.h:112
Entity ent
Entity to match.
Definition vm.h:110
cnt::sarray< Entity, MaxVarCnt > varBindings
Runtime variable bindings (Var0..Var7) provided by the query.
Definition vm.h:100
ArchetypeMatchStamps * pMatchesStampByArchetypeId
Per-archetype stamp table for O(1) dedup in hot loops.
Definition vm.h:80
std::span< const Archetype * > allArchetypes
Array of all archetypes in the world.
Definition vm.h:76
uint32_t matchesVersion
Current dedup version used with pMatchesStampByArchetypeId.
Definition vm.h:82
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_All
Idx of the last matched archetype against the ALL opcode.
Definition vm.h:84
bool skipOr
OR group was already satisfied by source terms.
Definition vm.h:104
uint32_t pc
Current stack position (program counter)
Definition vm.h:114
EntitySpan targetEntities
Entities for which we run the VM. If empty, we run against all of them.
Definition vm.h:72
QueryMask queryMask
Mask for speeding up simple query matching.
Definition vm.h:90
cnt::darr< const Archetype * > * pMatchesArr
Array of already matches archetypes. Reset before each exec().
Definition vm.h:78
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:93
const World * pWorld
World.
Definition vm.h:70
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_Or
Idx of the last matched archetype against the OR opcode.
Definition vm.h:86
ArchetypeLookupView archetypeLookup
entity -> archetypes lookup used to seed structural candidate archetypes
Definition vm.h:74
uint16_t flags
Flags copied over from QueryCtx::Data.
Definition vm.h:98
uint8_t varBindingMask
Bitmask of bindings set in varBindings.
Definition vm.h:102
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:96
VmLabel pc_ok
Stack position to go to if the opcode returns true.
Definition vm.h:236
VmLabel pc_fail
Stack position to go to if the opcode returns false.
Definition vm.h:238
EOpcode opcode
Opcode to execute.
Definition vm.h:234
uint8_t cost
Optional planner-side cost used for sorting compiled micro-op plans.
Definition vm.h:242
uint8_t arg
Optional opcode argument (e.g. index to a source term array).
Definition vm.h:240
Definition vm.h:501
Definition vm.h:541
Definition vm.h:519
cnt::sarray_ext< SourceTermOp, MAX_ITEMS_IN_QUERY > terms_not_src
Source lookup terms for NOT.
Definition vm.h:314
cnt::sarray_ext< Entity, MAX_ITEMS_IN_QUERY > ids_or
Array of ops that can be evaluated with a OR opcode.
Definition vm.h:306
cnt::sarray_ext< VarTermOp, MAX_ITEMS_IN_QUERY > terms_any_var
Variable terms for ANY.
Definition vm.h:322
uint8_t varMaskAll
Variable masks (Var0..Var7) used by variable terms.
Definition vm.h:326
cnt::sarray_ext< VarTermOp, MAX_ITEMS_IN_QUERY > terms_not_var
Variable terms for NOT.
Definition vm.h:320
cnt::sarray_ext< VarProgramStep, MaxVarCnt > var_programs
Variable programs.
Definition vm.h:324
cnt::sarray_ext< SourceTermOp, MAX_ITEMS_IN_QUERY > terms_all_src
Source lookup terms for ALL.
Definition vm.h:310
cnt::sarray_ext< Entity, MAX_ITEMS_IN_QUERY > ids_all
Array of ops that can be evaluated with a ALL opcode.
Definition vm.h:304
cnt::sarray_ext< VarTermOp, MAX_ITEMS_IN_QUERY > terms_or_var
Variable terms for OR.
Definition vm.h:318
cnt::sarray_ext< Entity, MAX_ITEMS_IN_QUERY > ids_not
Array of ops that can be evaluated with a NOT opcode.
Definition vm.h:308
cnt::sarray_ext< SourceTermOp, MAX_ITEMS_IN_QUERY > terms_or_src
Source lookup terms for OR.
Definition vm.h:312
cnt::sarray_ext< VarTermOp, MAX_ITEMS_IN_QUERY > terms_all_var
Variable terms for ALL.
Definition vm.h:316
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331
void append(const char *data, uint32_t size)
Appends size characters from data.
Definition str.h:389
void reserve(uint32_t len)
Reserves capacity for at least len characters.
Definition str.h:358