Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
vm.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdint>
5#include <type_traits>
6
7#include "gaia/cnt/darray.h"
8#include "gaia/cnt/set.h"
9#include "gaia/config/profiler.h"
10#include "gaia/core/utility.h"
11#include "gaia/ecs/api.h"
12#include "gaia/ecs/archetype.h"
13#include "gaia/ecs/archetype_common.h"
14#include "gaia/ecs/id.h"
15#include "gaia/ecs/query_common.h"
16#include "gaia/ecs/query_mask.h"
17#include "gaia/ecs/ser_binary.h"
18
19namespace gaia {
20 namespace ecs {
21 using EntityToArchetypeMap = cnt::map<EntityLookupKey, ArchetypeDArray>;
22
23 namespace vm {
24
25 enum class MatchingStyle { Simple, Wildcard, Complex };
26
68
69 inline const ArchetypeDArray* fetch_archetypes_for_select(
70 const EntityToArchetypeMap& map, const ArchetypeDArray& arr, Entity ent, const EntityLookupKey& key) {
71 GAIA_ASSERT(key != EntityBadLookupKey);
72
73 if (ent == Pair(All, All))
74 return &arr;
75
76 const auto it = map.find(key);
77 if (it == map.end() || it->second.empty())
78 return nullptr;
79
80 return &it->second;
81 }
82
83 inline const ArchetypeDArray*
84 fetch_archetypes_for_select(const EntityToArchetypeMap& map, const ArchetypeDArray& arr, Entity ent, Entity src) {
85 GAIA_ASSERT(src != EntityBad);
86
87 return fetch_archetypes_for_select(map, arr, ent, EntityLookupKey(src));
88 }
89
90 namespace detail {
91 enum class EOpcode : uint8_t {
93 All_Simple,
94 All_Wildcard,
95 All_Complex,
97 Any_NoAll,
98 Any_WithAll,
100 Not_Simple,
101 Not_Wildcard,
102 Not_Complex
103 };
104
105 using VmLabel = uint16_t;
106
107 struct CompiledOp {
109 EOpcode opcode;
111 VmLabel pc_ok;
113 VmLabel pc_fail;
114 };
115
125
126 inline uint32_t handle_last_archetype_match(
127 QueryArchetypeCacheIndexMap* pCont, EntityLookupKey entityKey, const ArchetypeDArray* pSrcArchetype) {
128 const auto cache_it = pCont->find(entityKey);
129 uint32_t lastMatchedIdx = 0;
130 if (cache_it == pCont->end())
131 pCont->emplace(entityKey, pSrcArchetype->size());
132 else {
133 lastMatchedIdx = cache_it->second;
134 cache_it->second = pSrcArchetype->size();
135 }
136 return lastMatchedIdx;
137 }
138
139 // Operator ALL (used by query::all)
140 struct OpAll {
141 static bool check_mask(const QueryMask& maskArchetype, const QueryMask& maskQuery) {
142 return match_entity_mask(maskArchetype, maskQuery);
143 }
144 static void restart([[maybe_unused]] uint32_t& idx) {}
145 static bool can_continue(bool hasMatch) {
146 return hasMatch;
147 }
148 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
149 return expectedMatches == totalMatches;
150 }
151 static uint32_t
152 handle_last_match(MatchingCtx& ctx, EntityLookupKey entityKey, const ArchetypeDArray* pSrcArchetype) {
153 return handle_last_archetype_match(ctx.pLastMatchedArchetypeIdx_All, entityKey, pSrcArchetype);
154 }
155 };
156 // Operator OR (used by query::any)
157 struct OpAny {
158 static bool check_mask(const QueryMask& maskArchetype, const QueryMask& maskQuery) {
159 return match_entity_mask(maskArchetype, maskQuery);
160 }
161 static void restart([[maybe_unused]] uint32_t& idx) {}
162 static bool can_continue(bool hasMatch) {
163 return hasMatch;
164 }
165 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
166 (void)expectedMatches;
167 return totalMatches > 0;
168 }
169 static uint32_t
170 handle_last_match(MatchingCtx& ctx, EntityLookupKey entityKey, const ArchetypeDArray* pSrcArchetype) {
171 return handle_last_archetype_match(ctx.pLastMatchedArchetypeIdx_Any, entityKey, pSrcArchetype);
172 }
173 };
174 // Operator NOT (used by query::no)
175 struct OpNo {
176 static bool check_mask(const QueryMask& maskArchetype, const QueryMask& maskQuery) {
177 return !match_entity_mask(maskArchetype, maskQuery);
178 }
179 static void restart(uint32_t& idx) {
180 idx = 0;
181 }
182 static bool can_continue(bool hasMatch) {
183 return !hasMatch;
184 }
185 static bool eval(uint32_t expectedMatches, uint32_t totalMatches) {
186 (void)expectedMatches;
187 return totalMatches == 0;
188 }
189 static uint32_t
190 handle_last_match(MatchingCtx& ctx, EntityLookupKey entityKey, const ArchetypeDArray* pSrcArchetype) {
191 return handle_last_archetype_match(ctx.pLastMatchedArchetypeIdx_Not, entityKey, pSrcArchetype);
192 }
193 };
194
195 template <typename OpKind>
196 inline bool match_inter_eval_matches(uint32_t queryIdMarches, uint32_t& outMatches) {
197 const bool hadAnyMatches = queryIdMarches > 0;
198
199 // We finished checking matches with an id from query.
200 // We need to check if we have sufficient amount of results in the run.
201 if (!OpKind::can_continue(hadAnyMatches))
202 return false;
203
204 // No matter the amount of matches we only care if at least one
205 // match happened with the id from query.
206 outMatches += (uint32_t)hadAnyMatches;
207 return true;
208 }
209
218 template <typename OpKind, typename CmpFunc>
219 GAIA_NODISCARD inline bool match_inter(EntitySpan queryIds, EntitySpan archetypeIds, CmpFunc func) {
220 const auto archetypeIdsCnt = (uint32_t)archetypeIds.size();
221 const auto queryIdsCnt = (uint32_t)queryIds.size();
222
223 // Arrays are sorted so we can do linear intersection lookup
224 uint32_t indices[2]{}; // 0 for query ids, 1 for archetype ids
225 uint32_t matches = 0;
226
227 // Ids in query and archetype are sorted.
228 // Therefore, to match any two ids we perform a linear intersection forward loop.
229 // The only exception are transitive ids in which case we need to start searching
230 // form the start.
231 // Finding just one match for any id in the query is enough to start checking
232 // the next it. We only have 3 different operations - ALL, OR, NOT.
233 //
234 // Example:
235 // - query #1 ------------------------
236 // queryIds : 5, 10
237 // archetypeIds: 1, 3, 5, 6, 7, 10
238 // - query #2 ------------------------
239 // queryIds : 1, 10, 11
240 // archetypeIds: 3, 5, 6, 7, 10, 15
241 // -----------------------------------
242 // indices[0] : 0, 1, 2
243 // indices[1] : 0, 1, 2, 3, 4, 5
244 //
245 // For query #1:
246 // We start matching 5 in the query with 1 in the archetype. They do not match.
247 // We continue with 3 in the archetype. No match.
248 // We continue with 5 in the archetype. Match.
249 // We try to match 10 in the query with 6 in the archetype. No match.
250 // ... etc.
251
252 while (indices[0] < queryIdsCnt) {
253 const auto idInQuery = queryIds[indices[0]];
254
255 // For * and transitive ids we have to search from the start.
256 if (idInQuery == All || idInQuery.id() == Is.id())
257 indices[1] = 0;
258
259 uint32_t queryIdMatches = 0;
260 while (indices[1] < archetypeIdsCnt) {
261 const auto idInArchetype = archetypeIds[indices[1]];
262
263 // See if we have a match
264 const auto res = func(idInQuery, idInArchetype);
265
266 // Once a match is found we start matching with the next id in query.
267 if (res.matched) {
268 ++indices[0];
269 ++indices[1];
270 ++queryIdMatches;
271
272 // Only continue with the next iteration unless the given Op determines it is
273 // no longer needed.
274 if (!match_inter_eval_matches<OpKind>(queryIdMatches, matches))
275 return false;
276
277 goto next_query_id;
278 } else {
279 ++indices[1];
280 }
281 }
282
283 if (!match_inter_eval_matches<OpKind>(queryIdMatches, matches))
284 return false;
285
286 ++indices[0];
287 // Make sure to continue from the right index on the archetype array.
288 // Some operators can keep moving forward (AND, ANY), but NOT needs to start
289 // matching from the beginning again if the previous query operator didn't find a match.
290 OpKind::restart(indices[1]);
291
292 next_query_id:
293 continue;
294 }
295
296 return OpKind::eval(queryIdsCnt, matches);
297 }
298
299 struct IdCmpResult {
300 bool matched;
301 };
302
303 GAIA_NODISCARD inline IdCmpResult cmp_ids(Entity idInQuery, Entity idInArchetype) {
304 return {idInQuery == idInArchetype};
305 }
306
307 GAIA_NODISCARD inline IdCmpResult cmp_ids_pairs(Entity idInQuery, Entity idInArchetype) {
308 if (idInQuery.pair()) {
309 // all(Pair<All, All>) aka "any pair"
310 if (idInQuery == Pair(All, All))
311 return {true};
312
313 // all(Pair<X, All>):
314 // X, AAA
315 // X, BBB
316 // ...
317 // X, ZZZ
318 if (idInQuery.gen() == All.id())
319 return {idInQuery.id() == idInArchetype.id()};
320
321 // all(Pair<All, X>):
322 // AAA, X
323 // BBB, X
324 // ...
325 // ZZZ, X
326 if (idInQuery.id() == All.id())
327 return {idInQuery.gen() == idInArchetype.gen()};
328 }
329
330 // 1:1 match needed for non-pairs
331 return cmp_ids(idInQuery, idInArchetype);
332 }
333
334 GAIA_NODISCARD inline IdCmpResult
335 cmp_ids_is(const World& w, const Archetype& archetype, Entity idInQuery, Entity idInArchetype) {
336 // all(Pair<Is, X>)
337 if (idInQuery.pair() && idInQuery.id() == Is.id()) {
338 auto archetypeIds = archetype.ids_view();
339 return {
340 idInQuery.gen() == idInArchetype.id() || // X vs Id
341 as_relations_trav_if(w, idInQuery, [&](Entity relation) {
342 const auto idx = core::get_index(archetypeIds, relation);
343 // Stop at the first match
344 return idx != BadIndex;
345 })};
346 }
347
348 // 1:1 match needed for non-pairs
349 return cmp_ids(idInQuery, idInArchetype);
350 }
351
352 GAIA_NODISCARD inline IdCmpResult
353 cmp_ids_is_pairs(const World& w, const Archetype& archetype, Entity idInQuery, Entity idInArchetype) {
354 if (idInQuery.pair()) {
355 // all(Pair<All, All>) aka "any pair"
356 if (idInQuery == Pair(All, All))
357 return {true};
358
359 // all(Pair<Is, X>)
360 if (idInQuery.id() == Is.id()) {
361 // (Is, X) in archetype == (Is, X) in query
362 if (idInArchetype == idInQuery)
363 return {true};
364
365 auto archetypeIds = archetype.ids_view();
366 const auto eQ = entity_from_id(w, idInQuery.gen());
367 if (eQ == idInArchetype)
368 return {true};
369
370 // If the archetype entity is an (Is, X) pair treat Is as X and try matching it with
371 // entities inheriting from e.
372 if (idInArchetype.id() == Is.id()) {
373 const auto eA = entity_from_id(w, idInArchetype.gen());
374 if (eA == eQ)
375 return {true};
376
377 return {as_relations_trav_if(w, eQ, [eA](Entity relation) {
378 return eA == relation;
379 })};
380 }
381
382 // Archetype entity is generic, try matching it with entities inheriting from e.
383 return {as_relations_trav_if(w, eQ, [&archetypeIds](Entity relation) {
384 // Relation does not necessary match the sorted order of components in the archetype
385 // so we need to search through all of its ids.
386 const auto idx = core::get_index(archetypeIds, relation);
387 // Stop at the first match
388 return idx != BadIndex;
389 })};
390 }
391
392 // all(Pair<All, X>):
393 // AAA, X
394 // BBB, X
395 // ...
396 // ZZZ, X
397 if (idInQuery.id() == All.id()) {
398 if (idInQuery.gen() == idInArchetype.gen())
399 return {true};
400
401 // If there are any Is pairs on the archetype we need to check if we match them
402 if (archetype.pairs_is() > 0) {
403 auto archetypeIds = archetype.ids_view();
404
405 const auto e = entity_from_id(w, idInQuery.gen());
406 return {as_relations_trav_if(w, e, [&](Entity relation) {
407 // Relation does not necessary match the sorted order of components in the archetype
408 // so we need to search through all of its ids.
409 const auto idx = core::get_index(archetypeIds, relation);
410 // Stop at the first match
411 return idx != BadIndex;
412 })};
413 }
414
415 // No match found
416 return {false};
417 }
418
419 // all(Pair<X, All>):
420 // X, AAA
421 // X, BBB
422 // ...
423 // X, ZZZ
424 if (idInQuery.gen() == All.id()) {
425 return {idInQuery.id() == idInArchetype.id()};
426 }
427 }
428
429 // 1:1 match needed for non-pairs
430 return cmp_ids(idInQuery, idInArchetype);
431 }
432
439 template <typename OpKind>
440 GAIA_NODISCARD inline bool match_res(const Archetype& archetype, EntitySpan queryIds) {
441 // Archetype has no pairs we can compare ids directly.
442 // This has better performance.
443 if (archetype.pairs() == 0) {
444 return match_inter<OpKind>(
445 queryIds, archetype.ids_view(),
446 // Cmp func
447 [](Entity idInQuery, Entity idInArchetype) {
448 return cmp_ids(idInQuery, idInArchetype);
449 });
450 }
451
452 // Pairs are present, we have to evaluate.
453 return match_inter<OpKind>(
454 queryIds, archetype.ids_view(),
455 // Cmp func
456 [](Entity idInQuery, Entity idInArchetype) {
457 return cmp_ids_pairs(idInQuery, idInArchetype);
458 });
459 }
460
467 template <typename OpKind>
468 GAIA_NODISCARD inline bool match_res_as(const World& w, const Archetype& archetype, EntitySpan queryIds) {
469 // Archetype has no pairs we can compare ids directly
470 if (archetype.pairs() == 0) {
471 return match_inter<OpKind>(
472 queryIds, archetype.ids_view(),
473 // cmp func
474 [&](Entity idInQuery, Entity idInArchetype) {
475 return cmp_ids_is(w, archetype, idInQuery, idInArchetype);
476 });
477 }
478
479 return match_inter<OpKind>(
480 queryIds, archetype.ids_view(),
481 // cmp func
482 [&](Entity idInQuery, Entity idInArchetype) {
483 return cmp_ids_is_pairs(w, archetype, idInQuery, idInArchetype);
484 });
485 }
486
487 template <typename OpKind, MatchingStyle Style>
488 inline void
489 match_archetype_inter(MatchingCtx& ctx, EntityLookupKey entityKey, const ArchetypeDArray* pSrcArchetypes) {
490 const auto& archetypes = *pSrcArchetypes;
491 auto& matchesArr = *ctx.pMatchesArr;
492 auto& matchesSet = *ctx.pMatchesSet;
493
494 uint32_t lastMatchedIdx = OpKind::handle_last_match(ctx, entityKey, pSrcArchetypes);
495 if (lastMatchedIdx >= archetypes.size())
496 return;
497
498 auto archetypeSpan = std::span(&archetypes[lastMatchedIdx], archetypes.size() - lastMatchedIdx);
499
500 if constexpr (Style == MatchingStyle::Complex) {
501 for (auto* pArchetype: archetypeSpan) {
502 if (matchesSet.contains(pArchetype))
503 continue;
504
505 if (!match_res_as<OpKind>(*ctx.pWorld, *pArchetype, ctx.idsToMatch))
506 continue;
507
508 matchesSet.emplace(pArchetype);
509 matchesArr.emplace_back(pArchetype);
510 }
511 }
512#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
513 else if constexpr (Style == MatchingStyle::Simple) {
514 for (auto* pArchetype: archetypeSpan) {
515 if (matchesSet.contains(pArchetype))
516 continue;
517
518 // Try early exit
519 if (!OpKind::check_mask(pArchetype->queryMask(), ctx.queryMask))
520 continue;
521
522 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
523 continue;
524
525 matchesSet.emplace(pArchetype);
526 matchesArr.emplace_back(pArchetype);
527 }
528 }
529#endif
530 else {
531 for (auto* pArchetype: archetypeSpan) {
532 if (matchesSet.contains(pArchetype))
533 continue;
534
535 if (!match_res<OpKind>(*pArchetype, ctx.idsToMatch))
536 continue;
537
538 matchesSet.emplace(pArchetype);
539 matchesArr.emplace_back(pArchetype);
540 }
541 }
542 }
543
544 template <MatchingStyle Style>
545 inline void match_archetype_all(MatchingCtx& ctx) {
546 if constexpr (Style == MatchingStyle::Complex) {
547 // For ALL we need all the archetypes to match. We start by checking
548 // if the first one is registered in the world at all.
549 const auto& allArchetypes = *ctx.pAllArchetypes;
550
551 if (ctx.ent.id() == Is.id()) {
552 ctx.ent = EntityBad;
553 const auto* pArchetypes = &allArchetypes;
554
555 match_archetype_inter<OpAll, Style>(ctx, EntityBadLookupKey, pArchetypes);
556 } else {
557 auto entityKey = EntityLookupKey(ctx.ent);
558
559 const auto* pArchetypes =
560 fetch_archetypes_for_select(*ctx.pEntityToArchetypeMap, *ctx.pAllArchetypes, ctx.ent, entityKey);
561 if (pArchetypes == nullptr)
562 return;
563
564 match_archetype_inter<OpAll, Style>(ctx, entityKey, pArchetypes);
565 }
566 } else {
567 auto entityKey = EntityLookupKey(ctx.ent);
568
569 // For ALL we need all the archetypes to match. We start by checking
570 // if the first one is registered in the world at all.
571 const auto* pArchetypes =
572 fetch_archetypes_for_select(*ctx.pEntityToArchetypeMap, *ctx.pAllArchetypes, ctx.ent, entityKey);
573 if (pArchetypes == nullptr)
574 return;
575
576 match_archetype_inter<OpAll, Style>(ctx, entityKey, pArchetypes);
577 }
578 }
579
580 inline void match_archetype_one(MatchingCtx& ctx) {
581 EntityLookupKey entityKey(ctx.ent);
582
583 // For ANY we need at least one archetype to match.
584 // However, because any of them can match, we need to check them all.
585 // Iterating all of them is caller's responsibility.
586 const auto* pArchetypes =
587 fetch_archetypes_for_select(*ctx.pEntityToArchetypeMap, *ctx.pAllArchetypes, ctx.ent, entityKey);
588 if (pArchetypes == nullptr)
589 return;
590
591 // TODO: Support MatchingStyle::Simple too
592 match_archetype_inter<OpAny, MatchingStyle::Wildcard>(ctx, entityKey, pArchetypes);
593 }
594
595 inline void match_archetype_one_as(MatchingCtx& ctx) {
596 const auto& allArchetypes = *ctx.pAllArchetypes;
597
598 // For ANY we need at least one archetype to match.
599 // However, because any of them can match, we need to check them all.
600 // Iterating all of them is caller's responsibility.
601
602 const ArchetypeDArray* pSrcArchetypes = nullptr;
603
604 EntityLookupKey entityKey = EntityBadLookupKey;
605
606 if (ctx.ent.id() == Is.id()) {
607 ctx.ent = EntityBad;
608 pSrcArchetypes = &allArchetypes;
609 } else {
610 entityKey = EntityLookupKey(ctx.ent);
611
612 pSrcArchetypes =
613 fetch_archetypes_for_select(*ctx.pEntityToArchetypeMap, *ctx.pAllArchetypes, ctx.ent, entityKey);
614 if (pSrcArchetypes == nullptr)
615 return;
616 }
617
618 match_archetype_inter<OpAny, MatchingStyle::Complex>(ctx, entityKey, pSrcArchetypes);
619 }
620
621 template <MatchingStyle Style>
622 inline void match_archetype_no_2(MatchingCtx& ctx) {
623 // We had some matches already (with ALL or ANY). We need to remove those
624 // that match with the NO list.
625
626 if constexpr (Style == MatchingStyle::Complex) {
627 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
628 auto* pArchetype = (*ctx.pMatchesArr)[i];
629
630 if (match_res_as<OpNo>(*ctx.pWorld, *pArchetype, ctx.idsToMatch)) {
631 ++i;
632 continue;
633 }
634
635 core::swap_erase(*ctx.pMatchesArr, i);
636 }
637 }
638#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
639 else if constexpr (Style == MatchingStyle::Simple) {
640 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
641 auto* pArchetype = (*ctx.pMatchesArr)[i];
642
643 // Try early exit
644 if (OpNo::check_mask(pArchetype->queryMask(), ctx.queryMask))
645 continue;
646
647 if (match_res<OpNo>(*pArchetype, ctx.idsToMatch)) {
648 ++i;
649 continue;
650 }
651
652 core::swap_erase(*ctx.pMatchesArr, i);
653 }
654 }
655#endif
656 else {
657 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
658 auto* pArchetype = (*ctx.pMatchesArr)[i];
659
660 if (match_res<OpNo>(*pArchetype, ctx.idsToMatch)) {
661 ++i;
662 continue;
663 }
664
665 core::swap_erase(*ctx.pMatchesArr, i);
666 }
667 }
668 }
669
671 static constexpr EOpcode Id = EOpcode::All_Simple;
672
673 bool exec(const QueryCompileCtx& comp, MatchingCtx& ctx) {
674 GAIA_PROF_SCOPE(vm::op_and_simple);
675
676 ctx.ent = comp.ids_all[0];
677 ctx.idsToMatch = std::span{comp.ids_all.data(), comp.ids_all.size()};
678
679 match_archetype_all<MatchingStyle::Simple>(ctx);
680
681 // If no ALL matches were found, we can quit right away.
682 return !ctx.pMatchesArr->empty();
683 }
684 };
685
687 static constexpr EOpcode Id = EOpcode::All_Wildcard;
688
689 bool exec(const QueryCompileCtx& comp, MatchingCtx& ctx) {
690 GAIA_PROF_SCOPE(vm::op_and_wildcard);
691
692 ctx.ent = comp.ids_all[0];
693 ctx.idsToMatch = std::span{comp.ids_all.data(), comp.ids_all.size()};
694
695 match_archetype_all<MatchingStyle::Wildcard>(ctx);
696
697 // If no ALL matches were found, we can quit right away.
698 return !ctx.pMatchesArr->empty();
699 }
700 };
701
703 static constexpr EOpcode Id = EOpcode::All_Complex;
704
705 bool exec(const QueryCompileCtx& comp, MatchingCtx& ctx) {
706 GAIA_PROF_SCOPE(vm::op_and_complex);
707
708 ctx.ent = comp.ids_all[0];
709 ctx.idsToMatch = std::span{comp.ids_all.data(), comp.ids_all.size()};
710
711 match_archetype_all<MatchingStyle::Complex>(ctx);
712
713 // If no ALL matches were found, we can quit right away.
714 return !ctx.pMatchesArr->empty();
715 }
716 };
717
719 static constexpr EOpcode Id = EOpcode::Any_NoAll;
720
721 bool exec(const QueryCompileCtx& comp, MatchingCtx& ctx) {
722 GAIA_PROF_SCOPE(vm::op_any);
723
724 const auto cnt = comp.ids_any.size();
725 ctx.idsToMatch = std::span{comp.ids_any.data(), cnt};
726
727 // Try find matches with optional components.
728 GAIA_FOR(cnt) {
729 ctx.ent = comp.ids_any[i];
730
731 // First viable item is not related to an Is relationship
732 if (ctx.as_mask_0 + ctx.as_mask_1 == 0U) {
733 match_archetype_one(ctx);
734 }
735 // First viable item is related to an Is relationship.
736 // In this case we need to gather all related archetypes.
737 else {
738 match_archetype_one_as(ctx);
739 }
740 }
741
742 return true;
743 }
744 };
745
747 static constexpr EOpcode Id = EOpcode::Any_WithAll;
748
749 bool exec(const QueryCompileCtx& comp, MatchingCtx& ctx) {
750 GAIA_PROF_SCOPE(vm::op_any);
751
752 ctx.idsToMatch = std::span{comp.ids_any.data(), comp.ids_any.size()};
753
754 const bool hasNoAsRelationships = ctx.as_mask_0 + ctx.as_mask_1 == 0U;
755#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
756 const bool isSimple = (ctx.flags & QueryCtx::QueryFlags::Complex) == 0U;
757#endif
758
759 // We tried to match ALL items. Only search among archetypes we already found.
760 // No last matched idx update is necessary because all ids were already checked
761 // during the ALL pass.
762 if (hasNoAsRelationships) {
763#if GAIA_USE_PARTITIONED_BLOOM_FILTER >= 0
764 if (isSimple) {
765 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
766 auto* pArchetype = (*ctx.pMatchesArr)[i];
767
768 GAIA_FOR_((uint32_t)comp.ids_any.size(), j) {
769 // Try early exit
770 if (!OpAny::check_mask(pArchetype->queryMask(), ctx.queryMask))
771 goto checkNextArchetype3;
772
773 // First viable item is not related to an Is relationship
774 if (match_res<OpAny>(*pArchetype, ctx.idsToMatch))
775 goto checkNextArchetype3;
776 }
777
778 // No match found among ANY. Remove the archetype from the matching ones
779 core::swap_erase(*ctx.pMatchesArr, i);
780 continue;
781
782 checkNextArchetype3:
783 ++i;
784 }
785 } else
786#endif
787 {
788 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
789 auto* pArchetype = (*ctx.pMatchesArr)[i];
790
791 GAIA_FOR_((uint32_t)comp.ids_any.size(), j) {
792 // First viable item is not related to an Is relationship
793 if (match_res<OpAny>(*pArchetype, ctx.idsToMatch))
794 goto checkNextArchetype1;
795
796 // First viable item is related to an Is relationship.
797 // In this case we need to gather all related archetypes.
798 if (match_res_as<OpAny>(*ctx.pWorld, *pArchetype, ctx.idsToMatch))
799 goto checkNextArchetype1;
800 }
801
802 // No match found among ANY. Remove the archetype from the matching ones
803 core::swap_erase(*ctx.pMatchesArr, i);
804 continue;
805
806 checkNextArchetype1:
807 ++i;
808 }
809 }
810 } else {
811 for (uint32_t i = 0; i < ctx.pMatchesArr->size();) {
812 auto* pArchetype = (*ctx.pMatchesArr)[i];
813
814 GAIA_FOR_((uint32_t)comp.ids_any.size(), j) {
815 // First viable item is related to an Is relationship.
816 // In this case we need to gather all related archetypes.
817 if (match_res_as<OpAny>(*ctx.pWorld, *pArchetype, ctx.idsToMatch))
818 goto checkNextArchetype2;
819 }
820
821 // No match found among ANY. Remove the archetype from the matching ones
822 core::swap_erase(*ctx.pMatchesArr, i);
823 continue;
824
825 checkNextArchetype2:
826 ++i;
827 }
828 }
829
830 return true;
831 }
832 };
833
835 static constexpr EOpcode Id = EOpcode::Not_Simple;
836
837 bool exec(const QueryCompileCtx& comp, MatchingCtx& ctx) {
838 GAIA_PROF_SCOPE(vm::op_not);
839
840 ctx.idsToMatch = std::span{comp.ids_not.data(), comp.ids_not.size()};
841 ctx.pMatchesSet->clear();
842
843 // We searched for nothing more than NOT matches
844 if (ctx.pMatchesArr->empty()) {
845 // If there are no previous matches (no ALL or ANY matches),
846 // we need to search among all archetypes.
847 match_archetype_inter<detail::OpNo, MatchingStyle::Simple>(ctx, EntityBadLookupKey, ctx.pAllArchetypes);
848 } else {
849 match_archetype_no_2<MatchingStyle::Simple>(ctx);
850 }
851
852 return true;
853 }
854 };
855
857 static constexpr EOpcode Id = EOpcode::Not_Wildcard;
858
859 bool exec(const QueryCompileCtx& comp, MatchingCtx& ctx) {
860 GAIA_PROF_SCOPE(vm::op_not);
861
862 ctx.idsToMatch = std::span{comp.ids_not.data(), comp.ids_not.size()};
863 ctx.pMatchesSet->clear();
864
865 // We searched for nothing more than NOT matches
866 if (ctx.pMatchesArr->empty()) {
867 // If there are no previous matches (no ALL or ANY matches),
868 // we need to search among all archetypes.
869 match_archetype_inter<detail::OpNo, MatchingStyle::Wildcard>(ctx, EntityBadLookupKey, ctx.pAllArchetypes);
870 } else {
871 match_archetype_no_2<MatchingStyle::Wildcard>(ctx);
872 }
873
874 return true;
875 }
876 };
877
879 static constexpr EOpcode Id = EOpcode::Not_Complex;
880
881 bool exec(const QueryCompileCtx& comp, MatchingCtx& ctx) {
882 GAIA_PROF_SCOPE(vm::op_not);
883
884 ctx.idsToMatch = std::span{comp.ids_not.data(), comp.ids_not.size()};
885 ctx.pMatchesSet->clear();
886
887 // We searched for nothing more than NOT matches
888 if (ctx.pMatchesArr->empty()) {
889 // If there are no previous matches (no ALL or ANY matches),
890 // we need to search among all archetypes.
891 match_archetype_inter<detail::OpNo, MatchingStyle::Complex>(ctx, EntityBadLookupKey, ctx.pAllArchetypes);
892 } else {
893 match_archetype_no_2<MatchingStyle::Complex>(ctx);
894 }
895
896 return true;
897 }
898 };
899 } // namespace detail
900
902 using OpcodeFunc = bool (*)(const detail::QueryCompileCtx& comp, MatchingCtx& ctx);
903 struct Opcodes {
904 OpcodeFunc exec;
905 };
906
907 static constexpr OpcodeFunc OpcodeFuncs[] = {
908 // OpcodeAll_Simple
909 [](const detail::QueryCompileCtx& comp, MatchingCtx& ctx) {
911 return op.exec(comp, ctx);
912 },
913 // OpcodeAll_Wildcard
914 [](const detail::QueryCompileCtx& comp, MatchingCtx& ctx) {
916 return op.exec(comp, ctx);
917 },
918 // OpcodeAll_Complex
919 [](const detail::QueryCompileCtx& comp, MatchingCtx& ctx) {
921 return op.exec(comp, ctx);
922 },
923 // OpcodeAny_NoAll
924 [](const detail::QueryCompileCtx& comp, MatchingCtx& ctx) {
926 return op.exec(comp, ctx);
927 },
928 // OpcodeAny_WithAll
929 [](const detail::QueryCompileCtx& comp, MatchingCtx& ctx) {
931 return op.exec(comp, ctx);
932 },
933 // OpcodeNot_Simple
934 [](const detail::QueryCompileCtx& comp, MatchingCtx& ctx) {
936 return op.exec(comp, ctx);
937 },
938 // OpcodeNot_Wildcard
939 [](const detail::QueryCompileCtx& comp, MatchingCtx& ctx) {
941 return op.exec(comp, ctx);
942 },
943 // OpcodeNot_Complex
944 [](const detail::QueryCompileCtx& comp, MatchingCtx& ctx) {
946 return op.exec(comp, ctx);
947 },
948 };
949
950 detail::QueryCompileCtx m_compCtx;
951
952 private:
953 GAIA_NODISCARD detail::VmLabel add_op(detail::CompiledOp&& op) {
954 const auto cnt = (detail::VmLabel)m_compCtx.ops.size();
955 op.pc_ok = cnt + 1;
956 op.pc_fail = cnt - 1;
957 m_compCtx.ops.push_back(GAIA_MOV(op));
958 return cnt;
959 }
960
961 public:
964 const EntityToArchetypeMap& entityToArchetypeMap, const ArchetypeDArray& allArchetypes,
965 QueryCtx& queryCtx) {
966 GAIA_PROF_SCOPE(vm::compile);
967
968 auto& data = queryCtx.data;
969
970 QueryTermSpan terms = data.terms_view_mut();
971 QueryTermSpan terms_all = terms.subspan(0, data.firstAny);
972 QueryTermSpan terms_any = terms.subspan(data.firstAny, data.firstNot - data.firstAny);
973 QueryTermSpan terms_not = terms.subspan(data.firstNot);
974
975 // ALL
976 if (!terms_all.empty()) {
977 GAIA_PROF_SCOPE(vm::compile_all);
978
979 const auto cnt = terms_all.size();
980 GAIA_FOR(cnt) {
981 auto& p = terms_all[i];
982 if (p.src == EntityBad) {
983 m_compCtx.ids_all.push_back(p.id);
984 continue;
985 }
986
987 // Fixed source
988 {
989 p.srcArchetype = archetype_from_entity(*queryCtx.w, p.src);
990
991 // Archetype needs to exist. If it does not we have nothing to do here.
992 if (p.srcArchetype == nullptr) {
993 m_compCtx.ops.clear();
994 return;
995 }
996 }
997 }
998 }
999
1000 // ANY
1001 if (!terms_any.empty()) {
1002 GAIA_PROF_SCOPE(vm::compile_any);
1003
1004 uint32_t archetypesWithId = 0;
1005
1006 const auto cnt = terms_any.size();
1007 GAIA_FOR(cnt) {
1008 auto& p = terms_any[i];
1009 if (p.src != EntityBad) {
1010 p.srcArchetype = archetype_from_entity(*queryCtx.w, p.src);
1011 if (p.srcArchetype == nullptr)
1012 continue;
1013 }
1014
1015 // Check if any archetype is associated with the entity id.
1016 // All ids must be registered in the world.
1017 const auto* pArchetypes =
1018 fetch_archetypes_for_select(entityToArchetypeMap, allArchetypes, p.id, EntityLookupKey(p.id));
1019 if (pArchetypes == nullptr)
1020 continue;
1021
1022 ++archetypesWithId;
1023
1024 m_compCtx.ids_any.push_back(p.id);
1025 }
1026
1027 // No archetypes with "any" entities exist. We can quit right away.
1028 if (archetypesWithId == 0) {
1029 m_compCtx.ops.clear();
1030 return;
1031 }
1032 }
1033
1034 // NOT
1035 if (!terms_not.empty()) {
1036 GAIA_PROF_SCOPE(vm::compile_not);
1037
1038 const auto cnt = terms_not.size();
1039 GAIA_FOR(cnt) {
1040 auto& p = terms_not[i];
1041 if (p.src != EntityBad)
1042 continue;
1043
1044 m_compCtx.ids_not.push_back(p.id);
1045 }
1046 }
1047
1048 create_opcodes(queryCtx);
1049 }
1050
1051 void create_opcodes(QueryCtx& queryCtx) {
1052 const bool isSimple = (queryCtx.data.flags & QueryCtx::QueryFlags::Complex) == 0U;
1053 const bool isAs = (queryCtx.data.as_mask_0 + queryCtx.data.as_mask_1) != 0U;
1054
1055 // Reset the list of opcodes
1056 m_compCtx.ops.clear();
1057
1058 // ALL
1059 if (!m_compCtx.ids_all.empty()) {
1060 detail::CompiledOp op{};
1061 if (isSimple)
1062 op.opcode = detail::EOpcode::All_Simple;
1063 else if (isAs)
1064 op.opcode = detail::EOpcode::All_Complex;
1065 else
1066 op.opcode = detail::EOpcode::All_Wildcard;
1067 (void)add_op(GAIA_MOV(op));
1068 }
1069
1070 // ANY
1071 if (!m_compCtx.ids_any.empty()) {
1072 detail::CompiledOp op{};
1073 op.opcode = m_compCtx.ids_all.empty() ? detail::EOpcode::Any_NoAll : detail::EOpcode::Any_WithAll;
1074 (void)add_op(GAIA_MOV(op));
1075 }
1076
1077 // NOT
1078 if (!m_compCtx.ids_not.empty()) {
1079 detail::CompiledOp op{};
1080 if (isSimple)
1081 op.opcode = detail::EOpcode::Not_Simple;
1082 else if (isAs)
1083 op.opcode = detail::EOpcode::Not_Complex;
1084 else
1085 op.opcode = detail::EOpcode::Not_Wildcard;
1086 (void)add_op(GAIA_MOV(op));
1087 }
1088
1089 // Mark as compiled
1090 queryCtx.data.flags &= ~QueryCtx::QueryFlags::Recompile;
1091 }
1092
1093 GAIA_NODISCARD bool is_compiled() const {
1094 return !m_compCtx.ops.empty();
1095 }
1096
1098 void exec(MatchingCtx& ctx) {
1099 GAIA_PROF_SCOPE(vm::exec);
1100
1101 ctx.pc = 0;
1102
1103 // Extract data from the buffer
1104 do {
1105 auto& stackItem = m_compCtx.ops[ctx.pc];
1106 const bool ret = OpcodeFuncs[(uint32_t)stackItem.opcode](m_compCtx, ctx);
1107 ctx.pc = ret ? stackItem.pc_ok : stackItem.pc_fail;
1108 } while (ctx.pc < m_compCtx.ops.size()); // (uint32_t)-1 falls in this category as well
1109 }
1110 };
1111
1112 } // namespace vm
1113 } // namespace ecs
1114} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Array of elements of type.
Definition sarray_ext_impl.h:27
Definition span_impl.h:99
Definition world.h:48
Wrapper for two Entities forming a relationship pair.
Definition id.h:395
void exec(MatchingCtx &ctx)
Executes compiled opcodes.
Definition vm.h:1098
void compile(const EntityToArchetypeMap &entityToArchetypeMap, const ArchetypeDArray &allArchetypes, QueryCtx &queryCtx)
Transforms inputs into virtual machine opcodes.
Definition vm.h:963
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Hashmap lookup structure used for Entity.
Definition id.h:336
Definition id.h:225
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:248
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:251
uint8_t flags
Query flags.
Definition query_common.h:260
Definition query_common.h:197
Definition vm.h:27
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_Not
Idx of the last matched archetype against the NOT opcode.
Definition vm.h:46
cnt::set< Archetype * > * pMatchesSet
Set of already matched archetypes. Reset before each exec().
Definition vm.h:38
ArchetypeDArray * pMatchesArr
Array of already matches archetypes. Reset before each exec().
Definition vm.h:40
EntitySpan idsToMatch
List of entity ids in a query to consider.
Definition vm.h:64
Entity ent
Entity to match.
Definition vm.h:62
const ArchetypeDArray * pAllArchetypes
Array of all archetypes in the world.
Definition vm.h:36
uint8_t flags
Flags copied over from QueryCtx::Data.
Definition vm.h:56
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_All
Idx of the last matched archetype against the ALL opcode.
Definition vm.h:42
uint32_t pc
Current stack position (program counter)
Definition vm.h:66
QueryMask queryMask
Mask for speeding up simple query matching.
Definition vm.h:48
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:51
const World * pWorld
World.
Definition vm.h:32
const EntityToArchetypeMap * pEntityToArchetypeMap
entity -> archetypes mapping
Definition vm.h:34
QueryArchetypeCacheIndexMap * pLastMatchedArchetypeIdx_Any
Idx of the last matched archetype against the ANY opcode.
Definition vm.h:44
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:54
VmLabel pc_ok
Stack position to go to if the opcode returns true.
Definition vm.h:111
VmLabel pc_fail
Stack position to go to if the opcode returns false.
Definition vm.h:113
EOpcode opcode
Opcode to execute.
Definition vm.h:109
Definition vm.h:140
Definition vm.h:157
Definition vm.h:175
cnt::sarray_ext< Entity, MAX_ITEMS_IN_QUERY > ids_all
Array of ops that can be evaluated with a ALL opcode.
Definition vm.h:119
cnt::sarray_ext< Entity, MAX_ITEMS_IN_QUERY > ids_any
Array of ops that can be evaluated with a ANY opcode.
Definition vm.h:121
cnt::sarray_ext< Entity, MAX_ITEMS_IN_QUERY > ids_not
Array of ops that can be evaluated with a NOT opcode.
Definition vm.h:123