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