508 static constexpr uint32_t ChunkBatchSize = 32;
509 friend class SystemBuilder;
514 const uint8_t* pCompIndices;
515 InheritedTermDataView inheritedData;
521 using ChunkSpan = std::span<const Chunk*>;
522 using ChunkSpanMut = std::span<Chunk*>;
526 struct DirectQueryScratch {
532 uint32_t seenVersion = 1;
536 GAIA_NODISCARD
bool uses_query_cache_storage()
const {
537 return m_cacheKind != QueryCacheKind::None;
540 GAIA_NODISCARD
bool uses_shared_cache_layer()
const {
541 return uses_query_cache_storage() && m_cacheScope == QueryCacheScope::Shared;
544 void invalidate_query_storage() {
545 if (uses_query_cache_storage())
546 (void)m_storage.try_del_from_cache();
547 m_storage.invalidate();
552 GAIA_NODISCARD
static DirectQueryScratch& direct_query_scratch() {
553 static thread_local DirectQueryScratch scratch;
560 static void ensure_direct_query_count_capacity(DirectQueryScratch& scratch, uint32_t entityId) {
561 if (entityId < scratch.counts.size())
564 const auto doubledSize = (uint32_t)scratch.counts.size() * 2U;
565 const auto minSize = doubledSize > 64U ? doubledSize : 64U;
566 const auto newSize = (entityId + 1U) > minSize ? (entityId + 1U) : minSize;
567 scratch.counts.resize(newSize, 0);
573 GAIA_NODISCARD
static uint32_t next_direct_query_seen_version(DirectQueryScratch& scratch) {
574 update_version(scratch.seenVersion);
575 if (scratch.seenVersion == 0) {
576 scratch.seenVersion = 1;
577 core::fill(scratch.counts.begin(), scratch.counts.end(), 0);
580 return scratch.seenVersion;
583 static constexpr CmdFunc CommandBufferRead[] = {
586 QueryCmd_AddItem cmd;
587 ser::load(buffer, cmd);
592 QueryCmd_AddFilter cmd;
593 ser::load(buffer, cmd);
599 ser::load(buffer, cmd);
604 QueryCmd_GroupBy cmd;
605 ser::load(buffer, cmd);
610 QueryCmd_GroupDep cmd;
611 ser::load(buffer, cmd);
616 QueryCmd_MatchPrefab cmd;
617 ser::load(buffer, cmd);
623 QueryImplStorage m_storage;
625 ArchetypeId* m_nextArchetypeId{};
627 uint32_t* m_worldVersion{};
629 const EntityToArchetypeMap* m_entityToArchetypeMap{};
631 const EntityToArchetypeVersionMap* m_entityToArchetypeMapVersions{};
633 const ArchetypeDArray* m_allArchetypes{};
637 uint8_t m_varNamesMask = 0;
641 uint8_t m_varBindingsMask = 0;
643 GroupId m_groupIdSet = 0;
645 uint32_t m_changedWorldVersion = 0;
650 QueryCacheKind m_cacheKind = QueryCacheKind::Default;
652 QueryCacheScope m_cacheScope = QueryCacheScope::Local;
654 uint16_t m_cacheSrcTrav = 0;
656 void* m_ctx =
nullptr;
658 bool m_mainThread =
false;
663 struct EachWalkData {
671 Entity cachedRelation = EntityBad;
673 TravOrder cachedOrder = TravOrder::Down;
675 Constraints cachedConstraints = Constraints::EnabledOnly;
677 uint32_t cachedRelationVersion = 0;
679 uint32_t cachedEntityVersion = 0;
681 uint32_t cachedResultCacheRevision = 0;
685 bool cacheValid =
false;
706 template <
typename T>
707 struct OnDemandDataHolder {
710 OnDemandDataHolder() =
default;
712 ~OnDemandDataHolder() {
716 OnDemandDataHolder(
const OnDemandDataHolder& other) {
717 if (other.pData !=
nullptr)
718 pData =
new T(*other.pData);
721 OnDemandDataHolder& operator=(
const OnDemandDataHolder& other) {
722 if (core::addressof(other) ==
this)
725 if (other.pData ==
nullptr) {
731 if (pData ==
nullptr)
732 pData =
new T(*other.pData);
734 *pData = *other.pData;
739 OnDemandDataHolder(OnDemandDataHolder&& other)
noexcept: pData(other.pData) {
740 other.pData =
nullptr;
743 OnDemandDataHolder& operator=(OnDemandDataHolder&& other)
noexcept {
744 if (core::addressof(other) ==
this)
749 other.pData =
nullptr;
753 GAIA_NODISCARD T* get() {
757 GAIA_NODISCARD
const T* get()
const {
761 GAIA_NODISCARD T& ensure() {
762 if (pData ==
nullptr)
774 OnDemandDataHolder<EachWalkData> m_eachWalkData;
777 struct DirectSeedRunData {
781 Entity cachedSeedTerm = EntityBad;
782 QueryMatchKind cachedSeedMatchKind = QueryMatchKind::Semantic;
783 Constraints cachedConstraints = Constraints::EnabledOnly;
784 uint32_t cachedRelVersion = 0;
785 uint32_t cachedWorldVersion = 0;
786 bool cacheValid =
false;
789 OnDemandDataHolder<DirectSeedRunData> m_directSeedRunData;
795 GAIA_NODISCARD
static QueryAccess merge_access(QueryAccess lhs, QueryAccess rhs) {
796 if (lhs == QueryAccess::Write || rhs == QueryAccess::Write)
797 return QueryAccess::Write;
798 if (lhs == QueryAccess::Read || rhs == QueryAccess::Read)
799 return QueryAccess::Read;
800 return QueryAccess::None;
808 if (entity == EntityBad || entity.
pair())
809 return QueryAccess::None;
811 QueryAccess
access = QueryAccess::None;
813 GAIA_FOR((uint32_t)terms.size()) {
814 const auto& term = terms[i];
815 if (term.id != entity || (term.op != QueryOpKind::All && term.op != QueryOpKind::Or))
819 return QueryAccess::Write;
820 access = QueryAccess::Read;
831 GAIA_NODISCARD
static QueryAccess
833 return merge_access(term_access(data, entity), accessSet.
access(entity));
840 GAIA_NODISCARD
static bool access_conflicts(QueryAccess lhs, QueryAccess rhs) {
841 return (lhs == QueryAccess::Write && rhs != QueryAccess::None) ||
842 (rhs == QueryAccess::Write && lhs != QueryAccess::None);
851 GAIA_NODISCARD
static bool conflicts_one_way(
855 GAIA_FOR((uint32_t)terms.size()) {
856 const auto id = terms[i].id;
857 const auto access = term_access(leftData,
id);
858 if (access_conflicts(
access, effective_access(rightData, rightAccess,
id)))
862 for (
const auto id: leftAccess.
reads_view()) {
863 if (access_conflicts(QueryAccess::Read, effective_access(rightData, rightAccess,
id)))
867 if (access_conflicts(QueryAccess::Write, effective_access(rightData, rightAccess,
id)))
877 template <
typename T>
878 GAIA_NODISCARD
Entity access_entity_inter() {
880 const auto& descRel = comp_cache_add<typename T::rel_type>(*m_storage.world());
881 const auto& descTgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
882 return Pair(descRel.entity, descTgt.entity);
884 using UO =
typename component_type_t<T>::TypeOriginal;
885 static_assert(core::is_raw_v<UO>,
"Use reads()/writes() with raw types only");
886 const auto& desc = comp_cache_add<T>(*m_storage.world());
900 GAIA_PROF_SCOPE(query::fetch);
903 GAIA_ASSERT(m_storage.is_initialized());
905 if (!uses_query_cache_storage()) {
906 if GAIA_UNLIKELY (!m_storage.has_owned_query_info()) {
910 m_storage.init_owned_query_info(
912 }
else if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad) {
913 recommit(m_storage.owned_query_info().ctx());
916 return m_storage.owned_query_info();
921 if GAIA_LIKELY (m_storage.m_identity.handle.id() != QueryIdBad) {
922 auto* pQueryInfo = m_storage.try_query_info_fast();
923 if GAIA_UNLIKELY (pQueryInfo ==
nullptr)
924 pQueryInfo = m_storage.m_pCache->try_get(m_storage.m_identity.handle);
927 if GAIA_LIKELY (pQueryInfo !=
nullptr) {
928 m_storage.cache_query_info(*pQueryInfo);
929 if GAIA_UNLIKELY (m_storage.m_identity.serId != QueryIdBad)
930 recommit(pQueryInfo->ctx());
934 m_storage.invalidate();
942 uses_shared_cache_layer()
943 ? m_storage.m_pCache->add(GAIA_MOV(
ctx), *m_entityToArchetypeMap, all_archetypes_view())
944 : m_storage.m_pCache->add_local(GAIA_MOV(
ctx), *m_entityToArchetypeMap, all_archetypes_view());
946 m_storage.cache_query_info(queryInfo);
947 m_storage.allow_to_destroy_again();
954 const auto kindError = validate_kind(queryInfo.
ctx());
955 if (kindError != QueryKindRes::OK) {
961 if (!uses_query_cache_storage()) {
963 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, m_varBindings,
969 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, last_archetype_id(),
970 m_varBindings, m_varBindingsMask);
971 m_storage.m_pCache->sync_archetype_cache(queryInfo);
980 if (!uses_query_cache_storage()) {
984 return queryInfo.
ensure_matches_one(archetype, targetEntities, m_varBindings, m_varBindingsMask);
993 const auto kindError = validate_kind(queryInfo.
ctx());
994 if (kindError != QueryKindRes::OK) {
1000 return matches_target_entities(queryInfo, archetype, targetEntities);
1021 if (m_cacheSrcTrav == maxItems)
1024 if (maxItems > MaxCacheSrcTrav) {
1025 GAIA_ASSERT(
false &&
"cache_src_trav should be a value smaller than MaxCacheSrcTrav");
1026 maxItems = MaxCacheSrcTrav;
1029 invalidate_each_walk_cache();
1030 invalidate_direct_seed_run_cache();
1031 invalidate_query_storage();
1032 m_cacheSrcTrav = maxItems;
1040 return m_cacheSrcTrav;
1063 GAIA_NODISCARD
void*
ctx()
const {
1080 m_mainThread = required;
1087 return m_mainThread;
1111 template <
typename T>
1113 return reads(access_entity_inter<T>());
1132 template <
typename T>
1134 return writes(access_entity_inter<T>());
1157 return effective_access(
fetch().
ctx().data, m_access, entity);
1170 return conflicts_one_way(leftData, m_access, rightData, other.m_access) ||
1171 conflicts_one_way(rightData, other.m_access, leftData, m_access);
1178 return !m_mainThread && !other.m_mainThread && !
conflicts_with(other);
1186 if (m_cacheKind == cacheKind)
1189 invalidate_each_walk_cache();
1190 invalidate_direct_seed_run_cache();
1191 invalidate_query_storage();
1192 m_cacheKind = cacheKind;
1201 if (m_cacheScope == cacheScope)
1204 invalidate_each_walk_cache();
1205 invalidate_direct_seed_run_cache();
1206 invalidate_query_storage();
1207 m_cacheScope = cacheScope;
1215 QueryCmd_MatchPrefab cmd{};
1222 GAIA_NODISCARD QueryCacheScope
scope()
const {
1223 return m_cacheScope;
1228 GAIA_NODISCARD QueryCacheKind
kind()
const {
1235 return validate_kind(
fetch().
ctx());
1255 GAIA_NODISCARD
bool uses_manual_src_trav_cache(
const QueryCtx&
ctx)
const {
1256 return m_cacheSrcTrav != 0 &&
1264 GAIA_NODISCARD
static bool uses_im_cache(
const QueryCtx&
ctx) {
1265 return ctx.data.cachePolicy == QueryCachePolicy::Immediate;
1271 GAIA_NODISCARD
static bool uses_lazy_cache(
const QueryCtx&
ctx) {
1272 return ctx.data.cachePolicy == QueryCachePolicy::Lazy;
1278 GAIA_NODISCARD
static bool uses_dyn_cache(
const QueryCtx&
ctx) {
1279 return ctx.data.cachePolicy == QueryCachePolicy::Dynamic;
1285 GAIA_NODISCARD QueryKindRes validate_kind(
const QueryCtx&
ctx)
const {
1286 if (m_cacheKind == QueryCacheKind::Auto) {
1287 if (uses_manual_src_trav_cache(
ctx))
1288 return QueryKindRes::AutoSrcTrav;
1291 if (m_cacheKind == QueryCacheKind::All) {
1292 if (uses_manual_src_trav_cache(
ctx))
1293 return QueryKindRes::AllSrcTrav;
1294 if (!uses_im_cache(
ctx))
1295 return QueryKindRes::AllNotIm;
1298 return QueryKindRes::OK;
1304 GAIA_NODISCARD
static const char*
kind_error_str(QueryKindRes error) {
1306 case QueryKindRes::OK:
1308 case QueryKindRes::AutoSrcTrav:
1309 return "QueryCacheKind::Auto rejects explicit traversed-source snapshot caching";
1310 case QueryKindRes::AllNotIm:
1311 return "QueryCacheKind::All requires a fully immediate structural cache";
1312 case QueryKindRes::AllSrcTrav:
1313 return "QueryCacheKind::All rejects explicit traversed-source snapshot caching";
1315 return "Unknown query kind validation error";
1321 GAIA_NODISCARD EachWalkData* each_walk_data() {
1322 return m_eachWalkData.get();
1327 GAIA_NODISCARD
const EachWalkData* each_walk_data()
const {
1328 return m_eachWalkData.get();
1333 GAIA_NODISCARD EachWalkData& ensure_each_walk_data() {
1334 return m_eachWalkData.ensure();
1338 void invalidate_each_walk_cache() {
1339 auto* pWalkData = each_walk_data();
1340 if (pWalkData !=
nullptr)
1341 pWalkData->cacheValid =
false;
1346 GAIA_NODISCARD DirectSeedRunData* direct_seed_run_data() {
1347 return m_directSeedRunData.get();
1352 GAIA_NODISCARD
const DirectSeedRunData* direct_seed_run_data()
const {
1353 return m_directSeedRunData.get();
1358 GAIA_NODISCARD DirectSeedRunData& ensure_direct_seed_run_data() {
1359 return m_directSeedRunData.ensure();
1363 void invalidate_direct_seed_run_cache() {
1364 auto* pRunData = direct_seed_run_data();
1365 if (pRunData !=
nullptr)
1366 pRunData->cacheValid =
false;
1370 void reset_changed_filter_state() {
1371 m_changedWorldVersion = 0;
1376 ArchetypeId last_archetype_id()
const {
1377 return *m_nextArchetypeId - 1;
1382 GAIA_NODISCARD std::span<const Archetype*> all_archetypes_view()
const {
1383 GAIA_ASSERT(m_allArchetypes !=
nullptr);
1384 return {(
const Archetype**)m_allArchetypes->data(), m_allArchetypes->size()};
1387 GAIA_NODISCARD
static bool is_query_var_entity(Entity entity) {
1388 return is_variable((EntityId)entity.id());
1391 GAIA_NODISCARD
static uint32_t query_var_idx(Entity entity) {
1392 GAIA_ASSERT(is_query_var_entity(entity));
1393 return (uint32_t)(entity.id() - Var0.
id());
1396 GAIA_NODISCARD Entity query_var_entity(uint32_t idx) {
1397 GAIA_ASSERT(idx < 8);
1398 return entity_from_id((
const World&)*m_storage.world(), (EntityId)(Var0.
id() + idx));
1401 GAIA_NODISCARD
static util::str_view normalize_var_name(util::str_view name) {
1402 auto trimmed = util::trim(name);
1403 if (trimmed.empty())
1406 if (trimmed.data()[0] ==
'$') {
1407 if (trimmed.size() == 1)
1409 trimmed = util::str_view(trimmed.data() + 1, trimmed.size() - 1);
1412 return util::trim(trimmed);
1415 GAIA_NODISCARD
static bool is_reserved_var_name(util::str_view varName) {
1416 return varName ==
"this";
1419 GAIA_NODISCARD Entity find_var_by_name(util::str_view rawName) {
1420 const auto varName = normalize_var_name(rawName);
1421 if (varName.empty() || is_reserved_var_name(varName))
1425 const auto bit = (uint8_t(1) << i);
1426 if ((m_varNamesMask & bit) == 0)
1428 if (m_varNames[i] == varName)
1429 return query_var_entity(i);
1435 bool set_var_name_internal(Entity varEntity, util::str_view rawName) {
1436 if (!is_query_var_entity(varEntity))
1439 const auto varName = normalize_var_name(rawName);
1440 if (varName.empty() || is_reserved_var_name(varName))
1443 const auto idx = query_var_idx(varEntity);
1444 const auto bit = (uint8_t(1) << idx);
1450 const auto otherBit = (uint8_t(1) << i);
1451 if ((m_varNamesMask & otherBit) == 0)
1453 if (!(m_varNames[i] == varName))
1456 GAIA_ASSERT2(
false,
"Variable name is already assigned to a different query variable");
1460 m_varNames[idx].assign(varName);
1461 m_varNamesMask |= bit;
1465 template <
typename T>
1466 void add_cmd(T& cmd) {
1467 invalidate_each_walk_cache();
1470 if constexpr (T::InvalidatesHash) {
1471 reset_changed_filter_state();
1472 m_storage.invalidate();
1475 auto& serBuffer = m_storage.ser_buffer();
1476 ser::save(serBuffer, T::Id);
1477 ser::save(serBuffer, T::InvalidatesHash);
1478 ser::save(serBuffer, cmd);
1481 void add_inter(QueryInput item) {
1483 GAIA_ASSERT((item.op != QueryOpKind::Not && item.op != QueryOpKind::Any) || item.access == QueryAccess::None);
1485 QueryCmd_AddItem cmd{item};
1489 GAIA_NODISCARD
static QueryAccess normalize_access(QueryOpKind op, Entity entity, QueryAccess
access) {
1490 if (op == QueryOpKind::Not || op == QueryOpKind::Any || entity.pair())
1491 return QueryAccess::None;
1494 if (
access == QueryAccess::None)
1495 return QueryAccess::Read;
1500 void add_entity_term(QueryOpKind op, Entity entity,
const QueryTermOptions& options) {
1501 const auto access = normalize_access(op, entity, options.access);
1503 {op,
access, entity, options.entSrc, options.entTrav, options.travKind, options.travDepth,
1504 options.matchKind});
1507 template <
typename T>
1508 void add_inter(QueryOpKind op) {
1513 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1514 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1516 e = Pair(desc_rel.entity, desc_tgt.entity);
1519 const auto& desc = comp_cache_add<T>(*m_storage.world());
1524 QueryAccess
access = QueryAccess::None;
1525 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1526 constexpr auto isReadWrite = core::is_mut_v<T>;
1527 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1530 add_inter({op,
access, e});
1533 template <
typename T>
1534 void add_inter(QueryOpKind op,
const QueryTermOptions& options) {
1539 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.world());
1540 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.world());
1542 e = Pair(desc_rel.entity, desc_tgt.entity);
1545 const auto& desc = comp_cache_add<T>(*m_storage.world());
1549 QueryAccess
access = QueryAccess::None;
1550 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1551 if (options.access != QueryAccess::None)
1554 constexpr auto isReadWrite = core::is_mut_v<T>;
1555 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1560 {op, normalize_access(op, e,
access), e, options.entSrc, options.entTrav, options.travKind,
1561 options.travDepth, options.matchKind});
1564 template <
typename Rel,
typename Tgt>
1565 void add_inter(QueryOpKind op) {
1566 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1567 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1568 static_assert(core::is_raw_v<UO_Rel>,
"Use add() with raw types only");
1569 static_assert(core::is_raw_v<UO_Tgt>,
"Use add() with raw types only");
1572 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1573 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1576 QueryAccess
access = QueryAccess::None;
1577 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1578 constexpr auto isReadWrite = core::is_mut_v<UO_Rel> || core::is_mut_v<UO_Tgt>;
1579 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1582 add_inter({op,
access, {descRel.entity, descTgt.entity}});
1587 void changed_inter(Entity entity) {
1588 QueryCmd_AddFilter cmd{entity};
1592 template <
typename T>
1593 void changed_inter() {
1594 using UO =
typename component_type_t<T>::TypeOriginal;
1595 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1598 const auto& desc = comp_cache_add<T>(*m_storage.world());
1599 changed_inter(desc.entity);
1602 template <
typename Rel,
typename Tgt>
1603 void changed_inter() {
1604 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1605 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1606 static_assert(core::is_raw_v<UO_Rel>,
"Use changed() with raw types only");
1607 static_assert(core::is_raw_v<UO_Tgt>,
"Use changed() with raw types only");
1610 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1611 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1612 changed_inter({descRel.entity, descTgt.entity});
1617 void sort_by_inter(Entity entity, TSortByFunc func) {
1618 QueryCmd_SortBy cmd{entity, func};
1622 template <
typename T>
1623 void sort_by_inter(TSortByFunc func) {
1624 using UO =
typename component_type_t<T>::TypeOriginal;
1625 if constexpr (std::is_same_v<UO, Entity>) {
1626 sort_by_inter(EntityBad, func);
1628 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1631 const auto& desc = comp_cache_add<T>(*m_storage.world());
1633 sort_by_inter(desc.entity, func);
1637 template <
typename Rel,
typename Tgt>
1638 void sort_by_inter(TSortByFunc func) {
1639 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1640 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1641 static_assert(core::is_raw_v<UO_Rel>,
"Use group_by() with raw types only");
1642 static_assert(core::is_raw_v<UO_Tgt>,
"Use group_by() with raw types only");
1645 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1646 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1648 sort_by_inter({descRel.entity, descTgt.entity}, func);
1653 void group_by_inter(Entity entity, TGroupByFunc func,
bool orderGroups =
false) {
1658 template <
typename T>
1659 void group_by_inter(Entity entity, TGroupByFunc func) {
1660 using UO =
typename component_type_t<T>::TypeOriginal;
1661 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1663 group_by_inter(entity, func);
1666 template <
typename Rel,
typename Tgt>
1667 void group_by_inter(TGroupByFunc func) {
1668 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1669 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1670 static_assert(core::is_raw_v<UO_Rel>,
"Use group_by() with raw types only");
1671 static_assert(core::is_raw_v<UO_Tgt>,
"Use group_by() with raw types only");
1674 const auto& descRel = comp_cache_add<Rel>(*m_storage.world());
1675 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.world());
1677 group_by_inter({descRel.entity, descTgt.entity}, func);
1682 void group_dep_inter(Entity relation) {
1683 GAIA_ASSERT(!relation.pair());
1684 QueryCmd_GroupDep cmd{relation};
1688 template <
typename T>
1689 void group_dep_inter() {
1690 using UO =
typename component_type_t<T>::TypeOriginal;
1691 static_assert(core::is_raw_v<UO>,
"Use group_dep() with raw types only");
1693 const auto& desc = comp_cache_add<T>(*m_storage.world());
1694 group_dep_inter(desc.entity);
1699 void set_group_id_inter(GroupId groupId) {
1703 invalidate_each_walk_cache();
1704 m_groupIdSet = groupId;
1707 void set_group_id_inter(Entity groupId) {
1708 set_group_id_inter(groupId.id());
1711 template <
typename T>
1712 void set_group_id_inter() {
1713 using UO =
typename component_type_t<T>::TypeOriginal;
1714 static_assert(core::is_raw_v<UO>,
"Use group_id() with raw types only");
1717 const auto& desc = comp_cache_add<T>(*m_storage.world());
1718 set_group_id_inter(desc.entity);
1723 void commit(QueryCtx&
ctx) {
1724 GAIA_PROF_SCOPE(query::commit);
1726#if GAIA_ASSERT_ENABLED
1727 GAIA_ASSERT(m_storage.m_identity.handle.id() == QueryIdBad);
1730 auto& serBuffer = m_storage.ser_buffer();
1734 while (serBuffer.tell() < serBuffer.bytes()) {
1736 bool invalidatesHash =
false;
1737 ser::load(serBuffer,
id);
1738 ser::load(serBuffer, invalidatesHash);
1739 (void)invalidatesHash;
1740 CommandBufferRead[
id](serBuffer,
ctx);
1744 if (uses_query_cache_storage()) {
1745 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1746 normalize_cache_src_trav(
ctx);
1748 if (uses_shared_cache_layer()) {
1749 auto& ctxData =
ctx.data;
1750 if (ctxData.changedCnt > 1) {
1751 core::sort(ctxData.changed.data(), ctxData.changed.data() + ctxData.changedCnt, SortComponentCond{});
1756 m_storage.ser_buffer_reset();
1760 if (uses_shared_cache_layer())
1761 calc_lookup_hash(
ctx);
1764 void recommit(QueryCtx&
ctx) {
1765 GAIA_PROF_SCOPE(query::recommit);
1767 auto& serBuffer = m_storage.ser_buffer();
1771 while (serBuffer.tell() < serBuffer.bytes()) {
1773 bool invalidatesHash =
false;
1774 ser::load(serBuffer,
id);
1775 ser::load(serBuffer, invalidatesHash);
1777 GAIA_ASSERT(!invalidatesHash);
1778 if (invalidatesHash)
1780 CommandBufferRead[
id](serBuffer,
ctx);
1782 if (uses_query_cache_storage()) {
1783 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1784 normalize_cache_src_trav(
ctx);
1788 m_storage.ser_buffer_reset();
1800 const Chunk& chunk,
const QueryInfo& queryInfo, uint32_t changedWorldVersion,
1801 std::span<const uint8_t> compIndices) {
1802 GAIA_ASSERT(!chunk.
empty() &&
"match_filters called on an empty chunk");
1804 const auto queryVersion = changedWorldVersion;
1805 const auto& data = queryInfo.
ctx().
data;
1807 return match_filters(chunk, queryInfo, changedWorldVersion);
1811 if (changedFields.empty())
1814 const auto changedCnt = (uint32_t)changedFields.size();
1815 if (changedCnt == 1) {
1816 const auto fieldIdx = changedFields[0];
1817 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1818 if (compIdx == (uint8_t)0xFF)
1819 return match_filters(chunk, queryInfo, changedWorldVersion);
1820 if (chunk.
changed(queryVersion, compIdx))
1826 GAIA_FOR(changedCnt) {
1827 const auto fieldIdx = changedFields[i];
1828 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1829 if (compIdx == (uint8_t)0xFF)
1830 return match_filters(chunk, queryInfo, changedWorldVersion);
1831 if (chunk.
changed(queryVersion, compIdx))
1843 GAIA_NODISCARD
static bool
1845 GAIA_ASSERT(!chunk.
empty() &&
"match_filters called on an empty chunk");
1847 const auto queryVersion = changedWorldVersion;
1851 if (filtered.empty())
1854 const auto filteredCnt = (uint32_t)filtered.size();
1858 if (filteredCnt == 1) {
1859 const auto compIdx = core::get_index(ids, filtered[0]);
1860 if (compIdx != BadIndex && chunk.
changed(queryVersion, compIdx))
1867 uint32_t lastIdx = 0;
1868 for (
const auto comp: filtered) {
1869 uint32_t compIdx = BadIndex;
1870 if (lastIdx < (uint32_t)ids.size()) {
1871 const auto suffixIdx =
1872 core::get_index(std::span<const Entity>(ids.data() + lastIdx, ids.size() - lastIdx), comp);
1873 if (suffixIdx != BadIndex)
1874 compIdx = lastIdx + suffixIdx;
1879 if (compIdx == BadIndex)
1880 compIdx = core::get_index(ids, comp);
1881 if (compIdx == BadIndex)
1884 if (chunk.
changed(queryVersion, compIdx))
1916 const auto& data = queryInfo.
ctx().
data;
1917 return data.
groupByFunc == group_by_func_depth_order &&
1918 world_relation_depth_order_prunes_disabled_subtrees(*queryInfo.
world(), data.
groupBy);
1925 GAIA_NODISCARD
static bool
1938 Chunk* pChunk, Constraints constraints,
bool needsBarrierCache,
bool barrierPasses, uint16_t& from,
1939 uint16_t& to)
noexcept {
1940 if (needsBarrierCache && constraints == Constraints::DisabledOnly && !barrierPasses) {
1942 to = pChunk->size();
1946 from = detail::ChunkIterImpl::start_index(pChunk, constraints);
1947 to = detail::ChunkIterImpl::end_index(pChunk, constraints);
1966 GAIA_NODISCARD
static bool
1971 const auto& world = *queryInfo.
world();
1976 const auto pair = ids[idsIdx];
1977 const auto parent = world_pair_target_if_alive(world,
pair);
1978 if (parent == EntityBad)
1980 if (!world_entity_enabled_hierarchy(world, parent, relation))
1995 int8_t barrierPasses = -1)
const {
1998 if (constraints == Constraints::EnabledOnly) {
2000 if (barrierPasses >= 0)
2001 return barrierPasses != 0;
2021 world_defer_parallel_begin(*m_pWorld, itemCount);
2024 world_defer_parallel_end(*m_pWorld);
2042 explicit ParallelSlot(uint32_t idxStart): m_scope(idxStart) {}
2051 template <
typename TIter>
2052 static void finish_iter_writes(TIter& it) {
2053 if (it.chunk() ==
nullptr)
2056 auto compIndices = it.touched_comp_indices();
2057 for (
auto compIdx: compIndices)
2058 const_cast<
Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2060 auto terms = it.touched_terms();
2064 auto entities = it.entity_rows();
2065 auto& world = *it.
world();
2067 const auto term = terms[i];
2068 if (!world_component_uses_sparse_storage(world, term)) {
2069 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
2070 if (compIdx != BadIndex) {
2071 const_cast<Chunk*
>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2076 GAIA_FOR_(entities.size(), j) {
2077 world_finish_write(world, term, entities[j]);
2082 static void finish_typed_chunk_writes_runtime(
2083 World& world, Chunk* pChunk, uint16_t from, uint16_t to,
const Entity* pArgIds,
const bool* pWriteFlags,
2084 uint32_t argCnt, uint32_t firstWriteArg,
void*
const* pSparseStores =
nullptr);
2086 template <
typename... T>
2087 static void finish_typed_chunk_writes(World& world, Chunk* pChunk, uint16_t from, uint16_t to);
2089 static void finish_typed_iter_writes_runtime(
2090 Iter& it,
const Entity* pArgIds,
const bool* pWriteFlags, uint32_t argCnt, uint32_t firstWriteArg);
2199 template <
typename TMode>
2201 if constexpr (std::is_same_v<TMode, IterModeDisabledOnly>)
2202 return Constraints::DisabledOnly;
2203 else if constexpr (std::is_same_v<TMode, IterModeAcceptAll>)
2204 return Constraints::AcceptAll;
2206 return Constraints::EnabledOnly;
2218 template <
typename Func,
typename TMode>
2221 it.init_query_state(pWorld, iter_mode_constraints<TMode>(),
false);
2222 it.set_archetype(batch.pArchetype);
2223 it.set_chunk(batch.pChunk, batch.from, batch.to);
2224 it.set_group_id(batch.groupId);
2225 it.set_comp_indices(batch.pCompIndices);
2226 it.set_inherited_data(batch.inheritedData);
2228 finish_iter_writes(it);
2229 it.clear_touched_writes();
2240 template <
typename Func>
2243 it.init_query_state(pWorld, constraints,
false);
2244 it.set_archetype(batch.pArchetype);
2246 it.set_group_id(batch.groupId);
2247 it.set_comp_indices(batch.pCompIndices);
2248 it.set_inherited_data(batch.inheritedData);
2250 it.clear_touched_writes();
2260 template <
typename Func,
typename TMode>
2262 GAIA_PROF_SCOPE(query::run_query_func);
2264 const auto chunkCnt = batches.size();
2265 GAIA_ASSERT(chunkCnt > 0);
2268 it.init_query_state(pWorld, iter_mode_constraints<TMode>(),
false);
2270 const Archetype* pLastArchetype =
nullptr;
2271 const uint8_t* pLastIndices =
nullptr;
2272 InheritedTermDataView lastInheritedData{};
2273 GroupId lastGroupId = GroupIdMax;
2275 const auto apply_batch = [&](
const ChunkBatch& batch) {
2276 if (batch.pArchetype != pLastArchetype) {
2277 it.set_archetype(batch.pArchetype);
2278 pLastArchetype = batch.pArchetype;
2281 if (batch.pCompIndices != pLastIndices) {
2282 it.set_comp_indices(batch.pCompIndices);
2283 pLastIndices = batch.pCompIndices;
2286 if (batch.inheritedData.data() != lastInheritedData.data()) {
2287 it.set_inherited_data(batch.inheritedData);
2288 lastInheritedData = batch.inheritedData;
2291 if (batch.groupId != lastGroupId) {
2292 it.set_group_id(batch.groupId);
2293 lastGroupId = batch.groupId;
2296 it.set_chunk(batch.pChunk, batch.from, batch.to);
2298 finish_iter_writes(it);
2299 it.clear_touched_writes();
2303 if GAIA_UNLIKELY (chunkCnt == 1) {
2304 apply_batch(batches[0]);
2316 gaia::prefetch(batches[1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2317 apply_batch(batches[0]);
2319 uint32_t chunkIdx = 1;
2320 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
2321 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
2322 apply_batch(batches[chunkIdx]);
2325 apply_batch(batches[chunkIdx]);
2331 template <
typename Func,
typename TMode>
2332 struct QueryJobCtx {
2334 World* pWorld =
nullptr;
2338 GAIA_USE_SMALLBLOCK(QueryJobCtx)
2341 template <
typename Func>
2342 struct QueryTaskJobCtx {
2343 QueryImpl* pSelf =
nullptr;
2345 QueryExecType execType = QueryExecType::Default;
2347 GAIA_USE_SMALLBLOCK(QueryTaskJobCtx)
2352 template <
typename Func>
2353 struct IterJobCallback {
2355 QueryImpl* pSelf =
nullptr;
2361 void operator()(Iter& it) {
2362 it.ctx(pSelf->ctx());
2369 template <
typename Func>
2370 struct TypedJobCallback {
2372 QueryImpl* pSelf =
nullptr;
2378 void operator()(Iter& it) {
2379 pSelf->each_iter(it, func);
2383 template <
typename Func>
2384 static void invoke_query_task_job(
void* pCtx) {
2385 auto&
ctx = *
reinterpret_cast<QueryTaskJobCtx<Func>*
>(pCtx);
2386 ctx.pSelf->each(
ctx.func,
ctx.execType);
2389 template <
typename Func>
2390 static void cleanup_query_task_job(
void* pCtx) {
2391 auto* pJobCtx =
reinterpret_cast<QueryTaskJobCtx<Func>*
>(pCtx);
2392 if (pJobCtx ==
nullptr)
2397 template <
typename Func,
typename TMode>
2398 static void cleanup_query_job(
void* pCtx) {
2399 auto* pJobCtx =
reinterpret_cast<QueryJobCtx<Func, TMode>*
>(pCtx);
2400 if (pJobCtx ==
nullptr)
2403 auto* pWorld = pJobCtx->pWorld;
2404 if (pWorld !=
nullptr) {
2409 world_defer_parallel_end(*pWorld);
2410 commit_cmd_buffer_st(*pWorld);
2411 commit_cmd_buffer_mt(*pWorld);
2412 if (pJobCtx->pSelf !=
nullptr)
2413 pJobCtx->pSelf->m_changedWorldVersion = *pJobCtx->pSelf->m_worldVersion;
2419 template <
typename Func,
typename TMode, QueryExecType ExecType>
2420 GAIA_NODISCARD SchedJob add_parallel_query_job(Func func) {
2421 static_assert(ExecType != QueryExecType::Default);
2422 if (m_batches.
empty()) {
2423 m_changedWorldVersion = *m_worldVersion;
2427 auto* pWorld = m_storage.world();
2430 auto* pCtx =
new QueryJobCtx<Func, TMode>{
this, pWorld, {}, GAIA_MOV(func)};
2431 pCtx->batches.resize(m_batches.
size());
2432 GAIA_EACH(m_batches) pCtx->batches[i] = m_batches[i];
2435 SchedParDesc desc{};
2437 desc.itemCount = (uint32_t)pCtx->batches.
size();
2439 desc.execType = ExecType;
2440 desc.invoke = [](
void* pInvokeCtx, uint32_t idxStart, uint32_t idxEnd) {
2441 auto&
ctx = *
reinterpret_cast<QueryJobCtx<Func, TMode>*
>(pInvokeCtx);
2442 ParallelSlot slot(idxStart);
2443 run_query_func<Func, TMode>(
ctx.pWorld,
ctx.func, std::span(&
ctx.batches[idxStart], idxEnd - idxStart));
2448 world_defer_parallel_begin(*pWorld, desc.itemCount);
2450 return sched_add_par(world_sched(*pWorld), desc, pCtx, &cleanup_query_job<Func, TMode>);
2453 template <
bool HasFilters>
2455 collect_runtime_parallel_batches(
const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints) {
2456 auto cacheView = queryInfo.cache_archetype_view();
2457 const bool hasSortedPlanPayload =
2459 const auto sortView =
2460 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2463 if (needsBarrierCache)
2464 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2466 if (!sortView.empty()) {
2467 for (
const auto& view: sortView) {
2468 const auto* pArchetype = cacheView[view.archetypeIdx];
2469 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2473 const auto viewFrom = view.startRow;
2474 const auto viewTo = (uint16_t)(view.startRow + view.count);
2475 uint16_t minStartRow = 0;
2476 uint16_t minEndRow = 0;
2477 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2478 const auto startRow = core::get_max(minStartRow, viewFrom);
2479 const auto endRow = core::get_min(minEndRow, viewTo);
2480 if (endRow == startRow)
2483 if constexpr (HasFilters) {
2484 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2488 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2489 const auto inheritedDataView =
2490 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2492 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2497 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
2498 const auto* pArchetype = cacheView[i];
2499 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2503 auto indicesView = queryInfo.indices_mapping_view(i);
2504 const auto inheritedDataView =
2505 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2506 const auto& chunks = pArchetype->chunks();
2507 for (
auto* pChunk: chunks) {
2511 if GAIA_UNLIKELY (from == to)
2514 if constexpr (HasFilters) {
2515 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2519 m_batches.
push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2524 template <
typename Func>
2525 GAIA_NODISCARD SchedJob add_query_task_job(Func func, QueryExecType execType) {
2526 auto* pCtx =
new QueryTaskJobCtx<Func>{
this, GAIA_MOV(func), execType};
2528 SchedTaskDesc desc{};
2530 desc.invoke = &invoke_query_task_job<Func>;
2531 desc.execType = execType;
2533 return sched_add(world_sched(*m_storage.world()), desc, pCtx, &cleanup_query_task_job<Func>);
2536 template <
typename Func, QueryExecType ExecType>
2537 GAIA_NODISCARD SchedJob add_iter_parallel_job(Func func) {
2538 static_assert(ExecType != QueryExecType::Default);
2540 auto& queryInfo =
fetch();
2542 const auto constraints = Constraints::EnabledOnly;
2547 return add_query_task_job(GAIA_MOV(func), ExecType);
2549 const auto cacheRange = selected_query_cache_range(queryInfo);
2550 if (cacheRange.hasSelectedGroup)
2551 return add_query_task_job(GAIA_MOV(func), ExecType);
2553 ::gaia::ecs::update_version(*m_worldVersion);
2556 collect_runtime_parallel_batches<true>(queryInfo, plan, constraints);
2558 collect_runtime_parallel_batches<false>(queryInfo, plan, constraints);
2560 using JobFunc = IterJobCallback<Func>;
2561 return add_parallel_query_job<JobFunc, IterModeEnabled, ExecType>(JobFunc{
this, GAIA_MOV(func)});
2566 template <
bool HasFilters,
typename TMode,
typename Func>
2567 void run_query_batch_no_group_id(
2568 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2569 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
2571 auto cacheView = queryInfo.cache_archetype_view();
2572 constexpr auto constraints = iter_mode_constraints<TMode>();
2574 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2577 const bool hasSortedBatchPayload =
2579 const auto sortView =
2580 hasSortedBatchPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2581 if (needsBarrierCache)
2582 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2584 lock(*m_storage.world());
2588 ChunkBatchArray chunkBatches;
2590 if (!sortView.empty()) {
2591 for (
const auto& view: sortView) {
2592 auto* pArchetype =
const_cast<Archetype*
>(cacheView[view.archetypeIdx]);
2593 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2597 const auto viewFrom = view.startRow;
2598 const auto viewTo = (uint16_t)(view.startRow + view.count);
2599 uint16_t minStartRow = 0;
2600 uint16_t minEndRow = 0;
2601 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2602 const auto startRow = core::get_max(minStartRow, viewFrom);
2603 const auto endRow = core::get_min(minEndRow, viewTo);
2604 const auto totalRows = endRow - startRow;
2608 if constexpr (HasFilters) {
2609 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2613 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2614 const auto inheritedDataView =
2615 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2617 chunkBatches.push_back(
2618 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2620 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2621 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2622 chunkBatches.clear();
2626 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2627 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
2628 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2632 auto indicesView = queryInfo.indices_mapping_view(i);
2633 const auto inheritedDataView =
2634 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2635 const auto& chunks = pArchetype->chunks();
2636 uint32_t chunkOffset = 0;
2637 uint32_t itemsLeft = chunks.size();
2638 while (itemsLeft > 0) {
2639 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2640 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2642 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2643 for (
auto* pChunk: chunkSpan) {
2647 if GAIA_UNLIKELY (from == to)
2650 if constexpr (HasFilters) {
2651 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2655 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2658 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2659 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2660 chunkBatches.clear();
2663 itemsLeft -= batchSize;
2664 chunkOffset += batchSize;
2670 if (!chunkBatches.empty())
2671 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2673 unlock(*m_storage.world());
2676 commit_cmd_buffer_st(*m_storage.world());
2677 commit_cmd_buffer_mt(*m_storage.world());
2680 template <
bool HasFilters,
typename TMode,
typename Func, QueryExecType ExecType>
2681 void run_query_batch_no_group_id_par(
2682 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2683 static_assert(ExecType != QueryExecType::Default);
2684 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
2686 auto cacheView = queryInfo.cache_archetype_view();
2687 constexpr auto constraints = iter_mode_constraints<TMode>();
2689 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2692 const bool hasSortedBatchPayload =
2694 const auto sortView =
2695 hasSortedBatchPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2696 if (needsBarrierCache)
2697 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2699 if (!sortView.empty()) {
2700 for (
const auto& view: sortView) {
2701 const auto* pArchetype = cacheView[view.archetypeIdx];
2702 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2706 const auto viewFrom = view.startRow;
2707 const auto viewTo = (uint16_t)(view.startRow + view.count);
2708 uint16_t minStartRow = 0;
2709 uint16_t minEndRow = 0;
2710 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2711 const auto startRow = core::get_max(minStartRow, viewFrom);
2712 const auto endRow = core::get_min(minEndRow, viewTo);
2713 const auto totalRows = endRow - startRow;
2717 if constexpr (HasFilters) {
2718 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2722 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2723 const auto inheritedDataView =
2724 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2727 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2730 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2731 const auto* pArchetype = cacheView[i];
2732 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2736 auto indicesView = queryInfo.indices_mapping_view(i);
2737 const auto inheritedDataView =
2738 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2739 const auto& chunks = pArchetype->chunks();
2740 for (
auto* pChunk: chunks) {
2744 if GAIA_UNLIKELY (from == to)
2747 if constexpr (HasFilters) {
2748 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2752 m_batches.
push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2757 if (m_batches.
empty())
2760 lock(*m_storage.world());
2762 struct ParallelQueryBatchCtx {
2766 ParallelQueryBatchCtx
ctx{
this, &func};
2767 SchedParDesc desc{};
2769 desc.itemCount = (uint32_t)m_batches.
size();
2771 desc.execType = ExecType;
2772 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2773 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
2774 ParallelSlot slot(idxStart);
2775 run_query_func<Func, TMode>(
2776 ctx.pSelf->m_storage.world(), *
ctx.pFunc,
2777 std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2784 ParallelScope
scope(*m_storage.world(), desc.itemCount);
2786 const auto& sched = world_sched(*m_storage.world());
2787 const auto token = sched_par(sched, desc);
2788 sched_wait(sched, token);
2789 sched_del(sched, token);
2792 unlock(*m_storage.world());
2797 commit_cmd_buffer_st(*m_storage.world());
2798 commit_cmd_buffer_mt(*m_storage.world());
2801 template <
bool HasFilters,
typename TMode,
typename Func>
2802 void run_query_batch_with_group_id(
2803 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2804 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
2806 ChunkBatchArray chunkBatches;
2808 auto cacheView = queryInfo.cache_archetype_view();
2809 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2810 constexpr auto constraints = iter_mode_constraints<TMode>();
2814 if (needsBarrierCache)
2815 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2817 lock(*m_storage.world());
2819 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2820 const auto* pArchetype = cacheView[i];
2821 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2825 auto indicesView = queryInfo.indices_mapping_view(i);
2826 const auto inheritedDataView =
2827 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2828 const auto& chunks = pArchetype->chunks();
2829 const auto groupId = queryInfo.group_id(i);
2831#if GAIA_ASSERT_ENABLED
2834 m_groupIdSet == 0 ||
2836 groupId == m_groupIdSet);
2839 uint32_t chunkOffset = 0;
2840 uint32_t itemsLeft = chunks.size();
2841 while (itemsLeft > 0) {
2842 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2843 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2845 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2846 for (
auto* pChunk: chunkSpan) {
2850 if GAIA_UNLIKELY (from == to)
2853 if constexpr (HasFilters) {
2854 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2858 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
2861 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2862 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2863 chunkBatches.clear();
2866 itemsLeft -= batchSize;
2867 chunkOffset += batchSize;
2872 if (!chunkBatches.empty())
2873 run_query_func<Func, TMode>(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()});
2875 unlock(*m_storage.world());
2878 commit_cmd_buffer_st(*m_storage.world());
2879 commit_cmd_buffer_mt(*m_storage.world());
2882 template <
bool HasFilters,
typename TMode,
typename Func, QueryExecType ExecType>
2883 void run_query_batch_with_group_id_par(
2884 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2885 static_assert(ExecType != QueryExecType::Default);
2886 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
2888 ChunkBatchArray chunkBatch;
2890 auto cacheView = queryInfo.cache_archetype_view();
2891 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2892 constexpr auto constraints = iter_mode_constraints<TMode>();
2896 if (needsBarrierCache)
2897 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2899#if GAIA_ASSERT_ENABLED
2900 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2901 const auto* pArchetype = cacheView[i];
2902 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2906 const auto groupId = queryInfo.group_id(i);
2909 m_groupIdSet == 0 ||
2911 groupId == m_groupIdSet);
2915 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2916 const Archetype* pArchetype = cacheView[i];
2917 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2921 auto indicesView = queryInfo.indices_mapping_view(i);
2922 const auto inheritedDataView =
2923 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2924 const auto groupId = queryInfo.group_id(i);
2925 const auto& chunks = pArchetype->chunks();
2926 for (
auto* pChunk: chunks) {
2930 if GAIA_UNLIKELY (from == to)
2933 if constexpr (HasFilters) {
2934 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2938 m_batches.
push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
2942 if (m_batches.
empty())
2945 lock(*m_storage.world());
2947 struct ParallelQueryBatchCtx {
2951 ParallelQueryBatchCtx
ctx{
this, &func};
2952 SchedParDesc desc{};
2954 desc.itemCount = (uint32_t)m_batches.
size();
2956 desc.execType = ExecType;
2957 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2958 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
2959 ParallelSlot slot(idxStart);
2960 run_query_func<Func, TMode>(
2961 ctx.pSelf->m_storage.world(), *
ctx.pFunc,
2962 std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2966 ParallelScope
scope(*m_storage.world(), desc.itemCount);
2968 const auto& sched = world_sched(*m_storage.world());
2969 const auto token = sched_par(sched, desc);
2970 sched_wait(sched, token);
2971 sched_del(sched, token);
2974 unlock(*m_storage.world());
2979 commit_cmd_buffer_st(*m_storage.world());
2980 commit_cmd_buffer_mt(*m_storage.world());
2985 template <
bool HasFilters, QueryExecType ExecType,
typename TMode,
typename Func>
2986 void run_query(
const QueryInfo& queryInfo, Func func) {
2987 GAIA_PROF_SCOPE(query::run_query);
2994 auto cache_view = queryInfo.cache_archetype_view();
2995 if (cache_view.empty())
2998 const auto cacheRange = selected_query_cache_range(queryInfo);
2999 if (!cacheRange.hasSelectedGroup) {
3001 if constexpr (ExecType != QueryExecType::Default)
3002 run_query_batch_no_group_id_par<HasFilters, TMode, Func, ExecType>(
3003 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3005 run_query_batch_no_group_id<HasFilters, TMode, Func>(
3006 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3009 if (!cacheRange.valid)
3012 if constexpr (ExecType != QueryExecType::Default)
3013 run_query_batch_with_group_id_par<HasFilters, TMode, Func, ExecType>(
3014 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3016 run_query_batch_with_group_id<HasFilters, TMode, Func>(
3017 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
3023 template <QueryExecType ExecType,
typename Func>
3024 void run_query_on_archetypes(QueryInfo& queryInfo, Func func, Constraints constraints) {
3028 lock(*m_storage.world());
3031 GAIA_PROF_SCOPE(query::run_query_a);
3037 auto cache_view = queryInfo.cache_archetype_view();
3041 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
3042 if (needsBarrierCache)
3043 queryInfo.ensure_depth_order_hierarchy_barrier_cache();
3044 GAIA_EACH(cache_view) {
3045 const auto* pArchetype = cache_view[i];
3046 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3050 auto indicesView = queryInfo.indices_mapping_view(i);
3051 const auto inheritedDataView =
3052 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3053 ChunkBatch batch{pArchetype,
nullptr, indicesView.data(), inheritedDataView, 0, 0, 0};
3058 unlock(*m_storage.world());
3064 template <QueryExecType ExecType,
typename TMode,
typename Func>
3065 void run_query_on_chunks(QueryInfo& queryInfo, Func func) {
3067 ::gaia::ecs::update_version(*m_worldVersion);
3069 const bool hasFilters = queryInfo.has_filters();
3071 run_query<true, ExecType, TMode>(queryInfo, func);
3073 run_query<false, ExecType, TMode>(queryInfo, func);
3076 m_changedWorldVersion = *m_worldVersion;
3079 template <
typename Func>
3081 run_query_func_runtime(World* pWorld, Func func, std::span<ChunkBatch> batches, Constraints constraints) {
3082 GAIA_PROF_SCOPE(query::run_query_func);
3084 const auto chunkCnt = batches.size();
3085 GAIA_ASSERT(chunkCnt > 0);
3088 it.init_query_state(pWorld, constraints,
false);
3090 const Archetype* pLastArchetype =
nullptr;
3091 const uint8_t* pLastIndices =
nullptr;
3092 InheritedTermDataView lastInheritedData{};
3093 GroupId lastGroupId = GroupIdMax;
3095 const auto apply_batch = [&](
const ChunkBatch& batch) {
3096 if (batch.pArchetype != pLastArchetype) {
3097 it.set_archetype(batch.pArchetype);
3098 pLastArchetype = batch.pArchetype;
3101 if (batch.pCompIndices != pLastIndices) {
3102 it.set_comp_indices(batch.pCompIndices);
3103 pLastIndices = batch.pCompIndices;
3106 if (batch.inheritedData.data() != lastInheritedData.data()) {
3107 it.set_inherited_data(batch.inheritedData);
3108 lastInheritedData = batch.inheritedData;
3111 if (batch.groupId != lastGroupId) {
3112 it.set_group_id(batch.groupId);
3113 lastGroupId = batch.groupId;
3116 it.set_chunk(batch.pChunk, batch.from, batch.to);
3118 finish_iter_writes(it);
3119 it.clear_touched_writes();
3122 if GAIA_UNLIKELY (chunkCnt == 1) {
3123 apply_batch(batches[0]);
3127 gaia::prefetch(batches[1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3128 apply_batch(batches[0]);
3130 uint32_t chunkIdx = 1;
3131 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
3132 gaia::prefetch(batches[chunkIdx + 1].pChunk, PrefetchHint::PREFETCH_HINT_T2);
3133 apply_batch(batches[chunkIdx]);
3136 apply_batch(batches[chunkIdx]);
3139 template <
bool HasFilters,
typename Func>
3140 void run_query_batch_no_group_id_runtime(
3141 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3142 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
3144 auto cacheView = queryInfo.cache_archetype_view();
3145 const bool hasSortedPlanPayload =
3147 const auto sortView =
3148 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
3151 if (needsBarrierCache)
3152 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3154 lock(*m_storage.world());
3155 ChunkBatchArray chunkBatches;
3157 if (!sortView.empty()) {
3158 for (
const auto& view: sortView) {
3159 auto* pArchetype =
const_cast<Archetype*
>(cacheView[view.archetypeIdx]);
3160 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3164 const auto viewFrom = view.startRow;
3165 const auto viewTo = (uint16_t)(view.startRow + view.count);
3166 uint16_t minStartRow = 0;
3167 uint16_t minEndRow = 0;
3168 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3169 const auto startRow = core::get_max(minStartRow, viewFrom);
3170 const auto endRow = core::get_min(minEndRow, viewTo);
3171 const auto totalRows = endRow - startRow;
3175 if constexpr (HasFilters) {
3176 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3180 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3181 const auto inheritedDataView =
3182 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3184 chunkBatches.push_back(
3185 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
3187 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3188 run_query_func_runtime(
3189 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3190 chunkBatches.clear();
3194 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3195 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
3196 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3200 auto indicesView = queryInfo.indices_mapping_view(i);
3201 const auto inheritedDataView =
3202 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3203 const auto& chunks = pArchetype->chunks();
3204 uint32_t chunkOffset = 0;
3205 uint32_t itemsLeft = chunks.size();
3206 while (itemsLeft > 0) {
3207 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3208 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3210 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3211 for (
auto* pChunk: chunkSpan) {
3215 if GAIA_UNLIKELY (from == to)
3218 if constexpr (HasFilters) {
3219 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3223 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
3226 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3227 run_query_func_runtime(
3228 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3229 chunkBatches.clear();
3232 itemsLeft -= batchSize;
3233 chunkOffset += batchSize;
3238 if (!chunkBatches.empty())
3239 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3241 unlock(*m_storage.world());
3242 commit_cmd_buffer_st(*m_storage.world());
3243 commit_cmd_buffer_mt(*m_storage.world());
3246 template <
bool HasFilters,
typename Func, QueryExecType ExecType>
3247 void run_query_batch_no_group_id_runtime_par(
3248 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3249 static_assert(ExecType != QueryExecType::Default);
3250 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
3252 auto cacheView = queryInfo.cache_archetype_view();
3253 const bool hasSortedPlanPayload =
3255 const auto sortView =
3256 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
3259 if (needsBarrierCache)
3260 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3262 if (!sortView.empty()) {
3263 for (
const auto& view: sortView) {
3264 const auto* pArchetype = cacheView[view.archetypeIdx];
3265 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3269 const auto viewFrom = view.startRow;
3270 const auto viewTo = (uint16_t)(view.startRow + view.count);
3271 uint16_t minStartRow = 0;
3272 uint16_t minEndRow = 0;
3273 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3274 const auto startRow = core::get_max(minStartRow, viewFrom);
3275 const auto endRow = core::get_min(minEndRow, viewTo);
3276 const auto totalRows = endRow - startRow;
3280 if constexpr (HasFilters) {
3281 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3285 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3286 const auto inheritedDataView =
3287 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3290 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
3293 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3294 const auto* pArchetype = cacheView[i];
3295 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3299 auto indicesView = queryInfo.indices_mapping_view(i);
3300 const auto inheritedDataView =
3301 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3302 const auto& chunks = pArchetype->chunks();
3303 for (
auto* pChunk: chunks) {
3307 if GAIA_UNLIKELY (from == to)
3310 if constexpr (HasFilters) {
3311 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3315 m_batches.
push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
3320 if (m_batches.
empty())
3323 lock(*m_storage.world());
3325 struct ParallelQueryBatchCtx {
3328 Constraints constraints;
3330 ParallelQueryBatchCtx
ctx{
this, &func, constraints};
3331 SchedParDesc desc{};
3333 desc.itemCount = (uint32_t)m_batches.
size();
3335 desc.execType = ExecType;
3336 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3337 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
3338 ParallelSlot slot(idxStart);
3339 run_query_func_runtime(
3340 ctx.pSelf->m_storage.world(), *
ctx.pFunc, std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3345 ParallelScope
scope(*m_storage.world(), desc.itemCount);
3347 const auto& sched = world_sched(*m_storage.world());
3348 const auto token = sched_par(sched, desc);
3349 sched_wait(sched, token);
3350 sched_del(sched, token);
3353 unlock(*m_storage.world());
3356 commit_cmd_buffer_st(*m_storage.world());
3357 commit_cmd_buffer_mt(*m_storage.world());
3360 template <
bool HasFilters,
typename Func>
3361 void run_query_batch_with_group_id_runtime(
3362 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3363 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
3365 ChunkBatchArray chunkBatches;
3366 auto cacheView = queryInfo.cache_archetype_view();
3369 if (needsBarrierCache)
3370 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3372 lock(*m_storage.world());
3374 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3375 const auto* pArchetype = cacheView[i];
3376 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3380 auto indicesView = queryInfo.indices_mapping_view(i);
3381 const auto inheritedDataView =
3382 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3383 const auto& chunks = pArchetype->chunks();
3384 const auto groupId = queryInfo.group_id(i);
3386 uint32_t chunkOffset = 0;
3387 uint32_t itemsLeft = chunks.size();
3388 while (itemsLeft > 0) {
3389 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3390 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3392 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3393 for (
auto* pChunk: chunkSpan) {
3397 if GAIA_UNLIKELY (from == to)
3400 if constexpr (HasFilters) {
3401 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3405 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3408 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3409 run_query_func_runtime(
3410 m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3411 chunkBatches.clear();
3414 itemsLeft -= batchSize;
3415 chunkOffset += batchSize;
3419 if (!chunkBatches.empty())
3420 run_query_func_runtime(m_storage.world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3422 unlock(*m_storage.world());
3423 commit_cmd_buffer_st(*m_storage.world());
3424 commit_cmd_buffer_mt(*m_storage.world());
3427 template <
bool HasFilters,
typename Func, QueryExecType ExecType>
3428 void run_query_batch_with_group_id_runtime_par(
3429 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3430 static_assert(ExecType != QueryExecType::Default);
3431 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
3433 ChunkBatchArray chunkBatch;
3434 auto cacheView = queryInfo.cache_archetype_view();
3437 if (needsBarrierCache)
3438 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3440 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3441 const auto* pArchetype = cacheView[i];
3442 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3446 auto indicesView = queryInfo.indices_mapping_view(i);
3447 const auto inheritedDataView =
3448 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3449 const auto groupId = queryInfo.group_id(i);
3450 const auto& chunks = pArchetype->chunks();
3451 for (
auto* pChunk: chunks) {
3455 if GAIA_UNLIKELY (from == to)
3458 if constexpr (HasFilters) {
3459 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3463 m_batches.
push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3467 if (m_batches.
empty())
3470 lock(*m_storage.world());
3472 struct ParallelQueryBatchCtx {
3475 Constraints constraints;
3477 ParallelQueryBatchCtx
ctx{
this, &func, constraints};
3478 SchedParDesc desc{};
3480 desc.itemCount = (uint32_t)m_batches.
size();
3482 desc.execType = ExecType;
3483 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3484 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
3485 ParallelSlot slot(idxStart);
3486 run_query_func_runtime(
3487 ctx.pSelf->m_storage.world(), *
ctx.pFunc, std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3492 ParallelScope
scope(*m_storage.world(), desc.itemCount);
3494 const auto& sched = world_sched(*m_storage.world());
3495 const auto token = sched_par(sched, desc);
3496 sched_wait(sched, token);
3497 sched_del(sched, token);
3500 unlock(*m_storage.world());
3503 commit_cmd_buffer_st(*m_storage.world());
3504 commit_cmd_buffer_mt(*m_storage.world());
3507 template <
bool HasFilters, QueryExecType ExecType,
typename Func>
3508 void run_query_runtime_planned(
3509 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3510 GAIA_PROF_SCOPE(query::run_query);
3514 const auto cacheRange = selected_query_cache_range(queryInfo);
3515 if (!cacheRange.hasSelectedGroup) {
3516 if constexpr (ExecType != QueryExecType::Default)
3517 run_query_batch_no_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3519 run_query_batch_no_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3521 if constexpr (ExecType != QueryExecType::Default)
3522 run_query_batch_with_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3524 run_query_batch_with_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3535 template <QueryExecType ExecType,
typename Func>
3536 void run_query_on_chunks_runtime_planned(
3537 QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3541 ::gaia::ecs::update_version(*m_worldVersion);
3543 run_query_runtime_planned<true, ExecType>(queryInfo, plan, constraints, func);
3545 run_query_runtime_planned<false, ExecType>(queryInfo, plan, constraints, func);
3547 m_changedWorldVersion = *m_worldVersion;
3554 GAIA_NODISCARD
bool can_use_direct_chunk_iteration_fastpath(
const QueryInfo& queryInfo)
const {
3555 const auto& data = queryInfo.ctx().data;
3563 GAIA_NODISCARD QueryCacheRange selected_query_cache_range(
const QueryInfo& queryInfo)
const {
3564 QueryCacheRange range{};
3565 range.idxTo = (uint32_t)queryInfo.cache_archetype_view().size();
3567 const auto& data = queryInfo.ctx().data;
3568 if (data.
groupBy == EntityBad || m_groupIdSet == 0)
3571 range.hasSelectedGroup =
true;
3572 const auto* pGroupData = queryInfo.selected_group_data(m_groupIdSet);
3573 if (pGroupData ==
nullptr) {
3576 range.valid =
false;
3580 range.idxFrom = pGroupData->idxFirst;
3581 range.idxTo = pGroupData->idxLast + 1;
3589 GAIA_NODISCARD QueryPlan
prepare_query_plan(
const QueryInfo& queryInfo,
const TypedQueryExecState& state)
const;
3591 template <
bool HasFilters,
typename Func,
typename... T>
3592 void run_query_on_chunks_direct_typed(
3593 QueryInfo& queryInfo,
const QueryPlan& plan,
const TypedQueryExecState& state, Func& func,
3594 core::func_type_list<T...>);
3596 template <
bool HasFilters,
typename Func,
typename... T>
3597 void run_query_on_chunks_sparse_typed(
3598 QueryInfo& queryInfo,
const QueryPlan& plan,
const TypedQueryExecState& state, Func& func,
3599 core::func_type_list<T...>);
3601 template <
typename Func,
typename... T>
3602 void run_query_on_sparse_entities_typed(
3603 QueryInfo& queryInfo,
const TypedQueryExecState& state, Func& func, core::func_type_list<T...>);
3605 void run_query_on_chunks_direct(
3606 QueryInfo& queryInfo,
const QueryPlan& plan,
const TypedQueryExecState& state,
void* pFunc,
3607 void (*runChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&));
3609 void run_query_on_chunks_direct_iter(
3610 QueryInfo& queryInfo,
const QueryPlan& plan,
const TypedQueryExecState& state,
void* pFunc,
3611 void (*runChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&));
3613 struct TypedQueryErasedOps {
3614 void (*runSparsePlan)(QueryImpl&, QueryInfo&,
const QueryPlan&,
void*,
const TypedQueryExecState&) =
nullptr;
3615 void (*runDirectFastChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&) =
nullptr;
3616 void (*runDirectChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&) =
nullptr;
3617 void (*runMappedChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&) =
nullptr;
3618 void (*invokeInherited)(World&, Entity,
const Entity*,
void*) =
nullptr;
3619 bool needsInheritedArgIds =
false;
3622 template <QueryExecType ExecType>
3624 QueryInfo& queryInfo,
const QueryPlan& plan,
void* pFunc,
const TypedQueryExecState& state,
3625 const TypedQueryErasedOps& ops);
3627 void each_typed_erased(
3628 QueryExecType execType,
void* pFunc,
const TypedQueryExecState& state,
const TypedQueryErasedOps& ops);
3630 template <QueryExecType ExecType,
typename Func>
3631 void each_typed_inter(QueryInfo& queryInfo, Func func);
3633 template <QueryExecType ExecType>
3634 void each_iter_inter_erased(
3635 QueryInfo& queryInfo,
const QueryPlan& plan,
void* pFunc,
const TypedQueryExecState& state,
3636 void (*runDirectFastChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&),
3637 void (*runMappedChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&));
3639 void each_walk_inter(
3640 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints,
void* pFunc,
3641 const TypedQueryExecState& state,
3642 void (*runChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&));
3651 const auto cacheRange = selected_query_cache_range(queryInfo);
3652 plan.idxFrom = cacheRange.idxFrom;
3653 plan.idxTo = cacheRange.idxTo;
3657 bool hasConstrainedDepthOrderBarrier = constraints != Constraints::AcceptAll && hasDepthOrderBarrier;
3666 if (hasSortedPayload || hasDepthOrderBarrier)
3670 if (cacheRange.hasSelectedGroup) {
3673 if (!cacheRange.valid) {
3684 if (plan.idxFrom >= plan.idxTo) {
3690 hasConstrainedDepthOrderBarrier =
false;
3694 if (hasConstrainedDepthOrderBarrier)
3697 if (hasSortedPayload) {
3713 if (hasConstrainedDepthOrderBarrier)
3715 if (!can_use_direct_chunk_iteration_fastpath(queryInfo))
3722 if (!can_use_direct_chunk_iteration_fastpath(queryInfo))
3737 template <
bool HasFilters,
bool HasGroups,
typename Func>
3740 ::gaia::ecs::update_version(*m_worldVersion);
3743 lock(*m_storage.world());
3746 it.init_query_state(queryInfo.
world(), constraints,
false);
3750 const bool canSkipProcessCheck =
3754 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
3755 if (canSkipProcessCheck) {
3756 if GAIA_UNLIKELY (pArchetype->is_req_del())
3762 const auto* pIndices = indicesView.data();
3763 const auto groupId = HasGroups ? queryInfo.
group_id(i) : GroupId(0);
3764 const auto& chunks = pArchetype->chunks();
3765 for (
auto* pChunk: chunks) {
3766 const auto from = detail::ChunkIterImpl::start_index(pChunk, constraints);
3767 const auto to = detail::ChunkIterImpl::end_index(pChunk, constraints);
3768 if GAIA_UNLIKELY (from == to)
3770 if constexpr (HasFilters) {
3771 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion, indicesView))
3775 it.set_query_chunk(pArchetype, pIndices, pChunk, from, to);
3776 if constexpr (HasGroups)
3777 it.set_group_id(groupId);
3780 GAIA_PROF_SCOPE(query_func);
3783 finish_iter_writes(it);
3784 it.clear_touched_writes();
3788 unlock(*m_storage.world());
3789 commit_cmd_buffer_st(*m_storage.world());
3790 commit_cmd_buffer_mt(*m_storage.world());
3791 m_changedWorldVersion = *m_worldVersion;
3799 template <QueryExecType ExecType,
typename Func>
3801 if constexpr (ExecType == QueryExecType::Default) {
3802 auto& queryInfo =
fetch();
3809 run_query_on_chunks_runtime_direct_plain_impl<true, true>(queryInfo, plan, constraints, func);
3811 run_query_on_chunks_runtime_direct_plain_impl<true, false>(queryInfo, plan, constraints, func);
3814 run_query_on_chunks_runtime_direct_plain_impl<false, true>(queryInfo, plan, constraints, func);
3816 run_query_on_chunks_runtime_direct_plain_impl<false, false>(queryInfo, plan, constraints, func);
3822 queryInfo, plan, ExecType,
static_cast<void*
>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
3826 each_runtime_erased(ExecType,
static_cast<void*
>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
3834 template <
typename Func,
typename TIter>
3836 auto& func = *
static_cast<Func*
>(pFunc);
3841 struct RuntimeIterCallback {
3844 void (*invoke)(
void*,
Iter&);
3846 void operator()(
Iter& it)
const {
3847 GAIA_PROF_SCOPE(query_func);
3853 struct TypedDirectChunkCallback {
3856 const TypedQueryExecState* pState;
3857 void (*runChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&);
3859 void operator()(Iter& it)
const {
3860 GAIA_PROF_SCOPE(query_func);
3861 it.ctx(pSelf->ctx());
3862 runChunk(*pSelf, it, pFunc, *pState);
3866 struct TypedMappedChunkCallback {
3868 const QueryInfo* pQueryInfo;
3870 const TypedQueryExecState* pState;
3871 void (*runChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&);
3873 void operator()(Iter& it)
const {
3874 GAIA_PROF_SCOPE(query_func);
3875 it.ctx(pSelf->ctx());
3876 runChunk(*pSelf, *pQueryInfo, it, pFunc, *pState);
3880 struct TypedIterErasedCallback {
3883 const TypedQueryExecState* pState;
3884 void (*runDirect)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&);
3885 void (*runChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&);
3887 void operator()(Iter& it)
const {
3888 GAIA_PROF_SCOPE(query_func);
3889 it.ctx(pSelf->ctx());
3890 pSelf->each_iter_erased(it, pFunc, *pState, runDirect, runChunk);
3901 QueryExecType execType,
void* pFunc,
void (*invoke)(
void*,
Iter&), Constraints constraints) {
3902 auto& queryInfo =
fetch();
3917 void (*invoke)(
void*,
Iter&), Constraints constraints) {
3918 RuntimeIterCallback cb{pFunc, m_ctx, invoke};
3921 each_direct_iter_inter(queryInfo, constraints, cb);
3926 case QueryExecType::Parallel:
3927 run_query_on_chunks_runtime_planned<QueryExecType::Parallel>(queryInfo, plan, constraints, cb);
3929 case QueryExecType::ParallelPerf:
3930 run_query_on_chunks_runtime_planned<QueryExecType::ParallelPerf>(queryInfo, plan, constraints, cb);
3932 case QueryExecType::ParallelEff:
3933 run_query_on_chunks_runtime_planned<QueryExecType::ParallelEff>(queryInfo, plan, constraints, cb);
3936 run_query_on_chunks_runtime_planned<QueryExecType::Default>(queryInfo, plan, constraints, cb);
3948 if (term.
src != EntityBad || term.
entTrav != EntityBad || term_has_variables(term))
3951 const auto id = term.
id;
3952 return (
id.
pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world,
id))) ||
3953 (!
id.pair() && world_component_is_non_fragmenting(world,
id));
3960 const auto id = term.
id;
3961 return term.
matchKind == QueryMatchKind::Semantic && term.
src == EntityBad && term.
entTrav == EntityBad &&
3962 !term_has_variables(term) &&
id.pair() &&
id.id() == Is.
id() && !is_wildcard(
id.
gen()) &&
3963 !is_variable((EntityId)
id.
gen());
3970 const auto id = term.
id;
3971 return term.
matchKind == QueryMatchKind::In && term.
src == EntityBad && term.
entTrav == EntityBad &&
3972 !term_has_variables(term) &&
id.pair() &&
id.id() == Is.
id() && !is_wildcard(
id.
gen()) &&
3973 !is_variable((EntityId)
id.
gen());
3988 return query_term_uses_potential_inherited_id_matching(term);
4001 GAIA_NODISCARD
static bool match_entity_term(
const World& world,
Entity entity,
const QueryTerm& term) {
4003 return world_has_entity_term(world, entity, term.
id);
4005 return world_has_entity_term_in(world, entity, term.
id);
4007 return world_has_entity_term_direct(world, entity, term.
id);
4011 GAIA_NODISCARD
static bool match_single_direct_target_term(
4016 return world_has_entity_term(world, entity, termId);
4018 return world_has_entity_term_in(world, entity, termId);
4020 return world_has_entity_term_direct(world, entity, termId);
4028 GAIA_NODISCARD
static uint32_t count_direct_term_entities(
const World& world,
const QueryTerm& term) {
4030 return world_count_direct_term_entities(world, term.id);
4032 return world_count_in_term_entities(world, term.id);
4034 return world_count_direct_term_entities_direct(world, term.id);
4037 static void collect_direct_term_entities(
const World& world,
const QueryTerm& term, cnt::darray<Entity>& out) {
4039 world_collect_direct_term_entities(world, term.id, out);
4043 world_collect_in_term_entities(world, term.id, out);
4047 world_collect_direct_term_entities_direct(world, term.id, out);
4050 template <
typename Func>
4051 GAIA_NODISCARD
static bool for_each_direct_term_entity(
const World& world,
const QueryTerm& term, Func&& func) {
4054 static bool thunk(
void* ctx, Entity entity) {
4055 return static_cast<Visitor*
>(ctx)->func(entity);
4059 Visitor visitor{func};
4061 return world_for_each_direct_term_entity(world, term.id, &visitor, &Visitor::thunk);
4063 return world_for_each_in_term_entity(world, term.id, &visitor, &Visitor::thunk);
4065 return world_for_each_direct_term_entity_direct(world, term.id, &visitor, &Visitor::thunk);
4069 GAIA_NODISCARD
static bool can_use_direct_entity_seed_eval(
const QueryInfo& queryInfo) {
4070 if (!queryInfo.can_direct_entity_seed_eval_shape())
4073 const auto& world = *queryInfo.world();
4074 bool hasSeedableTerm =
false;
4075 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4076 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Or)
4080 hasSeedableTerm =
true;
4083 return hasSeedableTerm;
4087 GAIA_NODISCARD
static bool can_use_direct_target_eval(
const QueryInfo& queryInfo) {
4088 return queryInfo.can_direct_target_eval();
4092 GAIA_NODISCARD
static bool has_only_direct_or_terms(
const QueryInfo& queryInfo) {
4093 return queryInfo.has_only_direct_or_terms();
4097 add_chunk_run(cnt::darray<detail::BfsChunkRun>& runs,
const EntityContainer& ec, uint32_t entityOffset) {
4099 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
4103 auto& run = runs.back();
4104 if (ec.pChunk == run.pChunk && ec.row == run.to) {
4105 run.to = (uint16_t)(run.to + 1);
4109 runs.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), entityOffset});
4112 struct DirectEntitySeedInfo {
4113 Entity seededAllTerm = EntityBad;
4114 QueryMatchKind seededAllMatchKind = QueryMatchKind::Semantic;
4115 bool seededFromAll =
false;
4116 bool seededFromOr =
false;
4120 struct DirectEntitySeedPlan {
4121 Entity bestAllTerm = EntityBad;
4122 uint32_t bestAllTermCount = UINT32_MAX;
4123 QueryMatchKind bestAllTermMatchKind = QueryMatchKind::Semantic;
4124 bool hasAllTerms =
false;
4125 bool hasOrTerms =
false;
4126 bool preferOrSeed =
false;
4129 struct DirectEntitySeedEvalPlan {
4132 const QueryTerm* pSingleAllTerm =
nullptr;
4134 bool alwaysMatch =
false;
4138 GAIA_NODISCARD
static bool should_prefer_direct_seed_term(
4139 const World& world,
const QueryTerm& candidate, uint32_t candidateCount,
const DirectEntitySeedPlan& plan) {
4141 const bool bestIsSemanticIs = plan.bestAllTermMatchKind != QueryMatchKind::Direct &&
4142 plan.bestAllTerm.pair() && plan.bestAllTerm.id() == Is.
id() &&
4143 !is_wildcard(plan.bestAllTerm.gen()) &&
4144 !is_variable((EntityId)plan.bestAllTerm.gen());
4145 const auto adjustedCandidateCount = candidateCount - (candidateIsSemanticIs && candidateCount > 0 ? 1U : 0U);
4146 const auto adjustedBestCount =
4147 plan.bestAllTermCount - (bestIsSemanticIs && plan.bestAllTermCount > 0 ? 1U : 0U);
4148 if (adjustedCandidateCount < adjustedBestCount)
4150 if (adjustedCandidateCount > adjustedBestCount)
4152 if (plan.bestAllTerm == EntityBad)
4155 if (candidateIsSemanticIs != bestIsSemanticIs)
4156 return candidateIsSemanticIs;
4159 const bool bestUsesInherited = plan.bestAllTermMatchKind == QueryMatchKind::Semantic &&
4160 !is_wildcard(plan.bestAllTerm) &&
4161 !is_variable((EntityId)plan.bestAllTerm.id()) &&
4162 (!plan.bestAllTerm.pair() || !is_variable((EntityId)plan.bestAllTerm.gen())) &&
4163 world_term_uses_inherit_policy(world, plan.bestAllTerm);
4164 if (candidateUsesInherited != bestUsesInherited)
4165 return !candidateUsesInherited;
4170 GAIA_NODISCARD
static DirectEntitySeedPlan
4171 direct_entity_seed_plan(
const World& world,
const QueryInfo& queryInfo) {
4172 DirectEntitySeedPlan plan;
4173 uint32_t totalOrTermCount = 0;
4175 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4176 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4178 if (term.op == QueryOpKind::All) {
4179 plan.hasAllTerms =
true;
4180 const auto cnt = count_direct_term_entities(world, term);
4181 if (should_prefer_direct_seed_term(world, term, cnt, plan)) {
4182 plan.bestAllTermCount = cnt;
4183 plan.bestAllTerm = term.id;
4184 plan.bestAllTermMatchKind = term.matchKind;
4186 }
else if (term.op == QueryOpKind::Or) {
4187 plan.hasOrTerms =
true;
4188 totalOrTermCount += count_direct_term_entities(world, term);
4192 plan.preferOrSeed = plan.hasOrTerms && (!plan.hasAllTerms || totalOrTermCount < plan.bestAllTermCount);
4197 GAIA_NODISCARD
static bool match_direct_entity_terms(
4198 const World& world, Entity entity,
const QueryInfo& queryInfo,
const DirectEntitySeedInfo& seedInfo) {
4199 bool hasOrTerms =
false;
4200 bool anyOrMatched =
false;
4202 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4203 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4205 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4206 term.matchKind == seedInfo.seededAllMatchKind)
4208 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4211 const bool present = match_entity_term(world, entity, term);
4213 case QueryOpKind::All:
4217 case QueryOpKind::Or:
4219 anyOrMatched |= present;
4221 case QueryOpKind::Not:
4225 case QueryOpKind::Any:
4226 case QueryOpKind::Count:
4231 return !hasOrTerms || anyOrMatched;
4234 GAIA_NODISCARD
static const QueryTerm*
4235 find_direct_all_seed_term(
const QueryInfo& queryInfo,
const DirectEntitySeedPlan& plan) {
4236 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4237 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4239 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4240 term.matchKind != plan.bestAllTermMatchKind)
4248 GAIA_NODISCARD
static DirectEntitySeedEvalPlan
4249 direct_all_seed_eval_plan(
const QueryInfo& queryInfo,
const DirectEntitySeedInfo& seedInfo) {
4250 DirectEntitySeedEvalPlan plan{};
4252 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4253 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4255 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4256 term.matchKind == seedInfo.seededAllMatchKind)
4259 if (term.op == QueryOpKind::All) {
4260 if (plan.pSingleAllTerm !=
nullptr)
4262 plan.pSingleAllTerm = &term;
4269 plan.alwaysMatch = plan.pSingleAllTerm ==
nullptr;
4278 GAIA_NODISCARD
static bool
4279 can_use_direct_seed_run_cache(
const World& world,
const QueryInfo& queryInfo,
const QueryTerm& seedTerm) {
4283 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4284 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4286 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count || term.op == QueryOpKind::Or)
4288 if (term.op == QueryOpKind::All && term.id == seedTerm.id && term.matchKind == seedTerm.matchKind)
4297 GAIA_NODISCARD std::span<const detail::BfsChunkRun> cached_direct_seed_runs(
4298 QueryInfo& queryInfo,
const QueryTerm& seedTerm,
const DirectEntitySeedInfo& seedInfo,
4299 Constraints constraints) {
4300 auto& runData = ensure_direct_seed_run_data();
4301 auto& world = *queryInfo.world();
4302 const auto cachedConstraints = constraints;
4303 const auto relVersion = world_rel_version(world, Is);
4304 const auto worldVersion = ::gaia::ecs::world_version(world);
4306 if (runData.cacheValid && runData.cachedSeedTerm == seedTerm.id &&
4307 runData.cachedSeedMatchKind == seedTerm.matchKind && runData.cachedConstraints == cachedConstraints &&
4308 runData.cachedRelVersion == relVersion && runData.cachedWorldVersion == worldVersion) {
4309 return {runData.cachedRuns.data(), runData.cachedRuns.size()};
4312 auto& runs = runData.cachedRuns;
4313 auto& entities = runData.cachedEntities;
4314 auto& chunkOrderedEntities = runData.cachedChunkOrderedEntities;
4317 chunkOrderedEntities.clear();
4319 (void)for_each_direct_term_entity(world, seedTerm, [&](Entity entity) {
4320 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4323 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4326 entities.push_back(entity);
4330 chunkOrderedEntities = entities;
4331 core::sort(chunkOrderedEntities, [&](Entity left, Entity right) {
4332 const auto& ecLeft = ::gaia::ecs::fetch(world, left);
4333 const auto& ecRight = ::gaia::ecs::fetch(world, right);
4334 if (ecLeft.pArchetype != ecRight.pArchetype)
4335 return ecLeft.pArchetype->id() < ecRight.pArchetype->id();
4336 if (ecLeft.pChunk != ecRight.pChunk)
4337 return ecLeft.pChunk < ecRight.pChunk;
4338 return ecLeft.row < ecRight.row;
4341 uint32_t entityOffset = 0;
4342 for (
const auto entity: chunkOrderedEntities) {
4343 const auto& ec = ::gaia::ecs::fetch(world, entity);
4344 add_chunk_run(runs, ec, entityOffset++);
4347 runData.cachedSeedTerm = seedTerm.id;
4348 runData.cachedSeedMatchKind = seedTerm.matchKind;
4349 runData.cachedConstraints = cachedConstraints;
4350 runData.cachedRelVersion = relVersion;
4351 runData.cachedWorldVersion = worldVersion;
4352 runData.cacheValid =
true;
4353 return {runs.data(), runs.size()};
4356 GAIA_NODISCARD std::span<const Entity> cached_direct_seed_chunk_entities(
4357 QueryInfo& queryInfo,
const QueryTerm& seedTerm,
const DirectEntitySeedInfo& seedInfo,
4358 Constraints constraints) {
4359 (void)cached_direct_seed_runs(queryInfo, seedTerm, seedInfo, constraints);
4360 auto& runData = ensure_direct_seed_run_data();
4361 return {runData.cachedChunkOrderedEntities.data(), runData.cachedChunkOrderedEntities.size()};
4364 template <
typename Func>
4365 GAIA_NODISCARD
static bool for_each_direct_all_seed(
4366 const World& world,
const QueryInfo& queryInfo,
const DirectEntitySeedPlan& plan, Constraints constraints,
4368 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
4369 GAIA_ASSERT(pSeedTerm !=
nullptr);
4370 if (pSeedTerm ==
nullptr)
4373 DirectEntitySeedInfo seedInfo{};
4374 seedInfo.seededAllTerm = pSeedTerm->id;
4375 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
4376 seedInfo.seededFromAll =
true;
4377 const auto evalPlan = direct_all_seed_eval_plan(queryInfo, seedInfo);
4378 const Archetype* pLastSingleAllArchetype =
nullptr;
4379 bool lastSingleAllMatch =
false;
4380 bool seedImpliesSingleAllTerm =
false;
4384 const auto seedTarget = pair_tgt(world, pSeedTerm->id);
4385 if (seedTarget != EntityBad)
4386 seedImpliesSingleAllTerm = match_entity_term(world, seedTarget, *evalPlan.pSingleAllTerm);
4391 return for_each_direct_term_entity(world, *pSeedTerm, [&](Entity entity) {
4392 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4395 if (evalPlan.alwaysMatch)
4396 return func(entity);
4397 if (evalPlan.pSingleAllTerm !=
nullptr) {
4398 if (seedImpliesSingleAllTerm)
4399 return func(entity);
4400 if (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4401 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm)) {
4402 const auto* pArchetype = world_entity_archetype(world, entity);
4403 if (pArchetype != pLastSingleAllArchetype) {
4404 lastSingleAllMatch = match_entity_term(world, entity, *evalPlan.pSingleAllTerm);
4405 pLastSingleAllArchetype = pArchetype;
4407 if (!lastSingleAllMatch)
4409 }
else if (!match_entity_term(world, entity, *evalPlan.pSingleAllTerm)) {
4412 return func(entity);
4414 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4417 return func(entity);
4422 GAIA_NODISCARD
static bool match_direct_entity_constraints(
4423 const World& world,
const QueryInfo& queryInfo, Entity entity, Constraints constraints) {
4424 if (!queryInfo.matches_prefab_entities() && world_entity_prefab(world, entity))
4427 if (constraints == Constraints::EnabledOnly)
4428 return world_entity_enabled(world, entity);
4429 if (constraints == Constraints::DisabledOnly)
4430 return !world_entity_enabled(world, entity);
4435 GAIA_NODISCARD
static bool can_use_archetype_bucket_count(
4436 const World& world,
const QueryInfo& queryInfo,
const DirectEntitySeedInfo& seedInfo) {
4437 if (!seedInfo.seededFromAll && !seedInfo.seededFromOr)
4440 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4441 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4443 if (seedInfo.seededFromAll && term.id == seedInfo.seededAllTerm && term.op == QueryOpKind::All)
4445 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4447 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Not)
4459 GAIA_NODISCARD
static uint32_t count_direct_entity_seed_by_archetype(
4460 const World& world,
const QueryInfo& queryInfo,
const cnt::darray<Entity>& seedEntities,
4461 const DirectEntitySeedInfo& seedInfo, Constraints constraints) {
4462 auto& scratch = direct_query_scratch();
4464 scratch.archetypes.clear();
4465 scratch.bucketEntities.clear();
4466 scratch.counts.clear();
4468 for (
const auto entity: seedEntities) {
4469 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4472 const auto* pArchetype = world_entity_archetype(world, entity);
4473 const auto idx = core::get_index(scratch.archetypes, pArchetype);
4474 if (idx == BadIndex) {
4475 scratch.archetypes.push_back(pArchetype);
4476 scratch.bucketEntities.push_back(entity);
4477 scratch.counts.push_back(1);
4479 ++scratch.counts[idx];
4484 const auto archetypeCnt = (uint32_t)scratch.archetypes.size();
4485 GAIA_FOR(archetypeCnt) {
4486 if (match_direct_entity_terms(world, scratch.bucketEntities[i], queryInfo, seedInfo))
4487 cnt += scratch.counts[i];
4494 GAIA_NODISCARD
static uint32_t
4495 count_direct_or_union(
const World& world,
const QueryInfo& queryInfo, Constraints constraints) {
4496 auto& scratch = direct_query_scratch();
4497 const auto seenVersion = next_direct_query_seen_version(scratch);
4498 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4501 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4502 if (term.op != QueryOpKind::Or)
4505 (void)for_each_direct_term_entity(world, term, [&](Entity entity) {
4506 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4509 const auto entityId = (uint32_t)entity.id();
4510 ensure_direct_query_count_capacity(scratch, entityId);
4512 if (scratch.counts[entityId] == seenVersion)
4514 scratch.counts[entityId] = seenVersion;
4516 bool rejected =
false;
4517 if (hasDirectNotTerms) {
4518 for (
const auto& notTerm: queryInfo.
ctx().data.terms_view()) {
4519 if (notTerm.op != QueryOpKind::Not)
4521 if (match_entity_term(world, entity, notTerm)) {
4542 GAIA_NODISCARD
static bool
4543 is_empty_direct_or_union(
const World& world,
const QueryInfo& queryInfo, Constraints constraints) {
4544 auto& scratch = direct_query_scratch();
4545 const auto seenVersion = next_direct_query_seen_version(scratch);
4546 const bool hasDirectNotTerms = has_direct_not_terms(queryInfo);
4548 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4549 if (term.op != QueryOpKind::Or)
4552 const bool completed = for_each_direct_term_entity(world, term, [&](Entity entity) {
4553 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4556 const auto entityId = (uint32_t)entity.id();
4557 ensure_direct_query_count_capacity(scratch, entityId);
4559 if (scratch.counts[entityId] == seenVersion)
4561 scratch.counts[entityId] = seenVersion;
4563 bool rejected =
false;
4564 if (hasDirectNotTerms) {
4565 for (
const auto& notTerm: queryInfo.
ctx().data.terms_view()) {
4566 if (notTerm.op != QueryOpKind::Not)
4568 if (match_entity_term(world, entity, notTerm)) {
4588 static DirectEntitySeedInfo
4589 build_direct_entity_seed(
const World& world,
const QueryInfo& queryInfo, cnt::darray<Entity>& out) {
4590 auto& scratch = direct_query_scratch();
4592 DirectEntitySeedInfo seedInfo{};
4593 const auto plan = direct_entity_seed_plan(world, queryInfo);
4595 if (plan.hasAllTerms && !plan.preferOrSeed) {
4596 if (plan.bestAllTerm != EntityBad) {
4597 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4598 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4600 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4601 term.matchKind != plan.bestAllTermMatchKind)
4603 collect_direct_term_entities(world, term, out);
4604 seedInfo.seededAllMatchKind = term.matchKind;
4607 seedInfo.seededFromAll =
true;
4608 seedInfo.seededAllTerm = plan.bestAllTerm;
4613 const auto seenVersion = next_direct_query_seen_version(scratch);
4615 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4616 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4618 if (term.op != QueryOpKind::Or)
4621 scratch.termEntities.clear();
4622 collect_direct_term_entities(world, term, scratch.termEntities);
4623 for (
const auto entity: scratch.termEntities) {
4624 const auto entityId = (uint32_t)entity.id();
4625 ensure_direct_query_count_capacity(scratch, entityId);
4627 if (scratch.counts[entityId] == seenVersion)
4629 scratch.counts[entityId] = seenVersion;
4630 out.push_back(entity);
4634 seedInfo.seededFromOr =
true;
4641 GAIA_NODISCARD
static bool has_direct_not_terms(
const QueryInfo& queryInfo) {
4642 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4643 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4645 if (term.op == QueryOpKind::Not)
4658 template <
typename Func>
4659 void for_each_direct_or_union(World& world,
const QueryInfo& queryInfo, Constraints constraints, Func&& func) {
4660 auto& scratch = direct_query_scratch();
4661 const auto seenVersion = next_direct_query_seen_version(scratch);
4662 DirectEntitySeedInfo seedInfo{};
4663 seedInfo.seededFromOr =
true;
4665 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4666 if (term.op != QueryOpKind::Or)
4669 (void)for_each_direct_term_entity(world, term, [&](Entity entity) {
4670 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
4673 const auto entityId = (uint32_t)entity.id();
4674 ensure_direct_query_count_capacity(scratch, entityId);
4676 if (scratch.counts[entityId] == seenVersion)
4678 scratch.counts[entityId] = seenVersion;
4680 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4695 template <
bool UseFilters>
4696 GAIA_NODISCARD
bool empty_inter(
const QueryInfo& queryInfo, Constraints constraints)
const {
4697 const auto cacheRange = selected_query_cache_range(queryInfo);
4699 if constexpr (!UseFilters) {
4700 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
4701 if (has_only_direct_or_terms(queryInfo))
4702 return is_empty_direct_or_union(*queryInfo.world(), queryInfo, constraints);
4704 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
4706 (void)for_each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
4714 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4715 const auto cacheView = queryInfo.cache_archetype_view();
4717 if (needsBarrierCache)
4718 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4719 if (!cacheRange.valid)
4721 const auto idxFrom = cacheRange.idxFrom;
4722 const auto idxTo = cacheRange.idxTo;
4724 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4725 const auto* pArchetype = cacheView[qi];
4726 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
4730 GAIA_PROF_SCOPE(query::empty);
4732 const auto& chunks = pArchetype->chunks();
4734 it.init_query_state(queryInfo.world(), constraints,
false);
4735 it.set_archetype(pArchetype);
4737 if (!hasEntityFilters) {
4738 for (
auto* pChunk: chunks) {
4744 it.set_chunk(pChunk, from, to);
4745 if constexpr (UseFilters) {
4746 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4754 const bool isNotEmpty = core::has_if(chunks, [&](Chunk* pChunk) {
4760 it.set_chunk(pChunk, from, to);
4761 if constexpr (UseFilters)
4762 if (it.size() == 0 || !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4764 if (!hasEntityFilters)
4765 return it.size() > 0;
4767 const auto entities = it.template view<Entity>();
4768 const auto cnt = it.size();
4770 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
4784 GAIA_NODISCARD
static bool match_entity_filters(
const World& world, Entity entity,
const QueryInfo& queryInfo) {
4785 bool hasOrTerms =
false;
4786 bool anyOrMatched =
false;
4787 const bool hasEntityFilterTerms = queryInfo.has_entity_filter_terms();
4789 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4790 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4793 const auto id = term.id;
4796 const bool isNonFragmentingTerm =
4797 (
id.pair() && world_relation_uses_non_fragmenting_storage(world, pair_rel(world,
id))) ||
4798 (!
id.pair() && world_component_is_non_fragmenting(world,
id));
4799 const bool needsEntityFilter = isNonFragmentingTerm || isDirectIsTerm || isInheritedTerm ||
4800 (hasEntityFilterTerms && term.op == QueryOpKind::Or);
4801 if (!needsEntityFilter)
4804 const bool present = match_entity_term(world, entity, term);
4806 case QueryOpKind::All:
4810 case QueryOpKind::Or:
4812 anyOrMatched |= present;
4814 case QueryOpKind::Not:
4818 case QueryOpKind::Any:
4819 case QueryOpKind::Count:
4824 return !hasOrTerms || anyOrMatched;
4833 matches_target_entities(QueryInfo& queryInfo,
const Archetype& archetype, EntitySpan targetEntities) {
4834 if (targetEntities.empty())
4837 const auto& world = *queryInfo.world();
4839 if (can_use_direct_target_eval(queryInfo)) {
4840 const auto directTargetEvalKind = queryInfo.direct_target_eval_kind();
4842 const auto termId = queryInfo.direct_target_eval_id();
4843 if (targetEntities.size() == 1) {
4844 const auto entity = targetEntities[0];
4845 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4847 return match_single_direct_target_term(world, entity, termId, directTargetEvalKind);
4850 for (
const auto entity: targetEntities) {
4851 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4853 if (match_single_direct_target_term(world, entity, termId, directTargetEvalKind))
4860 const DirectEntitySeedInfo seedInfo{};
4861 if (targetEntities.size() == 1) {
4862 const auto entity = targetEntities[0];
4863 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4865 return match_direct_entity_terms(world, entity, queryInfo, seedInfo);
4868 for (
const auto entity: targetEntities) {
4869 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4871 if (match_direct_entity_terms(world, entity, queryInfo, seedInfo))
4878 if (!
match_one(queryInfo, archetype, targetEntities))
4881 if (!queryInfo.has_entity_filter_terms())
4884 for (
const auto entity: targetEntities) {
4885 if (!match_direct_entity_constraints(world, queryInfo, entity, Constraints::EnabledOnly))
4887 if (match_entity_filters(world, entity, queryInfo))
4900 template <
bool UseFilters>
4901 GAIA_NODISCARD uint32_t count_inter(
const QueryInfo& queryInfo, Constraints constraints)
const {
4902 const auto cacheRange = selected_query_cache_range(queryInfo);
4904 if constexpr (!UseFilters) {
4905 if (!cacheRange.hasSelectedGroup && can_use_direct_entity_seed_eval(queryInfo)) {
4906 auto& scratch = direct_query_scratch();
4907 if (has_only_direct_or_terms(queryInfo))
4908 return count_direct_or_union(*queryInfo.world(), queryInfo, constraints);
4910 const auto plan = direct_entity_seed_plan(*queryInfo.world(), queryInfo);
4911 const auto seedInfo = build_direct_entity_seed(*queryInfo.world(), queryInfo, scratch.entities);
4913 if (can_use_archetype_bucket_count(*queryInfo.world(), queryInfo, seedInfo))
4914 return count_direct_entity_seed_by_archetype(
4915 *queryInfo.world(), queryInfo, scratch.entities, seedInfo, constraints);
4918 (void)for_each_direct_all_seed(*queryInfo.world(), queryInfo, plan, constraints, [&](Entity) {
4928 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
4929 const auto cacheView = queryInfo.cache_archetype_view();
4931 if (needsBarrierCache)
4932 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4934 if (!cacheRange.valid)
4936 const auto idxFrom = cacheRange.idxFrom;
4937 const auto idxTo = cacheRange.idxTo;
4939 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4940 const auto* pArchetype = cacheView[qi];
4941 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(qi);
4945 GAIA_PROF_SCOPE(query::count);
4947 const auto& chunks = pArchetype->chunks();
4949 it.init_query_state(queryInfo.world(), constraints,
false);
4950 it.set_archetype(pArchetype);
4952 if (!hasEntityFilters) {
4953 for (
auto* pChunk: chunks) {
4957 const uint16_t entityCnt = to - from;
4960 it.set_chunk(pChunk, from, to);
4962 if constexpr (UseFilters) {
4963 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4971 for (
auto* pChunk: chunks) {
4975 const uint16_t entityCnt = to - from;
4978 it.set_chunk(pChunk, from, to);
4981 if constexpr (UseFilters) {
4982 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4986 if (hasEntityFilters) {
4987 const auto entities = it.template view<Entity>();
4988 GAIA_FOR(entityCnt) {
4989 if (match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
5003 static void init_direct_entity_iter(
5004 const QueryInfo& queryInfo,
const World& world,
const EntityContainer& ec, Iter& it, uint8_t* pIndices,
5005 Entity* pTermIds,
const Archetype*& pLastArchetype) {
5006 GAIA_ASSERT(ec.pArchetype !=
nullptr);
5007 GAIA_ASSERT(ec.pChunk !=
nullptr);
5008 GAIA_ASSERT(ec.row < ec.pChunk->size());
5010 if (ec.pArchetype != pLastArchetype) {
5011 GAIA_FOR(ChunkHeader::MAX_COMPONENTS) {
5013 pTermIds[i] = EntityBad;
5016 const auto terms = queryInfo.ctx().data.terms_view();
5017 const auto queryIdCnt = (uint32_t)terms.size();
5018 auto indicesView = queryInfo.try_indices_mapping_view(ec.pArchetype);
5019 GAIA_FOR(queryIdCnt) {
5020 const auto& term = terms[i];
5021 const auto fieldIdx = term.fieldIndex;
5022 const auto queryId = term.id;
5023 pTermIds[fieldIdx] = queryId;
5024 if (!indicesView.empty()) {
5025 pIndices[fieldIdx] = indicesView[fieldIdx];
5028 if (!query_term_maps_to_current_archetype(term))
5031 if (!queryId.pair() && world_component_uses_sparse_storage(world, queryId)) {
5032#if GAIA_ASSERT_ENABLED
5033 const auto compIdx = core::get_index_unsafe(ec.pArchetype->ids_view(), queryId);
5034 GAIA_ASSERT(compIdx != BadIndex);
5036 pIndices[fieldIdx] = 0xFF;
5040 auto compIdx = world_component_index_comp_idx(world, *ec.pArchetype, queryId);
5041 if (compIdx == BadIndex)
5042 compIdx = core::get_index(ec.pArchetype->ids_view(), queryId);
5043 pIndices[fieldIdx] = (uint8_t)compIdx;
5046 it.set_archetype(ec.pArchetype);
5047 it.set_comp_indices(pIndices);
5048 const auto inheritedDataView = queryInfo.inherited_data_view(ec.pArchetype);
5049 it.set_inherited_data(inheritedDataView);
5050 it.set_term_ids(pTermIds);
5051 pLastArchetype = ec.pArchetype;
5054 it.set_chunk(ec.pChunk, ec.row, (uint16_t)(ec.row + 1));
5058 static void init_direct_entity_iter(
5059 const QueryInfo& queryInfo,
const World& world, Entity entity, Iter& it, uint8_t* pIndices,
5061 const auto& ec = ::gaia::ecs::fetch(world, entity);
5062 const Archetype* pLastArchetype =
nullptr;
5063 it.set_world(&world);
5064 init_direct_entity_iter(queryInfo, world, ec, it, pIndices, pTermIds, pLastArchetype);
5067 template <
typename Func>
5068 void each_chunk_runs_iter(
5069 QueryInfo& queryInfo, std::span<const detail::BfsChunkRun> runs, Constraints constraints, Func func) {
5070 auto& world = *queryInfo.world();
5072 it.init_query_state(&world, constraints,
false);
5073 const Archetype* pLastArchetype =
nullptr;
5074 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5075 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5077 for (
const auto& run: runs) {
5078 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
5079 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5080 it.set_chunk(run.pChunk, run.from, run.to);
5084 finish_iter_writes(it);
5085 it.clear_touched_writes();
5089 struct DirectChunkArgEvalDesc {
5090 Entity
id = EntityBad;
5091 bool isEntity =
false;
5092 bool isPair =
false;
5093 bool usesSparseStorage =
false;
5101 GAIA_NODISCARD
static bool can_use_direct_chunk_term_eval_arg(
5102 World& world,
const QueryInfo& queryInfo,
const DirectChunkArgEvalDesc& desc) {
5107 if (world_component_uses_sparse_storage(world, desc.id))
5110 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
5111 if (term.id != desc.id)
5113 if (!query_term_maps_to_current_archetype(term))
5124 GAIA_NODISCARD
static bool can_use_direct_chunk_term_eval_descs(
5125 World& world,
const QueryInfo& queryInfo,
const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5126 if (queryInfo.has_entity_filter_terms())
5130 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, pDescs[i]))
5143 GAIA_NODISCARD
static bool can_use_sparse_chunk_term_eval_descs(
5144 World& world,
const QueryInfo& queryInfo,
const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5145 if (queryInfo.has_entity_filter_terms())
5149 const auto& desc = pDescs[i];
5150 if (!desc.usesSparseStorage) {
5151 if (!can_use_direct_chunk_term_eval_arg(world, queryInfo, desc))
5156 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
5157 if (term.id != desc.id)
5178 template <
typename Func>
5179 void each_direct_entities_iter(
5180 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, Func func) {
5181 auto& world = *queryInfo.world();
5182 auto& walkData = ensure_each_walk_data();
5184 it.init_query_state(&world, constraints,
false);
5185 if (!walkData.cachedRuns.empty()) {
5186 each_chunk_runs_iter(queryInfo, walkData.cachedRuns, constraints, func);
5190 const Archetype* pLastArchetype =
nullptr;
5191 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5192 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5193 for (
const auto entity: entities) {
5194 const auto& ec = ::gaia::ecs::fetch(world, entity);
5195 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5198 finish_iter_writes(it);
5199 it.clear_touched_writes();
5209 template <
typename Func>
5210 void each_direct_iter_inter(QueryInfo& queryInfo, Constraints constraints, Func func) {
5211 auto& world = *queryInfo.world();
5212 const bool hasWriteTerms = queryInfo.ctx().data.readWriteMask != 0;
5213 const auto plan = direct_entity_seed_plan(world, queryInfo);
5215 auto exec_entity = [&](Entity entity) {
5216 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
5217 Entity termIds[ChunkHeader::MAX_COMPONENTS];
5219 it.set_constraints(constraints);
5220 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
5221 it.set_write_im(
false);
5224 finish_iter_writes(it);
5227 if (hasWriteTerms) {
5228 auto& scratch = direct_query_scratch();
5231 const auto seedInfo = build_direct_entity_seed(world, queryInfo, scratch.entities);
5232 for (
const auto entity: scratch.entities) {
5233 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
5235 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
5237 exec_entity(entity);
5242 if (!plan.preferOrSeed) {
5243 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
5244 if (pSeedTerm !=
nullptr && can_use_direct_seed_run_cache(world, queryInfo, *pSeedTerm)) {
5245 DirectEntitySeedInfo seedInfo{};
5246 seedInfo.seededAllTerm = pSeedTerm->id;
5247 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
5248 seedInfo.seededFromAll =
true;
5249 each_chunk_runs_iter(
5250 queryInfo, cached_direct_seed_runs(queryInfo, *pSeedTerm, seedInfo, constraints), constraints, func);
5255 if (plan.preferOrSeed) {
5256 for_each_direct_or_union(world, queryInfo, constraints, exec_entity);
5260 (void)for_each_direct_all_seed(world, queryInfo, plan, constraints, [&](Entity entity) {
5261 exec_entity(entity);
5278 QueryInfo& queryInfo, Constraints constraints,
void* pFunc,
const TypedQueryExecState& state,
5279 void (*runDirectChunk)(
QueryImpl&,
Iter&,
void*,
const TypedQueryExecState&),
bool needsInheritedArgIds,
5283 template <
bool UseFilters,
typename ContainerOut>
5284 void arr_inter(
QueryInfo& queryInfo, ContainerOut& outArray, Constraints constraints);
5300 World& world, QueryCache& queryCache, ArchetypeId& nextArchetypeId, uint32_t& worldVersion,
5301 const EntityToArchetypeMap& entityToArchetypeMap,
5302 const EntityToArchetypeVersionMap& entityToArchetypeMapVersions,
const ArchetypeDArray& allArchetypes):
5303 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion),
5304 m_entityToArchetypeMap(&entityToArchetypeMap),
5305 m_entityToArchetypeMapVersions(&entityToArchetypeMapVersions), m_allArchetypes(&allArchetypes) {
5306 m_storage.init(&world, &queryCache);
5309#if GAIA_ECS_TEST_HOOKS
5310 template <
typename Func>
5311 GAIA_NODISCARD QueryPlan test_typed_plan(Func func);
5316 GAIA_NODISCARD QueryPlan test_iter_plan(Constraints constraints = Constraints::EnabledOnly);
5321 GAIA_NODISCARD QueryId
id()
const {
5322 if (!uses_query_cache_storage())
5324 return m_storage.m_identity.handle.id();
5329 GAIA_NODISCARD uint32_t
gen()
const {
5330 if (!uses_query_cache_storage())
5332 return m_storage.m_identity.handle.gen();
5340 m_eachWalkData.reset();
5341 m_directSeedRunData.reset();
5342 reset_changed_filter_state();
5343 invalidate_each_walk_cache();
5344 invalidate_direct_seed_run_cache();
5349 (void)m_storage.try_del_from_cache();
5350 m_eachWalkData.reset();
5351 m_directSeedRunData.reset();
5352 reset_changed_filter_state();
5353 invalidate_each_walk_cache();
5354 invalidate_direct_seed_run_cache();
5360 return uses_query_cache_storage() && m_storage.is_cached();
5407 GAIA_ASSERT(str !=
nullptr);
5412 va_start(args, str);
5416 uint32_t parentDepth = 0;
5419 uint32_t varNamesCnt = 0;
5420 auto is_this_expr = [](std::span<const char> exprRaw) {
5421 auto expr = util::trim(exprRaw);
5422 return expr.size() == 5 && expr[0] ==
'$' && expr[1] ==
't' && expr[2] ==
'h' && expr[3] ==
'i' &&
5426 auto find_or_alloc_var = [&](std::span<const char> varExpr) ->
Entity {
5427 auto varNameSpan = util::trim(varExpr);
5428 if (varNameSpan.empty())
5432 if (is_reserved_var_name(varName)) {
5433 GAIA_ASSERT2(
false,
"$this is reserved and can only be used as a source expression: Id($this)");
5437 const auto namedVar = find_var_by_name(varName);
5438 if (namedVar != EntityBad)
5441 GAIA_FOR(varNamesCnt) {
5442 if (varNames[i].size() != varName.size())
5444 if (varNames[i].size() > 0 && memcmp(varNames[i].data(), varName.data(), varName.size()) != 0)
5446 return query_var_entity(i);
5449 if (varNamesCnt >= varNames.size()) {
5450 GAIA_ASSERT2(
false,
"Too many query variables in expression");
5454 const auto idx = varNamesCnt++;
5455 varNames[idx] = varName;
5457 const auto varEntity = query_var_entity(idx);
5458 (void)set_var_name_internal(varEntity, varName);
5462 auto parse_entity_expr = [&](
auto&& self, std::span<const char> exprRaw) ->
Entity {
5463 auto expr = util::trim(exprRaw);
5468 return find_or_alloc_var(expr.subspan(1));
5470 if (expr[0] ==
'(') {
5471 if (expr.back() !=
')') {
5472 GAIA_ASSERT2(
false,
"Expression '(' not terminated");
5476 const auto idStr = expr.subspan(1, expr.size() - 2);
5477 const auto commaIdx = core::get_index(idStr,
',');
5478 if (commaIdx == BadIndex) {
5479 GAIA_ASSERT2(
false,
"Pair expression does not contain ','");
5483 const auto first = self(self, idStr.subspan(0, commaIdx));
5484 if (first == EntityBad)
5486 const auto second = self(self, idStr.subspan(commaIdx + 1));
5487 if (second == EntityBad)
5493 return expr_to_entity((
const World&)*m_storage.world(), args, expr);
5496 auto parse_src_expr = [&](std::span<const char> srcExprRaw,
Entity& srcOut) ->
bool {
5497 auto srcExpr = util::trim(srcExprRaw);
5498 if (srcExpr.empty())
5502 if (is_this_expr(srcExpr)) {
5507 srcOut = parse_entity_expr(parse_entity_expr, srcExpr);
5508 return srcOut != EntityBad;
5512 auto expr = util::trim(exprRaw);
5516 if (expr.back() ==
')') {
5518 int32_t openIdx = -1;
5519 for (int32_t i = (int32_t)expr.size() - 1; i >= 0; --i) {
5520 if (expr[(uint32_t)i] ==
')')
5522 else if (expr[(uint32_t)i] ==
'(') {
5533 auto idExpr = util::trim(expr.subspan(0, (uint32_t)openIdx));
5534 auto srcExpr = util::trim(expr.subspan((uint32_t)openIdx + 1, expr.size() - (uint32_t)openIdx - 2));
5535 if (!idExpr.empty() && !srcExpr.empty()) {
5536 id = parse_entity_expr(parse_entity_expr, idExpr);
5537 if (
id == EntityBad)
5541 if (!parse_src_expr(srcExpr, src))
5550 id = parse_entity_expr(parse_entity_expr, expr);
5551 return id != EntityBad;
5554 auto add_term = [&](QueryOpKind op, std::span<const char> exprRaw) {
5555 auto expr = util::trim(exprRaw);
5559 bool isReadWrite =
false;
5560 if (!expr.empty() && expr[0] ==
'&') {
5562 expr = util::trim(expr.subspan(1));
5569 Entity entity = EntityBad;
5570 if (!parse_term_expr(expr, entity, options))
5574 case QueryOpKind::All:
5575 all(entity, options);
5577 case QueryOpKind::Or:
5578 or_(entity, options);
5580 case QueryOpKind::Not:
5581 no(entity, options);
5583 case QueryOpKind::Any:
5584 any(entity, options);
5594 auto process = [&]() {
5595 std::span<const char> exprRaw(&str[exp0], pos - exp0);
5598 auto expr = util::trim(exprRaw);
5603 bool hasOrChain =
false;
5606 const auto cnt = (uint32_t)expr.size();
5607 for (uint32_t i = 0; i + 1 < cnt; ++i) {
5608 const auto ch = expr[i];
5611 else if (ch ==
')') {
5612 GAIA_ASSERT(depth > 0);
5614 }
else if (depth == 0 && ch ==
'|' && expr[i + 1] ==
'|') {
5623 uint32_t partBeg = 0;
5624 const auto cnt = (uint32_t)expr.size();
5625 for (uint32_t i = 0; i < cnt; ++i) {
5626 const auto ch = expr[i];
5629 else if (ch ==
')') {
5630 GAIA_ASSERT(depth > 0);
5634 const bool isOr = i + 1 < cnt && depth == 0 && ch ==
'|' && expr[i + 1] ==
'|';
5635 const bool isEnd = i + 1 == cnt;
5636 if (!isOr && !isEnd)
5639 const auto partEnd = isOr ? i : (i + 1);
5640 auto partExpr = expr.subspan(partBeg, partEnd - partBeg);
5641 if (!add_term(QueryOpKind::Or, partExpr))
5653 QueryOpKind op = QueryOpKind::All;
5654 if (expr[0] ==
'?') {
5655 op = QueryOpKind::Any;
5656 expr = util::trim(expr.subspan(1));
5657 }
else if (expr[0] ==
'!') {
5658 op = QueryOpKind::Not;
5659 expr = util::trim(expr.subspan(1));
5662 return add_term(op, expr);
5665 for (; str[pos] != 0; ++pos) {
5666 if (str[pos] ==
'(')
5668 else if (str[pos] ==
')') {
5669 GAIA_ASSERT(parentDepth > 0);
5671 }
else if (str[pos] ==
',' && parentDepth == 0) {
5699 return all(
Pair(Is, entity), options);
5710 return all(
Pair(Is, entity), options);
5720 add_entity_term(QueryOpKind::All, entity, options);
5728 template <
typename T>
5734 template <
typename T>
5744 add_entity_term(QueryOpKind::Any, entity, options);
5752 template <
typename T>
5758 template <
typename T>
5769 add_entity_term(QueryOpKind::Or, entity, options);
5777 template <
typename T>
5783 template <
typename T>
5793 add_entity_term(QueryOpKind::Not, entity, options);
5801 template <
typename T>
5807 template <
typename T>
5817 [[maybe_unused]]
const bool ok = set_var_name_internal(varEntity, name);
5826 GAIA_ASSERT(name !=
nullptr);
5827 if (name ==
nullptr)
5838 const bool ok = is_query_var_entity(varEntity);
5843 const auto idx = query_var_idx(varEntity);
5844 m_varBindings[idx] = value;
5845 m_varBindingsMask |= (uint8_t(1) << idx);
5853 const auto varEntity = find_var_by_name(name);
5854 GAIA_ASSERT(varEntity != EntityBad);
5855 if (varEntity == EntityBad)
5857 return set_var(varEntity, value);
5864 GAIA_ASSERT(name !=
nullptr);
5865 if (name ==
nullptr)
5875 const bool ok = is_query_var_entity(varEntity);
5880 const auto idx = query_var_idx(varEntity);
5881 m_varBindingsMask &= (uint8_t)~(uint8_t(1) << idx);
5887 m_varBindingsMask = 0;
5897 changed_inter(entity);
5904 template <
typename T>
5916 sort_by_inter(entity, func);
5925 template <
typename T>
5934 template <
typename Rel,
typename Tgt>
5942 Entity m_relation = EntityBad;
5943 TravOrder m_order = TravOrder::Down;
5951 m_query(&query), m_relation(relation), m_order(order) {}
5956 template <
typename Func>
5958 m_query->each_walk(func, m_relation, m_order);
5987 template <
typename Rel>
6000 GAIA_ASSERT(!relation.pair());
6001 GAIA_ASSERT(world_relation_supports_depth_order(*m_storage.world(), relation));
6002 group_by_inter(relation, group_by_func_depth_order,
true);
6009 template <
typename Rel>
6021 group_by_inter(entity, func);
6031 template <
typename T>
6041 template <
typename Rel,
typename Tgt>
6051 group_dep_inter(relation);
6059 template <
typename Rel>
6068 set_group_id_inter(groupId);
6076 GAIA_ASSERT(!entity.
pair());
6077 set_group_id_inter(entity.
id());
6084 template <
typename T>
6092 template <
typename Container>
6093 void groups(Container& out,
bool sortGroups) {
6094 auto& queryInfo =
fetch();
6114 template <
typename Func>
6116 if constexpr (detail::is_query_iter_callback_v<Func>) {
6118 case QueryExecType::Parallel:
6119 return add_iter_parallel_job<Func, QueryExecType::Parallel>(GAIA_MOV(func));
6120 case QueryExecType::ParallelPerf:
6121 return add_iter_parallel_job<Func, QueryExecType::ParallelPerf>(GAIA_MOV(func));
6122 case QueryExecType::ParallelEff:
6123 return add_iter_parallel_job<Func, QueryExecType::ParallelEff>(GAIA_MOV(func));
6129 case QueryExecType::Parallel:
6130 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::Parallel>(
6131 TypedJobCallback<Func>{
this, GAIA_MOV(func)});
6132 case QueryExecType::ParallelPerf:
6133 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelPerf>(
6134 TypedJobCallback<Func>{
this, GAIA_MOV(func)});
6135 case QueryExecType::ParallelEff:
6136 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelEff>(
6137 TypedJobCallback<Func>{
this, GAIA_MOV(func)});
6143 return add_query_task_job(GAIA_MOV(func), execType);
6150 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
6152 each_runtime_inter<QueryExecType::Default, Func>(func, Constraints::EnabledOnly);
6158 template <
typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>,
int> = 0>
6166 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
6167 void each(Func func, QueryExecType execType) {
6168 each(func, execType, Constraints::EnabledOnly);
6175 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
6176 void each(Func func, Constraints constraints) {
6177 each(func, QueryExecType::Default, constraints);
6185 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
6186 void each(Func func, QueryExecType execType, Constraints constraints) {
6188 case QueryExecType::Parallel:
6189 each_runtime_inter<QueryExecType::Parallel, Func>(func, constraints);
6191 case QueryExecType::ParallelPerf:
6192 each_runtime_inter<QueryExecType::ParallelPerf, Func>(func, constraints);
6194 case QueryExecType::ParallelEff:
6195 each_runtime_inter<QueryExecType::ParallelEff, Func>(func, constraints);
6198 each_runtime_inter<QueryExecType::Default, Func>(func, constraints);
6207 template <
typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>,
int> = 0>
6208 void each(Func func, QueryExecType execType);
6216 template <
typename Func>
6220 void each_iter_erased(
6221 QueryExecType execType,
void* pFunc,
const TypedQueryExecState& state,
6222 void (*runDirectFastChunk)(
QueryImpl&,
Iter&,
void*,
const TypedQueryExecState&),
6225 void each_iter_erased(
6226 Iter& it,
void* pFunc,
const TypedQueryExecState& state,
6227 void (*runDirectFastChunk)(
QueryImpl&,
Iter&,
void*,
const TypedQueryExecState&),
6238 template <
typename Func>
6239 void each_arch(Func func, Constraints constraints = Constraints::EnabledOnly) {
6240 auto& queryInfo =
fetch();
6242 run_query_on_archetypes<QueryExecType::Default>(
6245 GAIA_PROF_SCOPE(query_func_a);
6263 bool empty(Constraints constraints = Constraints::EnabledOnly) {
6264 auto& queryInfo =
fetch();
6265 if (!queryInfo.
has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6266 return empty_inter<false>(queryInfo, constraints);
6273 return empty_inter<true>(queryInfo, constraints);
6275 return empty_inter<false>(queryInfo, constraints);
6287 uint32_t
count(Constraints constraints = Constraints::EnabledOnly) {
6288 auto& queryInfo =
fetch();
6289 if (!queryInfo.
has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6290 return count_inter<false>(queryInfo, constraints);
6296 return hasFilters ? count_inter<true>(queryInfo, constraints) : count_inter<false>(queryInfo, constraints);
6303 auto& queryInfo =
fetch();
6305 ::gaia::ecs::update_version(*m_worldVersion);
6307 if (!queryInfo.
has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6308 auto& world = *queryInfo.
world();
6309 if (has_only_direct_or_terms(queryInfo)) {
6310 for_each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](
Entity entity) {
6315 const auto plan = direct_entity_seed_plan(world, queryInfo);
6316 (void)for_each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](
Entity entity) {
6322 m_changedWorldVersion = *m_worldVersion;
6330 if (needsBarrierCache)
6333 const auto cacheRange = selected_query_cache_range(queryInfo);
6334 if (!cacheRange.valid) {
6335 m_changedWorldVersion = *m_worldVersion;
6338 const auto idxFrom = cacheRange.idxFrom;
6339 const auto idxTo = cacheRange.idxTo;
6342 it.init_query_state(queryInfo.
world(), Constraints::EnabledOnly,
false);
6343 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6344 const auto* pArchetype = cacheView[qi];
6345 const bool barrierPasses = !needsBarrierCache || queryInfo.
barrier_passes(qi);
6347 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6350 const auto& chunks = pArchetype->chunks();
6351 if (!hasEntityFilters) {
6352 for (
auto* pChunk: chunks) {
6357 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6360 const auto entityCnt = (uint32_t)(to - from);
6361 const auto entities = pChunk->entity_view();
6362 GAIA_FOR(entityCnt) {
6363 func(pCtx, entities[from + i]);
6369 it.set_archetype(pArchetype);
6370 for (
auto* pChunk: chunks) {
6371 it.set_chunk(pChunk);
6372 const auto entityCnt = it.
size();
6375 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6378 const auto entities = it.view<
Entity>();
6379 GAIA_FOR(entityCnt) {
6380 if (match_entity_filters(*queryInfo.
world(), entities[i], queryInfo))
6381 func(pCtx, entities[i]);
6386 m_changedWorldVersion = *m_worldVersion;
6391 auto& queryInfo =
fetch();
6393 ::gaia::ecs::update_version(*m_worldVersion);
6395 if (!queryInfo.
has_filters() && m_groupIdSet == 0 && can_use_direct_entity_seed_eval(queryInfo)) {
6396 auto& world = *queryInfo.
world();
6397 if (has_only_direct_or_terms(queryInfo)) {
6398 for_each_direct_or_union(world, queryInfo, Constraints::EnabledOnly, [&](
Entity entity) {
6403 const auto plan = direct_entity_seed_plan(world, queryInfo);
6404 (void)for_each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6410 m_changedWorldVersion = *m_worldVersion;
6418 if (needsBarrierCache)
6421 const auto cacheRange = selected_query_cache_range(queryInfo);
6422 if (!cacheRange.valid) {
6423 m_changedWorldVersion = *m_worldVersion;
6426 const auto idxFrom = cacheRange.idxFrom;
6427 const auto idxTo = cacheRange.idxTo;
6430 it.init_query_state(queryInfo.
world(), Constraints::EnabledOnly,
false);
6431 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6432 const auto* pArchetype = cacheView[qi];
6433 const bool barrierPasses = !needsBarrierCache || queryInfo.
barrier_passes(qi);
6435 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6438 const auto& chunks = pArchetype->chunks();
6439 if (!hasEntityFilters) {
6440 for (
auto* pChunk: chunks) {
6445 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6448 const auto oldSize = out.
size();
6449 const auto entityCnt = (uint32_t)(to - from);
6450 const auto entities = pChunk->entity_view();
6451 out.
resize(oldSize + entityCnt);
6452 GAIA_FOR(entityCnt) {
6453 out[oldSize + i] = entities[from + i];
6459 it.set_archetype(pArchetype);
6460 for (
auto* pChunk: chunks) {
6461 it.set_chunk(pChunk);
6462 const auto entityCnt = it.
size();
6465 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6468 const auto entities = it.view<Entity>();
6469 GAIA_FOR(entityCnt) {
6470 if (match_entity_filters(*queryInfo.
world(), entities[i], queryInfo))
6476 m_changedWorldVersion = *m_worldVersion;
6484 template <
typename Container>
6485 void arr(Container& outArray, Constraints constraints = Constraints::EnabledOnly);
6495 Constraints constraints = Constraints::EnabledOnly) {
6496 struct OrderedWalkTargetCtx {
6499 uint32_t dependentIdx = 0;
6504 uint32_t* pEdgeCnt =
nullptr;
6506 GAIA_NODISCARD
static uint32_t
6508 const auto targetId = entity.
id();
6510 uint32_t high = cnt;
6511 while (low < high) {
6512 const uint32_t mid = low + ((high - low) >> 1);
6513 if (entities[mid].
id() < targetId)
6519 if (low < cnt && entities[low].
id() == targetId)
6524 static void count_edge(
void* rawCtx,
Entity dependency) {
6525 auto& ctx = *
static_cast<OrderedWalkTargetCtx*
>(rawCtx);
6526 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6527 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6530 ++(*ctx.pOutdegree)[dependencyIdx];
6531 ++(*ctx.pIndegree)[ctx.dependentIdx];
6535 static void write_edge(
void* rawCtx,
Entity dependency) {
6536 auto& ctx = *
static_cast<OrderedWalkTargetCtx*
>(rawCtx);
6537 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6538 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6541 (*ctx.pEdges)[(*ctx.pWriteCursor)[dependencyIdx]++] = ctx.dependentIdx;
6545 auto& walkData = ensure_each_walk_data();
6546 auto& world = *m_storage.world();
6547 const uint32_t relationVersion = world_rel_version(world, relation);
6548 const uint32_t worldVersion = ::gaia::ecs::world_version(world);
6551 const bool needsTraversalBarrierState =
6552 constraints == Constraints::EnabledOnly && ::gaia::ecs::valid(world, relation);
6553 auto survives_disabled_barrier = [&](
Entity entity) {
6554 if (!needsTraversalBarrierState)
6558 GAIA_FOR(MAX_TRAV_DEPTH) {
6559 const auto next = target(world, curr, relation);
6560 if (next == EntityBad || next == curr)
6562 if (!world_entity_enabled(world, next))
6570 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6571 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6572 walkData.cachedEntityVersion == worldVersion &&
6573 walkData.cachedResultCacheRevision == resultCacheRevision && !queryInfo.
has_filters()) {
6574 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6577 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6578 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6579 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
6581 auto& chunks = walkData.scratchChunks;
6584 bool chunkChanged =
false;
6585 for (
auto* pArchetype: queryInfo) {
6589 for (
const auto* pChunk: pArchetype->chunks()) {
6590 if (pChunk ==
nullptr)
6593 chunks.push_back(pChunk);
6594 if (!chunkChanged && pChunk->changed(walkData.cachedEntityVersion))
6595 chunkChanged =
true;
6599 bool sameChunks = chunks.size() == walkData.cachedChunks.size();
6601 for (uint32_t i = 0; i < (uint32_t)chunks.size(); ++i) {
6602 if (chunks[i] != walkData.cachedChunks[i]) {
6609 if (sameChunks && !chunkChanged) {
6610 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6614 auto& entities = walkData.scratchEntities;
6616 arr(entities, constraints);
6617 if (entities.empty())
6620 if (needsTraversalBarrierState) {
6621 uint32_t writeIdx = 0;
6622 const auto cnt = (uint32_t)entities.size();
6624 const auto entity = entities[i];
6625 if (!survives_disabled_barrier(entity))
6627 entities[writeIdx++] = entity;
6629 entities.resize(writeIdx);
6630 if (entities.empty())
6634 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6635 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6636 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
6637 entities.size() == walkData.cachedInput.size()) {
6638 bool sameInput =
true;
6639 for (uint32_t i = 0; i < (uint32_t)entities.size(); ++i) {
6640 if (entities[i] != walkData.cachedInput[i]) {
6647 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6651 auto& ordered = walkData.cachedOutput;
6652 walkData.cachedInput = entities;
6654 if (!::gaia::ecs::valid(world, relation)) {
6656 return left.
id() < right.
id();
6662 return left.
id() < right.
id();
6665 const auto cnt = (uint32_t)entities.size();
6667 auto& indegree = walkData.scratchIndegree;
6668 indegree.resize(cnt);
6669 auto& outdegree = walkData.scratchOutdegree;
6670 outdegree.resize(cnt);
6671 for (uint32_t i = 0; i < cnt; ++i) {
6676 uint32_t edgeCnt = 0;
6677 OrderedWalkTargetCtx edgeCtx;
6678 edgeCtx.pEntities = &entities;
6680 edgeCtx.pIndegree = &indegree;
6681 edgeCtx.pOutdegree = &outdegree;
6682 edgeCtx.pEdgeCnt = &edgeCnt;
6683 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
6684 const auto dependent = entities[dependentIdx];
6685 edgeCtx.dependentIdx = dependentIdx;
6686 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::count_edge);
6689 auto& offsets = walkData.scratchOffsets;
6690 offsets.resize(cnt + 1);
6692 for (uint32_t i = 0; i < cnt; ++i)
6693 offsets[i + 1] = offsets[i] + outdegree[i];
6695 auto& writeCursor = walkData.scratchWriteCursor;
6696 writeCursor.resize(cnt);
6697 for (uint32_t i = 0; i < cnt; ++i)
6698 writeCursor[i] = offsets[i];
6700 auto& edges = walkData.scratchEdges;
6701 edges.resize(edgeCnt);
6702 edgeCtx.pWriteCursor = &writeCursor;
6703 edgeCtx.pEdges = &edges;
6704 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
6705 const auto dependent = entities[dependentIdx];
6706 edgeCtx.dependentIdx = dependentIdx;
6707 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::write_edge);
6710 ordered.reserve(cnt);
6712 const bool isUp = order == TravOrder::Up || order == TravOrder::ReverseUp;
6713 const bool needsReverse = order == TravOrder::ReverseUp || order == TravOrder::ReverseDown;
6715 auto& visited = walkData.scratchWriteCursor;
6716 visited.resize(cnt);
6717 for (uint32_t i = 0; i < cnt; ++i)
6720 auto& stack = walkData.scratchCurrLevel;
6722 auto& cursorStack = walkData.scratchNextLevel;
6723 cursorStack.clear();
6725 auto append_from_root = [&](uint32_t rootIdx) {
6726 stack.push_back(rootIdx);
6727 cursorStack.push_back(offsets[rootIdx]);
6729 while (!stack.empty()) {
6730 const auto idx = stack.back();
6731 if (visited[idx] == 0) {
6734 ordered.push_back(entities[idx]);
6737 bool pushedChild =
false;
6738 auto& cursor = cursorStack.back();
6739 while (cursor < offsets[idx + 1]) {
6740 const auto childIdx = edges[cursor++];
6741 if (visited[childIdx] != 0)
6744 stack.push_back(childIdx);
6745 cursorStack.push_back(offsets[childIdx]);
6754 ordered.push_back(entities[idx]);
6757 cursorStack.pop_back();
6761 for (uint32_t i = 0; i < cnt; ++i) {
6762 if (indegree[i] == 0 && visited[i] == 0)
6763 append_from_root(i);
6767 for (uint32_t i = 0; i < cnt; ++i) {
6768 if (visited[i] == 0)
6769 append_from_root(i);
6773 const auto orderedCnt = (uint32_t)ordered.size();
6774 for (uint32_t i = 0; i < orderedCnt / 2; ++i)
6775 core::swap(ordered[i], ordered[orderedCnt - i - 1]);
6779 walkData.cachedRelation = relation;
6780 walkData.cachedOrder = order;
6781 walkData.cachedConstraints = constraints;
6782 walkData.cachedRelationVersion = relationVersion;
6783 walkData.cachedEntityVersion = ::gaia::ecs::world_version(world);
6784 walkData.cachedResultCacheRevision = resultCacheRevision;
6785 walkData.cachedRuns.clear();
6788 const auto orderedCnt = (uint32_t)ordered.size();
6789 if (orderedCnt != 0) {
6790 for (uint32_t i = 0; i < orderedCnt; ++i) {
6791 const auto& ec = ::gaia::ecs::fetch(world, ordered[i]);
6792 if (walkData.cachedRuns.empty()) {
6793 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
6797 auto& run = walkData.cachedRuns.back();
6798 if (ec.pChunk == run.pChunk && ec.row == run.to) {
6799 run.to = (uint16_t)(run.to + 1);
6801 walkData.cachedRuns.push_back({ec.pArchetype, ec.pChunk, ec.row, (uint16_t)(ec.row + 1), i});
6808 auto& chunks = walkData.scratchChunks;
6810 for (
auto* pArchetype: queryInfo) {
6814 for (
const auto* pChunk: pArchetype->chunks()) {
6815 if (pChunk ==
nullptr)
6817 chunks.push_back(pChunk);
6820 walkData.cachedChunks = chunks;
6822 walkData.cachedChunks.clear();
6823 walkData.cacheValid =
true;
6825 return std::span<const Entity>(walkData.cachedOutput.data(), walkData.cachedOutput.size());
6837 template <
typename Func, std::enable_if_t<detail::is_query_walk_core_callback_v<Func>,
int> = 0>
6839 Func func,
Entity relation, TravOrder order = TravOrder::Down,
6840 Constraints constraints = Constraints::EnabledOnly) {
6841 auto& queryInfo =
fetch();
6845 if constexpr (std::is_invocable_v<Func, Iter&>) {
6846 each_direct_entities_iter(queryInfo, ordered, constraints, func);
6847 }
else if constexpr (std::is_invocable_v<Func, const Entity&> || std::is_invocable_v<Func, Entity>) {
6848 for (
const auto entity: ordered)
6859 template <
typename Func, std::enable_if_t<!detail::is_query_walk_core_callback_v<Func>,
int> = 0>
6861 Func func,
Entity relation, TravOrder order = TravOrder::Down,
6862 Constraints constraints = Constraints::EnabledOnly);
6869 auto& queryInfo =
fetch();
6871 if (uses_shared_cache_layer())
6872 GAIA_LOG_N(
"BEG DIAG Query %u.%u [S]",
id(),
gen());
6873 else if (uses_query_cache_storage())
6874 GAIA_LOG_N(
"BEG DIAG Query %u.%u [L]",
id(),
gen());
6876 GAIA_LOG_N(
"BEG DIAG Query [U]");
6877 for (
const auto* pArchetype: queryInfo)
6879 GAIA_LOG_N(
"END DIAG Query");
6885 auto& queryInfo =
fetch();
6892 if (uses_shared_cache_layer())
6893 GAIA_LOG_N(
"BEG DIAG Query Bytecode %u.%u [S]",
id(),
gen());
6894 else if (uses_query_cache_storage())
6895 GAIA_LOG_N(
"BEG DIAG Query Bytecode %u.%u [L]",
id(),
gen());
6897 GAIA_LOG_N(
"BEG DIAG Query Bytecode [U]");
6898 GAIA_LOG_N(
"%.*s", (
int)dump.size(), dump.data());
6899 GAIA_LOG_N(
"END DIAG Query");