540 static constexpr uint32_t ChunkBatchSize = 32;
541 friend class SystemBuilder;
546 const uint8_t* pCompIndices;
547 InheritedTermDataView inheritedData;
553 using ChunkSpan = std::span<const Chunk*>;
554 using ChunkSpanMut = std::span<Chunk*>;
560 struct DirectQueryScratch {
566 uint32_t seenVersion = 1;
568 DirectQueryScratch* pNext =
nullptr;
571 ~DirectQueryScratch() {
578 GAIA_NODISCARD
bool uses_query_cache_storage()
const {
579 return m_cacheKind != QueryCacheKind::None;
582 GAIA_NODISCARD
bool uses_shared_cache_layer()
const {
583 return uses_query_cache_storage() && m_cacheScope == QueryCacheScope::Shared;
586 void invalidate_query_storage() {
587 if (uses_query_cache_storage())
588 (void)m_storage.try_del_from_cache();
589 m_storage.invalidate();
593 GAIA_NOINLINE
static DirectQueryScratch& nested_direct_query_scratch(DirectQueryScratch& scratch) {
594 auto* pScratch = &scratch;
596 if (pScratch->pNext ==
nullptr)
597 pScratch->pNext =
new DirectQueryScratch;
598 pScratch = pScratch->pNext;
599 }
while (pScratch->inUse);
605 GAIA_NODISCARD
static DirectQueryScratch& direct_query_scratch() {
606 static thread_local DirectQueryScratch scratch;
607 if GAIA_UNLIKELY (scratch.inUse)
608 return nested_direct_query_scratch(scratch);
613 struct DirectQueryScratchScope {
614 DirectQueryScratch& scratch;
616 DirectQueryScratchScope(): scratch(direct_query_scratch()) {
617 scratch.inUse =
true;
619 ~DirectQueryScratchScope() {
620 scratch.inUse =
false;
622 DirectQueryScratchScope(
const DirectQueryScratchScope&) =
delete;
623 DirectQueryScratchScope& operator=(
const DirectQueryScratchScope&) =
delete;
629 static void ensure_direct_query_count_capacity(DirectQueryScratch& scratch, uint32_t entityId) {
630 if (entityId < scratch.counts.size())
633 const auto doubledSize = (uint32_t)scratch.counts.size() * 2U;
634 const auto minSize = doubledSize > 64U ? doubledSize : 64U;
635 const auto newSize = (entityId + 1U) > minSize ? (entityId + 1U) : minSize;
636 scratch.counts.resize(newSize, 0);
642 GAIA_NODISCARD
static uint32_t next_direct_query_seen_version(DirectQueryScratch& scratch) {
643 update_version(scratch.seenVersion);
644 if (scratch.seenVersion == 0) {
645 scratch.seenVersion = 1;
646 core::fill(scratch.counts.begin(), scratch.counts.end(), 0U);
649 return scratch.seenVersion;
652 static constexpr CmdFunc CommandBufferRead[] = {
655 QueryCmd_AddItem cmd;
656 ser::load(buffer, cmd);
661 QueryCmd_AddFilter cmd;
662 ser::load(buffer, cmd);
668 ser::load(buffer, cmd);
673 QueryCmd_GroupBy cmd;
674 ser::load(buffer, cmd);
679 QueryCmd_GroupDep cmd;
680 ser::load(buffer, cmd);
685 QueryCmd_MatchPrefab cmd;
686 ser::load(buffer, cmd);
692 QueryImplStorage m_storage;
694 ArchetypeId* m_nextArchetypeId{};
696 uint32_t* m_worldVersion{};
698 const EntityToArchetypeMap* m_entityToArchetypeMap{};
700 const EntityToArchetypeVersionMap* m_entityToArchetypeMapVersions{};
702 const ArchetypeDArray* m_allArchetypes{};
706 uint8_t m_varNamesMask = 0;
710 uint8_t m_varBindingsMask = 0;
712 GroupId m_groupIdSet = 0;
714 uint32_t m_changedWorldVersion = 0;
719 QueryCacheKind m_cacheKind = QueryCacheKind::Default;
721 QueryCacheScope m_cacheScope = QueryCacheScope::Local;
723 uint16_t m_cacheSrcTrav = 0;
725 void* m_ctx =
nullptr;
727 bool m_mainThread =
false;
732 struct EachWalkData {
740 Entity cachedRelation = EntityBad;
742 TravOrder cachedOrder = TravOrder::Down;
744 Constraints cachedConstraints = Constraints::EnabledOnly;
746 uint32_t cachedRelationVersion = 0;
748 uint32_t cachedEntityVersion = 0;
750 uint32_t cachedResultCacheRevision = 0;
754 bool cacheValid =
false;
775 template <
typename T>
776 struct OnDemandDataHolder {
779 OnDemandDataHolder() =
default;
781 ~OnDemandDataHolder() {
785 OnDemandDataHolder(
const OnDemandDataHolder& other) {
786 if (other.pData !=
nullptr)
787 pData =
new T(*other.pData);
790 OnDemandDataHolder& operator=(
const OnDemandDataHolder& other) {
791 if (core::addressof(other) ==
this)
794 if (other.pData ==
nullptr) {
800 if (pData ==
nullptr)
801 pData =
new T(*other.pData);
803 *pData = *other.pData;
808 OnDemandDataHolder(OnDemandDataHolder&& other)
noexcept: pData(other.pData) {
809 other.pData =
nullptr;
812 OnDemandDataHolder& operator=(OnDemandDataHolder&& other)
noexcept {
813 if (core::addressof(other) ==
this)
818 other.pData =
nullptr;
822 GAIA_NODISCARD T* get() {
826 GAIA_NODISCARD
const T* get()
const {
830 GAIA_NODISCARD T& ensure() {
831 if (pData ==
nullptr)
843 OnDemandDataHolder<EachWalkData> m_eachWalkData;
846 struct DirectSeedRunData {
850 Entity cachedSeedTerm = EntityBad;
851 QueryMatchKind cachedSeedMatchKind = QueryMatchKind::Semantic;
852 Constraints cachedConstraints = Constraints::EnabledOnly;
853 uint32_t cachedRelVersion = 0;
854 uint32_t cachedWorldVersion = 0;
855 bool cacheValid =
false;
858 OnDemandDataHolder<DirectSeedRunData> m_directSeedRunData;
861 void refresh_world_lifetime() {
862 if GAIA_LIKELY (m_storage.is_current())
864 reset_world_lifetime();
868 GAIA_NOINLINE
void reset_world_lifetime() {
869 GAIA_ASSERT(m_storage.is_initialized());
871 *m_storage.world(), *m_storage.m_pCache, *m_nextArchetypeId, *m_worldVersion, *m_entityToArchetypeMap,
872 *m_entityToArchetypeMapVersions, *m_allArchetypes);
873 fresh.m_cacheKind = m_cacheKind;
874 fresh.m_cacheScope = m_cacheScope;
875 fresh.m_cacheSrcTrav = m_cacheSrcTrav;
877 fresh.m_mainThread = m_mainThread;
878 *
this = GAIA_MOV(fresh);
885 GAIA_NODISCARD
static QueryAccess merge_access(QueryAccess lhs, QueryAccess rhs) {
886 if (lhs == QueryAccess::Write || rhs == QueryAccess::Write)
887 return QueryAccess::Write;
888 if (lhs == QueryAccess::Read || rhs == QueryAccess::Read)
889 return QueryAccess::Read;
890 return QueryAccess::None;
897 GAIA_NODISCARD
static bool access_ids_overlap(
Entity left,
Entity right) {
902 const auto endpointOverlaps = [](EntityId a, EntityId b) {
903 return a == b || is_wildcard(a) || is_wildcard(b) || is_variable(a) || is_variable(b);
905 return endpointOverlaps((EntityId)left.
id(), (EntityId)right.
id()) &&
906 endpointOverlaps((EntityId)left.
gen(), (EntityId)right.
gen());
914 template <
bool MatchPairs>
915 GAIA_NODISCARD GAIA_FORCEINLINE
static QueryAccess term_access(
const QueryCtx::Data& data,
Entity entity) {
916 if (entity == EntityBad)
917 return QueryAccess::None;
919 QueryAccess
access = QueryAccess::None;
921 GAIA_FOR((uint32_t)terms.size()) {
922 const auto& term = terms[i];
923 const bool matches = MatchPairs ? access_ids_overlap(term.id, entity) : term.id == entity;
925 (term.op != QueryOpKind::All && term.op != QueryOpKind::Or && term.op != QueryOpKind::Any) ||
926 term.access == QueryAccess::Match)
929 if (term.access == QueryAccess::Write)
930 return QueryAccess::Write;
931 access = QueryAccess::Read;
943 template <
bool MatchPairs>
944 GAIA_NODISCARD GAIA_FORCEINLINE
static QueryAccess
946 const auto access = term_access<MatchPairs>(data, entity);
947 if constexpr (!MatchPairs)
952 if (
access == QueryAccess::Write)
955 if (access_ids_overlap(
id, entity))
956 return QueryAccess::Write;
958 if (
access == QueryAccess::Read)
961 if (access_ids_overlap(
id, entity))
962 return QueryAccess::Read;
964 return QueryAccess::None;
972 GAIA_NODISCARD
static bool access_conflicts(QueryAccess lhs, QueryAccess rhs) {
973 return (lhs == QueryAccess::Write && rhs != QueryAccess::None) ||
974 (rhs == QueryAccess::Write && lhs != QueryAccess::None);
984 template <
bool MatchPairs>
985 GAIA_NODISCARD
static bool conflicts_one_way(
989 GAIA_FOR((uint32_t)terms.size()) {
990 const auto& term = terms[i];
991 const auto id = term.id;
992 if (
id == EntityBad ||
993 (term.op != QueryOpKind::All && term.op != QueryOpKind::Or && term.op != QueryOpKind::Any) ||
994 term.access == QueryAccess::Match)
997 const auto access = term.access == QueryAccess::Write ? QueryAccess::Write : QueryAccess::Read;
998 if (access_conflicts(
access, effective_access<MatchPairs>(rightData, rightAccess,
id)))
1002 for (
const auto id: leftAccess.
reads_view()) {
1003 if (access_conflicts(QueryAccess::Read, effective_access<MatchPairs>(rightData, rightAccess,
id)))
1007 if (access_conflicts(QueryAccess::Write, effective_access<MatchPairs>(rightData, rightAccess,
id)))
1017 template <
typename T>
1018 GAIA_NODISCARD
Entity access_entity_inter() {
1020 const auto& descRel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1021 const auto& descTgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1022 return Pair(descRel.entity, descTgt.entity);
1024 using UO =
typename component_type_t<T>::TypeOriginal;
1025 static_assert(core::is_raw_v<UO>,
"Use reads()/writes() with raw types only");
1026 const auto& desc = comp_cache_add<T>(*m_storage.world());
1040 GAIA_PROF_SCOPE(query::fetch);
1041 refresh_world_lifetime();
1044 GAIA_ASSERT(m_storage.is_initialized());
1046 if (!uses_query_cache_storage()) {
1047 if GAIA_UNLIKELY (!m_storage.has_owned_query_info()) {
1051 m_storage.init_owned_query_info(
1053 }
else if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad) {
1054 recommit(m_storage.owned_query_info().ctx());
1057 return m_storage.owned_query_info();
1062 if GAIA_LIKELY (m_storage.m_identity.handle.id() != QueryIdBad) {
1063 auto* pQueryInfo = m_storage.try_query_info_fast();
1064 if GAIA_UNLIKELY (pQueryInfo ==
nullptr)
1065 pQueryInfo = m_storage.m_pCache->try_get(m_storage.m_identity.handle);
1068 if GAIA_LIKELY (pQueryInfo !=
nullptr) {
1069 m_storage.cache_query_info(*pQueryInfo);
1070 if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad)
1071 recommit(pQueryInfo->ctx());
1075 m_storage.invalidate();
1083 uses_shared_cache_layer()
1084 ? m_storage.m_pCache->add(GAIA_MOV(
ctx), *m_entityToArchetypeMap, all_archetypes_view())
1085 : m_storage.m_pCache->add_local(GAIA_MOV(
ctx), *m_entityToArchetypeMap, all_archetypes_view());
1087 m_storage.cache_query_info(queryInfo);
1088 m_storage.allow_to_destroy_again();
1095 const auto kindError = validate_kind(queryInfo.
ctx());
1096 if (kindError != QueryKindRes::OK) {
1102 if (!uses_query_cache_storage()) {
1104 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, m_varBindings,
1110 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, last_archetype_id(),
1111 m_varBindings, m_varBindingsMask);
1112 m_storage.m_pCache->sync_archetype_cache(queryInfo);
1121 if (!uses_query_cache_storage()) {
1125 return queryInfo.
ensure_matches_one(archetype, targetEntities, m_varBindings, m_varBindingsMask);
1134 const auto kindError = validate_kind(queryInfo.
ctx());
1135 if (kindError != QueryKindRes::OK) {
1141 return matches_target_entities(queryInfo, archetype, targetEntities);
1162 if (m_cacheSrcTrav == maxItems)
1165 if (maxItems > MaxCacheSrcTrav) {
1166 GAIA_ASSERT(
false &&
"cache_src_trav should be a value smaller than MaxCacheSrcTrav");
1167 maxItems = MaxCacheSrcTrav;
1170 invalidate_each_walk_cache();
1171 invalidate_direct_seed_run_cache();
1172 invalidate_query_storage();
1173 m_cacheSrcTrav = maxItems;
1181 return m_cacheSrcTrav;
1208 GAIA_NODISCARD
void*
ctx()
const {
1225 m_mainThread = required;
1232 return m_mainThread;
1249 refresh_world_lifetime();
1258 template <
typename T>
1260 return reads(access_entity_inter<T>());
1272 refresh_world_lifetime();
1281 template <
typename T>
1283 return writes(access_entity_inter<T>());
1289 return m_storage.is_current() ? m_access.
reads_view() : std::span<const Entity>{};
1295 return m_storage.is_current() ? m_access.
writes_view() : std::span<const Entity>{};
1306 return entity.
pair() ? effective_access<true>(data, m_access, entity)
1307 : effective_access<false>(data, m_access, entity);
1324 return conflicts_one_way<true>(leftData, m_access, rightData, other.m_access);
1325 return conflicts_one_way<false>(leftData, m_access, rightData, other.m_access);
1332 return !m_mainThread && !other.m_mainThread && !
conflicts_with(other);
1340 if (m_cacheKind == cacheKind)
1343 invalidate_each_walk_cache();
1344 invalidate_direct_seed_run_cache();
1345 invalidate_query_storage();
1346 m_cacheKind = cacheKind;
1355 if (m_cacheScope == cacheScope)
1358 invalidate_each_walk_cache();
1359 invalidate_direct_seed_run_cache();
1360 invalidate_query_storage();
1361 m_cacheScope = cacheScope;
1369 QueryCmd_MatchPrefab cmd{};
1376 GAIA_NODISCARD QueryCacheScope
scope()
const {
1377 return m_cacheScope;
1382 GAIA_NODISCARD QueryCacheKind
kind()
const {
1389 return validate_kind(
fetch().
ctx());
1409 GAIA_NODISCARD
bool uses_manual_src_trav_cache(
const QueryCtx&
ctx)
const {
1410 return m_cacheSrcTrav != 0 &&
1418 GAIA_NODISCARD
static bool uses_im_cache(
const QueryCtx&
ctx) {
1419 return ctx.data.cachePolicy == QueryCachePolicy::Immediate;
1425 GAIA_NODISCARD
static bool uses_lazy_cache(
const QueryCtx&
ctx) {
1426 return ctx.data.cachePolicy == QueryCachePolicy::Lazy;
1432 GAIA_NODISCARD
static bool uses_dyn_cache(
const QueryCtx&
ctx) {
1433 return ctx.data.cachePolicy == QueryCachePolicy::Dynamic;
1439 GAIA_NODISCARD QueryKindRes validate_kind(
const QueryCtx&
ctx)
const {
1440 if (m_cacheKind == QueryCacheKind::Auto) {
1441 if (uses_manual_src_trav_cache(
ctx))
1442 return QueryKindRes::AutoSrcTrav;
1445 if (m_cacheKind == QueryCacheKind::All) {
1446 if (uses_manual_src_trav_cache(
ctx))
1447 return QueryKindRes::AllSrcTrav;
1448 if (!uses_im_cache(
ctx))
1449 return QueryKindRes::AllNotIm;
1452 return QueryKindRes::OK;
1458 GAIA_NODISCARD
static const char*
kind_error_str(QueryKindRes error) {
1460 case QueryKindRes::OK:
1462 case QueryKindRes::AutoSrcTrav:
1463 return "QueryCacheKind::Auto rejects explicit traversed-source snapshot caching";
1464 case QueryKindRes::AllNotIm:
1465 return "QueryCacheKind::All requires a fully immediate structural cache";
1466 case QueryKindRes::AllSrcTrav:
1467 return "QueryCacheKind::All rejects explicit traversed-source snapshot caching";
1469 return "Unknown query kind validation error";
1475 GAIA_NODISCARD EachWalkData* each_walk_data() {
1476 return m_eachWalkData.get();
1481 GAIA_NODISCARD
const EachWalkData* each_walk_data()
const {
1482 return m_eachWalkData.get();
1487 GAIA_NODISCARD EachWalkData& ensure_each_walk_data() {
1488 return m_eachWalkData.ensure();
1492 void invalidate_each_walk_cache() {
1493 auto* pWalkData = each_walk_data();
1494 if (pWalkData !=
nullptr)
1495 pWalkData->cacheValid =
false;
1500 GAIA_NODISCARD DirectSeedRunData* direct_seed_run_data() {
1501 return m_directSeedRunData.get();
1506 GAIA_NODISCARD
const DirectSeedRunData* direct_seed_run_data()
const {
1507 return m_directSeedRunData.get();
1512 GAIA_NODISCARD DirectSeedRunData& ensure_direct_seed_run_data() {
1513 return m_directSeedRunData.ensure();
1517 void invalidate_direct_seed_run_cache() {
1518 auto* pRunData = direct_seed_run_data();
1519 if (pRunData !=
nullptr)
1520 pRunData->cacheValid =
false;
1524 void reset_changed_filter_state() {
1525 m_changedWorldVersion = 0;
1530 ArchetypeId last_archetype_id()
const {
1531 return *m_nextArchetypeId - 1;
1536 GAIA_NODISCARD std::span<const Archetype*> all_archetypes_view()
const {
1537 GAIA_ASSERT(m_allArchetypes !=
nullptr);
1538 return {(
const Archetype**)m_allArchetypes->data(), m_allArchetypes->size()};
1541 GAIA_NODISCARD
static bool is_query_var_entity(Entity entity) {
1542 return is_variable((EntityId)entity.id());
1545 GAIA_NODISCARD
static uint32_t query_var_idx(Entity entity) {
1546 GAIA_ASSERT(is_query_var_entity(entity));
1547 return (uint32_t)(entity.id() - Var0.
id());
1550 GAIA_NODISCARD Entity query_var_entity(uint32_t idx) {
1551 GAIA_ASSERT(idx < 8);
1552 return entity_from_id((
const World&)*m_storage.world(), (EntityId)(Var0.
id() + idx));
1555 GAIA_NODISCARD
static util::str_view normalize_var_name(util::str_view name) {
1556 auto trimmed = util::trim(name);
1557 if (trimmed.empty())
1560 if (trimmed.data()[0] ==
'$') {
1561 if (trimmed.size() == 1)
1563 trimmed = util::str_view(trimmed.data() + 1, trimmed.size() - 1);
1566 return util::trim(trimmed);
1569 GAIA_NODISCARD
static bool is_reserved_var_name(util::str_view varName) {
1570 return varName ==
"this";
1573 GAIA_NODISCARD Entity find_var_by_name(util::str_view rawName) {
1574 const auto varName = normalize_var_name(rawName);
1575 if (varName.empty() || is_reserved_var_name(varName))
1579 const auto bit = (uint8_t(1) << i);
1580 if ((m_varNamesMask & bit) == 0)
1582 if (m_varNames[i] == varName)
1583 return query_var_entity(i);
1589 bool set_var_name_internal(Entity varEntity, util::str_view rawName) {
1590 if (!is_query_var_entity(varEntity))
1593 const auto varName = normalize_var_name(rawName);
1594 if (varName.empty() || is_reserved_var_name(varName))
1597 const auto idx = query_var_idx(varEntity);
1598 const auto bit = (uint8_t(1) << idx);
1604 const auto otherBit = (uint8_t(1) << i);
1605 if ((m_varNamesMask & otherBit) == 0)
1607 if (!(m_varNames[i] == varName))
1610 GAIA_ASSERT2(
false,
"Variable name is already assigned to a different query variable");
1614 m_varNames[idx].assign(varName);
1615 m_varNamesMask |= bit;
1619 template <
typename T>
1620 void add_cmd(T& cmd) {
1621 refresh_world_lifetime();
1622 invalidate_each_walk_cache();
1625 if constexpr (T::InvalidatesHash) {
1626 reset_changed_filter_state();
1627 m_storage.invalidate();
1630 auto& serBuffer = m_storage.ser_buffer();
1631 ser::save(serBuffer, T::Id);
1632 ser::save(serBuffer, T::InvalidatesHash);
1633 ser::save(serBuffer, cmd);
1636 void add_inter(QueryInput item) {
1637#if GAIA_ASSERT_ENABLED
1639 item.access != QueryAccess::Match || item.op == QueryOpKind::All || item.op == QueryOpKind::Or ||
1640 item.op == QueryOpKind::Any);
1642 GAIA_ASSERT(item.op != QueryOpKind::Not || item.access == QueryAccess::None);
1645 QueryCmd_AddItem cmd{item};
1649 GAIA_NODISCARD
static QueryAccess normalize_access(QueryOpKind op, QueryAccess
access) {
1650#if GAIA_ASSERT_ENABLED
1652 access != QueryAccess::Match || op == QueryOpKind::All || op == QueryOpKind::Or ||
1653 op == QueryOpKind::Any);
1655 if (op == QueryOpKind::Not)
1656 return QueryAccess::None;
1659 if (
access == QueryAccess::None)
1660 return QueryAccess::Read;
1665 void add_entity_term(QueryOpKind op, Entity entity,
const QueryTermOptions& options) {
1666 const auto access = normalize_access(op, options.access);
1668 {op,
access, entity, options.entSrc, options.entTrav, options.travKind, options.travDepth,
1669 options.matchKind});
1672 template <
typename T>
1673 void add_inter(QueryOpKind op) {
1678 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1679 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1681 e = Pair(desc_rel.entity, desc_tgt.entity);
1684 const auto& desc = comp_cache_add<T>(*m_storage.world());
1689 QueryAccess
access = QueryAccess::None;
1690 if (op != QueryOpKind::Not) {
1691 constexpr auto isReadWrite = core::is_mut_v<T>;
1692 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1695 add_inter({op,
access, e});
1698 template <
typename T>
1699 void add_inter(QueryOpKind op,
const QueryTermOptions& options) {
1704 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1705 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1707 e = Pair(desc_rel.entity, desc_tgt.entity);
1710 const auto& desc = comp_cache_add<T>(*m_storage.world());
1714 QueryAccess
access = options.access;
1715 if (op != QueryOpKind::Not &&
access == QueryAccess::None) {
1716 constexpr auto isReadWrite = core::is_mut_v<T>;
1717 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1721 {op, normalize_access(op,
access), e, options.entSrc, options.entTrav, options.travKind,
1722 options.travDepth, options.matchKind});
1725 template <
typename Rel,
typename Tgt>
1726 void add_inter(QueryOpKind op) {
1727 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1728 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1729 static_assert(core::is_raw_v<UO_Rel>,
"Use add() with raw types only");
1730 static_assert(core::is_raw_v<UO_Tgt>,
"Use add() with raw types only");
1733 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1734 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1737 QueryAccess
access = QueryAccess::None;
1738 if (op != QueryOpKind::Not) {
1739 constexpr auto isReadWrite = core::is_mut_v<UO_Rel> || core::is_mut_v<UO_Tgt>;
1740 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1743 add_inter({op,
access, {descRel.entity, descTgt.entity}});
1748 void changed_inter(Entity entity) {
1749 QueryCmd_AddFilter cmd{entity};
1753 template <
typename T>
1754 void changed_inter() {
1755 using UO =
typename component_type_t<T>::TypeOriginal;
1756 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1759 const auto& desc = comp_cache_add<T>(*m_storage.world());
1760 changed_inter(desc.entity);
1763 template <
typename Rel,
typename Tgt>
1764 void changed_inter() {
1765 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1766 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1767 static_assert(core::is_raw_v<UO_Rel>,
"Use changed() with raw types only");
1768 static_assert(core::is_raw_v<UO_Tgt>,
"Use changed() with raw types only");
1771 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1772 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1773 changed_inter({descRel.entity, descTgt.entity});
1778 void sort_by_inter(Entity entity, TSortByFunc func) {
1779 QueryCmd_SortBy cmd{entity, func};
1783 template <
typename T>
1784 void sort_by_inter(TSortByFunc func) {
1785 using UO =
typename component_type_t<T>::TypeOriginal;
1786 if constexpr (std::is_same_v<UO, Entity>) {
1787 sort_by_inter(EntityBad, func);
1789 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1792 const auto& desc = comp_cache_add<T>(*m_storage.world());
1794 sort_by_inter(desc.entity, func);
1798 template <
typename Rel,
typename Tgt>
1799 void sort_by_inter(TSortByFunc func) {
1800 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1801 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1802 static_assert(core::is_raw_v<UO_Rel>,
"Use group_by() with raw types only");
1803 static_assert(core::is_raw_v<UO_Tgt>,
"Use group_by() with raw types only");
1806 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1807 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1809 sort_by_inter({descRel.entity, descTgt.entity}, func);
1814 void group_by_inter(Entity entity, TGroupByFunc func,
bool orderGroups =
false) {
1819 template <
typename T>
1820 void group_by_inter(Entity entity, TGroupByFunc func) {
1821 using UO =
typename component_type_t<T>::TypeOriginal;
1822 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1824 group_by_inter(entity, func);
1827 template <
typename Rel,
typename Tgt>
1828 void group_by_inter(TGroupByFunc func) {
1829 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1830 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1831 static_assert(core::is_raw_v<UO_Rel>,
"Use group_by() with raw types only");
1832 static_assert(core::is_raw_v<UO_Tgt>,
"Use group_by() with raw types only");
1835 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1836 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1838 group_by_inter({descRel.entity, descTgt.entity}, func);
1843 void group_dep_inter(Entity relation) {
1844 GAIA_ASSERT(!relation.pair());
1845 QueryCmd_GroupDep cmd{relation};
1849 template <
typename T>
1850 void group_dep_inter() {
1851 using UO =
typename component_type_t<T>::TypeOriginal;
1852 static_assert(core::is_raw_v<UO>,
"Use group_dep() with raw types only");
1854 const auto& desc = comp_cache_add<T>(*m_storage.world());
1855 group_dep_inter(desc.entity);
1860 void set_group_id_inter(GroupId groupId) {
1861 refresh_world_lifetime();
1865 invalidate_each_walk_cache();
1866 m_groupIdSet = groupId;
1869 void set_group_id_inter(Entity groupId) {
1870 set_group_id_inter(groupId.id());
1873 template <
typename T>
1874 void set_group_id_inter() {
1875 using UO =
typename component_type_t<T>::TypeOriginal;
1876 static_assert(core::is_raw_v<UO>,
"Use group_id() with raw types only");
1879 const auto& desc = comp_cache_add<T>(*m_storage.world());
1880 set_group_id_inter(desc.entity);
1885 void commit(QueryCtx&
ctx) {
1886 GAIA_PROF_SCOPE(query::commit);
1888#if GAIA_ASSERT_ENABLED
1889 GAIA_ASSERT(m_storage.m_identity.handle.id() == QueryIdBad);
1892 auto& serBuffer = m_storage.ser_buffer();
1896 while (serBuffer.tell() < serBuffer.bytes()) {
1898 bool invalidatesHash =
false;
1899 ser::load(serBuffer,
id);
1900 ser::load(serBuffer, invalidatesHash);
1901 (void)invalidatesHash;
1902 CommandBufferRead[
id](serBuffer,
ctx);
1906 if (uses_query_cache_storage()) {
1907 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1908 normalize_cache_src_trav(
ctx);
1910 if (uses_shared_cache_layer()) {
1911 auto& ctxData =
ctx.data;
1912 if (ctxData.changedCnt > 1) {
1913 core::sort(ctxData.changed.data(), ctxData.changed.data() + ctxData.changedCnt, SortComponentCond{});
1918 m_storage.ser_buffer_reset();
1922 if (uses_shared_cache_layer())
1923 calc_lookup_hash(
ctx);
1926 void recommit(QueryCtx&
ctx) {
1927 GAIA_PROF_SCOPE(query::recommit);
1929 auto& serBuffer = m_storage.ser_buffer();
1933 while (serBuffer.tell() < serBuffer.bytes()) {
1935 bool invalidatesHash =
false;
1936 ser::load(serBuffer,
id);
1937 ser::load(serBuffer, invalidatesHash);
1939 GAIA_ASSERT(!invalidatesHash);
1940 if (invalidatesHash)
1942 CommandBufferRead[
id](serBuffer,
ctx);
1944 if (uses_query_cache_storage()) {
1945 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1946 normalize_cache_src_trav(
ctx);
1950 m_storage.ser_buffer_reset();
1962 const Chunk& chunk,
const QueryInfo& queryInfo, uint32_t changedWorldVersion,
1963 std::span<const uint8_t> compIndices) {
1964 GAIA_ASSERT(!chunk.
empty() &&
"match_filters called on an empty chunk");
1966 const auto queryVersion = changedWorldVersion;
1967 const auto& data = queryInfo.
ctx().
data;
1969 return match_filters(chunk, queryInfo, changedWorldVersion);
1973 if (changedFields.empty())
1976 const auto changedCnt = (uint32_t)changedFields.size();
1977 if (changedCnt == 1) {
1978 const auto fieldIdx = changedFields[0];
1979 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1980 if (compIdx == (uint8_t)0xFF)
1981 return match_filters(chunk, queryInfo, changedWorldVersion);
1982 if (chunk.
changed(queryVersion, compIdx))
1988 GAIA_FOR(changedCnt) {
1989 const auto fieldIdx = changedFields[i];
1990 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1991 if (compIdx == (uint8_t)0xFF)
1992 return match_filters(chunk, queryInfo, changedWorldVersion);
1993 if (chunk.
changed(queryVersion, compIdx))
2005 GAIA_NODISCARD
static bool
2007 GAIA_ASSERT(!chunk.
empty() &&
"match_filters called on an empty chunk");
2009 const auto queryVersion = changedWorldVersion;
2013 if (filtered.empty())
2016 const auto filteredCnt = (uint32_t)filtered.size();
2020 if (filteredCnt == 1) {
2021 const auto compIdx = core::get_index(ids, filtered[0]);
2022 if (compIdx != BadIndex && chunk.
changed(queryVersion, compIdx))
2029 uint32_t lastIdx = 0;
2030 for (
const auto comp: filtered) {
2031 uint32_t compIdx = BadIndex;
2032 if (lastIdx < (uint32_t)ids.size()) {
2033 const auto suffixIdx =
2034 core::get_index(std::span<const Entity>(ids.data() + lastIdx, ids.size() - lastIdx), comp);
2035 if (suffixIdx != BadIndex)
2036 compIdx = lastIdx + suffixIdx;
2041 if (compIdx == BadIndex)
2042 compIdx = core::get_index(ids, comp);
2043 if (compIdx == BadIndex)
2046 if (chunk.
changed(queryVersion, compIdx))
2078 const auto& data = queryInfo.
ctx().
data;
2079 return data.
groupByFunc == group_by_func_depth_order &&
2080 world_relation_depth_order_prunes_disabled_subtrees(*queryInfo.
world(), data.
groupBy);
2087 GAIA_NODISCARD
static bool
2100 Chunk* pChunk, Constraints constraints,
bool needsBarrierCache,
bool barrierPasses, uint16_t& from,
2101 uint16_t& to)
noexcept {
2102 if (needsBarrierCache && constraints == Constraints::DisabledOnly && !barrierPasses) {
2104 to = pChunk->size();
2108 from = detail::ChunkIterImpl::start_index(pChunk, constraints);
2109 to = detail::ChunkIterImpl::end_index(pChunk, constraints);
2128 GAIA_NODISCARD
static bool
2133 const auto& world = *queryInfo.
world();
2138 const auto pair = ids[idsIdx];
2139 const auto parent = world_pair_target_if_alive(world,
pair);
2140 if (parent == EntityBad)
2142 if (!world_entity_enabled_hierarchy(world, parent, relation))
2157 int8_t barrierPasses = -1)
const {
2160 if (constraints == Constraints::EnabledOnly) {
2162 if (barrierPasses >= 0)
2163 return barrierPasses != 0;
2183 world_defer_parallel_begin(*m_pWorld, itemCount);
2186 world_defer_parallel_end(*m_pWorld);
2204 explicit ParallelSlot(uint32_t idxStart): m_scope(idxStart) {}
2213 template <
typename TIter>
2214 static void finish_iter_writes(TIter& it) {
2215 if (it.chunk() ==
nullptr)
2218 auto compIndices = it.touched_comp_indices();
2219 for (
auto compIdx: compIndices)
2220 const_cast<
Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2222 auto terms = it.touched_terms();
2226 auto entities = it.entity_rows();
2227 auto& world = *it.
world();
2229 const auto term = terms[i];
2230 if (!world_component_uses_sparse_storage(world, term)) {
2231 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
2232 if (compIdx != BadIndex) {
2233 const_cast<Chunk*
>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2238 GAIA_FOR_(entities.size(), j) {
2239 world_finish_write(world, term, entities[j]);
2244 static void finish_typed_chunk_writes_runtime(
2245 World& world, Chunk* pChunk, uint16_t from, uint16_t to,
const Entity* pArgIds,
const bool* pWriteFlags,
2246 uint32_t argCnt, uint32_t firstWriteArg,
void*
const* pSparseStores =
nullptr);
2248 template <
typename... T>
2249 static void finish_typed_chunk_writes(World& world, Chunk* pChunk, uint16_t from, uint16_t to);
2251 static void finish_typed_iter_writes_runtime(
2252 Iter& it,
const Entity* pArgIds,
const bool* pWriteFlags, uint32_t argCnt, uint32_t firstWriteArg);
2361 template <
typename TMode>
2363 if constexpr (std::is_same_v<TMode, IterModeDisabledOnly>)
2364 return Constraints::DisabledOnly;
2365 else if constexpr (std::is_same_v<TMode, IterModeAcceptAll>)
2366 return Constraints::AcceptAll;
2368 return Constraints::EnabledOnly;
2380 template <
typename Func,
typename TMode>
2383 it.init_query_state(pWorld, iter_mode_constraints<TMode>(),
false);
2384 it.set_archetype(batch.pArchetype);
2385 it.set_chunk(batch.pChunk, batch.from, batch.to);
2386 it.set_group_id(batch.groupId);
2387 it.set_comp_indices(batch.pCompIndices);
2388 it.set_inherited_data(batch.inheritedData);
2390 finish_iter_writes(it);
2391 it.clear_touched_writes();
2402 template <
typename Func>
2405 it.init_query_state(pWorld, constraints,
false);
2406 it.set_archetype(batch.pArchetype);
2408 it.set_group_id(batch.groupId);
2409 it.set_comp_indices(batch.pCompIndices);
2410 it.set_inherited_data(batch.inheritedData);
2412 it.clear_touched_writes();
2422 template <
typename Func,
typename TMode>
2424 GAIA_PROF_SCOPE(query::run_query_func);
2426 const auto chunkCnt = batches.size();
2427 GAIA_ASSERT(chunkCnt > 0);
2430 it.init_query_state(pWorld, iter_mode_constraints<TMode>(),
false);
2432 const Archetype* pLastArchetype =
nullptr;
2433 const uint8_t* pLastIndices =
nullptr;
2434 InheritedTermDataView lastInheritedData{};
2435 GroupId lastGroupId = GroupIdMax;
2437 const auto apply_batch = [&](
const ChunkBatch& batch) {
2438 if (batch.pArchetype != pLastArchetype) {
2439 it.set_archetype(batch.pArchetype);
2440 pLastArchetype = batch.pArchetype;
2443 if (batch.pCompIndices != pLastIndices) {
2444 it.set_comp_indices(batch.pCompIndices);
2445 pLastIndices = batch.pCompIndices;
2448 if (batch.inheritedData.data() != lastInheritedData.data()) {
2449 it.set_inherited_data(batch.inheritedData);
2450 lastInheritedData = batch.inheritedData;
2453 if (batch.groupId != lastGroupId) {
2454 it.set_group_id(batch.groupId);
2455 lastGroupId = batch.groupId;
2458 it.set_chunk(batch.pChunk, batch.from, batch.to);
2460 finish_iter_writes(it);
2461 it.clear_touched_writes();
2465 if GAIA_UNLIKELY (chunkCnt == 1) {
2466 apply_batch(batches[0]);
2478 gaia::prefetch(batches[1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2479 apply_batch(batches[0]);
2481 uint32_t chunkIdx = 1;
2482 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
2483 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2484 apply_batch(batches[chunkIdx]);
2487 apply_batch(batches[chunkIdx]);
2493 template <
typename Func,
typename TMode>
2494 struct QueryJobCtx {
2496 World* pWorld =
nullptr;
2500 GAIA_USE_SMALLBLOCK(QueryJobCtx)
2503 template <
typename Func>
2504 struct QueryTaskJobCtx {
2505 QueryImpl* pSelf =
nullptr;
2507 QueryExecType execType = QueryExecType::Default;
2509 GAIA_USE_SMALLBLOCK(QueryTaskJobCtx)
2514 template <
typename Func>
2515 struct IterJobCallback {
2517 QueryImpl* pSelf =
nullptr;
2523 void operator()(Iter& it) {
2524 it.ctx(pSelf->ctx());
2525#if GAIA_ASSERT_ENABLED
2526 it.set_query_access(&pSelf->fetch().ctx().data);
2534 template <
typename Func>
2535 struct TypedJobCallback {
2537 QueryImpl* pSelf =
nullptr;
2543 void operator()(Iter& it) {
2544 pSelf->each_iter(it, func);
2548 template <
typename Func>
2549 static void invoke_query_task_job(
void* pCtx) {
2550 auto&
ctx = *
reinterpret_cast<QueryTaskJobCtx<Func>*
>(pCtx);
2551 ctx.pSelf->each(
ctx.func,
ctx.execType);
2554 template <
typename Func>
2555 static void cleanup_query_task_job(
void* pCtx) {
2556 auto* pJobCtx =
reinterpret_cast<QueryTaskJobCtx<Func>*
>(pCtx);
2557 if (pJobCtx ==
nullptr)
2562 template <
typename Func,
typename TMode>
2563 static void cleanup_query_job(
void* pCtx) {
2564 auto* pJobCtx =
reinterpret_cast<QueryJobCtx<Func, TMode>*
>(pCtx);
2565 if (pJobCtx ==
nullptr)
2568 auto* pWorld = pJobCtx->pWorld;
2569 if (pWorld !=
nullptr) {
2574 world_defer_parallel_end(*pWorld);
2575 commit_cmd_buffer_st(*pWorld);
2576 commit_cmd_buffer_mt(*pWorld);
2577 if (pJobCtx->pSelf !=
nullptr)
2578 pJobCtx->pSelf->m_changedWorldVersion = *pJobCtx->pSelf->m_worldVersion;
2584 template <
typename Func,
typename TMode, QueryExecType ExecType>
2585 GAIA_NODISCARD SchedJob add_parallel_query_job(Func func) {
2586 static_assert(ExecType != QueryExecType::Default);
2587 if (m_batches.
empty()) {
2588 m_changedWorldVersion = *m_worldVersion;
2592 auto* pWorld = m_storage.world();
2595 auto* pCtx =
new QueryJobCtx<Func, TMode>{
this, pWorld, {}, GAIA_MOV(func)};
2596 pCtx->batches.resize(m_batches.
size());
2597 GAIA_EACH(m_batches) pCtx->batches[i] = m_batches[i];
2600 SchedParDesc desc{};
2602 desc.itemCount = (uint32_t)pCtx->batches.
size();
2604 desc.execType = ExecType;
2605 desc.invoke = [](
void* pInvokeCtx, uint32_t idxStart, uint32_t idxEnd) {
2606 auto&
ctx = *
reinterpret_cast<QueryJobCtx<Func, TMode>*
>(pInvokeCtx);
2607 ParallelSlot slot(idxStart);
2608 run_query_func<Func, TMode>(
ctx.pWorld,
ctx.func, std::span(&
ctx.batches[idxStart], idxEnd - idxStart));
2613 world_defer_parallel_begin(*pWorld, desc.itemCount);
2615 return sched_add_par(world_sched(*pWorld), desc, pCtx, &cleanup_query_job<Func, TMode>);
2618 template <
bool HasFilters>
2620 collect_runtime_parallel_batches(
const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints) {
2621 auto cacheView = queryInfo.cache_archetype_view();
2622 const bool hasSortedPlanPayload =
2624 const auto sortView =
2625 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2628 if (needsBarrierCache)
2629 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2631 if (!sortView.empty()) {
2632 for (
const auto& view: sortView) {
2633 const auto* pArchetype = cacheView[view.archetypeIdx];
2634 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2638 const auto viewFrom = view.startRow;
2639 const auto viewTo = (uint16_t)(view.startRow + view.count);
2640 uint16_t minStartRow = 0;
2641 uint16_t minEndRow = 0;
2642 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2643 const auto startRow = core::get_max(minStartRow, viewFrom);
2644 const auto endRow = core::get_min(minEndRow, viewTo);
2645 if (endRow == startRow)
2648 if constexpr (HasFilters) {
2649 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2653 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2654 const auto inheritedDataView =
2655 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2656 push_matching_chunk_batch(
2657 m_batches, queryInfo, pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow,
2663 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
2664 const auto* pArchetype = cacheView[i];
2665 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2669 auto indicesView = queryInfo.indices_mapping_view(i);
2670 const auto inheritedDataView =
2671 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2672 const auto& chunks = pArchetype->chunks();
2673 for (
auto* pChunk: chunks) {
2677 if GAIA_UNLIKELY (from == to)
2680 if constexpr (HasFilters) {
2681 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2685 push_matching_chunk_batch(
2686 m_batches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to);
2691 template <
typename Func>
2692 GAIA_NODISCARD SchedJob add_query_task_job(Func func, QueryExecType execType) {
2693 auto* pCtx =
new QueryTaskJobCtx<Func>{
this, GAIA_MOV(func), execType};
2695 SchedTaskDesc desc{};
2697 desc.invoke = &invoke_query_task_job<Func>;
2698 desc.execType = execType;
2700 return sched_add(world_sched(*m_storage.world()), desc, pCtx, &cleanup_query_task_job<Func>);
2703 template <
typename Func, QueryExecType ExecType>
2704 GAIA_NODISCARD SchedJob add_iter_parallel_job(Func func) {
2705 static_assert(ExecType != QueryExecType::Default);
2707 auto& queryInfo =
fetch();
2709 const auto constraints = Constraints::EnabledOnly;
2714 return add_query_task_job(GAIA_MOV(func), ExecType);
2716 const auto cacheRange = selected_query_cache_range(queryInfo);
2717 if (cacheRange.hasSelectedGroup)
2718 return add_query_task_job(GAIA_MOV(func), ExecType);
2720 ::gaia::ecs::update_version(*m_worldVersion);
2723 collect_runtime_parallel_batches<true>(queryInfo, plan, constraints);
2725 collect_runtime_parallel_batches<false>(queryInfo, plan, constraints);
2727 using JobFunc = IterJobCallback<Func>;
2728 return add_parallel_query_job<JobFunc, IterModeEnabled, ExecType>(JobFunc{
this, GAIA_MOV(func)});
2733 template <
bool HasFilters,
typename TMode,
typename Func>
2734 void run_query_batch_no_group_id(
2735 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2736 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
2738 auto cacheView = queryInfo.cache_archetype_view();
2739 constexpr auto constraints = iter_mode_constraints<TMode>();
2741 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2744 const bool hasSortedBatchPayload =
2746 const auto sortView =
2747 hasSortedBatchPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2748 if (needsBarrierCache)
2749 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2751 lock(*m_storage.world());
2755 ChunkBatchArray chunkBatches;
2757 if (!sortView.empty()) {
2758 for (
const auto& view: sortView) {
2759 auto* pArchetype =
const_cast<Archetype*
>(cacheView[view.archetypeIdx]);
2760 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2764 const auto viewFrom = view.startRow;
2765 const auto viewTo = (uint16_t)(view.startRow + view.count);
2766 uint16_t minStartRow = 0;
2767 uint16_t minEndRow = 0;
2768 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2769 const auto startRow = core::get_max(minStartRow, viewFrom);
2770 const auto endRow = core::get_min(minEndRow, viewTo);
2771 const auto totalRows = endRow - startRow;
2775 if constexpr (HasFilters) {
2776 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2780 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2781 const auto inheritedDataView =
2782 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2784 chunkBatches.push_back(
2785 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2787 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2788 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2789 chunkBatches.clear();
2793 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2794 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
2795 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2799 auto indicesView = queryInfo.indices_mapping_view(i);
2800 const auto inheritedDataView =
2801 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2802 const auto& chunks = pArchetype->chunks();
2803 uint32_t chunkOffset = 0;
2804 uint32_t itemsLeft = chunks.size();
2805 while (itemsLeft > 0) {
2806 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2807 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2809 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2810 for (
auto* pChunk: chunkSpan) {
2814 if GAIA_UNLIKELY (from == to)
2817 if constexpr (HasFilters) {
2818 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2822 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2825 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2826 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2827 chunkBatches.clear();
2830 itemsLeft -= batchSize;
2831 chunkOffset += batchSize;
2837 if (!chunkBatches.empty())
2838 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2840 unlock(*m_storage.world());
2843 commit_cmd_buffer_st(*m_storage.world());
2844 commit_cmd_buffer_mt(*m_storage.world());
2847 template <
bool HasFilters,
typename TMode,
typename Func, QueryExecType ExecType>
2848 void run_query_batch_no_group_id_par(
2849 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2850 static_assert(ExecType != QueryExecType::Default);
2851 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
2853 auto cacheView = queryInfo.cache_archetype_view();
2854 constexpr auto constraints = iter_mode_constraints<TMode>();
2856 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2859 const bool hasSortedBatchPayload =
2861 const auto sortView =
2862 hasSortedBatchPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2863 if (needsBarrierCache)
2864 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2866 if (!sortView.empty()) {
2867 for (
const auto& view: sortView) {
2868 const auto* pArchetype = cacheView[view.archetypeIdx];
2869 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2873 const auto viewFrom = view.startRow;
2874 const auto viewTo = (uint16_t)(view.startRow + view.count);
2875 uint16_t minStartRow = 0;
2876 uint16_t minEndRow = 0;
2877 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2878 const auto startRow = core::get_max(minStartRow, viewFrom);
2879 const auto endRow = core::get_min(minEndRow, viewTo);
2880 const auto totalRows = endRow - startRow;
2884 if constexpr (HasFilters) {
2885 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2889 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2890 const auto inheritedDataView =
2891 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2894 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2897 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2898 const auto* pArchetype = cacheView[i];
2899 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2903 auto indicesView = queryInfo.indices_mapping_view(i);
2904 const auto inheritedDataView =
2905 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2906 const auto& chunks = pArchetype->chunks();
2907 for (
auto* pChunk: chunks) {
2911 if GAIA_UNLIKELY (from == to)
2914 if constexpr (HasFilters) {
2915 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2919 m_batches.
push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2924 if (m_batches.
empty())
2927 lock(*m_storage.world());
2929 struct ParallelQueryBatchCtx {
2933 ParallelQueryBatchCtx
ctx{
this, &func};
2934 SchedParDesc desc{};
2936 desc.itemCount = (uint32_t)m_batches.
size();
2938 desc.execType = ExecType;
2939 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2940 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
2941 ParallelSlot slot(idxStart);
2942 run_query_func<Func, TMode>(
2943 ctx.pSelf->m_storage.world(), *
ctx.pFunc,
2944 std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2951 ParallelScope
scope(*m_storage.world(), desc.itemCount);
2953 const auto& sched = world_sched(*m_storage.world());
2954 const auto token = sched_par(sched, desc);
2955 sched_wait(sched, token);
2956 sched_del(sched, token);
2959 unlock(*m_storage.world());
2964 commit_cmd_buffer_st(*m_storage.world());
2965 commit_cmd_buffer_mt(*m_storage.world());
2968 template <
bool HasFilters,
typename TMode,
typename Func>
2969 void run_query_batch_with_group_id(
2970 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2971 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
2973 ChunkBatchArray chunkBatches;
2975 auto cacheView = queryInfo.cache_archetype_view();
2976 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2977 constexpr auto constraints = iter_mode_constraints<TMode>();
2981 if (needsBarrierCache)
2982 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2984 lock(*m_storage.world());
2986 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2987 const auto* pArchetype = cacheView[i];
2988 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2992 auto indicesView = queryInfo.indices_mapping_view(i);
2993 const auto inheritedDataView =
2994 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2995 const auto& chunks = pArchetype->chunks();
2996 const auto groupId = queryInfo.group_id(i);
2998#if GAIA_ASSERT_ENABLED
3001 m_groupIdSet == 0 ||
3003 groupId == m_groupIdSet);
3006 uint32_t chunkOffset = 0;
3007 uint32_t itemsLeft = chunks.size();
3008 while (itemsLeft > 0) {
3009 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3010 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3012 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3013 for (
auto* pChunk: chunkSpan) {
3017 if GAIA_UNLIKELY (from == to)
3020 if constexpr (HasFilters) {
3021 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3025 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3028 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3029 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
3030 chunkBatches.clear();
3033 itemsLeft -= batchSize;
3034 chunkOffset += batchSize;
3039 if (!chunkBatches.empty())
3040 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
3042 unlock(*m_storage.world());
3045 commit_cmd_buffer_st(*m_storage.world());
3046 commit_cmd_buffer_mt(*m_storage.world());
3049 template <
bool HasFilters,
typename TMode,
typename Func, QueryExecType ExecType>
3050 void run_query_batch_with_group_id_par(
3051 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
3052 static_assert(ExecType != QueryExecType::Default);
3053 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
3055 ChunkBatchArray chunkBatch;
3057 auto cacheView = queryInfo.cache_archetype_view();
3058 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
3059 constexpr auto constraints = iter_mode_constraints<TMode>();
3063 if (needsBarrierCache)
3064 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3066#if GAIA_ASSERT_ENABLED
3067 for (uint32_t i = idxFrom; i < idxTo; ++i) {
3068 const auto* pArchetype = cacheView[i];
3069 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3073 const auto groupId = queryInfo.group_id(i);
3076 m_groupIdSet == 0 ||
3078 groupId == m_groupIdSet);
3082 for (uint32_t i = idxFrom; i < idxTo; ++i) {
3083 const Archetype* pArchetype = cacheView[i];
3084 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3088 auto indicesView = queryInfo.indices_mapping_view(i);
3089 const auto inheritedDataView =
3090 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3091 const auto groupId = queryInfo.group_id(i);
3092 const auto& chunks = pArchetype->chunks();
3093 for (
auto* pChunk: chunks) {
3097 if GAIA_UNLIKELY (from == to)
3100 if constexpr (HasFilters) {
3101 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3105 m_batches.
push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3109 if (m_batches.
empty())
3112 lock(*m_storage.world());
3114 struct ParallelQueryBatchCtx {
3118 ParallelQueryBatchCtx
ctx{
this, &func};
3119 SchedParDesc desc{};
3121 desc.itemCount = (uint32_t)m_batches.
size();
3123 desc.execType = ExecType;
3124 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3125 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
3126 ParallelSlot slot(idxStart);
3127 run_query_func<Func, TMode>(
3128 ctx.pSelf->m_storage.world(), *
ctx.pFunc,
3129 std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
3133 ParallelScope
scope(*m_storage.world(), desc.itemCount);
3135 const auto& sched = world_sched(*m_storage.world());
3136 const auto token = sched_par(sched, desc);
3137 sched_wait(sched, token);
3138 sched_del(sched, token);
3141 unlock(*m_storage.world());
3146 commit_cmd_buffer_st(*m_storage.world());
3147 commit_cmd_buffer_mt(*m_storage.world());
3152 template <
bool HasFilters, QueryExecType ExecType,
typename TMode,
typename Func>
3153 void run_query(
const QueryInfo& queryInfo, Func func) {
3154 GAIA_PROF_SCOPE(query::run_query);
3161 auto cache_view = queryInfo.cache_archetype_view();
3162 if (cache_view.empty())
3165 const auto cacheRange = selected_query_cache_range(queryInfo);
3166 if (!cacheRange.hasSelectedGroup) {
3168 if constexpr (ExecType != QueryExecType::Default)
3169 run_query_batch_no_group_id_par<HasFilters, TMode, Func, ExecType>(
3170 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3172 run_query_batch_no_group_id<HasFilters, TMode, Func>(
3173 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3176 if (!cacheRange.valid)
3179 if constexpr (ExecType != QueryExecType::Default)
3180 run_query_batch_with_group_id_par<HasFilters, TMode, Func, ExecType>(
3181 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3183 run_query_batch_with_group_id<HasFilters, TMode, Func>(
3184 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3190 template <QueryExecType ExecType,
typename Func>
3191 void run_query_on_archetypes(QueryInfo& queryInfo, Func func, Constraints constraints) {
3195 lock(*m_storage.world());
3198 GAIA_PROF_SCOPE(query::run_query_a);
3204 auto cache_view = queryInfo.cache_archetype_view();
3208 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
3209 if (needsBarrierCache)
3210 queryInfo.ensure_depth_order_hierarchy_barrier_cache();
3211 GAIA_EACH(cache_view) {
3212 const auto* pArchetype = cache_view[i];
3213 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3217 auto indicesView = queryInfo.indices_mapping_view(i);
3218 const auto inheritedDataView =
3219 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3220 ChunkBatch batch{pArchetype,
nullptr, indicesView.data(), inheritedDataView, 0, 0, 0};
3225 unlock(*m_storage.world());
3231 template <QueryExecType ExecType,
typename TMode,
typename Func>
3232 void run_query_on_chunks(QueryInfo& queryInfo, Func func) {
3234 ::gaia::ecs::update_version(*m_worldVersion);
3236 const bool hasFilters = queryInfo.has_filters();
3238 run_query<true, ExecType, TMode>(queryInfo, func);
3240 run_query<false, ExecType, TMode>(queryInfo, func);
3243 m_changedWorldVersion = *m_worldVersion;
3246 template <
typename Func>
3248 run_query_func_runtime(World* pWorld, Func func, std::span<ChunkBatch> batches, Constraints constraints) {
3249 GAIA_PROF_SCOPE(query::run_query_func);
3251 const auto chunkCnt = batches.size();
3252 GAIA_ASSERT(chunkCnt > 0);
3255 it.init_query_state(pWorld, constraints,
false);
3257 const Archetype* pLastArchetype =
nullptr;
3258 const uint8_t* pLastIndices =
nullptr;
3259 InheritedTermDataView lastInheritedData{};
3260 GroupId lastGroupId = GroupIdMax;
3262 const auto apply_batch = [&](
const ChunkBatch& batch) {
3263 if (batch.pArchetype != pLastArchetype) {
3264 it.set_archetype(batch.pArchetype);
3265 pLastArchetype = batch.pArchetype;
3268 if (batch.pCompIndices != pLastIndices) {
3269 it.set_comp_indices(batch.pCompIndices);
3270 pLastIndices = batch.pCompIndices;
3273 if (batch.inheritedData.data() != lastInheritedData.data()) {
3274 it.set_inherited_data(batch.inheritedData);
3275 lastInheritedData = batch.inheritedData;
3278 if (batch.groupId != lastGroupId) {
3279 it.set_group_id(batch.groupId);
3280 lastGroupId = batch.groupId;
3283 it.set_chunk(batch.pChunk, batch.from, batch.to);
3285 finish_iter_writes(it);
3286 it.clear_touched_writes();
3289 if GAIA_UNLIKELY (chunkCnt == 1) {
3290 apply_batch(batches[0]);
3294 gaia::prefetch(batches[1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3295 apply_batch(batches[0]);
3297 uint32_t chunkIdx = 1;
3298 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
3299 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3300 apply_batch(batches[chunkIdx]);
3303 apply_batch(batches[chunkIdx]);
3306 template <
bool HasFilters,
typename Func>
3307 void run_query_batch_no_group_id_runtime(
3308 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3309 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
3311 auto cacheView = queryInfo.cache_archetype_view();
3312 const bool hasSortedPlanPayload =
3314 const auto sortView =
3315 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
3318 if (needsBarrierCache)
3319 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3321 lock(*m_storage.world());
3322 ChunkBatchArray chunkBatches;
3323 const auto flush_chunk_batches = [&]() {
3324 if (chunkBatches.empty())
3326 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3327 chunkBatches.clear();
3330 if (!sortView.empty()) {
3331 for (
const auto& view: sortView) {
3332 auto* pArchetype =
const_cast<Archetype*
>(cacheView[view.archetypeIdx]);
3333 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3337 const auto viewFrom = view.startRow;
3338 const auto viewTo = (uint16_t)(view.startRow + view.count);
3339 uint16_t minStartRow = 0;
3340 uint16_t minEndRow = 0;
3341 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3342 const auto startRow = core::get_max(minStartRow, viewFrom);
3343 const auto endRow = core::get_min(minEndRow, viewTo);
3344 const auto totalRows = endRow - startRow;
3348 if constexpr (HasFilters) {
3349 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3353 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3354 const auto inheritedDataView =
3355 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3357 push_matching_chunk_batch(
3358 chunkBatches, queryInfo, pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow,
3359 endRow, flush_chunk_batches);
3361 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3362 run_query_func_runtime(
3363 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3364 chunkBatches.clear();
3368 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3369 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
3370 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3374 auto indicesView = queryInfo.indices_mapping_view(i);
3375 const auto inheritedDataView =
3376 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3377 const auto& chunks = pArchetype->chunks();
3378 uint32_t chunkOffset = 0;
3379 uint32_t itemsLeft = chunks.size();
3380 while (itemsLeft > 0) {
3381 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3382 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3384 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3385 for (
auto* pChunk: chunkSpan) {
3389 if GAIA_UNLIKELY (from == to)
3392 if constexpr (HasFilters) {
3393 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3397 push_matching_chunk_batch(
3398 chunkBatches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to,
3399 flush_chunk_batches);
3402 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3403 run_query_func_runtime(
3404 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3405 chunkBatches.clear();
3408 itemsLeft -= batchSize;
3409 chunkOffset += batchSize;
3414 if (!chunkBatches.empty())
3415 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3417 unlock(*m_storage.world());
3418 commit_cmd_buffer_st(*m_storage.world());
3419 commit_cmd_buffer_mt(*m_storage.world());
3422 template <
bool HasFilters,
typename Func, QueryExecType ExecType>
3423 void run_query_batch_no_group_id_runtime_par(
3424 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3425 static_assert(ExecType != QueryExecType::Default);
3426 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
3428 auto cacheView = queryInfo.cache_archetype_view();
3429 const bool hasSortedPlanPayload =
3431 const auto sortView =
3432 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
3435 if (needsBarrierCache)
3436 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3438 if (!sortView.empty()) {
3439 for (
const auto& view: sortView) {
3440 const auto* pArchetype = cacheView[view.archetypeIdx];
3441 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3445 const auto viewFrom = view.startRow;
3446 const auto viewTo = (uint16_t)(view.startRow + view.count);
3447 uint16_t minStartRow = 0;
3448 uint16_t minEndRow = 0;
3449 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3450 const auto startRow = core::get_max(minStartRow, viewFrom);
3451 const auto endRow = core::get_min(minEndRow, viewTo);
3452 const auto totalRows = endRow - startRow;
3456 if constexpr (HasFilters) {
3457 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3461 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3462 const auto inheritedDataView =
3463 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3465 push_matching_chunk_batch(
3466 m_batches, queryInfo, pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow,
3470 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3471 const auto* pArchetype = cacheView[i];
3472 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3476 auto indicesView = queryInfo.indices_mapping_view(i);
3477 const auto inheritedDataView =
3478 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3479 const auto& chunks = pArchetype->chunks();
3480 for (
auto* pChunk: chunks) {
3484 if GAIA_UNLIKELY (from == to)
3487 if constexpr (HasFilters) {
3488 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3492 push_matching_chunk_batch(
3493 m_batches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to);
3498 if (m_batches.
empty())
3501 lock(*m_storage.world());
3503 struct ParallelQueryBatchCtx {
3506 Constraints constraints;
3508 ParallelQueryBatchCtx
ctx{
this, &func, constraints};
3509 SchedParDesc desc{};
3511 desc.itemCount = (uint32_t)m_batches.
size();
3513 desc.execType = ExecType;
3514 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3515 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
3516 ParallelSlot slot(idxStart);
3517 run_query_func_runtime(
3518 ctx.pSelf->m_storage.world(), *
ctx.pFunc, std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3523 ParallelScope
scope(*m_storage.world(), desc.itemCount);
3525 const auto& sched = world_sched(*m_storage.world());
3526 const auto token = sched_par(sched, desc);
3527 sched_wait(sched, token);
3528 sched_del(sched, token);
3531 unlock(*m_storage.world());
3534 commit_cmd_buffer_st(*m_storage.world());
3535 commit_cmd_buffer_mt(*m_storage.world());
3538 template <
bool HasFilters,
typename Func>
3539 void run_query_batch_with_group_id_runtime(
3540 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3541 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
3543 ChunkBatchArray chunkBatches;
3544 const auto flush_chunk_batches = [&]() {
3545 if (chunkBatches.empty())
3547 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3548 chunkBatches.clear();
3550 auto cacheView = queryInfo.cache_archetype_view();
3553 if (needsBarrierCache)
3554 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3556 lock(*m_storage.world());
3558 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3559 const auto* pArchetype = cacheView[i];
3560 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3564 auto indicesView = queryInfo.indices_mapping_view(i);
3565 const auto inheritedDataView =
3566 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3567 const auto& chunks = pArchetype->chunks();
3568 const auto groupId = queryInfo.group_id(i);
3570 uint32_t chunkOffset = 0;
3571 uint32_t itemsLeft = chunks.size();
3572 while (itemsLeft > 0) {
3573 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3574 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3576 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3577 for (
auto* pChunk: chunkSpan) {
3581 if GAIA_UNLIKELY (from == to)
3584 if constexpr (HasFilters) {
3585 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3589 push_matching_chunk_batch(
3590 chunkBatches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from,
3591 to, flush_chunk_batches);
3594 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3595 run_query_func_runtime(
3596 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3597 chunkBatches.clear();
3600 itemsLeft -= batchSize;
3601 chunkOffset += batchSize;
3605 if (!chunkBatches.empty())
3606 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3608 unlock(*m_storage.world());
3609 commit_cmd_buffer_st(*m_storage.world());
3610 commit_cmd_buffer_mt(*m_storage.world());
3613 template <
bool HasFilters,
typename Func, QueryExecType ExecType>
3614 void run_query_batch_with_group_id_runtime_par(
3615 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3616 static_assert(ExecType != QueryExecType::Default);
3617 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
3619 ChunkBatchArray chunkBatch;
3620 auto cacheView = queryInfo.cache_archetype_view();
3623 if (needsBarrierCache)
3624 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3626 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3627 const auto* pArchetype = cacheView[i];
3628 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3632 auto indicesView = queryInfo.indices_mapping_view(i);
3633 const auto inheritedDataView =
3634 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3635 const auto groupId = queryInfo.group_id(i);
3636 const auto& chunks = pArchetype->chunks();
3637 for (
auto* pChunk: chunks) {
3641 if GAIA_UNLIKELY (from == to)
3644 if constexpr (HasFilters) {
3645 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3649 push_matching_chunk_batch(
3650 m_batches, queryInfo, pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to);
3654 if (m_batches.
empty())
3657 lock(*m_storage.world());
3659 struct ParallelQueryBatchCtx {
3662 Constraints constraints;
3664 ParallelQueryBatchCtx
ctx{
this, &func, constraints};
3665 SchedParDesc desc{};
3667 desc.itemCount = (uint32_t)m_batches.
size();
3669 desc.execType = ExecType;
3670 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3671 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
3672 ParallelSlot slot(idxStart);
3673 run_query_func_runtime(
3674 ctx.pSelf->m_storage.world(), *
ctx.pFunc, std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3679 ParallelScope
scope(*m_storage.world(), desc.itemCount);
3681 const auto& sched = world_sched(*m_storage.world());
3682 const auto token = sched_par(sched, desc);
3683 sched_wait(sched, token);
3684 sched_del(sched, token);
3687 unlock(*m_storage.world());
3690 commit_cmd_buffer_st(*m_storage.world());
3691 commit_cmd_buffer_mt(*m_storage.world());
3694 template <
bool HasFilters, QueryExecType ExecType,
typename Func>
3695 void run_query_runtime_planned(
3696 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3697 GAIA_PROF_SCOPE(query::run_query);
3701 const auto cacheRange = selected_query_cache_range(queryInfo);
3702 if (!cacheRange.hasSelectedGroup) {
3703 if constexpr (ExecType != QueryExecType::Default)
3704 run_query_batch_no_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3706 run_query_batch_no_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3708 if constexpr (ExecType != QueryExecType::Default)
3709 run_query_batch_with_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3711 run_query_batch_with_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3722 template <QueryExecType ExecType,
typename Func>
3723 void run_query_on_chunks_runtime_planned(
3724 QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3728 ::gaia::ecs::update_version(*m_worldVersion);
3730 run_query_runtime_planned<true, ExecType>(queryInfo, plan, constraints, func);
3732 run_query_runtime_planned<false, ExecType>(queryInfo, plan, constraints, func);
3734 m_changedWorldVersion = *m_worldVersion;
3741 GAIA_NODISCARD
bool can_use_direct_chunk_iteration_fastpath(
const QueryInfo& queryInfo)
const {
3742 const auto& data = queryInfo.ctx().data;
3750 GAIA_NODISCARD QueryCacheRange selected_query_cache_range(
const QueryInfo& queryInfo)
const {
3751 QueryCacheRange range{};
3752 range.idxTo = (uint32_t)queryInfo.cache_archetype_view().size();
3754 const auto& data = queryInfo.ctx().data;
3755 if (data.
groupBy == EntityBad || m_groupIdSet == 0)
3758 range.hasSelectedGroup =
true;
3759 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
3760 if (pGroupData ==
nullptr) {
3763 range.valid =
false;
3767 range.idxFrom = pGroupData->idxFirst;
3768 range.idxTo = pGroupData->idxLast + 1;
3776 GAIA_NODISCARD QueryPlan
prepare_query_plan(
const QueryInfo& queryInfo,
const TypedQueryExecState& state)
const;
3778 template <
bool HasFilters,
typename Func,
typename... T>
3779 void run_query_on_chunks_direct_typed(
3780 QueryInfo& queryInfo,
const QueryPlan& plan,
const TypedQueryExecState& state, Func& func,
3781 core::func_type_list<T...>);
3783 template <
bool HasFilters,
typename Func,
typename... T>
3784 void run_query_on_chunks_sparse_typed(
3785 QueryInfo& queryInfo,
const QueryPlan& plan,
const TypedQueryExecState& state, Func& func,
3786 core::func_type_list<T...>);
3788 template <
typename Func,
typename... T>
3789 void run_query_on_sparse_entities_typed(
3790 QueryInfo& queryInfo,
const TypedQueryExecState& state, Func& func, core::func_type_list<T...>);
3792 void run_query_on_chunks_direct(
3793 QueryInfo& queryInfo,
const QueryPlan& plan,
const TypedQueryExecState& state,
void* pFunc,
3794 void (*runChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&));
3796 void run_query_on_chunks_direct_iter(
3797 QueryInfo& queryInfo,
const QueryPlan& plan,
const TypedQueryExecState& state,
void* pFunc,
3798 void (*runChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&));
3800 struct TypedQueryErasedOps {
3801 void (*runSparsePlan)(QueryImpl&, QueryInfo&,
const QueryPlan&,
void*,
const TypedQueryExecState&) =
nullptr;
3802 void (*runDirectFastChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&) =
nullptr;
3803 void (*runDirectChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&) =
nullptr;
3804 void (*runMappedChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&) =
nullptr;
3805 void (*invokeInherited)(World&, Entity,
const Entity*,
void*) =
nullptr;
3806 bool needsInheritedArgIds =
false;
3809 template <QueryExecType ExecType>
3811 QueryInfo& queryInfo,
const QueryPlan& plan,
void* pFunc,
const TypedQueryExecState& state,
3812 const TypedQueryErasedOps& ops);
3814 void each_typed_erased(
3815 QueryExecType execType,
void* pFunc,
const TypedQueryExecState& state,
const TypedQueryErasedOps& ops);
3817 template <QueryExecType ExecType,
typename Func>
3818 void each_typed_inter(QueryInfo& queryInfo, Func func);
3820 template <QueryExecType ExecType>
3821 void each_iter_inter_erased(
3822 QueryInfo& queryInfo,
const QueryPlan& plan,
void* pFunc,
const TypedQueryExecState& state,
3823 void (*runDirectFastChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&),
3824 void (*runMappedChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&));
3826 void each_walk_inter(
3827 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints,
void* pFunc,
3828 const TypedQueryExecState& state,
3829 void (*runChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&));
3838 const auto cacheRange = selected_query_cache_range(queryInfo);
3839 plan.idxFrom = cacheRange.idxFrom;
3840 plan.idxTo = cacheRange.idxTo;
3844 bool hasConstrainedDepthOrderBarrier = constraints != Constraints::AcceptAll && hasDepthOrderBarrier;
3853 if (hasSortedPayload || hasDepthOrderBarrier)
3857 if (cacheRange.hasSelectedGroup) {
3860 if (!cacheRange.valid) {
3871 if (plan.idxFrom >= plan.idxTo) {
3877 hasConstrainedDepthOrderBarrier =
false;
3881 if (hasConstrainedDepthOrderBarrier)
3884 if (hasSortedPayload) {
3903 if (hasConstrainedDepthOrderBarrier)
3905 if (!can_use_direct_chunk_iteration_fastpath(queryInfo))
3912 if (!can_use_direct_chunk_iteration_fastpath(queryInfo))
3927 template <
bool HasFilters,
bool HasGroups,
typename Func>
3930 ::gaia::ecs::update_version(*m_worldVersion);
3933 lock(*m_storage.world());
3936 it.init_query_state(queryInfo.
world(), constraints,
false);
3937#if GAIA_ASSERT_ENABLED
3938 it.set_query_access(&queryInfo.
ctx().
data);
3943 const bool canSkipProcessCheck =
3947 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
3948 if (canSkipProcessCheck) {
3949 if GAIA_UNLIKELY (pArchetype->is_req_del())
3955 const auto* pIndices = indicesView.data();
3956 const auto groupId = HasGroups ? queryInfo.
group_id(i) : GroupId(0);
3957 const auto& chunks = pArchetype->chunks();
3958 for (
auto* pChunk: chunks) {
3959 const auto from = detail::ChunkIterImpl::start_index(pChunk, constraints);
3960 const auto to = detail::ChunkIterImpl::end_index(pChunk, constraints);
3961 if GAIA_UNLIKELY (from == to)
3963 if constexpr (HasFilters) {
3964 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion, indicesView))
3968 it.set_query_chunk(pArchetype, pIndices, pChunk, from, to);
3969 if constexpr (HasGroups)
3970 it.set_group_id(groupId);
3973 GAIA_PROF_SCOPE(query_func);
3976 finish_iter_writes(it);
3977 it.clear_touched_writes();
3981 unlock(*m_storage.world());
3982 commit_cmd_buffer_st(*m_storage.world());
3983 commit_cmd_buffer_mt(*m_storage.world());
3984 m_changedWorldVersion = *m_worldVersion;
3992 template <QueryExecType ExecType,
typename Func>
3994 if constexpr (ExecType == QueryExecType::Default) {
3995 auto& queryInfo =
fetch();
4002 run_query_on_chunks_runtime_direct_plain_impl<true, true>(queryInfo, plan, constraints, func);
4004 run_query_on_chunks_runtime_direct_plain_impl<true, false>(queryInfo, plan, constraints, func);
4007 run_query_on_chunks_runtime_direct_plain_impl<false, true>(queryInfo, plan, constraints, func);
4009 run_query_on_chunks_runtime_direct_plain_impl<false, false>(queryInfo, plan, constraints, func);
4015 queryInfo, plan, ExecType,
static_cast<void*
>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
4017 each_runtime_erased(ExecType,
static_cast<void*
>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
4026 template <
typename Func,
typename TIter>
4028 auto& func = *
static_cast<Func*
>(pFunc);
4033 struct RuntimeIterCallback {
4036 void (*invoke)(
void*,
Iter&);
4037#if GAIA_ASSERT_ENABLED
4042 void operator()(
Iter& it)
const {
4043 GAIA_PROF_SCOPE(query_func);
4045#if GAIA_ASSERT_ENABLED
4046 it.set_query_access(pQueryAccess);
4052 struct TypedDirectChunkCallback {
4055 const TypedQueryExecState* pState;
4056 void (*runChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&);
4058 void operator()(Iter& it)
const {
4059 GAIA_PROF_SCOPE(query_func);
4060 it.ctx(pSelf->ctx());
4061 runChunk(*pSelf, it, pFunc, *pState);
4065 struct TypedMappedChunkCallback {
4067 const QueryInfo* pQueryInfo;
4069 const TypedQueryExecState* pState;
4070 void (*runChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&);
4072 void operator()(Iter& it)
const {
4073 GAIA_PROF_SCOPE(query_func);
4074 it.ctx(pSelf->ctx());
4075 runChunk(*pSelf, *pQueryInfo, it, pFunc, *pState);
4079 struct TypedIterErasedCallback {
4082 const TypedQueryExecState* pState;
4083 void (*runDirect)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&);
4084 void (*runChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&);
4086 void operator()(Iter& it)
const {
4087 GAIA_PROF_SCOPE(query_func);
4088 it.ctx(pSelf->ctx());
4089 pSelf->each_iter_erased(it, pFunc, *pState, runDirect, runChunk);
4100 QueryExecType execType,
void* pFunc,
void (*invoke)(
void*,
Iter&), Constraints constraints) {
4101 auto& queryInfo =
fetch();
4116 void (*invoke)(
void*,
Iter&), Constraints constraints) {
4117 RuntimeIterCallback cb{pFunc, m_ctx, invoke};
4118#if GAIA_ASSERT_ENABLED
4119 cb.pQueryAccess = &queryInfo.
ctx().
data;
4123 each_direct_iter_inter(queryInfo, constraints, cb);
4128 case QueryExecType::Parallel:
4129 run_query_on_chunks_runtime_planned<QueryExecType::Parallel>(queryInfo, plan, constraints, cb);
4131 case QueryExecType::ParallelPerf:
4132 run_query_on_chunks_runtime_planned<QueryExecType::ParallelPerf>(queryInfo, plan, constraints, cb);
4134 case QueryExecType::ParallelEff:
4135 run_query_on_chunks_runtime_planned<QueryExecType::ParallelEff>(queryInfo, plan, constraints, cb);
4138 run_query_on_chunks_runtime_planned<QueryExecType::Default>(queryInfo, plan, constraints, cb);
4150 if (term.
src != EntityBad || term.
entTrav != EntityBad || term_has_variables(term))
4153 const auto id = term.
id;
4154 return (
id.
pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world,
id))) ||
4155 (!
id.pair() && world_component_is_non_fragmenting(world,
id));
4162 const auto id = term.
id;
4163 return term.
matchKind == QueryMatchKind::Semantic && term.
src == EntityBad && term.
entTrav == EntityBad &&
4164 !term_has_variables(term) &&
id.pair() &&
id.id() == Is.
id() && !is_wildcard(
id.
gen()) &&
4165 !is_variable((EntityId)
id.
gen());
4172 const auto id = term.
id;
4173 return term.
matchKind == QueryMatchKind::In && term.
src == EntityBad && term.
entTrav == EntityBad &&
4174 !term_has_variables(term) &&
id.pair() &&
id.id() == Is.
id() && !is_wildcard(
id.
gen()) &&
4175 !is_variable((EntityId)
id.
gen());
4190 return query_term_uses_potential_inherited_id_matching(term);
4203 GAIA_NODISCARD
static bool match_entity_term(
const World& world,
Entity entity,
const QueryTerm& term) {
4205 return world_has_entity_term(world, entity, term.
id);
4207 return world_has_entity_term_in(world, entity, term.
id);
4209 return world_has_entity_term_direct(world, entity, term.
id);
4212 GAIA_NODISCARD
static uint32_t count_direct_term_entities(
const World& world,
const QueryTerm& term) {
4214 return world_count_direct_term_entities(world, term.
id);
4216 return world_count_in_term_entities(world, term.
id);
4218 return world_count_direct_term_entities_direct(world, term.
id);
4221 static void collect_direct_term_entities(
const World& world,
const QueryTerm& term,
cnt::darray<Entity>& out) {
4223 world_collect_direct_term_entities(world, term.id, out);
4227 world_collect_in_term_entities(world, term.id, out);
4231 world_collect_direct_term_entities_direct(world, term.id, out);
4234 template <
typename Func>
4235 GAIA_NODISCARD
static bool each_direct_term_entity(
const World& world,
const QueryTerm& term, Func&& func) {
4238 static bool thunk(
void* ctx, Entity entity) {
4239 return static_cast<Visitor*
>(ctx)->func(entity);
4243 Visitor visitor{func};
4245 return world_each_direct_term_entity(world, term.id, &visitor, &Visitor::thunk);
4247 return world_each_in_term_entity(world, term.id, &visitor, &Visitor::thunk);
4249 return world_each_direct_term_entity_direct(world, term.id, &visitor, &Visitor::thunk);
4253 GAIA_NODISCARD
static bool can_use_direct_entity_seed_eval(
const QueryInfo& queryInfo) {
4254 if (!queryInfo.can_direct_entity_seed_eval_shape())
4257 const auto& world = *queryInfo.world();
4258 bool hasSeedableTerm =
false;
4259 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4260 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Or)
4264 hasSeedableTerm =
true;
4267 return hasSeedableTerm;
4271 GAIA_NODISCARD
static bool can_use_direct_target_eval(
const QueryInfo& queryInfo) {
4272 return queryInfo.can_direct_target_eval();
4276 GAIA_NODISCARD
static bool has_only_direct_or_terms(
const QueryInfo& queryInfo) {
4277 return queryInfo.has_only_direct_or_terms();
4281 add_chunk_run(cnt::darray<detail::BfsChunkRun>& runs,
const EntityContainer& ec, uint32_t entityOffset) {
4283 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
4287 auto& run = runs.back();
4288 if (ec.pChunk == run.pChunk && ec.row == run.to) {
4289 run.to = (uint16_t)(run.to + 1);
4293 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
4296 struct DirectEntitySeedInfo {
4297 Entity seededAllTerm = EntityBad;
4298 QueryMatchKind seededAllMatchKind = QueryMatchKind::Semantic;
4299 bool seededFromAll =
false;
4300 bool seededFromOr =
false;
4304 struct DirectEntitySeedPlan {
4305 Entity bestAllTerm = EntityBad;
4306 uint32_t bestAllTermCount = UINT32_MAX;
4307 QueryMatchKind bestAllTermMatchKind = QueryMatchKind::Semantic;
4308 bool hasAllTerms =
false;
4309 bool hasOrTerms =
false;
4310 bool preferOrSeed =
false;
4313 struct DirectEntitySeedEvalPlan {
4316 const QueryTerm* pSingleAllTerm =
nullptr;
4318 bool alwaysMatch =
false;
4322 GAIA_NODISCARD
static bool should_prefer_direct_seed_term(
4323 const World& world,
const QueryTerm& candidate, uint32_t candidateCount,
const DirectEntitySeedPlan& plan) {
4325 const bool bestIsSemanticIs = plan.bestAllTermMatchKind != QueryMatchKind::Direct &&
4326 plan.bestAllTerm.pair() && plan.bestAllTerm.id() == Is.
id() &&
4327 !is_wildcard(plan.bestAllTerm.gen()) &&
4328 !is_variable((EntityId)plan.bestAllTerm.gen());
4329 const auto adjustedCandidateCount = candidateCount - (candidateIsSemanticIs && candidateCount > 0 ? 1U : 0U);
4330 const auto adjustedBestCount =
4331 plan.bestAllTermCount - (bestIsSemanticIs && plan.bestAllTermCount > 0 ? 1U : 0U);
4332 if (adjustedCandidateCount < adjustedBestCount)
4334 if (adjustedCandidateCount > adjustedBestCount)
4336 if (plan.bestAllTerm == EntityBad)
4339 if (candidateIsSemanticIs != bestIsSemanticIs)
4340 return candidateIsSemanticIs;
4343 const bool bestUsesInherited = plan.bestAllTermMatchKind == QueryMatchKind::Semantic &&
4344 !is_wildcard(plan.bestAllTerm) &&
4345 !is_variable((EntityId)plan.bestAllTerm.id()) &&
4346 (!plan.bestAllTerm.pair() || !is_variable((EntityId)plan.bestAllTerm.gen())) &&
4347 world_term_uses_inherit_policy(world, plan.bestAllTerm);
4348 if (candidateUsesInherited != bestUsesInherited)
4349 return !candidateUsesInherited;
4354 GAIA_NODISCARD
static DirectEntitySeedPlan
4355 direct_entity_seed_plan(
const World& world,
const QueryInfo& queryInfo) {
4356 DirectEntitySeedPlan plan;
4357 uint32_t totalOrTermCount = 0;
4359 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4360 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4362 if (term.op == QueryOpKind::All) {
4363 plan.hasAllTerms =
true;
4364 const auto cnt = count_direct_term_entities(world, term);
4365 if (should_prefer_direct_seed_term(world, term, cnt, plan)) {
4366 plan.bestAllTermCount = cnt;
4367 plan.bestAllTerm = term.id;
4368 plan.bestAllTermMatchKind = term.matchKind;
4370 }
else if (term.op == QueryOpKind::Or) {
4371 plan.hasOrTerms =
true;
4372 totalOrTermCount += count_direct_term_entities(world, term);
4376 plan.preferOrSeed = plan.hasOrTerms && (!plan.hasAllTerms || totalOrTermCount < plan.bestAllTermCount);
4381 GAIA_NODISCARD
static bool match_direct_entity_terms(
4382 const World& world, Entity entity,
const QueryInfo& queryInfo,
const DirectEntitySeedInfo& seedInfo) {
4383 bool hasOrTerms =
false;
4384 bool anyOrMatched =
false;
4386 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4387 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4389 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4390 term.matchKind == seedInfo.seededAllMatchKind)
4392 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4395 const bool present = match_entity_term(world, entity, term);
4397 case QueryOpKind::All:
4401 case QueryOpKind::Or:
4403 anyOrMatched |= present;
4405 case QueryOpKind::Not:
4409 case QueryOpKind::Any:
4410 case QueryOpKind::Count:
4415 return !hasOrTerms || anyOrMatched;
4419 GAIA_NODISCARD
static bool match_remaining_direct_storage_terms(
4420 const World& world, Entity entity,
const QueryInfo& queryInfo, Entity seedTerm) {
4421 bool hasOrTerms =
false;
4422 bool anyOrMatched =
false;
4423 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4424 if (term.op == QueryOpKind::All && term.id == seedTerm && term.matchKind == QueryMatchKind::Direct)
4427 const bool present = world_has_entity_term_direct(world, entity, term.id);
4429 case QueryOpKind::All:
4433 case QueryOpKind::Or:
4435 anyOrMatched |= present;
4437 case QueryOpKind::Not:
4441 case QueryOpKind::Any:
4442 case QueryOpKind::Count:
4447 return !hasOrTerms || anyOrMatched;
4451 GAIA_NODISCARD
static bool match_specialized_direct_target_terms(
4452 const World& world, Entity entity,
const QueryInfo& queryInfo, Entity termId,
4455 return world_has_entity_term_direct(world, entity, termId);
4457 DirectEntitySeedInfo seedInfo{};
4461 return world_has_entity_term(world, entity, termId);
4463 return world_has_entity_term_in(world, entity, termId);
4465 if (!world_has_entity_term_direct(world, entity, termId))
4467 return match_remaining_direct_storage_terms(world, entity, queryInfo, termId);
4469 if (!world_has_entity_term_direct(world, entity, termId))
4471 seedInfo.seededAllMatchKind = QueryMatchKind::Direct;
4474 if (!world_has_entity_term_in(world, entity, termId))
4476 seedInfo.seededAllMatchKind = QueryMatchKind::In;
4480 if (!world_has_entity_term(world, entity, termId))
4482 seedInfo.seededAllMatchKind = QueryMatchKind::Semantic;
4489 seedInfo.seededAllTerm = termId;
4490 seedInfo.seededFromAll =
true;
4491 return match_direct_entity_terms(world, entity, queryInfo, seedInfo);
4494 GAIA_NODISCARD
static const QueryTerm*
4495 find_direct_all_seed_term(
const QueryInfo& queryInfo,
const DirectEntitySeedPlan& plan) {
4496 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4497 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4499 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4500 term.matchKind != plan.bestAllTermMatchKind)
4508 GAIA_NODISCARD
static DirectEntitySeedEvalPlan
4509 direct_all_seed_eval_plan(
const QueryInfo& queryInfo,
const DirectEntitySeedInfo& seedInfo) {
4510 DirectEntitySeedEvalPlan plan{};
4512 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4513 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4515 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4516 term.matchKind == seedInfo.seededAllMatchKind)
4519 if (term.op == QueryOpKind::All) {
4520 if (plan.pSingleAllTerm !=
nullptr)
4522 plan.pSingleAllTerm = &term;
4529 plan.alwaysMatch = plan.pSingleAllTerm ==
nullptr;
4538 GAIA_NODISCARD
static bool
4539 can_use_direct_seed_run_cache(
const World& world,
const QueryInfo& queryInfo,
const QueryTerm& seedTerm) {
4543 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4544 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4546 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count || term.op == QueryOpKind::Or)
4548 if (term.op == QueryOpKind::All && term.id == seedTerm.id && term.matchKind == seedTerm.matchKind)
4557 GAIA_NODISCARD std::span<const detail::BfsChunkRun> cached_direct_seed_runs(
4558 QueryInfo& queryInfo,
const QueryTerm& seedTerm,
const DirectEntitySeedInfo& seedInfo,
4559 Constraints constraints) {
4560 auto& runData = ensure_direct_seed_run_data();
4561 auto& world = *queryInfo.world();
4562 const auto cachedConstraints = constraints;
4563 const auto relVersion = world_rel_version(world, Is);
4564 const auto worldVersion = ::gaia::ecs::world_version(world);
4566 if (runData.cacheValid && runData.cachedSeedTerm == seedTerm.id &&
4567 runData.cachedSeedMatchKind == seedTerm.matchKind && runData.cachedConstraints == cachedConstraints &&
4568 runData.cachedRelVersion == relVersion && runData.cachedWorldVersion == worldVersion) {
4569 return {runData.cachedRuns.data(), runData.cachedRuns.size()};
4572 auto& runs = runData.cachedRuns;
4573 auto& entities = runData.cachedEntities;
4574 auto& chunkOrderedEntities = runData.cachedChunkOrderedEntities;
4577 chunkOrderedEntities.clear();
4579 (void)each_direct_term_entity(world, seedTerm, [&](Entity entity) {
4580 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4583 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4586 entities.push_back(entity);
4590 chunkOrderedEntities = entities;
4591 core::sort(chunkOrderedEntities, [&](Entity left, Entity right) {
4592 const auto& ecLeft = ::gaia::ecs::fetch(world, left);
4593 const auto& ecRight = ::gaia::ecs::fetch(world, right);
4594 if (ecLeft.pArchetype != ecRight.pArchetype)
4595 return ecLeft.pArchetype->id() < ecRight.pArchetype->id();
4596 if (ecLeft.pChunk != ecRight.pChunk)
4597 return ecLeft.pChunk < ecRight.pChunk;
4598 return ecLeft.row < ecRight.row;
4601 uint32_t entityOffset = 0;
4602 for (
const auto entity: chunkOrderedEntities) {
4603 const auto& ec = ::gaia::ecs::fetch(world, entity);
4604 add_chunk_run(runs, ec, entityOffset++);
4607 runData.cachedSeedTerm = seedTerm.id;
4608 runData.cachedSeedMatchKind = seedTerm.matchKind;
4609 runData.cachedConstraints = cachedConstraints;
4610 runData.cachedRelVersion = relVersion;
4611 runData.cachedWorldVersion = worldVersion;
4612 runData.cacheValid =
true;
4613 return {runs.data(), runs.size()};
4616 GAIA_NODISCARD std::span<const Entity> cached_direct_seed_chunk_entities(
4617 QueryInfo& queryInfo,
const QueryTerm& seedTerm,
const DirectEntitySeedInfo& seedInfo,
4618 Constraints constraints) {
4619 (void)cached_direct_seed_runs(queryInfo, seedTerm, seedInfo, constraints);
4620 auto& runData = ensure_direct_seed_run_data();
4621 return {runData.cachedChunkOrderedEntities.data(), runData.cachedChunkOrderedEntities.size()};
4624 template <
typename Func>
4625 GAIA_NODISCARD
static bool each_direct_all_seed(
4626 const World& world,
const QueryInfo& queryInfo,
const DirectEntitySeedPlan& plan, Constraints constraints,
4628 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
4629 GAIA_ASSERT(pSeedTerm !=
nullptr);
4630 if (pSeedTerm ==
nullptr)
4633 DirectEntitySeedInfo seedInfo{};
4634 seedInfo.seededAllTerm = pSeedTerm->id;
4635 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
4636 seedInfo.seededFromAll =
true;
4637 const auto evalPlan = direct_all_seed_eval_plan(queryInfo, seedInfo);
4638 const Archetype* pLastSingleAllArchetype =
nullptr;
4639 bool lastSingleAllMatch =
false;
4640 bool seedImpliesSingleAllTerm =
false;
4644 const auto seedTarget = pair_tgt(world, pSeedTerm->id);
4645 if (seedTarget != EntityBad)
4646 seedImpliesSingleAllTerm = match_entity_term(world, seedTarget, *evalPlan.pSingleAllTerm);
4651 return each_direct_term_entity(world, *pSeedTerm, [&](Entity entity) {
4652 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4655 if (evalPlan.alwaysMatch)
4656 return func(entity);
4657 if (evalPlan.pSingleAllTerm !=
nullptr) {
4658 if (seedImpliesSingleAllTerm)
4659 return func(entity);
4660 if (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4661 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm)) {
4662 const auto* pArchetype = world_entity_archetype(world, entity);
4663 if (pArchetype != pLastSingleAllArchetype) {
4664 lastSingleAllMatch = match_entity_term(world, entity, *evalPlan.pSingleAllTerm);
4665 pLastSingleAllArchetype = pArchetype;
4667 if (!lastSingleAllMatch)
4669 }
else if (!match_entity_term(world, entity, *evalPlan.pSingleAllTerm)) {
4672 return func(entity);
4674 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4677 return func(entity);
4682 GAIA_NODISCARD
static bool match_direct_entity_constraints(
4683 const World& world,
const QueryInfo& queryInfo, Entity entity, Constraints constraints) {
4684 if (!queryInfo.matches_prefab_entities() && world_entity_prefab(world, entity))
4687 if (constraints == Constraints::EnabledOnly)
4688 return world_entity_enabled(world, entity);
4689 if (constraints == Constraints::DisabledOnly)
4690 return !world_entity_enabled(world, entity);
4695 GAIA_NODISCARD
static bool can_use_archetype_bucket_count(
4696 const World& world,
const QueryInfo& queryInfo,
const DirectEntitySeedInfo& seedInfo) {
4697 if (!seedInfo.seededFromAll && !seedInfo.seededFromOr)
4700 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4701 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4703 if (seedInfo.seededFromAll && term.id == seedInfo.seededAllTerm && term.op == QueryOpKind::All)
4705 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4707 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Not)
4719 GAIA_NODISCARD
static uint32_t count_direct_entity_seed_by_archetype(
4720 const World& world,
const QueryInfo& queryInfo,
const cnt::darray<Entity>& seedEntities,
4721 const DirectEntitySeedInfo& seedInfo, Constraints constraints, DirectQueryScratch& scratch) {
4722 scratch.archetypes.clear();
4723 scratch.bucketEntities.clear();
4724 scratch.counts.clear();
4726 for (
const auto entity: seedEntities) {
4727 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4730 const auto* pArchetype = world_entity_archetype(world, entity);
4731 const auto idx = core::get_index(scratch.archetypes, pArchetype);
4732 if (idx == BadIndex) {
4733 scratch.archetypes.push_back(pArchetype);
4734 scratch.bucketEntities.push_back(entity);
4735 scratch.counts.push_back(1);
4737 ++scratch.counts[idx];
4742 const auto archetypeCnt = (uint32_t)scratch.archetypes.size();
4743 GAIA_FOR(archetypeCnt) {
4744 if (match_direct_entity_terms(world, scratch.bucketEntities[i], queryInfo, seedInfo))
4745 cnt += scratch.counts[i];
4752 GAIA_NODISCARD
static uint32_t
4753 count_direct_or_union(
const World& world,
const QueryInfo& queryInfo, Constraints constraints) {
4754 DirectQueryScratchScope scratchScope;
4755 auto& scratch = scratchScope.scratch;
4756 const auto seenVersion = next_direct_query_seen_version(scratch);
4757 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4760 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4761 if (term.op != QueryOpKind::Or)
4764 (void)each_direct_term_entity(world, term, [&](Entity entity) {
4765 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4768 const auto entityId = (uint32_t)entity.id();
4769 ensure_direct_query_count_capacity(scratch, entityId);
4771 if (scratch.counts[entityId] == seenVersion)
4773 scratch.counts[entityId] = seenVersion;
4775 bool rejected =
false;
4776 if (hasDirectNotTerms) {
4777 for (
const auto& notTerm: queryInfo.
ctx().data.terms_view()) {
4778 if (notTerm.op != QueryOpKind::Not)
4780 if (match_entity_term(world, entity, notTerm)) {
4801 GAIA_NODISCARD
static bool
4802 is_empty_direct_or_union(
const World& world,
const QueryInfo& queryInfo, Constraints constraints) {
4803 DirectQueryScratchScope scratchScope;
4804 auto& scratch = scratchScope.scratch;
4805 const auto seenVersion = next_direct_query_seen_version(scratch);
4806 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4808 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4809 if (term.op != QueryOpKind::Or)
4812 const bool completed = each_direct_term_entity(world, term, [&](Entity entity) {
4813 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4816 const auto entityId = (uint32_t)entity.id();
4817 ensure_direct_query_count_capacity(scratch, entityId);
4819 if (scratch.counts[entityId] == seenVersion)
4821 scratch.counts[entityId] = seenVersion;
4823 bool rejected =
false;
4824 if (hasDirectNotTerms) {
4825 for (
const auto& notTerm: queryInfo.
ctx().data.terms_view()) {
4826 if (notTerm.op != QueryOpKind::Not)
4828 if (match_entity_term(world, entity, notTerm)) {
4848 static DirectEntitySeedInfo
4849 build_direct_entity_seed(
const World& world,
const QueryInfo& queryInfo, DirectQueryScratch& scratch) {
4850 auto& out = scratch.entities;
4852 DirectEntitySeedInfo seedInfo{};
4853 const auto plan = direct_entity_seed_plan(world, queryInfo);
4855 if (plan.hasAllTerms && !plan.preferOrSeed) {
4856 if (plan.bestAllTerm != EntityBad) {
4857 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4858 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4860 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4861 term.matchKind != plan.bestAllTermMatchKind)
4863 collect_direct_term_entities(world, term, out);
4864 seedInfo.seededAllMatchKind = term.matchKind;
4867 seedInfo.seededFromAll =
true;
4868 seedInfo.seededAllTerm = plan.bestAllTerm;
4873 const auto seenVersion = next_direct_query_seen_version(scratch);
4875 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4876 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4878 if (term.op != QueryOpKind::Or)
4881 scratch.termEntities.clear();
4882 collect_direct_term_entities(world, term, scratch.termEntities);
4883 for (
const auto entity: scratch.termEntities) {
4884 const auto entityId = (uint32_t)entity.id();
4885 ensure_direct_query_count_capacity(scratch, entityId);
4887 if (scratch.counts[entityId] == seenVersion)
4889 scratch.counts[entityId] = seenVersion;
4894 seedInfo.seededFromOr =
true;
4901 GAIA_NODISCARD
static bool has_direct_not_terms(
const QueryInfo& queryInfo) {
4902 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4903 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4905 if (term.op == QueryOpKind::Not)
4919 template <
typename Func>
4920 GAIA_FORCEINLINE
void
4921 each_direct_or_union(World& world,
const QueryInfo& queryInfo, Constraints constraints, Func&& func) {
4922 DirectQueryScratchScope scratchScope;
4923 auto& scratch = scratchScope.scratch;
4924 const auto seenVersion = next_direct_query_seen_version(scratch);
4925 DirectEntitySeedInfo seedInfo{};
4926 seedInfo.seededFromOr =
true;
4928 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4929 if (term.op != QueryOpKind::Or)
4932 (void)each_direct_term_entity(world, term, [&](Entity entity) {
4933 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4936 const auto entityId = (uint32_t)entity.id();
4937 ensure_direct_query_count_capacity(scratch, entityId);
4939 if (scratch.counts[entityId] == seenVersion)
4941 scratch.counts[entityId] = seenVersion;
4943 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4958 template <
bool UseFilters>
4959 GAIA_NODISCARD
bool empty_inter(
const QueryInfo& queryInfo, Constraints constraints)
const {
4960 const auto cacheRange = selected_query_cache_range(queryInfo);
4962 if constexpr (!UseFilters) {
4963 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
4964 if (has_only_direct_or_terms(queryInfo))
4965 return is_empty_direct_or_union(*queryInfo.world(), queryInfo, constraints);
4967 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
4969 (void)each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
4977 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4978 const auto cacheView = queryInfo.cache_archetype_view();
4980 if (needsBarrierCache)
4981 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4982 if (!cacheRange.valid)
4984 const auto idxFrom = cacheRange.idxFrom;
4985 const auto idxTo = cacheRange.idxTo;
4987 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4988 const auto* pArchetype = cacheView[qi];
4989 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
4993 GAIA_PROF_SCOPE(query::empty);
4995 const auto& chunks = pArchetype->chunks();
4997 it.init_query_state(queryInfo.world(), constraints,
false);
4998#if GAIA_ASSERT_ENABLED
4999 it.set_query_access(&queryInfo.ctx().data);
5001 it.set_archetype(pArchetype);
5003 if (!hasEntityFilters) {
5004 for (
auto* pChunk: chunks) {
5010 it.set_chunk(pChunk, from, to);
5011 if constexpr (UseFilters) {
5012 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
5020 const bool isNotEmpty = core::has_if(chunks, [&](Chunk* pChunk) {
5026 it.set_chunk(pChunk, from, to);
5027 if constexpr (UseFilters)
5028 if (it.size() == 0 || !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
5030 if (!hasEntityFilters)
5031 return it.size() > 0;
5033 const auto entities = it.template view<Entity>();
5034 const auto cnt = it.size();
5036 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
5059 template <
typename Emit>
5061 each_matching_iter_range(
const QueryInfo& queryInfo, Chunk* pChunk, uint16_t from, uint16_t to, Emit&& emit) {
5062 if (pChunk ==
nullptr || from >= to)
5064 if (!queryInfo.has_entity_filter_terms()) {
5069 const auto entities = pChunk->entity_view();
5070 uint16_t rangeFrom = from;
5071 bool inRange =
false;
5072 for (uint16_t row = from; row < to; ++row) {
5073 const bool match = match_entity_filters(*queryInfo.world(), entities[row], queryInfo);
5079 }
else if (inRange) {
5080 emit(rangeFrom, row);
5085 emit(rangeFrom, to);
5100 template <
typename Batches>
5101 static void push_matching_chunk_batch(
5102 Batches& batches,
const QueryInfo& queryInfo,
const Archetype* pArchetype, Chunk* pChunk,
5103 const uint8_t* pCompIndices, InheritedTermDataView inheritedData, GroupId groupId, uint16_t from,
5105 each_matching_iter_range(queryInfo, pChunk, from, to, [&](uint16_t rangeFrom, uint16_t rangeTo) {
5106 batches.push_back({pArchetype, pChunk, pCompIndices, inheritedData, groupId, rangeFrom, rangeTo});
5116 template <
typename Flush>
5117 static void push_matching_chunk_batch(
5118 ChunkBatchArray& batches,
const QueryInfo& queryInfo,
const Archetype* pArchetype, Chunk* pChunk,
5119 const uint8_t* pCompIndices, InheritedTermDataView inheritedData, GroupId groupId, uint16_t from,
5120 uint16_t to, Flush&& flush) {
5121 each_matching_iter_range(queryInfo, pChunk, from, to, [&](uint16_t rangeFrom, uint16_t rangeTo) {
5122 if (batches.size() == batches.max_size())
5124 batches.push_back({pArchetype, pChunk, pCompIndices, inheritedData, groupId, rangeFrom, rangeTo});
5129 GAIA_NODISCARD
static bool match_entity_filters(
const World& world, Entity entity,
const QueryInfo& queryInfo) {
5130 bool hasOrTerms =
false;
5131 bool anyOrMatched =
false;
5132 const bool hasEntityFilterTerms = queryInfo.has_entity_filter_terms();
5134 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
5135 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
5138 const auto id = term.id;
5141 const bool isNonFragmentingTerm =
5142 (
id.pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world,
id))) ||
5143 (!
id.pair() && world_component_is_non_fragmenting(world,
id));
5144 const bool needsEntityFilter = isNonFragmentingTerm || isDirectIsTerm || isInheritedTerm ||
5145 (hasEntityFilterTerms && term.op == QueryOpKind::Or);
5146 if (!needsEntityFilter)
5149 const bool present = match_entity_term(world, entity, term);
5151 case QueryOpKind::All:
5155 case QueryOpKind::Or:
5157 anyOrMatched |= present;
5159 case QueryOpKind::Not:
5163 case QueryOpKind::Any:
5164 case QueryOpKind::Count:
5169 return !hasOrTerms || anyOrMatched;
5178 matches_target_entities(QueryInfo& queryInfo,
const Archetype& archetype, EntitySpan targetEntities) {
5179 if (targetEntities.empty())
5182 const auto& world = *queryInfo.world();
5184 if (can_use_direct_target_eval(queryInfo)) {
5185 const auto directTargetEvalKind = queryInfo.direct_target_eval_kind();
5187 const auto termId = queryInfo.direct_target_eval_id();
5188 if (targetEntities.size() == 1) {
5189 const auto entity = targetEntities[0];
5190 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5192 return match_specialized_direct_target_terms(world, entity, queryInfo, termId, directTargetEvalKind);
5195 for (
const auto entity: targetEntities) {
5196 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5198 if (match_specialized_direct_target_terms(world, entity, queryInfo, termId, directTargetEvalKind))
5205 const DirectEntitySeedInfo seedInfo{};
5206 if (targetEntities.size() == 1) {
5207 const auto entity = targetEntities[0];
5208 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5210 return match_direct_entity_terms(world, entity, queryInfo, seedInfo);
5213 for (
const auto entity: targetEntities) {
5214 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5216 if (match_direct_entity_terms(world, entity, queryInfo, seedInfo))
5223 if (!
match_one(queryInfo, archetype, targetEntities))
5226 if (!queryInfo.has_entity_filter_terms())
5229 for (
const auto entity: targetEntities) {
5230 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
5232 if (match_entity_filters(world, entity, queryInfo))
5245 template <
bool UseFilters>
5246 GAIA_NODISCARD uint32_t count_inter(
const QueryInfo& queryInfo, Constraints constraints)
const {
5247 const auto cacheRange = selected_query_cache_range(queryInfo);
5249 if constexpr (!UseFilters) {
5250 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
5251 if (has_only_direct_or_terms(queryInfo))
5252 return count_direct_or_union(*queryInfo.world(), queryInfo, constraints);
5254 DirectQueryScratchScope scratchScope;
5255 auto& scratch = scratchScope.scratch;
5257 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
5258 const auto seedInfo = build_direct_entity_seed(*queryInfo.world(), queryInfo, scratch);
5260 if (can_use_archetype_bucket_count(*queryInfo.world(), queryInfo, seedInfo))
5261 return count_direct_entity_seed_by_archetype(
5262 *queryInfo.world(), queryInfo, scratch.entities, seedInfo, constraints, scratch);
5265 (void)each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
5275 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
5276 const auto cacheView = queryInfo.cache_archetype_view();
5278 if (needsBarrierCache)
5279 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
5281 if (!cacheRange.valid)
5283 const auto idxFrom = cacheRange.idxFrom;
5284 const auto idxTo = cacheRange.idxTo;
5286 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
5287 const auto* pArchetype = cacheView[qi];
5288 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
5292 GAIA_PROF_SCOPE(query::count);
5294 const auto& chunks = pArchetype->chunks();
5296 it.init_query_state(queryInfo.world(), constraints,
false);
5297#if GAIA_ASSERT_ENABLED
5298 it.set_query_access(&queryInfo.ctx().data);
5300 it.set_archetype(pArchetype);
5302 if (!hasEntityFilters) {
5303 for (
auto* pChunk: chunks) {
5307 const uint16_t entityCnt = to - from;
5310 it.set_chunk(pChunk, from, to);
5312 if constexpr (UseFilters) {
5313 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
5321 for (
auto* pChunk: chunks) {
5325 const uint16_t entityCnt = to - from;
5328 it.set_chunk(pChunk, from, to);
5331 if constexpr (UseFilters) {
5332 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
5336 if (hasEntityFilters) {
5337 const auto entities = it.template view<Entity>();
5338 GAIA_FOR(entityCnt) {
5339 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
5353 static void init_direct_entity_iter(
5354 const QueryInfo& queryInfo,
const World& world,
const EntityContainer& ec, Iter& it, uint8_t* pIndices,
5355 Entity* pTermIds,
const Archetype*& pLastArchetype) {
5356 GAIA_ASSERT(ec.pArchetype !=
nullptr);
5357 GAIA_ASSERT(ec.pChunk !=
nullptr);
5358 GAIA_ASSERT(ec.row < ec.pChunk->size());
5359#if GAIA_ASSERT_ENABLED
5360 it.set_query_access(&queryInfo.ctx().data);
5363 if (ec.pArchetype != pLastArchetype) {
5364 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
5366 pTermIds[i] = EntityBad;
5369 const auto terms = queryInfo.ctx().data.terms_view();
5370 const auto queryIdCnt = (uint32_t)terms.size();
5371 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
5372 GAIA_FOR(queryIdCnt) {
5373 const auto& term = terms[i];
5374 const auto fieldIdx = term.fieldIndex;
5375 const auto queryId = term.id;
5376 pTermIds[fieldIdx] = queryId;
5377 if (!indicesView.empty()) {
5378 pIndices[fieldIdx] = indicesView[fieldIdx];
5381 if (!query_term_maps_to_current_archetype(term))
5384 if (world_component_uses_sparse_storage(world, queryId)) {
5385 pIndices[fieldIdx] = 0xFF;
5389 auto compIdx = world_component_index_comp_idx(world, *ec.pArchetype, queryId);
5390 if (compIdx == BadIndex || compIdx == ComponentIndexBad)
5391 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
5392 pIndices[fieldIdx] =
5393 (compIdx != BadIndex && compIdx != ComponentIndexBad && compIdx < ec.pArchetype->ids_view().size())
5398 it.set_archetype(ec.pArchetype);
5399 it.set_comp_indices(pIndices);
5400 const auto inheritedDataView = queryInfo.inherited_data_view(ec.pArchetype);
5401 it.set_inherited_data(inheritedDataView);
5402 it.set_term_ids(pTermIds);
5403 pLastArchetype = ec.pArchetype;
5406 it.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
5410 static void init_direct_entity_iter(
5411 const QueryInfo& queryInfo,
const World& world, Entity entity, Iter& it, uint8_t* pIndices,
5413 const auto& ec = ::gaia::ecs::fetch(world, entity);
5414 const Archetype* pLastArchetype =
nullptr;
5415 it.set_world(&world);
5416 init_direct_entity_iter(queryInfo, world, ec, it, pIndices, pTermIds, pLastArchetype);
5419 template <
typename Func>
5420 void each_chunk_runs_iter(
5421 QueryInfo& queryInfo, std::span<const detail::BfsChunkRun> runs, Constraints constraints, Func func) {
5422 auto& world = *queryInfo.world();
5424 it.init_query_state(&world, constraints,
false);
5425 const Archetype* pLastArchetype =
nullptr;
5426 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5427 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5429 for (
const auto& run: runs) {
5430 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
5431 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5432 it.set_chunk(run.pChunk, run.from, run.to);
5436 finish_iter_writes(it);
5437 it.clear_touched_writes();
5441 struct DirectChunkArgEvalDesc {
5442 Entity
id = EntityBad;
5443 bool isEntity =
false;
5444 bool isPair =
false;
5445 bool usesSparseStorage =
false;
5453 GAIA_NODISCARD
static bool can_use_direct_chunk_term_eval_arg(
5454 World& world,
const QueryInfo& queryInfo,
const DirectChunkArgEvalDesc& desc) {
5459 if (world_component_uses_sparse_storage(world, desc.id))
5462 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
5463 if (term.id != desc.id)
5465 if (!query_term_maps_to_current_archetype(term))
5476 GAIA_NODISCARD
static bool can_use_direct_chunk_term_eval_descs(
5477 World& world,
const QueryInfo& queryInfo,
const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5478 if (queryInfo.has_entity_filter_terms())
5482 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, pDescs[i]))
5495 GAIA_NODISCARD
static bool can_use_sparse_chunk_term_eval_descs(
5496 World& world,
const QueryInfo& queryInfo,
const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5497 if (queryInfo.has_entity_filter_terms())
5501 const auto& desc = pDescs[i];
5502 if (!desc.usesSparseStorage) {
5503 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, desc))
5508 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
5509 if (term.id != desc.id)
5530 template <
typename Func>
5531 void each_direct_entities_iter(
5532 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, Func func) {
5533 auto& world = *queryInfo.world();
5534 auto& walkData = ensure_each_walk_data();
5536 it.init_query_state(&world, constraints,
false);
5537 if (!walkData.cachedRuns.empty()) {
5538 each_chunk_runs_iter(queryInfo, walkData.cachedRuns, constraints, func);
5542 const Archetype* pLastArchetype =
nullptr;
5543 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5544 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5545 for (
const auto entity: entities) {
5546 const auto& ec = ::gaia::ecs::fetch(world, entity);
5547 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5550 finish_iter_writes(it);
5551 it.clear_touched_writes();
5560 void finish_unlocked_each() {
5561 auto* pWorld = m_storage.world();
5562 commit_cmd_buffer_st(*pWorld);
5563 commit_cmd_buffer_mt(*pWorld);
5564 m_changedWorldVersion = *m_worldVersion;
5573 template <
typename Func>
5574 void each_direct_iter_inter(QueryInfo& queryInfo, Constraints constraints, Func func) {
5575 auto& world = *queryInfo.world();
5576 const bool hasWriteTerms = queryInfo.ctx().data.readWriteMask != 0;
5577 const auto plan = direct_entity_seed_plan(world, queryInfo);
5579 auto exec_entity = [&](Entity entity) {
5580 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5581 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5583 it.set_constraints(constraints);
5584 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
5585 it.set_write_im(
false);
5588 finish_iter_writes(it);
5591 if (hasWriteTerms) {
5592 DirectQueryScratchScope scratchScope;
5593 auto& scratch = scratchScope.scratch;
5596 const auto seedInfo = build_direct_entity_seed(world, queryInfo, scratch);
5597 for (
const auto entity: scratch.entities) {
5598 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
5600 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
5602 exec_entity(entity);
5604 }
else if (plan.preferOrSeed) {
5605 each_direct_or_union(world, queryInfo, constraints, exec_entity);
5607 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
5608 if (pSeedTerm !=
nullptr && can_use_direct_seed_run_cache(world, queryInfo, *pSeedTerm)) {
5609 DirectEntitySeedInfo seedInfo{};
5610 seedInfo.seededAllTerm = pSeedTerm->id;
5611 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
5612 seedInfo.seededFromAll =
true;
5613 each_chunk_runs_iter(
5614 queryInfo, cached_direct_seed_runs(queryInfo, *pSeedTerm, seedInfo, constraints), constraints, func);
5616 (void)each_direct_all_seed(world, queryInfo, plan, constraints, [&](Entity entity) {
5617 exec_entity(entity);
5623 finish_unlocked_each();
5638 QueryInfo& queryInfo, Constraints constraints,
void* pFunc,
const TypedQueryExecState& state,
5639 void (*runDirectChunk)(
QueryImpl&,
Iter&,
void*,
const TypedQueryExecState&),
bool needsInheritedArgIds,
5643 template <
bool UseFilters,
typename ContainerOut>
5644 void arr_inter(
QueryInfo& queryInfo, ContainerOut& outArray, Constraints constraints);
5660 World& world, QueryCache& queryCache, ArchetypeId& nextArchetypeId, uint32_t& worldVersion,
5661 const EntityToArchetypeMap& entityToArchetypeMap,
5662 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions,
const ArchetypeDArray& allArchetypes):
5663 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion),
5664 m_entityToArchetypeMap(&entityToArchetypeMap),
5665 m_entityToArchetypeMapVersions(&entityToArchetypeMapVersions), m_allArchetypes(&allArchetypes) {
5666 m_storage.init(&world, &queryCache);
5669#if GAIA_ECS_TEST_HOOKS
5670 template <
typename Func>
5671 GAIA_NODISCARD QueryPlan test_typed_plan(Func func);
5676 GAIA_NODISCARD QueryPlan test_iter_plan(Constraints constraints = Constraints::EnabledOnly);
5681 GAIA_NODISCARD QueryId
id()
const {
5682 if (!uses_query_cache_storage() || !m_storage.is_current())
5684 return m_storage.m_identity.handle.id();
5689 GAIA_NODISCARD uint32_t
gen()
const {
5690 if (!uses_query_cache_storage() || !m_storage.is_current())
5692 return m_storage.m_identity.handle.gen();
5700 m_eachWalkData.reset();
5701 m_directSeedRunData.reset();
5702 reset_changed_filter_state();
5703 invalidate_each_walk_cache();
5704 invalidate_direct_seed_run_cache();
5709 (void)m_storage.try_del_from_cache();
5710 m_eachWalkData.reset();
5711 m_directSeedRunData.reset();
5712 reset_changed_filter_state();
5713 invalidate_each_walk_cache();
5714 invalidate_direct_seed_run_cache();
5720 return uses_query_cache_storage() && m_storage.is_cached();
5767 refresh_world_lifetime();
5768 GAIA_ASSERT(str !=
nullptr);
5773 va_start(args, str);
5777 uint32_t parentDepth = 0;
5780 uint32_t varNamesCnt = 0;
5781 auto is_this_expr = [](std::span<const char> exprRaw) {
5782 auto expr = util::trim(exprRaw);
5783 return expr.size() == 5 && expr[0] ==
'$' && expr[1] ==
't' && expr[2] ==
'h' && expr[3] ==
'i' &&
5787 auto find_or_alloc_var = [&](std::span<const char> varExpr) ->
Entity {
5788 auto varNameSpan = util::trim(varExpr);
5789 if (varNameSpan.empty())
5793 if (is_reserved_var_name(varName)) {
5794 GAIA_ASSERT2(
false,
"$this is reserved and can only be used as a source expression: Id($this)");
5798 const auto namedVar = find_var_by_name(varName);
5799 if (namedVar != EntityBad)
5802 GAIA_FOR(varNamesCnt) {
5803 if (varNames[i].size() != varName.size())
5805 if (varNames[i].size() > 0 && memcmp(varNames[i].data(), varName.data(), varName.size()) != 0)
5807 return query_var_entity(i);
5810 if (varNamesCnt >= varNames.size()) {
5811 GAIA_ASSERT2(
false,
"Too many query variables in expression");
5815 const auto idx = varNamesCnt++;
5816 varNames[idx] = varName;
5818 const auto varEntity = query_var_entity(idx);
5819 (void)set_var_name_internal(varEntity, varName);
5823 auto parse_entity_expr = [&](
auto&& self, std::span<const char> exprRaw) ->
Entity {
5824 auto expr = util::trim(exprRaw);
5829 return find_or_alloc_var(expr.subspan(1));
5831 if (expr[0] ==
'(') {
5832 if (expr.back() !=
')') {
5833 GAIA_ASSERT2(
false,
"Expression '(' not terminated");
5837 const auto idStr = expr.subspan(1, expr.size() - 2);
5838 const auto commaIdx = core::get_index(idStr,
',');
5839 if (commaIdx == BadIndex) {
5840 GAIA_ASSERT2(
false,
"Pair expression does not contain ','");
5844 const auto first = self(self, idStr.subspan(0, commaIdx));
5845 if (first == EntityBad)
5847 const auto second = self(self, idStr.subspan(commaIdx + 1));
5848 if (second == EntityBad)
5854 return expr_to_entity((
const World&)*m_storage.world(), args, expr);
5857 auto parse_src_expr = [&](std::span<const char> srcExprRaw,
Entity& srcOut) ->
bool {
5858 auto srcExpr = util::trim(srcExprRaw);
5859 if (srcExpr.empty())
5863 if (is_this_expr(srcExpr)) {
5868 srcOut = parse_entity_expr(parse_entity_expr, srcExpr);
5869 return srcOut != EntityBad;
5873 auto expr = util::trim(exprRaw);
5877 if (expr.back() ==
')') {
5879 int32_t openIdx = -1;
5880 for (int32_t i = (int32_t)expr.size() - 1; i >= 0; --i) {
5881 if (expr[(uint32_t)i] ==
')')
5883 else if (expr[(uint32_t)i] ==
'(') {
5894 auto idExpr = util::trim(expr.subspan(0, (uint32_t)openIdx));
5895 auto srcExpr = util::trim(expr.subspan((uint32_t)openIdx + 1, expr.size() - (uint32_t)openIdx - 2));
5896 if (!idExpr.empty() && !srcExpr.empty()) {
5897 id = parse_entity_expr(parse_entity_expr, idExpr);
5898 if (
id == EntityBad)
5902 if (!parse_src_expr(srcExpr, src))
5911 id = parse_entity_expr(parse_entity_expr, expr);
5912 return id != EntityBad;
5915 auto add_term = [&](QueryOpKind op, std::span<const char> exprRaw) {
5916 auto expr = util::trim(exprRaw);
5920 bool isReadWrite =
false;
5921 if (!expr.empty() && expr[0] ==
'&') {
5923 expr = util::trim(expr.subspan(1));
5930 Entity entity = EntityBad;
5931 if (!parse_term_expr(expr, entity, options))
5935 case QueryOpKind::All:
5936 all(entity, options);
5938 case QueryOpKind::Or:
5939 or_(entity, options);
5941 case QueryOpKind::Not:
5942 no(entity, options);
5944 case QueryOpKind::Any:
5945 any(entity, options);
5955 auto process = [&]() {
5956 std::span<const char> exprRaw(&str[exp0], pos - exp0);
5959 auto expr = util::trim(exprRaw);
5964 bool hasOrChain =
false;
5967 const auto cnt = (uint32_t)expr.size();
5968 for (uint32_t i = 0; i + 1 < cnt; ++i) {
5969 const auto ch = expr[i];
5972 else if (ch ==
')') {
5973 GAIA_ASSERT(depth > 0);
5975 }
else if (depth == 0 && ch ==
'|' && expr[i + 1] ==
'|') {
5984 uint32_t partBeg = 0;
5985 const auto cnt = (uint32_t)expr.size();
5986 for (uint32_t i = 0; i < cnt; ++i) {
5987 const auto ch = expr[i];
5990 else if (ch ==
')') {
5991 GAIA_ASSERT(depth > 0);
5995 const bool isOr = i + 1 < cnt && depth == 0 && ch ==
'|' && expr[i + 1] ==
'|';
5996 const bool isEnd = i + 1 == cnt;
5997 if (!isOr && !isEnd)
6000 const auto partEnd = isOr ? i : (i + 1);
6001 auto partExpr = expr.subspan(partBeg, partEnd - partBeg);
6002 if (!add_term(QueryOpKind::Or, partExpr))
6014 QueryOpKind op = QueryOpKind::All;
6015 if (expr[0] ==
'?') {
6016 op = QueryOpKind::Any;
6017 expr = util::trim(expr.subspan(1));
6018 }
else if (expr[0] ==
'!') {
6019 op = QueryOpKind::Not;
6020 expr = util::trim(expr.subspan(1));
6023 return add_term(op, expr);
6026 for (; str[pos] != 0; ++pos) {
6027 if (str[pos] ==
'(')
6029 else if (str[pos] ==
')') {
6030 GAIA_ASSERT(parentDepth > 0);
6032 }
else if (str[pos] ==
',' && parentDepth == 0) {
6060 return all(
Pair(Is, entity), options);
6071 return all(
Pair(Is, entity), options);
6081 refresh_world_lifetime();
6082#if GAIA_ASSERT_ENABLED
6083 GAIA_ASSERT(m_storage.m_identity.serId != QueryIdBad &&
"no_access() requires a pending query term");
6085 if (m_storage.m_identity.serId == QueryIdBad)
6087 auto& buffer = m_storage.ser_buffer();
6088 const auto end = buffer.bytes();
6089 const uint32_t commandSizes[] = {ser::bytes(QueryCmd_AddItem{}), ser::bytes(QueryCmd_AddFilter{}),
6090 ser::bytes(QueryCmd_SortBy{}), ser::bytes(QueryCmd_GroupBy{}),
6091 ser::bytes(QueryCmd_GroupDep{}), ser::bytes(QueryCmd_MatchPrefab{})};
6092#if GAIA_ASSERT_ENABLED
6094 sizeof(commandSizes) /
sizeof(commandSizes[0]) ==
6095 sizeof(CommandBufferRead) /
sizeof(CommandBufferRead[0]),
6096 "Every query command needs a serialized size");
6098 uint32_t termOffset = BadIndex;
6100 while (buffer.tell() < end) {
6102 bool invalidatesHash =
false;
6103 ser::load(buffer,
id);
6104 ser::load(buffer, invalidatesHash);
6105 (void)invalidatesHash;
6106 if (
id == QueryCmdType::ADD_ITEM)
6107 termOffset = buffer.tell();
6108 buffer.skip(commandSizes[
id]);
6110#if GAIA_ASSERT_ENABLED
6111 GAIA_ASSERT(termOffset != BadIndex &&
"no_access() requires a preceding all(), or_(), or any() term");
6113 buffer.seek(termOffset);
6114 QueryCmd_AddItem cmd{};
6115 ser::load(buffer, cmd);
6116#if GAIA_ASSERT_ENABLED
6118 cmd.item.op == QueryOpKind::All || cmd.item.op == QueryOpKind::Or || cmd.item.op == QueryOpKind::Any);
6120 cmd.item.access = QueryAccess::Match;
6121 buffer.seek(termOffset);
6122 ser::save(buffer, cmd);
6132 add_entity_term(QueryOpKind::All, entity, options);
6140 template <
typename T>
6146 template <
typename T>
6156 add_entity_term(QueryOpKind::Any, entity, options);
6164 template <
typename T>
6170 template <
typename T>
6181 add_entity_term(QueryOpKind::Or, entity, options);
6189 template <
typename T>
6195 template <
typename T>
6205 add_entity_term(QueryOpKind::Not, entity, options);
6213 template <
typename T>
6219 template <
typename T>
6229 refresh_world_lifetime();
6230 [[maybe_unused]]
const bool ok = set_var_name_internal(varEntity, name);
6239 GAIA_ASSERT(name !=
nullptr);
6240 if (name ==
nullptr)
6251 refresh_world_lifetime();
6252 const bool ok = is_query_var_entity(varEntity);
6257 const auto idx = query_var_idx(varEntity);
6258 m_varBindings[idx] = value;
6259 m_varBindingsMask |= (uint8_t(1) << idx);
6267 refresh_world_lifetime();
6268 const auto varEntity = find_var_by_name(name);
6269 GAIA_ASSERT(varEntity != EntityBad);
6270 if (varEntity == EntityBad)
6272 return set_var(varEntity, value);
6279 GAIA_ASSERT(name !=
nullptr);
6280 if (name ==
nullptr)
6290 const bool ok = is_query_var_entity(varEntity);
6295 const auto idx = query_var_idx(varEntity);
6296 m_varBindingsMask &= (uint8_t)~(uint8_t(1) << idx);
6302 m_varBindingsMask = 0;
6312 changed_inter(entity);
6319 template <
typename T>
6331 sort_by_inter(entity, func);
6340 template <
typename T>
6349 template <
typename Rel,
typename Tgt>
6357 Entity m_relation = EntityBad;
6358 TravOrder m_order = TravOrder::Down;
6366 m_query(&query), m_relation(relation), m_order(order) {}
6371 template <
typename Func>
6373 m_query->each_walk(func, m_relation, m_order);
6402 template <
typename Rel>
6415 GAIA_ASSERT(!relation.pair());
6416 GAIA_ASSERT(world_relation_supports_depth_order(*m_storage.world(), relation));
6417 group_by_inter(relation, group_by_func_depth_order,
true);
6424 template <
typename Rel>
6436 group_by_inter(entity, func);
6446 template <
typename T>
6456 template <
typename Rel,
typename Tgt>
6466 group_dep_inter(relation);
6474 template <
typename Rel>
6483 set_group_id_inter(groupId);
6491 GAIA_ASSERT(!entity.
pair());
6492 set_group_id_inter(entity.
id());
6499 template <
typename T>
6507 template <
typename Container>
6508 void groups(Container& out,
bool sortGroups) {
6509 auto& queryInfo =
fetch();
6529 template <
typename Func>
6531#if GAIA_ASSERT_ENABLED
6532 if constexpr (!detail::is_query_iter_callback_v<Func>)
6533 validate_typed_access<Func>();
6535 if constexpr (detail::is_query_iter_callback_v<Func>) {
6537 case QueryExecType::Parallel:
6538 return add_iter_parallel_job<Func, QueryExecType::Parallel>(GAIA_MOV(func));
6539 case QueryExecType::ParallelPerf:
6540 return add_iter_parallel_job<Func, QueryExecType::ParallelPerf>(GAIA_MOV(func));
6541 case QueryExecType::ParallelEff:
6542 return add_iter_parallel_job<Func, QueryExecType::ParallelEff>(GAIA_MOV(func));
6548 case QueryExecType::Parallel:
6549 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::Parallel>(
6550 TypedJobCallback<Func>{
this, GAIA_MOV(func)});
6551 case QueryExecType::ParallelPerf:
6552 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelPerf>(
6553 TypedJobCallback<Func>{
this, GAIA_MOV(func)});
6554 case QueryExecType::ParallelEff:
6555 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelEff>(
6556 TypedJobCallback<Func>{
this, GAIA_MOV(func)});
6562 return add_query_task_job(GAIA_MOV(func), execType);
6565#if GAIA_ASSERT_ENABLED
6568 template <
typename Func>
6569 void validate_typed_access();
6580 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
6582 each_runtime_inter<QueryExecType::Default, Func>(func, Constraints::EnabledOnly);
6588 template <
typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>,
int> = 0>
6596 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
6597 void each(Func func, QueryExecType execType) {
6598 each(func, execType, Constraints::EnabledOnly);
6605 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
6606 void each(Func func, Constraints constraints) {
6607 each(func, QueryExecType::Default, constraints);
6615 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
6616 void each(Func func, QueryExecType execType, Constraints constraints) {
6618 case QueryExecType::Parallel:
6619 each_runtime_inter<QueryExecType::Parallel, Func>(func, constraints);
6621 case QueryExecType::ParallelPerf:
6622 each_runtime_inter<QueryExecType::ParallelPerf, Func>(func, constraints);
6624 case QueryExecType::ParallelEff:
6625 each_runtime_inter<QueryExecType::ParallelEff, Func>(func, constraints);
6628 each_runtime_inter<QueryExecType::Default, Func>(func, constraints);
6637 template <
typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>,
int> = 0>
6638 void each(Func func, QueryExecType execType);
6646 template <
typename Func>
6650 void each_iter_erased(
6651 QueryExecType execType,
void* pFunc,
const TypedQueryExecState& state,
6652 void (*runDirectFastChunk)(
QueryImpl&,
Iter&,
void*,
const TypedQueryExecState&),
6655 void each_iter_erased(
6656 Iter& it,
void* pFunc,
const TypedQueryExecState& state,
6657 void (*runDirectFastChunk)(
QueryImpl&,
Iter&,
void*,
const TypedQueryExecState&),
6668 template <
typename Func>
6669 void each_arch(Func func, Constraints constraints = Constraints::EnabledOnly) {
6670 auto& queryInfo =
fetch();
6672 run_query_on_archetypes<QueryExecType::Default>(
6675 GAIA_PROF_SCOPE(query_func_a);
6677#if GAIA_ASSERT_ENABLED
6678 it.set_query_access(&queryInfo.
ctx().
data);
6696 bool empty(Constraints constraints = Constraints::EnabledOnly) {
6697 auto& queryInfo =
fetch();
6698 if (!queryInfo.
has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6699 return empty_inter<false>(queryInfo, constraints);
6706 return empty_inter<true>(queryInfo, constraints);
6708 return empty_inter<false>(queryInfo, constraints);
6720 uint32_t
count(Constraints constraints = Constraints::EnabledOnly) {
6721 auto& queryInfo =
fetch();
6722 if (!queryInfo.
has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6723 return count_inter<false>(queryInfo, constraints);
6729 return hasFilters ? count_inter<true>(queryInfo, constraints) : count_inter<false>(queryInfo, constraints);
6736 auto& queryInfo =
fetch();
6738 ::gaia::ecs::update_version(*m_worldVersion);
6740 if (!queryInfo.
has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6741 auto& world = *queryInfo.
world();
6742 if (has_only_direct_or_terms(queryInfo)) {
6743 each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](
Entity entity) {
6748 const auto plan = direct_entity_seed_plan(world, queryInfo);
6749 (void)each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](
Entity entity) {
6755 m_changedWorldVersion = *m_worldVersion;
6763 if (needsBarrierCache)
6766 const auto cacheRange = selected_query_cache_range(queryInfo);
6767 if (!cacheRange.valid) {
6768 m_changedWorldVersion = *m_worldVersion;
6771 const auto idxFrom = cacheRange.idxFrom;
6772 const auto idxTo = cacheRange.idxTo;
6775 it.init_query_state(queryInfo.
world(), Constraints::EnabledOnly,
false);
6776 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6777 const auto* pArchetype = cacheView[qi];
6778 const bool barrierPasses = !needsBarrierCache || queryInfo.
barrier_passes(qi);
6780 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6783 const auto& chunks = pArchetype->chunks();
6784 if (!hasEntityFilters) {
6785 for (
auto* pChunk: chunks) {
6790 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6793 const auto entityCnt = (uint32_t)(to - from);
6794 const auto entities = pChunk->entity_view();
6795 GAIA_FOR(entityCnt) {
6796 func(pCtx, entities[from + i]);
6802 it.set_archetype(pArchetype);
6803 for (
auto* pChunk: chunks) {
6804 it.set_chunk(pChunk);
6805 const auto entityCnt = it.
size();
6808 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6811 const auto entities = it.view<
Entity>();
6812 GAIA_FOR(entityCnt) {
6813 if (match_entity_filters(*queryInfo.
world(), entities[i], queryInfo))
6814 func(pCtx, entities[i]);
6819 m_changedWorldVersion = *m_worldVersion;
6824 auto& queryInfo =
fetch();
6826 ::gaia::ecs::update_version(*m_worldVersion);
6828 if (!queryInfo.
has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6829 auto& world = *queryInfo.
world();
6830 if (has_only_direct_or_terms(queryInfo)) {
6831 each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](
Entity entity) {
6836 const auto plan = direct_entity_seed_plan(world, queryInfo);
6837 (void)each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6843 m_changedWorldVersion = *m_worldVersion;
6851 if (needsBarrierCache)
6854 const auto cacheRange = selected_query_cache_range(queryInfo);
6855 if (!cacheRange.valid) {
6856 m_changedWorldVersion = *m_worldVersion;
6859 const auto idxFrom = cacheRange.idxFrom;
6860 const auto idxTo = cacheRange.idxTo;
6863 it.init_query_state(queryInfo.
world(), Constraints::EnabledOnly,
false);
6864 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6865 const auto* pArchetype = cacheView[qi];
6866 const bool barrierPasses = !needsBarrierCache || queryInfo.
barrier_passes(qi);
6868 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6871 const auto& chunks = pArchetype->chunks();
6872 if (!hasEntityFilters) {
6873 for (
auto* pChunk: chunks) {
6878 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6881 const auto oldSize = out.
size();
6882 const auto entityCnt = (uint32_t)(to - from);
6883 const auto entities = pChunk->entity_view();
6884 out.
resize(oldSize + entityCnt, cnt::ResizePolicy::Scale);
6885 GAIA_FOR(entityCnt) {
6886 out[oldSize + i] = entities[from + i];
6892 it.set_archetype(pArchetype);
6893 for (
auto* pChunk: chunks) {
6894 it.set_chunk(pChunk);
6895 const auto entityCnt = it.
size();
6898 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6901 const auto entities = it.view<Entity>();
6902 GAIA_FOR(entityCnt) {
6903 if (match_entity_filters(*queryInfo.
world(), entities[i], queryInfo))
6909 m_changedWorldVersion = *m_worldVersion;
6917 template <
typename Container>
6918 void arr(Container& outArray, Constraints constraints = Constraints::EnabledOnly);
6928 Constraints constraints = Constraints::EnabledOnly) {
6929 struct OrderedWalkTargetCtx {
6932 uint32_t dependentIdx = 0;
6937 uint32_t* pEdgeCnt =
nullptr;
6939 GAIA_NODISCARD
static uint32_t
6941 const auto targetId = entity.
id();
6943 uint32_t high = cnt;
6944 while (low < high) {
6945 const uint32_t mid = low + ((high - low) >> 1);
6946 if (entities[mid].
id() < targetId)
6952 if (low < cnt && entities[low].
id() == targetId)
6957 static void count_edge(
void* rawCtx,
Entity dependency) {
6958 auto& ctx = *
static_cast<OrderedWalkTargetCtx*
>(rawCtx);
6959 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6960 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6963 ++(*ctx.pOutdegree)[dependencyIdx];
6964 ++(*ctx.pIndegree)[ctx.dependentIdx];
6968 static void write_edge(
void* rawCtx,
Entity dependency) {
6969 auto& ctx = *
static_cast<OrderedWalkTargetCtx*
>(rawCtx);
6970 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6971 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6974 (*ctx.pEdges)[(*ctx.pWriteCursor)[dependencyIdx]++] = ctx.dependentIdx;
6978 auto& walkData = ensure_each_walk_data();
6979 auto& world = *m_storage.world();
6980 const uint32_t relationVersion = world_rel_version(world, relation);
6981 const uint32_t worldVersion = ::gaia::ecs::world_version(world);
6984 const bool needsTraversalBarrierState =
6985 constraints == Constraints::EnabledOnly && ::gaia::ecs::valid(world, relation);
6986 auto survives_disabled_barrier = [&](
Entity entity) {
6987 if (!needsTraversalBarrierState)
6991 GAIA_FOR(MAX_TRAV_DEPTH) {
6992 const auto next = target(world, curr, relation);
6993 if (next == EntityBad || next == curr)
6995 if (!world_entity_enabled(world, next))
7003 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
7004 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
7005 walkData.cachedEntityVersion == worldVersion &&
7006 walkData.cachedResultCacheRevision == resultCacheRevision && !queryInfo.
has_filters()) {
7007 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
7010 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
7011 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
7012 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
7014 auto& chunks = walkData.scratchChunks;
7017 bool chunkChanged =
false;
7018 for (
auto* pArchetype: queryInfo) {
7022 for (
const auto* pChunk: pArchetype->chunks()) {
7023 if (pChunk ==
nullptr)
7026 chunks.push_back(pChunk);
7027 if (!chunkChanged && pChunk->changed(walkData.cachedEntityVersion))
7028 chunkChanged =
true;
7032 bool sameChunks = chunks.size() == walkData.cachedChunks.size();
7034 for (uint32_t i = 0; i < (uint32_t)chunks.size(); ++i) {
7035 if (chunks[i] != walkData.cachedChunks[i]) {
7042 if (sameChunks && !chunkChanged) {
7043 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
7047 auto& entities = walkData.scratchEntities;
7049 arr(entities, constraints);
7050 if (entities.empty())
7053 if (needsTraversalBarrierState) {
7054 uint32_t writeIdx = 0;
7055 const auto cnt = (uint32_t)entities.size();
7057 const auto entity = entities[i];
7058 if (!survives_disabled_barrier(entity))
7060 entities[writeIdx++] = entity;
7062 entities.resize(writeIdx);
7063 if (entities.empty())
7067 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
7068 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
7069 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
7070 entities.size() == walkData.cachedInput.size()) {
7071 bool sameInput =
true;
7072 for (uint32_t i = 0; i < (uint32_t)entities.size(); ++i) {
7073 if (entities[i] != walkData.cachedInput[i]) {
7080 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
7084 auto& ordered = walkData.cachedOutput;
7085 walkData.cachedInput = entities;
7087 if (!::gaia::ecs::valid(world, relation)) {
7094 const auto cnt = (uint32_t)entities.size();
7096 auto& indegree = walkData.scratchIndegree;
7097 indegree.resize(cnt);
7098 auto& outdegree = walkData.scratchOutdegree;
7099 outdegree.resize(cnt);
7100 for (uint32_t i = 0; i < cnt; ++i) {
7105 uint32_t edgeCnt = 0;
7106 OrderedWalkTargetCtx edgeCtx;
7107 edgeCtx.pEntities = &entities;
7109 edgeCtx.pIndegree = &indegree;
7110 edgeCtx.pOutdegree = &outdegree;
7111 edgeCtx.pEdgeCnt = &edgeCnt;
7112 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
7113 const auto dependent = entities[dependentIdx];
7114 edgeCtx.dependentIdx = dependentIdx;
7115 world_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::count_edge);
7118 auto& offsets = walkData.scratchOffsets;
7119 offsets.resize(cnt + 1);
7121 for (uint32_t i = 0; i < cnt; ++i)
7122 offsets[i + 1] = offsets[i] + outdegree[i];
7124 auto& writeCursor = walkData.scratchWriteCursor;
7125 writeCursor.resize(cnt);
7126 for (uint32_t i = 0; i < cnt; ++i)
7127 writeCursor[i] = offsets[i];
7129 auto& edges = walkData.scratchEdges;
7130 edges.resize(edgeCnt);
7131 edgeCtx.pWriteCursor = &writeCursor;
7132 edgeCtx.pEdges = &edges;
7133 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
7134 const auto dependent = entities[dependentIdx];
7135 edgeCtx.dependentIdx = dependentIdx;
7136 world_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::write_edge);
7139 ordered.reserve(cnt);
7141 const bool isUp = order == TravOrder::Up || order == TravOrder::ReverseUp;
7142 const bool needsReverse = order == TravOrder::ReverseUp || order == TravOrder::ReverseDown;
7144 auto& visited = walkData.scratchWriteCursor;
7145 visited.resize(cnt);
7146 for (uint32_t i = 0; i < cnt; ++i)
7149 auto& stack = walkData.scratchCurrLevel;
7151 auto& cursorStack = walkData.scratchNextLevel;
7152 cursorStack.clear();
7154 auto append_from_root = [&](uint32_t rootIdx) {
7155 stack.push_back(rootIdx);
7156 cursorStack.push_back(offsets[rootIdx]);
7158 while (!stack.empty()) {
7159 const auto idx = stack.back();
7160 if (visited[idx] == 0) {
7163 ordered.push_back(entities[idx]);
7166 bool pushedChild =
false;
7167 auto& cursor = cursorStack.back();
7168 while (cursor < offsets[idx + 1]) {
7169 const auto childIdx = edges[cursor++];
7170 if (visited[childIdx] != 0)
7173 stack.push_back(childIdx);
7174 cursorStack.push_back(offsets[childIdx]);
7183 ordered.push_back(entities[idx]);
7186 cursorStack.pop_back();
7190 for (uint32_t i = 0; i < cnt; ++i) {
7191 if (indegree[i] == 0 && visited[i] == 0)
7192 append_from_root(i);
7196 for (uint32_t i = 0; i < cnt; ++i) {
7197 if (visited[i] == 0)
7198 append_from_root(i);
7202 const auto orderedCnt = (uint32_t)ordered.size();
7203 for (uint32_t i = 0; i < orderedCnt / 2; ++i)
7204 core::swap(ordered[i], ordered[orderedCnt - i - 1]);
7208 walkData.cachedRelation = relation;
7209 walkData.cachedOrder = order;
7210 walkData.cachedConstraints = constraints;
7211 walkData.cachedRelationVersion = relationVersion;
7212 walkData.cachedEntityVersion = ::gaia::ecs::world_version(world);
7213 walkData.cachedResultCacheRevision = resultCacheRevision;
7214 walkData.cachedRuns.clear();
7217 const auto orderedCnt = (uint32_t)ordered.size();
7218 if (orderedCnt != 0) {
7219 for (uint32_t i = 0; i < orderedCnt; ++i) {
7220 const auto& ec = ::gaia::ecs::fetch(world, ordered[i]);
7221 if (walkData.cachedRuns.empty()) {
7222 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
7226 auto& run = walkData.cachedRuns.back();
7227 if (ec.pChunk == run.pChunk && ec.row == run.to) {
7228 run.to = (uint16_t)(run.to + 1);
7230 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
7237 auto& chunks = walkData.scratchChunks;
7239 for (
auto* pArchetype: queryInfo) {
7243 for (
const auto* pChunk: pArchetype->chunks()) {
7244 if (pChunk ==
nullptr)
7246 chunks.push_back(pChunk);
7249 walkData.cachedChunks = chunks;
7251 walkData.cachedChunks.clear();
7252 walkData.cacheValid =
true;
7254 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
7266 template <
typename Func, std::enable_if_t<detail::is_query_walk_core_callback_v<Func>,
int> = 0>
7268 Func func,
Entity relation, TravOrder order = TravOrder::Down,
7269 Constraints constraints = Constraints::EnabledOnly) {
7270 auto& queryInfo =
fetch();
7274 if constexpr (std::is_invocable_v<Func, Iter&>) {
7275 each_direct_entities_iter(queryInfo, ordered, constraints, func);
7276 }
else if constexpr (std::is_invocable_v<Func, const Entity&> || std::is_invocable_v<Func, Entity>) {
7277 for (
const auto entity: ordered)
7288 template <
typename Func, std::enable_if_t<!detail::is_query_walk_core_callback_v<Func>,
int> = 0>
7290 Func func,
Entity relation, TravOrder order = TravOrder::Down,
7291 Constraints constraints = Constraints::EnabledOnly);
7298 auto& queryInfo =
fetch();
7300 if (uses_shared_cache_layer())
7301 GAIA_LOG_N(
"BEG DIAG Query %u.%u [S]",
id(),
gen());
7302 else if (uses_query_cache_storage())
7303 GAIA_LOG_N(
"BEG DIAG Query %u.%u [L]",
id(),
gen());
7305 GAIA_LOG_N(
"BEG DIAG Query [U]");
7306 for (
const auto* pArchetype: queryInfo)
7308 GAIA_LOG_N(
"END DIAG Query");
7314 auto& queryInfo =
fetch();
7321 if (uses_shared_cache_layer())
7322 GAIA_LOG_N(
"BEG DIAG Query Bytecode %u.%u [S]",
id(),
gen());
7323 else if (uses_query_cache_storage())
7324 GAIA_LOG_N(
"BEG DIAG Query Bytecode %u.%u [L]",
id(),
gen());
7326 GAIA_LOG_N(
"BEG DIAG Query Bytecode [U]");
7327 GAIA_LOG_N(
"%.*s", (
int)dump.size(), dump.data());
7328 GAIA_LOG_N(
"END DIAG Query");