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