501 static constexpr uint32_t ChunkBatchSize = 32;
502 friend class SystemBuilder;
507 const uint8_t* pCompIndices;
519 struct DirectQueryScratch {
525 uint32_t seenVersion = 1;
529 GAIA_NODISCARD
bool uses_query_cache_storage()
const {
530 return m_cacheKind != QueryCacheKind::None;
533 GAIA_NODISCARD
bool uses_shared_cache_layer()
const {
534 return uses_query_cache_storage() && m_cacheScope == QueryCacheScope::Shared;
537 void invalidate_query_storage() {
538 if (uses_query_cache_storage())
545 GAIA_NODISCARD
static DirectQueryScratch& direct_query_scratch() {
546 static thread_local DirectQueryScratch scratch;
553 static void ensure_direct_query_count_capacity(DirectQueryScratch& scratch, uint32_t entityId) {
554 if (entityId < scratch.counts.size())
557 const auto doubledSize = (uint32_t)scratch.counts.size() * 2U;
558 const auto minSize = doubledSize > 64U ? doubledSize : 64U;
559 const auto newSize = (entityId + 1U) > minSize ? (entityId + 1U) : minSize;
560 scratch.counts.resize(newSize, 0);
566 GAIA_NODISCARD
static uint32_t next_direct_query_seen_version(DirectQueryScratch& scratch) {
567 update_version(scratch.seenVersion);
568 if (scratch.seenVersion == 0) {
569 scratch.seenVersion = 1;
570 core::fill(scratch.counts.begin(), scratch.counts.end(), 0);
573 return scratch.seenVersion;
576 static constexpr CmdFunc CommandBufferRead[] = {
580 ser::load(buffer, cmd);
586 ser::load(buffer, cmd);
592 ser::load(buffer, cmd);
598 ser::load(buffer, cmd);
604 ser::load(buffer, cmd);
610 ser::load(buffer, cmd);
618 ArchetypeId* m_nextArchetypeId{};
620 uint32_t* m_worldVersion{};
630 uint8_t m_varNamesMask = 0;
634 uint8_t m_varBindingsMask = 0;
636 GroupId m_groupIdSet = 0;
638 uint32_t m_changedWorldVersion = 0;
643 QueryCacheKind m_cacheKind = QueryCacheKind::Default;
645 QueryCacheScope m_cacheScope = QueryCacheScope::Local;
647 uint16_t m_cacheSrcTrav = 0;
649 void* m_ctx =
nullptr;
651 bool m_mainThread =
false;
656 struct EachWalkData {
664 Entity cachedRelation = EntityBad;
666 TravOrder cachedOrder = TravOrder::Down;
668 Constraints cachedConstraints = Constraints::EnabledOnly;
670 uint32_t cachedRelationVersion = 0;
672 uint32_t cachedEntityVersion = 0;
676 bool cacheValid =
false;
697 template <
typename T>
698 struct OnDemandDataHolder {
701 OnDemandDataHolder() =
default;
703 ~OnDemandDataHolder() {
707 OnDemandDataHolder(
const OnDemandDataHolder& other) {
708 if (other.pData !=
nullptr)
709 pData =
new T(*other.pData);
712 OnDemandDataHolder& operator=(
const OnDemandDataHolder& other) {
713 if (core::addressof(other) ==
this)
716 if (other.pData ==
nullptr) {
722 if (pData ==
nullptr)
723 pData =
new T(*other.pData);
725 *pData = *other.pData;
730 OnDemandDataHolder(OnDemandDataHolder&& other)
noexcept: pData(other.pData) {
731 other.pData =
nullptr;
734 OnDemandDataHolder& operator=(OnDemandDataHolder&& other)
noexcept {
735 if (core::addressof(other) ==
this)
740 other.pData =
nullptr;
744 GAIA_NODISCARD T* get() {
748 GAIA_NODISCARD
const T* get()
const {
752 GAIA_NODISCARD T& ensure() {
753 if (pData ==
nullptr)
765 OnDemandDataHolder<EachWalkData> m_eachWalkData;
768 struct DirectSeedRunData {
772 Entity cachedSeedTerm = EntityBad;
773 QueryMatchKind cachedSeedMatchKind = QueryMatchKind::Semantic;
774 Constraints cachedConstraints = Constraints::EnabledOnly;
775 uint32_t cachedRelVersion = 0;
776 uint32_t cachedWorldVersion = 0;
777 bool cacheValid =
false;
780 OnDemandDataHolder<DirectSeedRunData> m_directSeedRunData;
786 GAIA_NODISCARD
static QueryAccess merge_access(QueryAccess lhs, QueryAccess rhs) {
787 if (lhs == QueryAccess::Write || rhs == QueryAccess::Write)
788 return QueryAccess::Write;
789 if (lhs == QueryAccess::Read || rhs == QueryAccess::Read)
790 return QueryAccess::Read;
791 return QueryAccess::None;
799 if (entity == EntityBad || entity.pair())
800 return QueryAccess::None;
802 QueryAccess
access = QueryAccess::None;
803 const auto terms = data.terms_view();
804 GAIA_FOR((uint32_t)terms.size()) {
805 const auto& term = terms[i];
806 if (term.id != entity || (term.op != QueryOpKind::All && term.op != QueryOpKind::Or))
809 if ((data.readWriteMask & (uint16_t(1) << i)) != 0)
810 return QueryAccess::Write;
811 access = QueryAccess::Read;
822 GAIA_NODISCARD
static QueryAccess
824 return merge_access(term_access(data, entity), accessSet.
access(entity));
831 GAIA_NODISCARD
static bool access_conflicts(QueryAccess lhs, QueryAccess rhs) {
832 return (lhs == QueryAccess::Write && rhs != QueryAccess::None) ||
833 (rhs == QueryAccess::Write && lhs != QueryAccess::None);
842 GAIA_NODISCARD
static bool conflicts_one_way(
845 const auto terms = leftData.terms_view();
846 GAIA_FOR((uint32_t)terms.size()) {
847 const auto id = terms[i].id;
848 const auto access = term_access(leftData,
id);
849 if (access_conflicts(
access, effective_access(rightData, rightAccess,
id)))
853 for (
const auto id: leftAccess.
reads_view()) {
854 if (access_conflicts(QueryAccess::Read, effective_access(rightData, rightAccess,
id)))
858 if (access_conflicts(QueryAccess::Write, effective_access(rightData, rightAccess,
id)))
868 template <
typename T>
869 GAIA_NODISCARD
Entity access_entity_inter() {
871 const auto& descRel = comp_cache_add<typename T::rel_type>(*m_storage.
world());
872 const auto& descTgt = comp_cache_add<typename T::tgt_type>(*m_storage.
world());
873 return Pair(descRel.entity, descTgt.entity);
875 using UO =
typename component_type_t<T>::TypeOriginal;
876 static_assert(core::is_raw_v<UO>,
"Use reads()/writes() with raw types only");
877 const auto& desc = comp_cache_add<T>(*m_storage.
world());
884 static inline bool SilenceInvalidCacheKindAssertions =
false;
890 GAIA_PROF_SCOPE(query::fetch);
895 if (!uses_query_cache_storage()) {
913 if GAIA_UNLIKELY (pQueryInfo ==
nullptr)
917 if GAIA_LIKELY (pQueryInfo !=
nullptr) {
920 recommit(pQueryInfo->ctx());
932 uses_shared_cache_layer()
933 ? m_storage.
m_pCache->
add(GAIA_MOV(
ctx), *m_entityToArchetypeMap, all_archetypes_view())
944 const auto kindError = validate_kind(queryInfo.
ctx());
945 if (kindError != QueryKindRes::OK) {
946 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions,
kind_error_str(kindError));
951 if (!uses_query_cache_storage()) {
952 queryInfo.ensure_matches_transient(
953 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, m_varBindings,
958 queryInfo.ensure_matches(
959 *m_entityToArchetypeMap, all_archetypes_view(), *m_entityToArchetypeMapVersions, last_archetype_id(),
960 m_varBindings, m_varBindingsMask);
961 m_storage.
m_pCache->sync_archetype_cache(queryInfo);
970 if (!uses_query_cache_storage()) {
971 return queryInfo.ensure_matches_one_transient(archetype, targetEntities, m_varBindings, m_varBindingsMask);
974 return queryInfo.ensure_matches_one(archetype, targetEntities, m_varBindings, m_varBindingsMask);
983 const auto kindError = validate_kind(queryInfo.
ctx());
984 if (kindError != QueryKindRes::OK) {
985 GAIA_ASSERT2(SilenceInvalidCacheKindAssertions,
kind_error_str(kindError));
1008 if (m_cacheSrcTrav == maxItems)
1011 if (maxItems > MaxCacheSrcTrav) {
1012 GAIA_ASSERT(
false &&
"cache_src_trav should be a value smaller than MaxCacheSrcTrav");
1013 maxItems = MaxCacheSrcTrav;
1016 invalidate_each_walk_cache();
1017 invalidate_direct_seed_run_cache();
1018 invalidate_query_storage();
1019 m_cacheSrcTrav = maxItems;
1027 return m_cacheSrcTrav;
1050 GAIA_NODISCARD
void*
ctx()
const {
1064 QueryImpl& main_thread(
bool required =
true) {
1065 m_mainThread = required;
1072 return m_mainThread;
1094 template <
typename T>
1096 return reads(access_entity_inter<T>());
1115 template <
typename T>
1117 return writes(access_entity_inter<T>());
1140 return effective_access(
fetch().
ctx().data, m_access, entity);
1151 const auto& leftData =
fetch().
ctx().data;
1152 const auto& rightData = other.
fetch().
ctx().data;
1153 return conflicts_one_way(leftData, m_access, rightData, other.m_access) ||
1154 conflicts_one_way(rightData, other.m_access, leftData, m_access);
1161 return !m_mainThread && !other.m_mainThread && !
conflicts_with(other);
1169 if (m_cacheKind == cacheKind)
1172 invalidate_each_walk_cache();
1173 invalidate_direct_seed_run_cache();
1174 invalidate_query_storage();
1175 m_cacheKind = cacheKind;
1184 if (m_cacheScope == cacheScope)
1187 invalidate_each_walk_cache();
1188 invalidate_direct_seed_run_cache();
1189 invalidate_query_storage();
1190 m_cacheScope = cacheScope;
1205 GAIA_NODISCARD QueryCacheScope
scope()
const {
1206 return m_cacheScope;
1211 GAIA_NODISCARD QueryCacheKind
kind()
const {
1218 return validate_kind(
fetch().
ctx());
1238 GAIA_NODISCARD
bool uses_manual_src_trav_cache(
const QueryCtx&
ctx)
const {
1239 return m_cacheSrcTrav != 0 &&
1240 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasSourceTerms) &&
1241 ctx.data.deps.has_dep_flag(QueryCtx::DependencyHasTraversalTerms);
1247 GAIA_NODISCARD
static bool uses_im_cache(
const QueryCtx&
ctx) {
1248 return ctx.data.cachePolicy == QueryCachePolicy::Immediate;
1254 GAIA_NODISCARD
static bool uses_lazy_cache(
const QueryCtx&
ctx) {
1255 return ctx.data.cachePolicy == QueryCachePolicy::Lazy;
1261 GAIA_NODISCARD
static bool uses_dyn_cache(
const QueryCtx&
ctx) {
1262 return ctx.data.cachePolicy == QueryCachePolicy::Dynamic;
1268 GAIA_NODISCARD QueryKindRes validate_kind(
const QueryCtx&
ctx)
const {
1269 if (m_cacheKind == QueryCacheKind::Auto) {
1270 if (uses_manual_src_trav_cache(
ctx))
1271 return QueryKindRes::AutoSrcTrav;
1274 if (m_cacheKind == QueryCacheKind::All) {
1275 if (uses_manual_src_trav_cache(
ctx))
1276 return QueryKindRes::AllSrcTrav;
1277 if (!uses_im_cache(
ctx))
1278 return QueryKindRes::AllNotIm;
1281 return QueryKindRes::OK;
1287 GAIA_NODISCARD
static const char*
kind_error_str(QueryKindRes error) {
1289 case QueryKindRes::OK:
1291 case QueryKindRes::AutoSrcTrav:
1292 return "QueryCacheKind::Auto rejects explicit traversed-source snapshot caching";
1293 case QueryKindRes::AllNotIm:
1294 return "QueryCacheKind::All requires a fully immediate structural cache";
1295 case QueryKindRes::AllSrcTrav:
1296 return "QueryCacheKind::All rejects explicit traversed-source snapshot caching";
1298 return "Unknown query kind validation error";
1304 GAIA_NODISCARD EachWalkData* each_walk_data() {
1305 return m_eachWalkData.get();
1310 GAIA_NODISCARD
const EachWalkData* each_walk_data()
const {
1311 return m_eachWalkData.get();
1316 GAIA_NODISCARD EachWalkData& ensure_each_walk_data() {
1317 return m_eachWalkData.ensure();
1321 void invalidate_each_walk_cache() {
1322 auto* pWalkData = each_walk_data();
1323 if (pWalkData !=
nullptr)
1324 pWalkData->cacheValid =
false;
1329 GAIA_NODISCARD DirectSeedRunData* direct_seed_run_data() {
1330 return m_directSeedRunData.get();
1335 GAIA_NODISCARD
const DirectSeedRunData* direct_seed_run_data()
const {
1336 return m_directSeedRunData.get();
1341 GAIA_NODISCARD DirectSeedRunData& ensure_direct_seed_run_data() {
1342 return m_directSeedRunData.ensure();
1346 void invalidate_direct_seed_run_cache() {
1347 auto* pRunData = direct_seed_run_data();
1348 if (pRunData !=
nullptr)
1349 pRunData->cacheValid =
false;
1353 void reset_changed_filter_state() {
1354 m_changedWorldVersion = 0;
1359 ArchetypeId last_archetype_id()
const {
1360 return *m_nextArchetypeId - 1;
1366 GAIA_ASSERT(m_allArchetypes !=
nullptr);
1367 return {(
const Archetype**)m_allArchetypes->data(), m_allArchetypes->size()};
1370 GAIA_NODISCARD
static bool is_query_var_entity(Entity entity) {
1371 return is_variable((EntityId)entity.id());
1374 GAIA_NODISCARD
static uint32_t query_var_idx(Entity entity) {
1375 GAIA_ASSERT(is_query_var_entity(entity));
1376 return (uint32_t)(entity.id() - Var0.id());
1379 GAIA_NODISCARD Entity query_var_entity(uint32_t idx) {
1380 GAIA_ASSERT(idx < 8);
1381 return entity_from_id((
const World&)*m_storage.
world(), (EntityId)(Var0.id() + idx));
1384 GAIA_NODISCARD
static util::str_view normalize_var_name(util::str_view name) {
1385 auto trimmed = util::trim(name);
1386 if (trimmed.empty())
1389 if (trimmed.data()[0] ==
'$') {
1390 if (trimmed.size() == 1)
1392 trimmed = util::str_view(trimmed.data() + 1, trimmed.size() - 1);
1395 return util::trim(trimmed);
1398 GAIA_NODISCARD
static bool is_reserved_var_name(util::str_view varName) {
1399 return varName ==
"this";
1402 GAIA_NODISCARD Entity find_var_by_name(util::str_view rawName) {
1403 const auto varName = normalize_var_name(rawName);
1404 if (varName.empty() || is_reserved_var_name(varName))
1408 const auto bit = (uint8_t(1) << i);
1409 if ((m_varNamesMask & bit) == 0)
1411 if (m_varNames[i] == varName)
1412 return query_var_entity(i);
1418 bool set_var_name_internal(Entity varEntity, util::str_view rawName) {
1419 if (!is_query_var_entity(varEntity))
1422 const auto varName = normalize_var_name(rawName);
1423 if (varName.empty() || is_reserved_var_name(varName))
1426 const auto idx = query_var_idx(varEntity);
1427 const auto bit = (uint8_t(1) << idx);
1433 const auto otherBit = (uint8_t(1) << i);
1434 if ((m_varNamesMask & otherBit) == 0)
1436 if (!(m_varNames[i] == varName))
1439 GAIA_ASSERT2(
false,
"Variable name is already assigned to a different query variable");
1443 m_varNames[idx].assign(varName);
1444 m_varNamesMask |= bit;
1448 template <
typename T>
1449 void add_cmd(T& cmd) {
1450 invalidate_each_walk_cache();
1453 if constexpr (T::InvalidatesHash) {
1454 reset_changed_filter_state();
1459 ser::save(serBuffer, T::Id);
1460 ser::save(serBuffer, T::InvalidatesHash);
1461 ser::save(serBuffer, cmd);
1464 void add_inter(QueryInput item) {
1466 GAIA_ASSERT((item.op != QueryOpKind::Not && item.op != QueryOpKind::Any) || item.access == QueryAccess::None);
1468 QueryCmd_AddItem cmd{item};
1472 GAIA_NODISCARD
static QueryAccess normalize_access(QueryOpKind op, Entity entity, QueryAccess
access) {
1473 if (op == QueryOpKind::Not || op == QueryOpKind::Any || entity.pair())
1474 return QueryAccess::None;
1477 if (
access == QueryAccess::None)
1478 return QueryAccess::Read;
1483 void add_entity_term(QueryOpKind op, Entity entity,
const QueryTermOptions& options) {
1484 const auto access = normalize_access(op, entity, options.access);
1486 {op,
access, entity, options.entSrc, options.entTrav, options.travKind, options.travDepth,
1487 options.matchKind});
1490 template <
typename T>
1491 void add_inter(QueryOpKind op) {
1494 if constexpr (is_pair<T>::value) {
1496 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.
world());
1497 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.
world());
1499 e = Pair(desc_rel.entity, desc_tgt.entity);
1502 const auto& desc = comp_cache_add<T>(*m_storage.
world());
1507 QueryAccess
access = QueryAccess::None;
1508 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1509 constexpr auto isReadWrite = core::is_mut_v<T>;
1510 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1513 add_inter({op,
access, e});
1516 template <
typename T>
1517 void add_inter(QueryOpKind op,
const QueryTermOptions& options) {
1520 if constexpr (is_pair<T>::value) {
1522 const auto& desc_rel = comp_cache_add<typename T::rel_type>(*m_storage.
world());
1523 const auto& desc_tgt = comp_cache_add<typename T::tgt_type>(*m_storage.
world());
1525 e = Pair(desc_rel.entity, desc_tgt.entity);
1528 const auto& desc = comp_cache_add<T>(*m_storage.
world());
1532 QueryAccess
access = QueryAccess::None;
1533 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1534 if (options.access != QueryAccess::None)
1537 constexpr auto isReadWrite = core::is_mut_v<T>;
1538 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1543 {op, normalize_access(op, e,
access), e, options.entSrc, options.entTrav, options.travKind,
1544 options.travDepth, options.matchKind});
1547 template <
typename Rel,
typename Tgt>
1548 void add_inter(QueryOpKind op) {
1549 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1550 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1551 static_assert(core::is_raw_v<UO_Rel>,
"Use add() with raw types only");
1552 static_assert(core::is_raw_v<UO_Tgt>,
"Use add() with raw types only");
1555 const auto& descRel = comp_cache_add<Rel>(*m_storage.
world());
1556 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.
world());
1559 QueryAccess
access = QueryAccess::None;
1560 if (op != QueryOpKind::Not && op != QueryOpKind::Any) {
1561 constexpr auto isReadWrite = core::is_mut_v<UO_Rel> || core::is_mut_v<UO_Tgt>;
1562 access = isReadWrite ? QueryAccess::Write : QueryAccess::Read;
1565 add_inter({op,
access, {descRel.entity, descTgt.entity}});
1570 void changed_inter(Entity entity) {
1571 QueryCmd_AddFilter cmd{entity};
1575 template <
typename T>
1576 void changed_inter() {
1577 using UO =
typename component_type_t<T>::TypeOriginal;
1578 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1581 const auto& desc = comp_cache_add<T>(*m_storage.
world());
1582 changed_inter(desc.entity);
1585 template <
typename Rel,
typename Tgt>
1586 void changed_inter() {
1587 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1588 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1589 static_assert(core::is_raw_v<UO_Rel>,
"Use changed() with raw types only");
1590 static_assert(core::is_raw_v<UO_Tgt>,
"Use changed() with raw types only");
1593 const auto& descRel = comp_cache_add<Rel>(*m_storage.
world());
1594 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.
world());
1595 changed_inter({descRel.entity, descTgt.entity});
1600 void sort_by_inter(Entity entity, TSortByFunc func) {
1601 QueryCmd_SortBy cmd{entity, func};
1605 template <
typename T>
1606 void sort_by_inter(TSortByFunc func) {
1607 using UO =
typename component_type_t<T>::TypeOriginal;
1608 if constexpr (std::is_same_v<UO, Entity>) {
1609 sort_by_inter(EntityBad, func);
1611 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1614 const auto& desc = comp_cache_add<T>(*m_storage.
world());
1616 sort_by_inter(desc.entity, func);
1620 template <
typename Rel,
typename Tgt>
1621 void sort_by_inter(TSortByFunc func) {
1622 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1623 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1624 static_assert(core::is_raw_v<UO_Rel>,
"Use group_by() with raw types only");
1625 static_assert(core::is_raw_v<UO_Tgt>,
"Use group_by() with raw types only");
1628 const auto& descRel = comp_cache_add<Rel>(*m_storage.
world());
1629 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.
world());
1631 sort_by_inter({descRel.entity, descTgt.entity}, func);
1636 void group_by_inter(Entity entity, TGroupByFunc func,
bool orderGroups =
false) {
1637 QueryCmd_GroupBy cmd{entity, func, orderGroups ? (uint16_t)QueryCtx::QueryFlags::OrderGroups : (uint16_t)0};
1641 template <
typename T>
1642 void group_by_inter(Entity entity, TGroupByFunc func) {
1643 using UO =
typename component_type_t<T>::TypeOriginal;
1644 static_assert(core::is_raw_v<UO>,
"Use changed() with raw types only");
1646 group_by_inter(entity, func);
1649 template <
typename Rel,
typename Tgt>
1650 void group_by_inter(TGroupByFunc func) {
1651 using UO_Rel =
typename component_type_t<Rel>::TypeOriginal;
1652 using UO_Tgt =
typename component_type_t<Tgt>::TypeOriginal;
1653 static_assert(core::is_raw_v<UO_Rel>,
"Use group_by() with raw types only");
1654 static_assert(core::is_raw_v<UO_Tgt>,
"Use group_by() with raw types only");
1657 const auto& descRel = comp_cache_add<Rel>(*m_storage.
world());
1658 const auto& descTgt = comp_cache_add<Tgt>(*m_storage.
world());
1660 group_by_inter({descRel.entity, descTgt.entity}, func);
1665 void group_dep_inter(Entity relation) {
1666 GAIA_ASSERT(!relation.pair());
1667 QueryCmd_GroupDep cmd{relation};
1671 template <
typename T>
1672 void group_dep_inter() {
1673 using UO =
typename component_type_t<T>::TypeOriginal;
1674 static_assert(core::is_raw_v<UO>,
"Use group_dep() with raw types only");
1676 const auto& desc = comp_cache_add<T>(*m_storage.
world());
1677 group_dep_inter(desc.entity);
1682 void set_group_id_inter(GroupId groupId) {
1686 invalidate_each_walk_cache();
1687 m_groupIdSet = groupId;
1690 void set_group_id_inter(Entity groupId) {
1691 set_group_id_inter(groupId.id());
1694 template <
typename T>
1695 void set_group_id_inter() {
1696 using UO =
typename component_type_t<T>::TypeOriginal;
1697 static_assert(core::is_raw_v<UO>,
"Use group_id() with raw types only");
1700 const auto& desc = comp_cache_add<T>(*m_storage.
world());
1701 set_group_id_inter(desc.entity);
1706 void commit(QueryCtx&
ctx) {
1707 GAIA_PROF_SCOPE(query::commit);
1709#if GAIA_ASSERT_ENABLED
1717 while (serBuffer.tell() < serBuffer.bytes()) {
1719 bool invalidatesHash =
false;
1720 ser::load(serBuffer,
id);
1721 ser::load(serBuffer, invalidatesHash);
1722 (void)invalidatesHash;
1723 CommandBufferRead[
id](serBuffer,
ctx);
1727 if (uses_query_cache_storage()) {
1728 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1729 normalize_cache_src_trav(
ctx);
1731 if (uses_shared_cache_layer()) {
1732 auto& ctxData =
ctx.data;
1733 if (ctxData.changedCnt > 1) {
1734 core::sort(ctxData.changed.data(), ctxData.changed.data() + ctxData.changedCnt, SortComponentCond{});
1743 if (uses_shared_cache_layer())
1744 calc_lookup_hash(
ctx);
1747 void recommit(QueryCtx&
ctx) {
1748 GAIA_PROF_SCOPE(query::recommit);
1754 while (serBuffer.tell() < serBuffer.bytes()) {
1756 bool invalidatesHash =
false;
1757 ser::load(serBuffer,
id);
1758 ser::load(serBuffer, invalidatesHash);
1760 GAIA_ASSERT(!invalidatesHash);
1761 if (invalidatesHash)
1763 CommandBufferRead[
id](serBuffer,
ctx);
1765 if (uses_query_cache_storage()) {
1766 ctx.data.cacheSrcTrav = m_cacheSrcTrav;
1767 normalize_cache_src_trav(
ctx);
1782 const Chunk& chunk,
const QueryInfo& queryInfo, uint32_t changedWorldVersion,
1784 GAIA_ASSERT(!chunk.
empty() &&
"match_filters called on an empty chunk");
1786 const auto queryVersion = changedWorldVersion;
1787 const auto& data = queryInfo.
ctx().data;
1788 if ((data.flags & (QueryCtx::QueryFlags::HasVariableTerms | QueryCtx::QueryFlags::HasSourceTerms)) != 0)
1789 return match_filters(chunk, queryInfo, changedWorldVersion);
1791 const auto changedFields = data.changed_fields_view();
1793 if (changedFields.empty())
1796 const auto changedCnt = (uint32_t)changedFields.size();
1797 if (changedCnt == 1) {
1798 const auto fieldIdx = changedFields[0];
1799 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1800 if (compIdx == (uint8_t)0xFF)
1801 return match_filters(chunk, queryInfo, changedWorldVersion);
1802 if (chunk.
changed(queryVersion, compIdx))
1808 GAIA_FOR(changedCnt) {
1809 const auto fieldIdx = changedFields[i];
1810 const auto compIdx = fieldIdx < compIndices.size() ? compIndices[fieldIdx] : (uint8_t)0xFF;
1811 if (compIdx == (uint8_t)0xFF)
1812 return match_filters(chunk, queryInfo, changedWorldVersion);
1813 if (chunk.
changed(queryVersion, compIdx))
1824 GAIA_NODISCARD
static bool
1826 GAIA_ASSERT(!chunk.
empty() &&
"match_filters called on an empty chunk");
1828 const auto queryVersion = changedWorldVersion;
1829 const auto& filtered = queryInfo.
ctx().data.changed_view();
1832 if (filtered.empty())
1835 const auto filteredCnt = (uint32_t)filtered.size();
1836 auto ids = chunk.ids_view();
1839 if (filteredCnt == 1) {
1840 const auto compIdx = core::get_index(ids, filtered[0]);
1848 uint32_t lastIdx = 0;
1849 for (
const auto comp: filtered) {
1851 if (lastIdx < (uint32_t)ids.size()) {
1852 const auto suffixIdx =
1855 compIdx = lastIdx + suffixIdx;
1861 compIdx = core::get_index(ids, comp);
1865 if (chunk.
changed(queryVersion, compIdx))
1876 GAIA_NODISCARD
bool can_process_archetype(
const QueryInfo& queryInfo,
const Archetype& archetype)
const {
1893 const auto& data = queryInfo.
ctx().data;
1894 return data.groupByFunc == group_by_func_depth_order &&
1895 world_depth_order_prunes_disabled_subtrees(*queryInfo.
world(), data.groupBy);
1902 GAIA_NODISCARD
static bool
1915 Chunk* pChunk, Constraints constraints,
bool needsBarrierCache,
bool barrierPasses, uint16_t& from,
1916 uint16_t& to)
noexcept {
1917 if (needsBarrierCache && constraints == Constraints::DisabledOnly && !barrierPasses) {
1919 to = pChunk->size();
1923 from = detail::ChunkIterImpl::start_index(pChunk, constraints);
1924 to = detail::ChunkIterImpl::end_index(pChunk, constraints);
1943 GAIA_NODISCARD
static bool
1948 const auto& world = *queryInfo.
world();
1949 const auto relation = queryInfo.
ctx().data.
groupBy;
1950 auto ids = archetype.ids_view();
1952 for (
auto idsIdx: archetype.pair_rel_indices(relation)) {
1953 const auto pair = ids[idsIdx];
1954 const auto parent = world_pair_target_if_alive(world,
pair);
1955 if (parent == EntityBad)
1957 if (!world_entity_enabled_hierarchy(world, parent, relation))
1972 int8_t barrierPasses = -1)
const {
1973 if (!can_process_archetype(queryInfo, archetype))
1975 if (constraints == Constraints::EnabledOnly) {
1977 if (barrierPasses >= 0)
1978 return barrierPasses != 0;
1986 template <
typename TIter>
1987 static void finish_iter_writes(TIter& it) {
1988 if (it.chunk() ==
nullptr)
1991 auto compIndices = it.touched_comp_indices();
1992 for (
auto compIdx: compIndices)
1993 const_cast<
Chunk*>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
1995 auto terms = it.touched_terms();
1999 auto entities = it.entity_rows();
2000 auto& world = *it.world();
2002 const auto term = terms[i];
2003 if (!world_is_out_of_line_component(world, term)) {
2004 const auto compIdx = core::get_index(it.chunk()->ids_view(), term);
2006 const_cast<Chunk*
>(it.chunk())->finish_write(compIdx, it.row_begin(), it.row_end());
2011 GAIA_FOR_(entities.size(), j) {
2012 world_finish_write(world, term, entities[j]);
2017 static void finish_typed_chunk_writes_runtime(
2018 World& world, Chunk* pChunk, uint16_t from, uint16_t to,
const Entity* pArgIds,
const bool* pWriteFlags,
2019 uint32_t argCnt, uint32_t firstWriteArg);
2021 template <
typename... T>
2022 static void finish_typed_chunk_writes(World& world, Chunk* pChunk, uint16_t from, uint16_t to);
2024 static void finish_typed_iter_writes_runtime(
2025 Iter& it,
const Entity* pArgIds,
const bool* pWriteFlags, uint32_t argCnt, uint32_t firstWriteArg);
2131 template <
typename TMode>
2133 if constexpr (std::is_same_v<TMode, IterModeDisabledOnly>)
2134 return Constraints::DisabledOnly;
2135 else if constexpr (std::is_same_v<TMode, IterModeAcceptAll>)
2136 return Constraints::AcceptAll;
2138 return Constraints::EnabledOnly;
2150 template <
typename Func,
typename TMode>
2154 it.set_archetype(batch.pArchetype);
2155 it.set_chunk(batch.pChunk, batch.from, batch.to);
2156 it.set_group_id(batch.groupId);
2157 it.set_comp_indices(batch.pCompIndices);
2158 it.set_inherited_data(batch.inheritedData);
2160 finish_iter_writes(it);
2161 it.clear_touched_writes();
2172 template <
typename Func>
2176 it.set_archetype(batch.pArchetype);
2178 it.set_group_id(batch.groupId);
2179 it.set_comp_indices(batch.pCompIndices);
2180 it.set_inherited_data(batch.inheritedData);
2182 it.clear_touched_writes();
2192 template <
typename Func,
typename TMode>
2194 GAIA_PROF_SCOPE(query::run_query_func);
2196 const auto chunkCnt = batches.size();
2197 GAIA_ASSERT(chunkCnt > 0);
2202 const Archetype* pLastArchetype =
nullptr;
2203 const uint8_t* pLastIndices =
nullptr;
2205 GroupId lastGroupId = GroupIdMax;
2207 const auto apply_batch = [&](
const ChunkBatch& batch) {
2208 if (batch.pArchetype != pLastArchetype) {
2209 it.set_archetype(batch.pArchetype);
2210 pLastArchetype = batch.pArchetype;
2213 if (batch.pCompIndices != pLastIndices) {
2214 it.set_comp_indices(batch.pCompIndices);
2215 pLastIndices = batch.pCompIndices;
2218 if (batch.inheritedData.data() != lastInheritedData.data()) {
2219 it.set_inherited_data(batch.inheritedData);
2220 lastInheritedData = batch.inheritedData;
2223 if (batch.groupId != lastGroupId) {
2224 it.set_group_id(batch.groupId);
2225 lastGroupId = batch.groupId;
2228 it.set_chunk(batch.pChunk, batch.from, batch.to);
2230 finish_iter_writes(it);
2231 it.clear_touched_writes();
2235 if GAIA_UNLIKELY (chunkCnt == 1) {
2236 apply_batch(batches[0]);
2249 apply_batch(batches[0]);
2251 uint32_t chunkIdx = 1;
2252 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
2254 apply_batch(batches[chunkIdx]);
2257 apply_batch(batches[chunkIdx]);
2262 template <
typename Func,
typename TMode>
2265 World* pWorld =
nullptr;
2272 template <
typename Func>
2276 QueryExecType execType = QueryExecType::Default;
2283 template <
typename Func>
2300 template <
typename Func>
2314 template <
typename Func>
2315 static void invoke_query_task_job(
void* pCtx) {
2316 auto&
ctx = *
reinterpret_cast<QueryTaskJobCtx<Func>*
>(pCtx);
2317 ctx.pSelf->each(
ctx.func,
ctx.execType);
2320 template <
typename Func>
2321 static void cleanup_query_task_job(
void* pCtx) {
2322 auto* pJobCtx =
reinterpret_cast<QueryTaskJobCtx<Func>*
>(pCtx);
2323 if (pJobCtx ==
nullptr)
2328 template <
typename Func,
typename TMode>
2329 static void cleanup_query_job(
void* pCtx) {
2330 auto* pJobCtx =
reinterpret_cast<QueryJobCtx<Func, TMode>*
>(pCtx);
2331 if (pJobCtx ==
nullptr)
2334 auto* pWorld = pJobCtx->pWorld;
2335 if (pWorld !=
nullptr) {
2337 commit_cmd_buffer_st(*pWorld);
2338 commit_cmd_buffer_mt(*pWorld);
2339 if (pJobCtx->pSelf !=
nullptr)
2340 pJobCtx->pSelf->m_changedWorldVersion = *pJobCtx->pSelf->m_worldVersion;
2346 template <
typename Func,
typename TMode, QueryExecType ExecType>
2347 GAIA_NODISCARD SchedJob add_parallel_query_job(Func func) {
2348 static_assert(ExecType != QueryExecType::Default);
2349 if (m_batches.empty()) {
2350 m_changedWorldVersion = *m_worldVersion;
2354 auto* pWorld = m_storage.
world();
2357 auto* pCtx =
new QueryJobCtx<Func, TMode>{
this, pWorld, {}, GAIA_MOV(func)};
2358 pCtx->batches.resize(m_batches.size());
2359 GAIA_EACH(m_batches) pCtx->batches[i] = m_batches[i];
2362 SchedParDesc desc{};
2364 desc.itemCount = (uint32_t)pCtx->batches.size();
2366 desc.execType = ExecType;
2367 desc.invoke = [](
void* pInvokeCtx, uint32_t idxStart, uint32_t idxEnd) {
2368 auto&
ctx = *
reinterpret_cast<QueryJobCtx<Func, TMode>*
>(pInvokeCtx);
2369 run_query_func<Func, TMode>(
ctx.pWorld,
ctx.func,
std::span(&
ctx.batches[idxStart], idxEnd - idxStart));
2372 return sched_add_par(world_sched(*pWorld), desc, pCtx, &cleanup_query_job<Func, TMode>);
2375 template <
bool HasFilters>
2377 collect_runtime_parallel_batches(
const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints) {
2378 auto cacheView = queryInfo.cache_archetype_view();
2379 const bool hasSortedPlanPayload =
2381 const auto sortView =
2382 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2385 if (needsBarrierCache)
2386 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2388 if (!sortView.empty()) {
2389 for (
const auto& view: sortView) {
2390 const auto* pArchetype = cacheView[view.archetypeIdx];
2391 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2395 const auto viewFrom = view.startRow;
2396 const auto viewTo = (uint16_t)(view.startRow + view.count);
2397 uint16_t minStartRow = 0;
2398 uint16_t minEndRow = 0;
2399 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2400 const auto startRow = core::get_max(minStartRow, viewFrom);
2401 const auto endRow = core::get_min(minEndRow, viewTo);
2402 if (endRow == startRow)
2405 if constexpr (HasFilters) {
2406 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2410 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2411 const auto inheritedDataView =
2412 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2413 m_batches.push_back(
2414 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2419 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
2420 const auto* pArchetype = cacheView[i];
2421 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2425 auto indicesView = queryInfo.indices_mapping_view(i);
2426 const auto inheritedDataView =
2427 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2428 const auto& chunks = pArchetype->chunks();
2429 for (
auto* pChunk: chunks) {
2433 if GAIA_UNLIKELY (from == to)
2436 if constexpr (HasFilters) {
2437 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2441 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2446 template <
typename Func>
2447 GAIA_NODISCARD SchedJob add_query_task_job(Func func, QueryExecType execType) {
2448 auto* pCtx =
new QueryTaskJobCtx<Func>{
this, GAIA_MOV(func), execType};
2450 SchedTaskDesc desc{};
2452 desc.invoke = &invoke_query_task_job<Func>;
2453 desc.execType = execType;
2455 return sched_add(world_sched(*m_storage.
world()), desc, pCtx, &cleanup_query_task_job<Func>);
2458 template <
typename Func, QueryExecType ExecType>
2459 GAIA_NODISCARD SchedJob add_iter_parallel_job(Func func) {
2460 static_assert(ExecType != QueryExecType::Default);
2462 auto& queryInfo =
fetch();
2464 const auto constraints = Constraints::EnabledOnly;
2469 return add_query_task_job(GAIA_MOV(func), ExecType);
2472 if (cacheRange.hasSelectedGroup)
2473 return add_query_task_job(GAIA_MOV(func), ExecType);
2475 ::gaia::ecs::update_version(*m_worldVersion);
2478 collect_runtime_parallel_batches<true>(queryInfo, plan, constraints);
2480 collect_runtime_parallel_batches<false>(queryInfo, plan, constraints);
2482 using JobFunc = IterJobCallback<Func>;
2483 return add_parallel_query_job<JobFunc, IterModeEnabled, ExecType>(JobFunc{
this, GAIA_MOV(func)});
2488 template <
bool HasFilters,
typename TMode,
typename Func>
2489 void run_query_batch_no_group_id(
2490 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2491 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
2493 auto cacheView = queryInfo.cache_archetype_view();
2494 constexpr auto constraints = iter_mode_constraints<TMode>();
2496 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2499 const bool hasSortedBatchPayload =
2501 const auto sortView =
2502 hasSortedBatchPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2503 if (needsBarrierCache)
2504 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2506 lock(*m_storage.
world());
2510 ChunkBatchArray chunkBatches;
2512 if (!sortView.empty()) {
2513 for (
const auto& view: sortView) {
2514 auto* pArchetype =
const_cast<Archetype*
>(cacheView[view.archetypeIdx]);
2515 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2519 const auto viewFrom = view.startRow;
2520 const auto viewTo = (uint16_t)(view.startRow + view.count);
2521 uint16_t minStartRow = 0;
2522 uint16_t minEndRow = 0;
2523 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2524 const auto startRow = core::get_max(minStartRow, viewFrom);
2525 const auto endRow = core::get_min(minEndRow, viewTo);
2526 const auto totalRows = endRow - startRow;
2530 if constexpr (HasFilters) {
2531 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2535 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2536 const auto inheritedDataView =
2537 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2539 chunkBatches.push_back(
2540 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2542 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2543 run_query_func<Func, TMode>(m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()});
2544 chunkBatches.clear();
2548 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2549 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
2550 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2554 auto indicesView = queryInfo.indices_mapping_view(i);
2555 const auto inheritedDataView =
2556 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2557 const auto& chunks = pArchetype->chunks();
2558 uint32_t chunkOffset = 0;
2559 uint32_t itemsLeft = chunks.size();
2560 while (itemsLeft > 0) {
2561 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2562 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2564 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2565 for (
auto* pChunk: chunkSpan) {
2569 if GAIA_UNLIKELY (from == to)
2572 if constexpr (HasFilters) {
2573 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2577 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2580 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2581 run_query_func<Func, TMode>(m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()});
2582 chunkBatches.clear();
2585 itemsLeft -= batchSize;
2586 chunkOffset += batchSize;
2592 if (!chunkBatches.empty())
2593 run_query_func<Func, TMode>(m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()});
2595 unlock(*m_storage.
world());
2598 commit_cmd_buffer_st(*m_storage.
world());
2599 commit_cmd_buffer_mt(*m_storage.
world());
2602 template <
bool HasFilters,
typename TMode,
typename Func, QueryExecType ExecType>
2603 void run_query_batch_no_group_id_par(
2604 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2605 static_assert(ExecType != QueryExecType::Default);
2606 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
2608 auto cacheView = queryInfo.cache_archetype_view();
2609 constexpr auto constraints = iter_mode_constraints<TMode>();
2611 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2614 const bool hasSortedBatchPayload =
2616 const auto sortView =
2617 hasSortedBatchPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
2618 if (needsBarrierCache)
2619 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2621 if (!sortView.empty()) {
2622 for (
const auto& view: sortView) {
2623 const auto* pArchetype = cacheView[view.archetypeIdx];
2624 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
2628 const auto viewFrom = view.startRow;
2629 const auto viewTo = (uint16_t)(view.startRow + view.count);
2630 uint16_t minStartRow = 0;
2631 uint16_t minEndRow = 0;
2632 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
2633 const auto startRow = core::get_max(minStartRow, viewFrom);
2634 const auto endRow = core::get_min(minEndRow, viewTo);
2635 const auto totalRows = endRow - startRow;
2639 if constexpr (HasFilters) {
2640 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
2644 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
2645 const auto inheritedDataView =
2646 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
2648 m_batches.push_back(
2649 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
2652 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2653 const auto* pArchetype = cacheView[i];
2654 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2658 auto indicesView = queryInfo.indices_mapping_view(i);
2659 const auto inheritedDataView =
2660 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2661 const auto& chunks = pArchetype->chunks();
2662 for (
auto* pChunk: chunks) {
2666 if GAIA_UNLIKELY (from == to)
2669 if constexpr (HasFilters) {
2670 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2674 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
2679 if (m_batches.empty())
2682 lock(*m_storage.
world());
2684 struct ParallelQueryBatchCtx {
2688 ParallelQueryBatchCtx
ctx{
this, &func};
2689 SchedParDesc desc{};
2691 desc.itemCount = (uint32_t)m_batches.size();
2693 desc.execType = ExecType;
2694 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2695 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
2696 run_query_func<Func, TMode>(
2697 ctx.pSelf->m_storage.world(), *
ctx.pFunc,
2698 std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2701 const auto& sched = world_sched(*m_storage.
world());
2702 const auto token = sched_par(sched, desc);
2703 sched_wait(sched, token);
2704 sched_del(sched, token);
2707 unlock(*m_storage.
world());
2710 commit_cmd_buffer_st(*m_storage.
world());
2711 commit_cmd_buffer_mt(*m_storage.
world());
2714 template <
bool HasFilters,
typename TMode,
typename Func>
2715 void run_query_batch_with_group_id(
2716 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2717 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
2719 ChunkBatchArray chunkBatches;
2721 auto cacheView = queryInfo.cache_archetype_view();
2722 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2723 constexpr auto constraints = iter_mode_constraints<TMode>();
2727 if (needsBarrierCache)
2728 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2730 lock(*m_storage.
world());
2732 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2733 const auto* pArchetype = cacheView[i];
2734 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2738 auto indicesView = queryInfo.indices_mapping_view(i);
2739 const auto inheritedDataView =
2740 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2741 const auto& chunks = pArchetype->chunks();
2742 const auto groupId = queryInfo.group_id(i);
2744#if GAIA_ASSERT_ENABLED
2747 m_groupIdSet == 0 ||
2749 groupId == m_groupIdSet);
2752 uint32_t chunkOffset = 0;
2753 uint32_t itemsLeft = chunks.size();
2754 while (itemsLeft > 0) {
2755 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
2756 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
2758 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
2759 for (
auto* pChunk: chunkSpan) {
2763 if GAIA_UNLIKELY (from == to)
2766 if constexpr (HasFilters) {
2767 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2771 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
2774 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
2775 run_query_func<Func, TMode>(m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()});
2776 chunkBatches.clear();
2779 itemsLeft -= batchSize;
2780 chunkOffset += batchSize;
2785 if (!chunkBatches.empty())
2786 run_query_func<Func, TMode>(m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()});
2788 unlock(*m_storage.
world());
2791 commit_cmd_buffer_st(*m_storage.
world());
2792 commit_cmd_buffer_mt(*m_storage.
world());
2795 template <
bool HasFilters,
typename TMode,
typename Func, QueryExecType ExecType>
2796 void run_query_batch_with_group_id_par(
2797 const QueryInfo& queryInfo,
const uint32_t idxFrom,
const uint32_t idxTo, Func func) {
2798 static_assert(ExecType != QueryExecType::Default);
2799 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
2801 ChunkBatchArray chunkBatch;
2803 auto cacheView = queryInfo.cache_archetype_view();
2804 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2805 constexpr auto constraints = iter_mode_constraints<TMode>();
2809 if (needsBarrierCache)
2810 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
2812#if GAIA_ASSERT_ENABLED
2813 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2814 const auto* pArchetype = cacheView[i];
2815 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2819 const auto groupId = queryInfo.group_id(i);
2822 m_groupIdSet == 0 ||
2824 groupId == m_groupIdSet);
2828 for (uint32_t i = idxFrom; i < idxTo; ++i) {
2829 const Archetype* pArchetype = cacheView[i];
2830 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2834 auto indicesView = queryInfo.indices_mapping_view(i);
2835 const auto inheritedDataView =
2836 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2837 const auto groupId = queryInfo.group_id(i);
2838 const auto& chunks = pArchetype->chunks();
2839 for (
auto* pChunk: chunks) {
2843 if GAIA_UNLIKELY (from == to)
2846 if constexpr (HasFilters) {
2847 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
2851 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
2855 if (m_batches.empty())
2858 lock(*m_storage.
world());
2860 struct ParallelQueryBatchCtx {
2864 ParallelQueryBatchCtx
ctx{
this, &func};
2865 SchedParDesc desc{};
2867 desc.itemCount = (uint32_t)m_batches.size();
2869 desc.execType = ExecType;
2870 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
2871 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
2872 run_query_func<Func, TMode>(
2873 ctx.pSelf->m_storage.world(), *
ctx.pFunc,
2874 std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart));
2877 const auto& sched = world_sched(*m_storage.
world());
2878 const auto token = sched_par(sched, desc);
2879 sched_wait(sched, token);
2880 sched_del(sched, token);
2883 unlock(*m_storage.
world());
2886 commit_cmd_buffer_st(*m_storage.
world());
2887 commit_cmd_buffer_mt(*m_storage.
world());
2892 template <
bool HasFilters, QueryExecType ExecType,
typename TMode,
typename Func>
2893 void run_query(
const QueryInfo& queryInfo, Func func) {
2894 GAIA_PROF_SCOPE(query::run_query);
2901 auto cache_view = queryInfo.cache_archetype_view();
2902 if (cache_view.empty())
2906 if (!cacheRange.hasSelectedGroup) {
2908 if constexpr (ExecType != QueryExecType::Default)
2909 run_query_batch_no_group_id_par<HasFilters, TMode, Func, ExecType>(
2910 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
2912 run_query_batch_no_group_id<HasFilters, TMode, Func>(
2913 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
2916 if (!cacheRange.valid)
2919 if constexpr (ExecType != QueryExecType::Default)
2920 run_query_batch_with_group_id_par<HasFilters, TMode, Func, ExecType>(
2921 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
2923 run_query_batch_with_group_id<HasFilters, TMode, Func>(
2924 queryInfo, cacheRange.idxFrom, cacheRange.idxTo, func);
2930 template <QueryExecType ExecType,
typename Func>
2931 void run_query_on_archetypes(QueryInfo& queryInfo, Func func, Constraints constraints) {
2935 lock(*m_storage.
world());
2938 GAIA_PROF_SCOPE(query::run_query_a);
2944 auto cache_view = queryInfo.cache_archetype_view();
2948 const bool hasInheritedData = queryInfo.has_inherited_data_payload();
2949 if (needsBarrierCache)
2950 queryInfo.ensure_depth_order_hierarchy_barrier_cache();
2951 GAIA_EACH(cache_view) {
2952 const auto* pArchetype = cache_view[i];
2953 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
2957 auto indicesView = queryInfo.indices_mapping_view(i);
2958 const auto inheritedDataView =
2959 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
2960 ChunkBatch batch{pArchetype,
nullptr, indicesView.data(), inheritedDataView, 0, 0, 0};
2965 unlock(*m_storage.
world());
2971 template <QueryExecType ExecType,
typename TMode,
typename Func>
2972 void run_query_on_chunks(QueryInfo& queryInfo, Func func) {
2974 ::gaia::ecs::update_version(*m_worldVersion);
2976 const bool hasFilters = queryInfo.has_filters();
2978 run_query<true, ExecType, TMode>(queryInfo, func);
2980 run_query<false, ExecType, TMode>(queryInfo, func);
2983 m_changedWorldVersion = *m_worldVersion;
2986 template <
typename Func>
2988 run_query_func_runtime(World* pWorld, Func func,
std::span<ChunkBatch> batches, Constraints constraints) {
2989 GAIA_PROF_SCOPE(query::run_query_func);
2991 const auto chunkCnt = batches.size();
2992 GAIA_ASSERT(chunkCnt > 0);
2995 it.init_query_state(pWorld, constraints,
false);
2997 const Archetype* pLastArchetype =
nullptr;
2998 const uint8_t* pLastIndices =
nullptr;
2999 InheritedTermDataView lastInheritedData{};
3000 GroupId lastGroupId = GroupIdMax;
3002 const auto apply_batch = [&](
const ChunkBatch& batch) {
3003 if (batch.pArchetype != pLastArchetype) {
3004 it.set_archetype(batch.pArchetype);
3005 pLastArchetype = batch.pArchetype;
3008 if (batch.pCompIndices != pLastIndices) {
3009 it.set_comp_indices(batch.pCompIndices);
3010 pLastIndices = batch.pCompIndices;
3013 if (batch.inheritedData.data() != lastInheritedData.data()) {
3014 it.set_inherited_data(batch.inheritedData);
3015 lastInheritedData = batch.inheritedData;
3018 if (batch.groupId != lastGroupId) {
3019 it.set_group_id(batch.groupId);
3020 lastGroupId = batch.groupId;
3023 it.set_chunk(batch.pChunk, batch.from, batch.to);
3025 finish_iter_writes(it);
3026 it.clear_touched_writes();
3029 if GAIA_UNLIKELY (chunkCnt == 1) {
3030 apply_batch(batches[0]);
3035 apply_batch(batches[0]);
3037 uint32_t chunkIdx = 1;
3038 for (; chunkIdx < chunkCnt - 1; ++chunkIdx) {
3040 apply_batch(batches[chunkIdx]);
3043 apply_batch(batches[chunkIdx]);
3046 template <
bool HasFilters,
typename Func>
3047 void run_query_batch_no_group_id_runtime(
3048 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3049 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id);
3051 auto cacheView = queryInfo.cache_archetype_view();
3052 const bool hasSortedPlanPayload =
3054 const auto sortView =
3055 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
3058 if (needsBarrierCache)
3059 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3061 lock(*m_storage.
world());
3062 ChunkBatchArray chunkBatches;
3064 if (!sortView.empty()) {
3065 for (
const auto& view: sortView) {
3066 auto* pArchetype =
const_cast<Archetype*
>(cacheView[view.archetypeIdx]);
3067 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3071 const auto viewFrom = view.startRow;
3072 const auto viewTo = (uint16_t)(view.startRow + view.count);
3073 uint16_t minStartRow = 0;
3074 uint16_t minEndRow = 0;
3075 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3076 const auto startRow = core::get_max(minStartRow, viewFrom);
3077 const auto endRow = core::get_min(minEndRow, viewTo);
3078 const auto totalRows = endRow - startRow;
3082 if constexpr (HasFilters) {
3083 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3087 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3088 const auto inheritedDataView =
3089 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3091 chunkBatches.push_back(
3092 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
3094 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3095 run_query_func_runtime(
3096 m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3097 chunkBatches.clear();
3101 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3102 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
3103 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3107 auto indicesView = queryInfo.indices_mapping_view(i);
3108 const auto inheritedDataView =
3109 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3110 const auto& chunks = pArchetype->chunks();
3111 uint32_t chunkOffset = 0;
3112 uint32_t itemsLeft = chunks.size();
3113 while (itemsLeft > 0) {
3114 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3115 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3117 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3118 for (
auto* pChunk: chunkSpan) {
3122 if GAIA_UNLIKELY (from == to)
3125 if constexpr (HasFilters) {
3126 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3130 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
3133 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3134 run_query_func_runtime(
3135 m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3136 chunkBatches.clear();
3139 itemsLeft -= batchSize;
3140 chunkOffset += batchSize;
3145 if (!chunkBatches.empty())
3146 run_query_func_runtime(m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3148 unlock(*m_storage.
world());
3149 commit_cmd_buffer_st(*m_storage.
world());
3150 commit_cmd_buffer_mt(*m_storage.
world());
3153 template <
bool HasFilters,
typename Func, QueryExecType ExecType>
3154 void run_query_batch_no_group_id_runtime_par(
3155 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3156 static_assert(ExecType != QueryExecType::Default);
3157 GAIA_PROF_SCOPE(query::run_query_batch_no_group_id_par);
3159 auto cacheView = queryInfo.cache_archetype_view();
3160 const bool hasSortedPlanPayload =
3162 const auto sortView =
3163 hasSortedPlanPayload ? queryInfo.cache_sort_view() :
decltype(queryInfo.cache_sort_view()){};
3166 if (needsBarrierCache)
3167 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3169 if (!sortView.empty()) {
3170 for (
const auto& view: sortView) {
3171 const auto* pArchetype = cacheView[view.archetypeIdx];
3172 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
3176 const auto viewFrom = view.startRow;
3177 const auto viewTo = (uint16_t)(view.startRow + view.count);
3178 uint16_t minStartRow = 0;
3179 uint16_t minEndRow = 0;
3180 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
3181 const auto startRow = core::get_max(minStartRow, viewFrom);
3182 const auto endRow = core::get_min(minEndRow, viewTo);
3183 const auto totalRows = endRow - startRow;
3187 if constexpr (HasFilters) {
3188 if (!
match_filters(*view.pChunk, queryInfo, m_changedWorldVersion))
3192 auto indicesView = queryInfo.indices_mapping_view(view.archetypeIdx);
3193 const auto inheritedDataView =
3194 hasInheritedData ? queryInfo.inherited_data_view(view.archetypeIdx) : InheritedTermDataView{};
3196 m_batches.push_back(
3197 {pArchetype, view.pChunk, indicesView.data(), inheritedDataView, 0U, startRow, endRow});
3200 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3201 const auto* pArchetype = cacheView[i];
3202 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3206 auto indicesView = queryInfo.indices_mapping_view(i);
3207 const auto inheritedDataView =
3208 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3209 const auto& chunks = pArchetype->chunks();
3210 for (
auto* pChunk: chunks) {
3214 if GAIA_UNLIKELY (from == to)
3217 if constexpr (HasFilters) {
3218 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3222 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, 0, from, to});
3227 if (m_batches.empty())
3230 lock(*m_storage.
world());
3232 struct ParallelQueryBatchCtx {
3235 Constraints constraints;
3237 ParallelQueryBatchCtx
ctx{
this, &func, constraints};
3238 SchedParDesc desc{};
3240 desc.itemCount = (uint32_t)m_batches.size();
3242 desc.execType = ExecType;
3243 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3244 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
3245 run_query_func_runtime(
3246 ctx.pSelf->m_storage.world(), *
ctx.pFunc,
std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3250 const auto& sched = world_sched(*m_storage.
world());
3251 const auto token = sched_par(sched, desc);
3252 sched_wait(sched, token);
3253 sched_del(sched, token);
3256 unlock(*m_storage.
world());
3257 commit_cmd_buffer_st(*m_storage.
world());
3258 commit_cmd_buffer_mt(*m_storage.
world());
3261 template <
bool HasFilters,
typename Func>
3262 void run_query_batch_with_group_id_runtime(
3263 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3264 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id);
3266 ChunkBatchArray chunkBatches;
3267 auto cacheView = queryInfo.cache_archetype_view();
3270 if (needsBarrierCache)
3271 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3273 lock(*m_storage.
world());
3275 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3276 const auto* pArchetype = cacheView[i];
3277 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3281 auto indicesView = queryInfo.indices_mapping_view(i);
3282 const auto inheritedDataView =
3283 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3284 const auto& chunks = pArchetype->chunks();
3285 const auto groupId = queryInfo.group_id(i);
3287 uint32_t chunkOffset = 0;
3288 uint32_t itemsLeft = chunks.size();
3289 while (itemsLeft > 0) {
3290 const auto maxBatchSize = chunkBatches.max_size() - chunkBatches.size();
3291 const auto batchSize = itemsLeft > maxBatchSize ? maxBatchSize : itemsLeft;
3293 ChunkSpanMut chunkSpan((Chunk**)&chunks[chunkOffset], batchSize);
3294 for (
auto* pChunk: chunkSpan) {
3298 if GAIA_UNLIKELY (from == to)
3301 if constexpr (HasFilters) {
3302 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3306 chunkBatches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3309 if GAIA_UNLIKELY (chunkBatches.size() == chunkBatches.max_size()) {
3310 run_query_func_runtime(
3311 m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3312 chunkBatches.clear();
3315 itemsLeft -= batchSize;
3316 chunkOffset += batchSize;
3320 if (!chunkBatches.empty())
3321 run_query_func_runtime(m_storage.
world(), func, {chunkBatches.data(), chunkBatches.size()}, constraints);
3323 unlock(*m_storage.
world());
3324 commit_cmd_buffer_st(*m_storage.
world());
3325 commit_cmd_buffer_mt(*m_storage.
world());
3328 template <
bool HasFilters,
typename Func, QueryExecType ExecType>
3329 void run_query_batch_with_group_id_runtime_par(
3330 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3331 static_assert(ExecType != QueryExecType::Default);
3332 GAIA_PROF_SCOPE(query::run_query_batch_with_group_id_par);
3334 ChunkBatchArray chunkBatch;
3335 auto cacheView = queryInfo.cache_archetype_view();
3338 if (needsBarrierCache)
3339 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
3341 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
3342 const auto* pArchetype = cacheView[i];
3343 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
3347 auto indicesView = queryInfo.indices_mapping_view(i);
3348 const auto inheritedDataView =
3349 hasInheritedData ? queryInfo.inherited_data_view(i) : InheritedTermDataView{};
3350 const auto groupId = queryInfo.group_id(i);
3351 const auto& chunks = pArchetype->chunks();
3352 for (
auto* pChunk: chunks) {
3356 if GAIA_UNLIKELY (from == to)
3359 if constexpr (HasFilters) {
3360 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
3364 m_batches.push_back({pArchetype, pChunk, indicesView.data(), inheritedDataView, groupId, from, to});
3368 if (m_batches.empty())
3371 lock(*m_storage.
world());
3373 struct ParallelQueryBatchCtx {
3376 Constraints constraints;
3378 ParallelQueryBatchCtx
ctx{
this, &func, constraints};
3379 SchedParDesc desc{};
3381 desc.itemCount = (uint32_t)m_batches.size();
3383 desc.execType = ExecType;
3384 desc.invoke = [](
void* pCtx, uint32_t idxStart, uint32_t idxEnd) {
3385 auto&
ctx = *
reinterpret_cast<ParallelQueryBatchCtx*
>(pCtx);
3386 run_query_func_runtime(
3387 ctx.pSelf->m_storage.world(), *
ctx.pFunc,
std::span(&
ctx.pSelf->m_batches[idxStart], idxEnd - idxStart),
3391 const auto& sched = world_sched(*m_storage.
world());
3392 const auto token = sched_par(sched, desc);
3393 sched_wait(sched, token);
3394 sched_del(sched, token);
3397 unlock(*m_storage.
world());
3398 commit_cmd_buffer_st(*m_storage.
world());
3399 commit_cmd_buffer_mt(*m_storage.
world());
3402 template <
bool HasFilters, QueryExecType ExecType,
typename Func>
3403 void run_query_runtime_planned(
3404 const QueryInfo& queryInfo,
const QueryPlan& plan, Constraints constraints, Func func) {
3405 GAIA_PROF_SCOPE(query::run_query);
3410 if (!cacheRange.hasSelectedGroup) {
3411 if constexpr (ExecType != QueryExecType::Default)
3412 run_query_batch_no_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3414 run_query_batch_no_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3416 if constexpr (ExecType != QueryExecType::Default)
3417 run_query_batch_with_group_id_runtime_par<HasFilters, Func, ExecType>(queryInfo, plan, constraints, func);
3419 run_query_batch_with_group_id_runtime<HasFilters>(queryInfo, plan, constraints, func);
3430 template <QueryExecType ExecType,
typename Func>
3436 ::gaia::ecs::update_version(*m_worldVersion);
3438 run_query_runtime_planned<true, ExecType>(queryInfo, plan, constraints, func);
3440 run_query_runtime_planned<false, ExecType>(queryInfo, plan, constraints, func);
3442 m_changedWorldVersion = *m_worldVersion;
3450 const auto& data = queryInfo.
ctx().data;
3451 return data.sortByFunc ==
nullptr &&
3462 const auto& data = queryInfo.
ctx().data;
3463 if (data.groupBy == EntityBad || m_groupIdSet == 0)
3466 range.hasSelectedGroup =
true;
3468 if (pGroupData ==
nullptr) {
3471 range.valid =
false;
3475 range.idxFrom = pGroupData->idxFirst;
3476 range.idxTo = pGroupData->idxLast + 1;
3486 template <
bool HasFilters,
typename Func,
typename... T>
3491 void run_query_on_chunks_direct(
3495 void run_query_on_chunks_direct_iter(
3499 template <QueryExecType ExecType>
3505 bool needsInheritedArgIds,
void (*invokeInherited)(
World&,
Entity,
const Entity*,
void*));
3507 void each_typed_erased(
3512 bool needsInheritedArgIds,
void (*invokeInherited)(
World&,
Entity,
const Entity*,
void*));
3514 template <QueryExecType ExecType,
typename Func>
3515 void each_typed_inter(
QueryInfo& queryInfo, Func func);
3517 template <QueryExecType ExecType>
3518 void each_iter_inter_erased(
3523 void each_walk_inter(
3535 plan.idxFrom = cacheRange.idxFrom;
3536 plan.idxTo = cacheRange.idxTo;
3540 bool hasConstrainedDepthOrderBarrier = constraints != Constraints::AcceptAll && hasDepthOrderBarrier;
3549 if (hasSortedPayload || hasDepthOrderBarrier)
3553 if (cacheRange.hasSelectedGroup) {
3556 if (!cacheRange.valid) {
3567 if (plan.idxFrom >= plan.idxTo) {
3573 hasConstrainedDepthOrderBarrier =
false;
3577 if (hasConstrainedDepthOrderBarrier)
3580 if (hasSortedPayload) {
3596 if (hasConstrainedDepthOrderBarrier)
3620 template <
bool HasFilters,
bool HasGroups,
typename Func>
3623 ::gaia::ecs::update_version(*m_worldVersion);
3626 lock(*m_storage.
world());
3632 auto* pArchetype =
const_cast<Archetype*
>(cacheView[i]);
3637 const auto* pIndices = indicesView.data();
3638 const auto groupId = HasGroups ? queryInfo.
group_id(i) : GroupId(0);
3639 const auto& chunks = pArchetype->chunks();
3640 for (
auto* pChunk: chunks) {
3641 const auto from = detail::ChunkIterImpl::start_index(pChunk, constraints);
3642 const auto to = detail::ChunkIterImpl::end_index(pChunk, constraints);
3643 if GAIA_UNLIKELY (from == to)
3645 if constexpr (HasFilters) {
3646 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion, indicesView))
3651 if constexpr (HasGroups)
3652 it.set_group_id(groupId);
3655 GAIA_PROF_SCOPE(query_func);
3658 finish_iter_writes(it);
3659 it.clear_touched_writes();
3663 unlock(*m_storage.
world());
3664 commit_cmd_buffer_st(*m_storage.
world());
3665 commit_cmd_buffer_mt(*m_storage.
world());
3666 m_changedWorldVersion = *m_worldVersion;
3674 template <QueryExecType ExecType,
typename Func>
3676 if constexpr (ExecType == QueryExecType::Default) {
3677 auto& queryInfo =
fetch();
3684 run_query_on_chunks_runtime_direct_plain_impl<true, true>(queryInfo, plan, constraints, func);
3686 run_query_on_chunks_runtime_direct_plain_impl<true, false>(queryInfo, plan, constraints, func);
3689 run_query_on_chunks_runtime_direct_plain_impl<false, true>(queryInfo, plan, constraints, func);
3691 run_query_on_chunks_runtime_direct_plain_impl<false, false>(queryInfo, plan, constraints, func);
3697 queryInfo, plan, ExecType,
static_cast<void*
>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
3701 each_runtime_erased(ExecType,
static_cast<void*
>(&func), &invoke_runtime_iter<Func, Iter>, constraints);
3709 template <
typename Func,
typename TIter>
3711 auto& func = *
static_cast<Func*
>(pFunc);
3718 void (*invoke)(
void*,
Iter&);
3720 void operator()(
Iter& it)
const {
3721 GAIA_PROF_SCOPE(query_func);
3733 void operator()(
Iter& it)
const {
3734 GAIA_PROF_SCOPE(query_func);
3736 runChunk(*pSelf, it, pFunc, *pState);
3747 void operator()(
Iter& it)
const {
3748 GAIA_PROF_SCOPE(query_func);
3750 runChunk(*pSelf, *pQueryInfo, it, pFunc, *pState);
3761 void operator()(
Iter& it)
const {
3762 GAIA_PROF_SCOPE(query_func);
3764 pSelf->each_iter_erased(it, pFunc, *pState, runDirect, runChunk);
3774 QueryExecType execType,
void* pFunc,
void (*invoke)(
void*,
Iter&), Constraints constraints) {
3775 auto& queryInfo =
fetch();
3790 void (*invoke)(
void*,
Iter&), Constraints constraints) {
3799 case QueryExecType::Parallel:
3800 run_query_on_chunks_runtime_planned<QueryExecType::Parallel>(queryInfo, plan, constraints, cb);
3802 case QueryExecType::ParallelPerf:
3803 run_query_on_chunks_runtime_planned<QueryExecType::ParallelPerf>(queryInfo, plan, constraints, cb);
3805 case QueryExecType::ParallelEff:
3806 run_query_on_chunks_runtime_planned<QueryExecType::ParallelEff>(queryInfo, plan, constraints, cb);
3809 run_query_on_chunks_runtime_planned<QueryExecType::Default>(queryInfo, plan, constraints, cb);
3821 if (term.
src != EntityBad || term.
entTrav != EntityBad || term_has_variables(term))
3824 const auto id = term.
id;
3825 return (
id.
pair() && world_is_exclusive_dont_fragment_relation(world, pair_rel(world,
id))) ||
3826 (!
id.pair() && world_is_non_fragmenting_out_of_line_component(world,
id));
3833 const auto id = term.
id;
3834 return term.
matchKind == QueryMatchKind::Semantic && term.
src == EntityBad && term.
entTrav == EntityBad &&
3835 !term_has_variables(term) &&
id.pair() &&
id.id() == Is.id() && !is_wildcard(
id.
gen()) &&
3836 !is_variable((EntityId)
id.
gen());
3843 const auto id = term.
id;
3844 return term.
matchKind == QueryMatchKind::In && term.
src == EntityBad && term.
entTrav == EntityBad &&
3845 !term_has_variables(term) &&
id.pair() &&
id.id() == Is.id() && !is_wildcard(
id.
gen()) &&
3846 !is_variable((EntityId)
id.
gen());
3861 return query_term_uses_potential_inherited_id_matching(term);
3875 return world_has_entity_term(world, entity, term.
id);
3877 return world_has_entity_term_in(world, entity, term.
id);
3879 return world_has_entity_term_direct(world, entity, term.
id);
3886 case QueryCtx::DirectTargetEvalKind::SingleAllSemanticIs:
3887 case QueryCtx::DirectTargetEvalKind::SingleAllInherited:
3888 return world_has_entity_term(world, entity, termId);
3889 case QueryCtx::DirectTargetEvalKind::SingleAllInIs:
3890 return world_has_entity_term_in(world, entity, termId);
3891 case QueryCtx::DirectTargetEvalKind::SingleAllDirect:
3892 return world_has_entity_term_direct(world, entity, termId);
3893 case QueryCtx::DirectTargetEvalKind::Generic:
3900 GAIA_NODISCARD
static uint32_t count_direct_term_entities(
const World& world,
const QueryTerm& term) {
3902 return world_count_direct_term_entities(world, term.
id);
3904 return world_count_in_term_entities(world, term.
id);
3906 return world_count_direct_term_entities_direct(world, term.
id);
3911 world_collect_direct_term_entities(world, term.
id, out);
3915 world_collect_in_term_entities(world, term.
id, out);
3919 world_collect_direct_term_entities_direct(world, term.
id, out);
3922 template <
typename Func>
3923 GAIA_NODISCARD
static bool for_each_direct_term_entity(
const World& world,
const QueryTerm& term, Func&& func) {
3926 static bool thunk(
void* ctx, Entity entity) {
3927 return static_cast<Visitor*
>(ctx)->func(entity);
3931 Visitor visitor{func};
3933 return world_for_each_direct_term_entity(world, term.id, &visitor, &Visitor::thunk);
3935 return world_for_each_in_term_entity(world, term.id, &visitor, &Visitor::thunk);
3937 return world_for_each_direct_term_entity_direct(world, term.id, &visitor, &Visitor::thunk);
3945 const auto& world = *queryInfo.
world();
3946 bool hasSeedableTerm =
false;
3947 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
3948 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Or)
3952 hasSeedableTerm =
true;
3955 return hasSeedableTerm;
3975 auto& run = runs.back();
3976 if (ec.
pChunk == run.pChunk && ec.
row == run.to) {
3977 run.to = (uint16_t)(run.to + 1);
3985 Entity seededAllTerm = EntityBad;
3986 QueryMatchKind seededAllMatchKind = QueryMatchKind::Semantic;
3987 bool seededFromAll =
false;
3988 bool seededFromOr =
false;
3993 Entity bestAllTerm = EntityBad;
3994 uint32_t bestAllTermCount = UINT32_MAX;
3995 QueryMatchKind bestAllTermMatchKind = QueryMatchKind::Semantic;
3996 bool hasAllTerms =
false;
3997 bool hasOrTerms =
false;
3998 bool preferOrSeed =
false;
4013 const bool bestIsSemanticIs = plan.bestAllTermMatchKind != QueryMatchKind::Direct &&
4014 plan.bestAllTerm.pair() && plan.bestAllTerm.id() == Is.id() &&
4015 !is_wildcard(plan.bestAllTerm.gen()) &&
4016 !is_variable((EntityId)plan.bestAllTerm.gen());
4017 const auto adjustedCandidateCount = candidateCount - (candidateIsSemanticIs && candidateCount > 0 ? 1U : 0U);
4018 const auto adjustedBestCount =
4019 plan.bestAllTermCount - (bestIsSemanticIs && plan.bestAllTermCount > 0 ? 1U : 0U);
4020 if (adjustedCandidateCount < adjustedBestCount)
4022 if (adjustedCandidateCount > adjustedBestCount)
4024 if (plan.bestAllTerm == EntityBad)
4027 if (candidateIsSemanticIs != bestIsSemanticIs)
4028 return candidateIsSemanticIs;
4031 const bool bestUsesInherited = plan.bestAllTermMatchKind == QueryMatchKind::Semantic &&
4032 !is_wildcard(plan.bestAllTerm) &&
4033 !is_variable((EntityId)plan.bestAllTerm.id()) &&
4034 (!plan.bestAllTerm.pair() || !is_variable((EntityId)plan.bestAllTerm.gen())) &&
4035 world_term_uses_inherit_policy(world, plan.bestAllTerm);
4036 if (candidateUsesInherited != bestUsesInherited)
4037 return !candidateUsesInherited;
4042 GAIA_NODISCARD
static DirectEntitySeedPlan
4043 direct_entity_seed_plan(
const World& world,
const QueryInfo& queryInfo) {
4044 DirectEntitySeedPlan plan;
4045 uint32_t totalOrTermCount = 0;
4047 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4048 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4050 if (term.op == QueryOpKind::All) {
4051 plan.hasAllTerms =
true;
4052 const auto cnt = count_direct_term_entities(world, term);
4054 plan.bestAllTermCount = cnt;
4055 plan.bestAllTerm = term.id;
4056 plan.bestAllTermMatchKind = term.matchKind;
4058 }
else if (term.op == QueryOpKind::Or) {
4059 plan.hasOrTerms =
true;
4060 totalOrTermCount += count_direct_term_entities(world, term);
4064 plan.preferOrSeed = plan.hasOrTerms && (!plan.hasAllTerms || totalOrTermCount < plan.bestAllTermCount);
4071 bool hasOrTerms =
false;
4072 bool anyOrMatched =
false;
4074 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4075 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4077 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4078 term.matchKind == seedInfo.seededAllMatchKind)
4080 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4085 case QueryOpKind::All:
4089 case QueryOpKind::Or:
4091 anyOrMatched |= present;
4093 case QueryOpKind::Not:
4097 case QueryOpKind::Any:
4098 case QueryOpKind::Count:
4103 return !hasOrTerms || anyOrMatched;
4107 find_direct_all_seed_term(
const QueryInfo& queryInfo,
const DirectEntitySeedPlan& plan) {
4108 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4109 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4111 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4112 term.matchKind != plan.bestAllTermMatchKind)
4120 GAIA_NODISCARD
static DirectEntitySeedEvalPlan
4121 direct_all_seed_eval_plan(
const QueryInfo& queryInfo,
const DirectEntitySeedInfo& seedInfo) {
4122 DirectEntitySeedEvalPlan plan{};
4124 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4125 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4127 if (seedInfo.seededFromAll && term.op == QueryOpKind::All && term.id == seedInfo.seededAllTerm &&
4128 term.matchKind == seedInfo.seededAllMatchKind)
4131 if (term.op == QueryOpKind::All) {
4132 if (plan.pSingleAllTerm !=
nullptr)
4141 plan.alwaysMatch = plan.pSingleAllTerm ==
nullptr;
4150 GAIA_NODISCARD
static bool
4155 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4156 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4158 if (term.op == QueryOpKind::Any || term.op == QueryOpKind::Count || term.op == QueryOpKind::Or)
4160 if (term.op == QueryOpKind::All && term.id == seedTerm.
id && term.matchKind == seedTerm.
matchKind)
4170 QueryInfo& queryInfo,
const QueryTerm& seedTerm,
const DirectEntitySeedInfo& seedInfo,
4171 Constraints constraints) {
4172 auto& runData = ensure_direct_seed_run_data();
4173 auto& world = *queryInfo.
world();
4174 const auto cachedConstraints = constraints;
4175 const auto relVersion = world_rel_version(world, Is);
4176 const auto worldVersion = ::gaia::ecs::world_version(world);
4178 if (runData.cacheValid && runData.cachedSeedTerm == seedTerm.
id &&
4179 runData.cachedSeedMatchKind == seedTerm.
matchKind && runData.cachedConstraints == cachedConstraints &&
4180 runData.cachedRelVersion == relVersion && runData.cachedWorldVersion == worldVersion) {
4181 return {runData.cachedRuns.data(), runData.cachedRuns.size()};
4184 auto& runs = runData.cachedRuns;
4185 auto& entities = runData.cachedEntities;
4186 auto& chunkOrderedEntities = runData.cachedChunkOrderedEntities;
4189 chunkOrderedEntities.clear();
4191 (void)for_each_direct_term_entity(world, seedTerm, [&](Entity entity) {
4198 entities.push_back(entity);
4202 chunkOrderedEntities = entities;
4203 core::sort(chunkOrderedEntities, [&](Entity left, Entity right) {
4204 const auto& ecLeft = ::gaia::ecs::fetch(world, left);
4205 const auto& ecRight = ::gaia::ecs::fetch(world, right);
4206 if (ecLeft.pArchetype != ecRight.pArchetype)
4207 return ecLeft.pArchetype->id() < ecRight.pArchetype->id();
4208 if (ecLeft.pChunk != ecRight.pChunk)
4209 return ecLeft.pChunk < ecRight.pChunk;
4210 return ecLeft.row < ecRight.row;
4213 uint32_t entityOffset = 0;
4214 for (
const auto entity: chunkOrderedEntities) {
4215 const auto& ec = ::gaia::ecs::fetch(world, entity);
4216 add_chunk_run(runs, ec, entityOffset++);
4219 runData.cachedSeedTerm = seedTerm.
id;
4220 runData.cachedSeedMatchKind = seedTerm.
matchKind;
4221 runData.cachedConstraints = cachedConstraints;
4222 runData.cachedRelVersion = relVersion;
4223 runData.cachedWorldVersion = worldVersion;
4224 runData.cacheValid =
true;
4225 return {runs.data(), runs.size()};
4229 QueryInfo& queryInfo,
const QueryTerm& seedTerm,
const DirectEntitySeedInfo& seedInfo,
4230 Constraints constraints) {
4231 (void)cached_direct_seed_runs(queryInfo, seedTerm, seedInfo, constraints);
4232 auto& runData = ensure_direct_seed_run_data();
4233 return {runData.cachedChunkOrderedEntities.data(), runData.cachedChunkOrderedEntities.size()};
4236 template <
typename Func>
4237 GAIA_NODISCARD
static bool for_each_direct_all_seed(
4238 const World& world,
const QueryInfo& queryInfo,
const DirectEntitySeedPlan& plan, Constraints constraints,
4240 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
4241 GAIA_ASSERT(pSeedTerm !=
nullptr);
4242 if (pSeedTerm ==
nullptr)
4245 DirectEntitySeedInfo seedInfo{};
4246 seedInfo.seededAllTerm = pSeedTerm->id;
4247 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
4248 seedInfo.seededFromAll =
true;
4249 const auto evalPlan = direct_all_seed_eval_plan(queryInfo, seedInfo);
4250 const Archetype* pLastSingleAllArchetype =
nullptr;
4251 bool lastSingleAllMatch =
false;
4252 bool seedImpliesSingleAllTerm =
false;
4256 const auto seedTarget = pair_tgt(world, pSeedTerm->id);
4257 if (seedTarget != EntityBad)
4258 seedImpliesSingleAllTerm =
match_entity_term(world, seedTarget, *evalPlan.pSingleAllTerm);
4263 return for_each_direct_term_entity(world, *pSeedTerm, [&](Entity entity) {
4267 if (evalPlan.alwaysMatch)
4268 return func(entity);
4269 if (evalPlan.pSingleAllTerm !=
nullptr) {
4270 if (seedImpliesSingleAllTerm)
4271 return func(entity);
4272 if (uses_non_direct_is_matching(*evalPlan.pSingleAllTerm) ||
4273 uses_inherited_id_matching(world, *evalPlan.pSingleAllTerm)) {
4274 const auto* pArchetype = world_entity_archetype(world, entity);
4275 if (pArchetype != pLastSingleAllArchetype) {
4276 lastSingleAllMatch = match_entity_term(world, entity, *evalPlan.pSingleAllTerm);
4277 pLastSingleAllArchetype = pArchetype;
4279 if (!lastSingleAllMatch)
4284 return func(entity);
4289 return func(entity);
4299 if (constraints == Constraints::EnabledOnly)
4300 return world_entity_enabled(world, entity);
4301 if (constraints == Constraints::DisabledOnly)
4302 return !world_entity_enabled(world, entity);
4309 if (!seedInfo.seededFromAll && !seedInfo.seededFromOr)
4312 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4313 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4315 if (seedInfo.seededFromAll && term.id == seedInfo.seededAllTerm && term.op == QueryOpKind::All)
4317 if (seedInfo.seededFromOr && term.op == QueryOpKind::Or)
4319 if (term.op != QueryOpKind::All && term.op != QueryOpKind::Not)
4334 auto& scratch = direct_query_scratch();
4336 scratch.archetypes.clear();
4337 scratch.bucketEntities.clear();
4338 scratch.counts.clear();
4340 for (
const auto entity: seedEntities) {
4344 const auto* pArchetype = world_entity_archetype(world, entity);
4345 const auto idx = core::get_index(scratch.archetypes, pArchetype);
4347 scratch.archetypes.push_back(pArchetype);
4348 scratch.bucketEntities.push_back(entity);
4349 scratch.counts.push_back(1);
4351 ++scratch.counts[idx];
4356 const auto archetypeCnt = (uint32_t)scratch.archetypes.size();
4357 GAIA_FOR(archetypeCnt) {
4359 cnt += scratch.counts[i];
4366 GAIA_NODISCARD
static uint32_t
4368 auto& scratch = direct_query_scratch();
4369 const auto seenVersion = next_direct_query_seen_version(scratch);
4373 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4374 if (term.op != QueryOpKind::Or)
4377 (void)for_each_direct_term_entity(world, term, [&](
Entity entity) {
4381 const auto entityId = (uint32_t)entity.id();
4382 ensure_direct_query_count_capacity(scratch, entityId);
4384 if (scratch.counts[entityId] == seenVersion)
4386 scratch.counts[entityId] = seenVersion;
4388 bool rejected =
false;
4389 if (hasDirectNotTerms) {
4390 for (
const auto& notTerm: queryInfo.
ctx().data.terms_view()) {
4391 if (notTerm.op != QueryOpKind::Not)
4414 GAIA_NODISCARD
static bool
4416 auto& scratch = direct_query_scratch();
4417 const auto seenVersion = next_direct_query_seen_version(scratch);
4420 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4421 if (term.op != QueryOpKind::Or)
4424 const bool completed = for_each_direct_term_entity(world, term, [&](
Entity entity) {
4428 const auto entityId = (uint32_t)entity.id();
4429 ensure_direct_query_count_capacity(scratch, entityId);
4431 if (scratch.counts[entityId] == seenVersion)
4433 scratch.counts[entityId] = seenVersion;
4435 bool rejected =
false;
4436 if (hasDirectNotTerms) {
4437 for (
const auto& notTerm: queryInfo.
ctx().data.terms_view()) {
4438 if (notTerm.op != QueryOpKind::Not)
4460 static DirectEntitySeedInfo
4462 auto& scratch = direct_query_scratch();
4465 const auto plan = direct_entity_seed_plan(world, queryInfo);
4467 if (plan.hasAllTerms && !plan.preferOrSeed) {
4468 if (plan.bestAllTerm != EntityBad) {
4469 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4470 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4472 if (term.op != QueryOpKind::All || term.id != plan.bestAllTerm ||
4473 term.matchKind != plan.bestAllTermMatchKind)
4475 collect_direct_term_entities(world, term, out);
4476 seedInfo.seededAllMatchKind = term.matchKind;
4479 seedInfo.seededFromAll =
true;
4480 seedInfo.seededAllTerm = plan.bestAllTerm;
4485 const auto seenVersion = next_direct_query_seen_version(scratch);
4487 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4488 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4490 if (term.op != QueryOpKind::Or)
4493 scratch.termEntities.clear();
4494 collect_direct_term_entities(world, term, scratch.termEntities);
4495 for (
const auto entity: scratch.termEntities) {
4496 const auto entityId = (uint32_t)entity.id();
4497 ensure_direct_query_count_capacity(scratch, entityId);
4499 if (scratch.counts[entityId] == seenVersion)
4501 scratch.counts[entityId] = seenVersion;
4502 out.push_back(entity);
4506 seedInfo.seededFromOr =
true;
4514 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4515 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4517 if (term.op == QueryOpKind::Not)
4530 template <
typename Func>
4532 auto& scratch = direct_query_scratch();
4533 const auto seenVersion = next_direct_query_seen_version(scratch);
4535 seedInfo.seededFromOr =
true;
4537 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4538 if (term.op != QueryOpKind::Or)
4541 (void)for_each_direct_term_entity(world, term, [&](
Entity entity) {
4545 const auto entityId = (uint32_t)entity.id();
4546 ensure_direct_query_count_capacity(scratch, entityId);
4548 if (scratch.counts[entityId] == seenVersion)
4550 scratch.counts[entityId] = seenVersion;
4567 template <
bool UseFilters>
4571 if constexpr (!UseFilters) {
4576 const auto plan = direct_entity_seed_plan(*queryInfo.
world(), queryInfo);
4578 (void)for_each_direct_all_seed(*queryInfo.
world(), queryInfo, plan, constraints, [&](
Entity) {
4589 if (needsBarrierCache)
4590 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4591 if (!cacheRange.valid)
4593 const auto idxFrom = cacheRange.idxFrom;
4594 const auto idxTo = cacheRange.idxTo;
4596 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4597 const auto* pArchetype = cacheView[qi];
4598 const bool barrierPasses = !needsBarrierCache || queryInfo.
barrier_passes(qi);
4602 GAIA_PROF_SCOPE(query::empty);
4604 const auto& chunks = pArchetype->chunks();
4607 it.set_archetype(pArchetype);
4609 if (!hasEntityFilters) {
4610 for (
auto* pChunk: chunks) {
4616 it.set_chunk(pChunk, from, to);
4617 if constexpr (UseFilters) {
4618 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4626 const bool isNotEmpty = core::has_if(chunks, [&](
Chunk* pChunk) {
4632 it.set_chunk(pChunk, from, to);
4633 if constexpr (UseFilters)
4634 if (it.size() == 0 || !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4636 if (!hasEntityFilters)
4637 return it.size() > 0;
4639 const auto entities = it.template view<Entity>();
4640 const auto cnt = it.size();
4657 bool hasOrTerms =
false;
4658 bool anyOrMatched =
false;
4661 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4662 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4665 const auto id = term.id;
4668 const bool isAdjunctTerm =
4669 (
id.pair() && world_is_exclusive_dont_fragment_relation(world, pair_rel(world,
id))) ||
4670 (!
id.
pair() && world_is_non_fragmenting_out_of_line_component(world,
id));
4671 const bool needsEntityFilter = isAdjunctTerm || isDirectIsTerm || isInheritedTerm ||
4672 (hasEntityFilterTerms && term.op == QueryOpKind::Or);
4673 if (!needsEntityFilter)
4678 case QueryOpKind::All:
4682 case QueryOpKind::Or:
4684 anyOrMatched |= present;
4686 case QueryOpKind::Not:
4690 case QueryOpKind::Any:
4691 case QueryOpKind::Count:
4696 return !hasOrTerms || anyOrMatched;
4706 if (targetEntities.empty())
4709 const auto& world = *queryInfo.
world();
4713 if (directTargetEvalKind != QueryCtx::DirectTargetEvalKind::Generic) {
4715 if (targetEntities.size() == 1) {
4716 const auto entity = targetEntities[0];
4722 for (
const auto entity: targetEntities) {
4733 if (targetEntities.size() == 1) {
4734 const auto entity = targetEntities[0];
4740 for (
const auto entity: targetEntities) {
4750 if (!
match_one(queryInfo, archetype, targetEntities))
4756 for (
const auto entity: targetEntities) {
4772 template <
bool UseFilters>
4776 if constexpr (!UseFilters) {
4778 auto& scratch = direct_query_scratch();
4782 const auto plan = direct_entity_seed_plan(*queryInfo.
world(), queryInfo);
4787 *queryInfo.
world(), queryInfo, scratch.entities, seedInfo, constraints);
4790 (void)for_each_direct_all_seed(*queryInfo.
world(), queryInfo, plan, constraints, [&](
Entity) {
4803 if (needsBarrierCache)
4804 const_cast<QueryInfo&
>(queryInfo).ensure_depth_order_hierarchy_barrier_cache();
4806 if (!cacheRange.valid)
4808 const auto idxFrom = cacheRange.idxFrom;
4809 const auto idxTo = cacheRange.idxTo;
4811 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
4812 const auto* pArchetype = cacheView[qi];
4813 const bool barrierPasses = !needsBarrierCache || queryInfo.
barrier_passes(qi);
4817 GAIA_PROF_SCOPE(query::count);
4819 const auto& chunks = pArchetype->chunks();
4822 it.set_archetype(pArchetype);
4824 if (!hasEntityFilters) {
4825 for (
auto* pChunk: chunks) {
4829 const auto entityCnt = to - from;
4832 it.set_chunk(pChunk, from, to);
4834 if constexpr (UseFilters) {
4835 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4843 for (
auto* pChunk: chunks) {
4847 const auto entityCnt = to - from;
4850 it.set_chunk(pChunk, from, to);
4853 if constexpr (UseFilters) {
4854 if (!
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
4858 if (hasEntityFilters) {
4859 const auto entities = it.template view<Entity>();
4860 GAIA_FOR(entityCnt) {
4875 static void init_direct_entity_iter(
4879 GAIA_ASSERT(ec.
pChunk !=
nullptr);
4885 pTermIds[i] = EntityBad;
4888 const auto terms = queryInfo.
ctx().data.terms_view();
4889 const auto queryIdCnt = (uint32_t)terms.size();
4891 GAIA_FOR(queryIdCnt) {
4892 const auto& term = terms[i];
4893 const auto fieldIdx = term.fieldIndex;
4894 const auto queryId = term.id;
4895 pTermIds[fieldIdx] = queryId;
4896 if (!indicesView.empty()) {
4897 pIndices[fieldIdx] = indicesView[fieldIdx];
4900 if (!queryId.pair() && world_is_out_of_line_component(world, queryId)) {
4901#if GAIA_ASSERT_ENABLED
4902 const auto compIdx = core::get_index_unsafe(ec.
pArchetype->ids_view(), queryId);
4905 pIndices[fieldIdx] = 0xFF;
4909 auto compIdx = world_component_index_comp_idx(world, *ec.
pArchetype, queryId);
4911 compIdx = core::get_index(ec.
pArchetype->ids_view(), queryId);
4912 pIndices[fieldIdx] = (uint8_t)compIdx;
4916 it.set_comp_indices(pIndices);
4918 it.set_inherited_data(inheritedDataView);
4919 it.set_term_ids(pTermIds);
4923 it.set_chunk(ec.
pChunk, ec.
row, (uint16_t)(ec.
row + 1));
4927 static void init_direct_entity_iter(
4928 const QueryInfo& queryInfo,
const World& world, Entity entity, Iter& it, uint8_t* pIndices,
4930 const auto& ec = ::gaia::ecs::fetch(world, entity);
4931 const Archetype* pLastArchetype =
nullptr;
4932 it.set_world(&world);
4933 init_direct_entity_iter(queryInfo, world, ec, it, pIndices, pTermIds, pLastArchetype);
4936 template <
typename Func>
4937 void each_chunk_runs_iter(
4939 auto& world = *queryInfo.world();
4941 it.init_query_state(&world, constraints,
false);
4942 const Archetype* pLastArchetype =
nullptr;
4946 for (
const auto& run: runs) {
4947 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
4948 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
4949 it.set_chunk(run.pChunk, run.from, run.to);
4953 finish_iter_writes(it);
4954 it.clear_touched_writes();
4960 bool isEntity =
false;
4961 bool isPair =
false;
4975 if (world_is_out_of_line_component(world, desc.id))
4978 for (
const auto& term: queryInfo.
ctx().data.terms_view()) {
4979 if (term.id != desc.id)
4981 if (term.src != EntityBad || term.entTrav != EntityBad || term_has_variables(term))
4992 GAIA_NODISCARD
static bool can_use_direct_chunk_term_eval_descs(
4993 World& world,
const QueryInfo& queryInfo,
const DirectChunkArgEvalDesc* pDescs, uint32_t descCnt) {
5005 template <
typename Func>
5006 void each_direct_entities_iter(
5008 auto& world = *queryInfo.world();
5009 auto& walkData = ensure_each_walk_data();
5011 it.init_query_state(&world, constraints,
false);
5012 if (!walkData.cachedRuns.empty()) {
5013 each_chunk_runs_iter(queryInfo, walkData.cachedRuns, constraints, func);
5017 const Archetype* pLastArchetype =
nullptr;
5020 for (
const auto entity: entities) {
5021 const auto& ec = ::gaia::ecs::fetch(world, entity);
5022 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
5025 finish_iter_writes(it);
5026 it.clear_touched_writes();
5036 template <
typename Func>
5038 auto& world = *queryInfo.
world();
5040 const auto plan = direct_entity_seed_plan(world, queryInfo);
5042 auto exec_entity = [&](
Entity entity) {
5046 it.set_constraints(constraints);
5047 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
5048 it.set_write_im(
false);
5051 finish_iter_writes(it);
5054 if (hasWriteTerms) {
5055 auto& scratch = direct_query_scratch();
5059 for (
const auto entity: scratch.entities) {
5064 exec_entity(entity);
5069 if (!plan.preferOrSeed) {
5070 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
5073 seedInfo.seededAllTerm = pSeedTerm->id;
5074 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
5075 seedInfo.seededFromAll =
true;
5076 each_chunk_runs_iter(
5077 queryInfo, cached_direct_seed_runs(queryInfo, *pSeedTerm, seedInfo, constraints), constraints, func);
5082 if (plan.preferOrSeed) {
5087 (void)for_each_direct_all_seed(world, queryInfo, plan, constraints, [&](
Entity entity) {
5088 exec_entity(entity);
5099 template <
bool UseFilters,
typename ContainerOut>
5100 void arr_inter(
QueryInfo& queryInfo, ContainerOut& outArray, Constraints constraints);
5107 World& world,
QueryCache& queryCache, ArchetypeId& nextArchetypeId, uint32_t& worldVersion,
5110 m_nextArchetypeId(&nextArchetypeId), m_worldVersion(&worldVersion),
5111 m_entityToArchetypeMap(&entityToArchetypeMap),
5112 m_entityToArchetypeMapVersions(&entityToArchetypeMapVersions), m_allArchetypes(&allArchetypes) {
5113 m_storage.
init(&world, &queryCache);
5116#if GAIA_ECS_TEST_HOOKS
5117 template <
typename Func>
5118 GAIA_NODISCARD QueryPlan test_typed_plan(Func func);
5123 GAIA_NODISCARD QueryPlan test_iter_plan(Constraints constraints = Constraints::EnabledOnly);
5128 GAIA_NODISCARD QueryId
id()
const {
5129 if (!uses_query_cache_storage())
5136 GAIA_NODISCARD uint32_t
gen()
const {
5137 if (!uses_query_cache_storage())
5147 m_eachWalkData.reset();
5148 m_directSeedRunData.reset();
5149 reset_changed_filter_state();
5150 invalidate_each_walk_cache();
5151 invalidate_direct_seed_run_cache();
5157 m_eachWalkData.reset();
5158 m_directSeedRunData.reset();
5159 reset_changed_filter_state();
5160 invalidate_each_walk_cache();
5161 invalidate_direct_seed_run_cache();
5167 return uses_query_cache_storage() && m_storage.
is_cached();
5214 GAIA_ASSERT(str !=
nullptr);
5219 va_start(args, str);
5223 uint32_t parentDepth = 0;
5226 uint32_t varNamesCnt = 0;
5228 auto expr = util::trim(exprRaw);
5229 return expr.size() == 5 && expr[0] ==
'$' && expr[1] ==
't' && expr[2] ==
'h' && expr[3] ==
'i' &&
5234 auto varNameSpan = util::trim(varExpr);
5235 if (varNameSpan.empty())
5239 if (is_reserved_var_name(varName)) {
5240 GAIA_ASSERT2(
false,
"$this is reserved and can only be used as a source expression: Id($this)");
5244 const auto namedVar = find_var_by_name(varName);
5245 if (namedVar != EntityBad)
5248 GAIA_FOR(varNamesCnt) {
5249 if (varNames[i].size() != varName.size())
5251 if (varNames[i].size() > 0 && memcmp(varNames[i].data(), varName.data(), varName.size()) != 0)
5253 return query_var_entity(i);
5256 if (varNamesCnt >= varNames.size()) {
5257 GAIA_ASSERT2(
false,
"Too many query variables in expression");
5261 const auto idx = varNamesCnt++;
5262 varNames[idx] = varName;
5264 const auto varEntity = query_var_entity(idx);
5265 (void)set_var_name_internal(varEntity, varName);
5270 auto expr = util::trim(exprRaw);
5275 return find_or_alloc_var(expr.subspan(1));
5277 if (expr[0] ==
'(') {
5278 if (expr.back() !=
')') {
5279 GAIA_ASSERT2(
false,
"Expression '(' not terminated");
5283 const auto idStr = expr.subspan(1, expr.size() - 2);
5284 const auto commaIdx = core::get_index(idStr,
',');
5286 GAIA_ASSERT2(
false,
"Pair expression does not contain ','");
5290 const auto first = self(self, idStr.subspan(0, commaIdx));
5291 if (first == EntityBad)
5293 const auto second = self(self, idStr.subspan(commaIdx + 1));
5294 if (second == EntityBad)
5300 return expr_to_entity((
const World&)*m_storage.
world(), args, expr);
5304 auto srcExpr = util::trim(srcExprRaw);
5305 if (srcExpr.empty())
5309 if (is_this_expr(srcExpr)) {
5314 srcOut = parse_entity_expr(parse_entity_expr, srcExpr);
5315 return srcOut != EntityBad;
5319 auto expr = util::trim(exprRaw);
5323 if (expr.back() ==
')') {
5325 int32_t openIdx = -1;
5326 for (int32_t i = (int32_t)expr.size() - 1; i >= 0; --i) {
5327 if (expr[(uint32_t)i] ==
')')
5329 else if (expr[(uint32_t)i] ==
'(') {
5340 auto idExpr = util::trim(expr.subspan(0, (uint32_t)openIdx));
5341 auto srcExpr = util::trim(expr.subspan((uint32_t)openIdx + 1, expr.size() - (uint32_t)openIdx - 2));
5342 if (!idExpr.empty() && !srcExpr.empty()) {
5343 id = parse_entity_expr(parse_entity_expr, idExpr);
5344 if (
id == EntityBad)
5348 if (!parse_src_expr(srcExpr, src))
5357 id = parse_entity_expr(parse_entity_expr, expr);
5358 return id != EntityBad;
5362 auto expr = util::trim(exprRaw);
5366 bool isReadWrite =
false;
5367 if (!expr.empty() && expr[0] ==
'&') {
5369 expr = util::trim(expr.subspan(1));
5376 Entity entity = EntityBad;
5377 if (!parse_term_expr(expr, entity, options))
5381 case QueryOpKind::All:
5382 all(entity, options);
5384 case QueryOpKind::Or:
5385 or_(entity, options);
5387 case QueryOpKind::Not:
5388 no(entity, options);
5390 case QueryOpKind::Any:
5391 any(entity, options);
5401 auto process = [&]() {
5405 auto expr = util::trim(exprRaw);
5410 bool hasOrChain =
false;
5413 const auto cnt = (uint32_t)expr.size();
5414 for (uint32_t i = 0; i + 1 < cnt; ++i) {
5415 const auto ch = expr[i];
5418 else if (ch ==
')') {
5419 GAIA_ASSERT(depth > 0);
5421 }
else if (depth == 0 && ch ==
'|' && expr[i + 1] ==
'|') {
5430 uint32_t partBeg = 0;
5431 const auto cnt = (uint32_t)expr.size();
5432 for (uint32_t i = 0; i < cnt; ++i) {
5433 const auto ch = expr[i];
5436 else if (ch ==
')') {
5437 GAIA_ASSERT(depth > 0);
5441 const bool isOr = i + 1 < cnt && depth == 0 && ch ==
'|' && expr[i + 1] ==
'|';
5442 const bool isEnd = i + 1 == cnt;
5443 if (!isOr && !isEnd)
5446 const auto partEnd = isOr ? i : (i + 1);
5447 auto partExpr = expr.subspan(partBeg, partEnd - partBeg);
5448 if (!add_term(QueryOpKind::Or, partExpr))
5460 QueryOpKind op = QueryOpKind::All;
5461 if (expr[0] ==
'?') {
5462 op = QueryOpKind::Any;
5463 expr = util::trim(expr.subspan(1));
5464 }
else if (expr[0] ==
'!') {
5465 op = QueryOpKind::Not;
5466 expr = util::trim(expr.subspan(1));
5469 return add_term(op, expr);
5472 for (; str[pos] != 0; ++pos) {
5473 if (str[pos] ==
'(')
5475 else if (str[pos] ==
')') {
5476 GAIA_ASSERT(parentDepth > 0);
5478 }
else if (str[pos] ==
',' && parentDepth == 0) {
5506 return all(
Pair(Is, entity), options);
5517 return all(
Pair(Is, entity), options);
5527 add_entity_term(QueryOpKind::All, entity, options);
5535 template <
typename T>
5541 template <
typename T>
5551 add_entity_term(QueryOpKind::Any, entity, options);
5559 template <
typename T>
5565 template <
typename T>
5576 add_entity_term(QueryOpKind::Or, entity, options);
5584 template <
typename T>
5590 template <
typename T>
5600 add_entity_term(QueryOpKind::Not, entity, options);
5608 template <
typename T>
5614 template <
typename T>
5623 [[maybe_unused]]
const bool ok = set_var_name_internal(varEntity, name);
5629 GAIA_ASSERT(name !=
nullptr);
5630 if (name ==
nullptr)
5640 const bool ok = is_query_var_entity(varEntity);
5645 const auto idx = query_var_idx(varEntity);
5646 m_varBindings[idx] = value;
5647 m_varBindingsMask |= (uint8_t(1) << idx);
5654 const auto varEntity = find_var_by_name(name);
5655 GAIA_ASSERT(varEntity != EntityBad);
5656 if (varEntity == EntityBad)
5658 return set_var(varEntity, value);
5662 GAIA_ASSERT(name !=
nullptr);
5663 if (name ==
nullptr)
5671 const bool ok = is_query_var_entity(varEntity);
5676 const auto idx = query_var_idx(varEntity);
5677 m_varBindingsMask &= (uint8_t)~(uint8_t(1) << idx);
5682 m_varBindingsMask = 0;
5692 changed_inter(entity);
5699 template <
typename T>
5710 sort_by_inter(entity, func);
5718 template <
typename T>
5726 template <
typename Rel,
typename Tgt>
5733 Entity m_relation = EntityBad;
5734 TravOrder m_order = TravOrder::Down;
5742 m_query(&query), m_relation(relation), m_order(order) {}
5747 template <
typename Func>
5749 m_query->each_walk(func, m_relation, m_order);
5778 template <
typename Rel>
5791 GAIA_ASSERT(!relation.pair());
5792 GAIA_ASSERT(world_supports_depth_order(*m_storage.
world(), relation));
5793 group_by_inter(relation, group_by_func_depth_order,
true);
5800 template <
typename Rel>
5812 group_by_inter(entity, func);
5822 template <
typename T>
5832 template <
typename Rel,
typename Tgt>
5841 group_dep_inter(relation);
5848 template <
typename Rel>
5856 set_group_id_inter(groupId);
5863 GAIA_ASSERT(!entity.pair());
5864 set_group_id_inter(entity.id());
5870 template <
typename T>
5888 template <
typename Func>
5890 if constexpr (detail::is_query_iter_callback_v<Func>) {
5892 case QueryExecType::Parallel:
5893 return add_iter_parallel_job<Func, QueryExecType::Parallel>(GAIA_MOV(func));
5894 case QueryExecType::ParallelPerf:
5895 return add_iter_parallel_job<Func, QueryExecType::ParallelPerf>(GAIA_MOV(func));
5896 case QueryExecType::ParallelEff:
5897 return add_iter_parallel_job<Func, QueryExecType::ParallelEff>(GAIA_MOV(func));
5903 case QueryExecType::Parallel:
5904 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::Parallel>(
5906 case QueryExecType::ParallelPerf:
5907 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelPerf>(
5909 case QueryExecType::ParallelEff:
5910 return add_iter_parallel_job<TypedJobCallback<Func>, QueryExecType::ParallelEff>(
5917 return add_query_task_job(GAIA_MOV(func), execType);
5924 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
5926 each_runtime_inter<QueryExecType::Default, Func>(func, Constraints::EnabledOnly);
5929 template <
typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>,
int> = 0>
5930 void each(Func func);
5937 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
5938 void each(Func func, QueryExecType execType) {
5939 each(func, execType, Constraints::EnabledOnly);
5942 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
5943 void each(Func func, Constraints constraints) {
5944 each(func, QueryExecType::Default, constraints);
5947 template <
typename Func, std::enable_if_t<detail::is_query_iter_callback_v<Func>,
int> = 0>
5948 void each(Func func, QueryExecType execType, Constraints constraints) {
5950 case QueryExecType::Parallel:
5951 each_runtime_inter<QueryExecType::Parallel, Func>(func, constraints);
5953 case QueryExecType::ParallelPerf:
5954 each_runtime_inter<QueryExecType::ParallelPerf, Func>(func, constraints);
5956 case QueryExecType::ParallelEff:
5957 each_runtime_inter<QueryExecType::ParallelEff, Func>(func, constraints);
5960 each_runtime_inter<QueryExecType::Default, Func>(func, constraints);
5965 template <
typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>,
int> = 0>
5966 void each(Func func, QueryExecType execType);
5974 template <
typename Func>
5977 void each_iter_erased(
5978 QueryExecType execType,
void* pFunc,
const TypedQueryExecState& state,
5979 void (*runDirectFastChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&),
5980 void (*runMappedChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&));
5982 void each_iter_erased(
5983 Iter& it,
void* pFunc,
const TypedQueryExecState& state,
5984 void (*runDirectFastChunk)(QueryImpl&, Iter&,
void*,
const TypedQueryExecState&),
5985 void (*runMappedChunk)(QueryImpl&,
const QueryInfo&, Iter&,
void*,
const TypedQueryExecState&));
5994 template <
typename Func>
5995 void each_arch(Func func, Constraints constraints = Constraints::EnabledOnly) {
5996 auto& queryInfo =
fetch();
5998 run_query_on_archetypes<QueryExecType::Default>(
6001 GAIA_PROF_SCOPE(query_func_a);
6018 bool empty(Constraints constraints = Constraints::EnabledOnly) {
6019 auto& queryInfo =
fetch();
6021 return empty_inter<false>(queryInfo, constraints);
6028 return empty_inter<true>(queryInfo, constraints);
6030 return empty_inter<false>(queryInfo, constraints);
6041 uint32_t
count(Constraints constraints = Constraints::EnabledOnly) {
6042 auto& queryInfo =
fetch();
6044 return count_inter<false>(queryInfo, constraints);
6050 return hasFilters ? count_inter<true>(queryInfo, constraints) : count_inter<false>(queryInfo, constraints);
6057 auto& queryInfo =
fetch();
6059 ::gaia::ecs::update_version(*m_worldVersion);
6062 auto& world = *queryInfo.
world();
6069 const auto plan = direct_entity_seed_plan(world, queryInfo);
6070 (void)for_each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](
Entity entity) {
6076 m_changedWorldVersion = *m_worldVersion;
6084 if (needsBarrierCache)
6088 if (!cacheRange.valid) {
6089 m_changedWorldVersion = *m_worldVersion;
6092 const auto idxFrom = cacheRange.idxFrom;
6093 const auto idxTo = cacheRange.idxTo;
6097 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6098 const auto* pArchetype = cacheView[qi];
6099 const bool barrierPasses = !needsBarrierCache || queryInfo.
barrier_passes(qi);
6101 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6104 const auto& chunks = pArchetype->chunks();
6105 if (!hasEntityFilters) {
6106 for (
auto* pChunk: chunks) {
6107 const auto from = Iter::start_index(pChunk);
6108 const auto to = Iter::end_index(pChunk);
6111 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6114 const auto entityCnt = (uint32_t)(to - from);
6115 const auto entities = pChunk->entity_view();
6116 GAIA_FOR(entityCnt) {
6117 func(pCtx, entities[from + i]);
6123 it.set_archetype(pArchetype);
6124 for (
auto* pChunk: chunks) {
6125 it.set_chunk(pChunk);
6126 const auto entityCnt = it.size();
6129 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6133 GAIA_FOR(entityCnt) {
6135 func(pCtx, entities[i]);
6140 m_changedWorldVersion = *m_worldVersion;
6144 auto& queryInfo =
fetch();
6146 ::gaia::ecs::update_version(*m_worldVersion);
6149 auto& world = *queryInfo.
world();
6152 out.push_back(entity);
6156 const auto plan = direct_entity_seed_plan(world, queryInfo);
6157 (void)for_each_direct_all_seed(world, queryInfo, plan, Constraints::EnabledOnly, [&](Entity entity) {
6158 out.push_back(entity);
6163 m_changedWorldVersion = *m_worldVersion;
6171 if (needsBarrierCache)
6175 if (!cacheRange.valid) {
6176 m_changedWorldVersion = *m_worldVersion;
6179 const auto idxFrom = cacheRange.idxFrom;
6180 const auto idxTo = cacheRange.idxTo;
6183 it.init_query_state(queryInfo.
world(), Constraints::EnabledOnly,
false);
6184 for (uint32_t qi = idxFrom; qi < idxTo; ++qi) {
6185 const auto* pArchetype = cacheView[qi];
6186 const bool barrierPasses = !needsBarrierCache || queryInfo.
barrier_passes(qi);
6188 queryInfo, *pArchetype, Constraints::EnabledOnly, barrierPasses))
6191 const auto& chunks = pArchetype->chunks();
6192 if (!hasEntityFilters) {
6193 for (
auto* pChunk: chunks) {
6194 const auto from = Iter::start_index(pChunk);
6195 const auto to = Iter::end_index(pChunk);
6198 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6201 const auto oldSize = out.size();
6202 const auto entityCnt = (uint32_t)(to - from);
6203 const auto entities = pChunk->entity_view();
6204 out.resize(oldSize + entityCnt);
6205 GAIA_FOR(entityCnt) {
6206 out[oldSize + i] = entities[from + i];
6212 it.set_archetype(pArchetype);
6213 for (
auto* pChunk: chunks) {
6214 it.set_chunk(pChunk);
6215 const auto entityCnt = it.size();
6218 if (hasFilters && !
match_filters(*pChunk, queryInfo, m_changedWorldVersion))
6221 const auto entities = it.view<Entity>();
6222 GAIA_FOR(entityCnt) {
6224 out.push_back(entities[i]);
6229 m_changedWorldVersion = *m_worldVersion;
6236 template <
typename Container>
6237 void arr(Container& outArray, Constraints constraints = Constraints::EnabledOnly);
6247 Constraints constraints = Constraints::EnabledOnly) {
6248 struct OrderedWalkTargetCtx {
6251 uint32_t dependentIdx = 0;
6256 uint32_t* pEdgeCnt =
nullptr;
6258 GAIA_NODISCARD
static uint32_t
6260 const auto targetId = entity.id();
6262 uint32_t high = cnt;
6263 while (low < high) {
6264 const uint32_t mid = low + ((high - low) >> 1);
6265 if (entities[mid].
id() < targetId)
6271 if (low < cnt && entities[low].
id() == targetId)
6276 static void count_edge(
void* rawCtx,
Entity dependency) {
6277 auto& ctx = *
static_cast<OrderedWalkTargetCtx*
>(rawCtx);
6278 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6279 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6282 ++(*ctx.pOutdegree)[dependencyIdx];
6283 ++(*ctx.pIndegree)[ctx.dependentIdx];
6287 static void write_edge(
void* rawCtx,
Entity dependency) {
6288 auto& ctx = *
static_cast<OrderedWalkTargetCtx*
>(rawCtx);
6289 const auto dependencyIdx = find_entity_idx(*ctx.pEntities, ctx.cnt, dependency);
6290 if (dependencyIdx == ctx.cnt || dependencyIdx == ctx.dependentIdx)
6293 (*ctx.pEdges)[(*ctx.pWriteCursor)[dependencyIdx]++] = ctx.dependentIdx;
6297 auto& walkData = ensure_each_walk_data();
6298 auto& world = *m_storage.
world();
6299 const uint32_t relationVersion = world_rel_version(world, relation);
6300 const uint32_t worldVersion = ::gaia::ecs::world_version(world);
6302 const bool needsTraversalBarrierState =
6303 constraints == Constraints::EnabledOnly && ::gaia::ecs::valid(world, relation);
6304 auto survives_disabled_barrier = [&](
Entity entity) {
6305 if (!needsTraversalBarrierState)
6309 GAIA_FOR(MAX_TRAV_DEPTH) {
6310 const auto next = target(world, curr, relation);
6311 if (next == EntityBad || next == curr)
6313 if (!world_entity_enabled(world, next))
6321 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6322 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6323 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
6325 auto& chunks = walkData.scratchChunks;
6328 bool chunkChanged =
false;
6329 for (
auto* pArchetype: queryInfo) {
6330 if (pArchetype ==
nullptr || !can_process_archetype(queryInfo, *pArchetype))
6333 for (
const auto* pChunk: pArchetype->chunks()) {
6334 if (pChunk ==
nullptr)
6337 chunks.push_back(pChunk);
6338 if (!chunkChanged && pChunk->changed(walkData.cachedEntityVersion))
6339 chunkChanged =
true;
6343 bool sameChunks = chunks.size() == walkData.cachedChunks.size();
6345 for (uint32_t i = 0; i < (uint32_t)chunks.size(); ++i) {
6346 if (chunks[i] != walkData.cachedChunks[i]) {
6353 if (sameChunks && !chunkChanged) {
6358 auto& entities = walkData.scratchEntities;
6360 arr(entities, constraints);
6361 if (entities.empty())
6364 if (needsTraversalBarrierState) {
6365 uint32_t writeIdx = 0;
6366 const auto cnt = (uint32_t)entities.size();
6368 const auto entity = entities[i];
6369 if (!survives_disabled_barrier(entity))
6371 entities[writeIdx++] = entity;
6373 entities.resize(writeIdx);
6374 if (entities.empty())
6378 if (walkData.cacheValid && walkData.cachedRelation == relation && walkData.cachedOrder == order &&
6379 walkData.cachedConstraints == constraints && walkData.cachedRelationVersion == relationVersion &&
6380 (!needsTraversalBarrierState || walkData.cachedEntityVersion == worldVersion) &&
6381 entities.size() == walkData.cachedInput.size()) {
6382 bool sameInput =
true;
6383 for (uint32_t i = 0; i < (uint32_t)entities.size(); ++i) {
6384 if (entities[i] != walkData.cachedInput[i]) {
6395 auto& ordered = walkData.cachedOutput;
6396 walkData.cachedInput = entities;
6398 if (!::gaia::ecs::valid(world, relation)) {
6400 return left.id() < right.id();
6406 return left.id() < right.id();
6409 const auto cnt = (uint32_t)entities.size();
6411 auto& indegree = walkData.scratchIndegree;
6412 indegree.resize(cnt);
6413 auto& outdegree = walkData.scratchOutdegree;
6414 outdegree.resize(cnt);
6415 for (uint32_t i = 0; i < cnt; ++i) {
6420 uint32_t edgeCnt = 0;
6421 OrderedWalkTargetCtx edgeCtx;
6422 edgeCtx.pEntities = &entities;
6424 edgeCtx.pIndegree = &indegree;
6425 edgeCtx.pOutdegree = &outdegree;
6426 edgeCtx.pEdgeCnt = &edgeCnt;
6427 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
6428 const auto dependent = entities[dependentIdx];
6429 edgeCtx.dependentIdx = dependentIdx;
6430 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::count_edge);
6433 auto& offsets = walkData.scratchOffsets;
6434 offsets.resize(cnt + 1);
6436 for (uint32_t i = 0; i < cnt; ++i)
6437 offsets[i + 1] = offsets[i] + outdegree[i];
6439 auto& writeCursor = walkData.scratchWriteCursor;
6440 writeCursor.resize(cnt);
6441 for (uint32_t i = 0; i < cnt; ++i)
6442 writeCursor[i] = offsets[i];
6444 auto& edges = walkData.scratchEdges;
6445 edges.resize(edgeCnt);
6446 edgeCtx.pWriteCursor = &writeCursor;
6447 edgeCtx.pEdges = &edges;
6448 for (uint32_t dependentIdx = 0; dependentIdx < cnt; ++dependentIdx) {
6449 const auto dependent = entities[dependentIdx];
6450 edgeCtx.dependentIdx = dependentIdx;
6451 world_for_each_target(world, dependent, relation, &edgeCtx, &OrderedWalkTargetCtx::write_edge);
6454 ordered.reserve(cnt);
6456 const bool isUp = order == TravOrder::Up || order == TravOrder::ReverseUp;
6457 const bool needsReverse = order == TravOrder::ReverseUp || order == TravOrder::ReverseDown;
6459 auto& visited = walkData.scratchWriteCursor;
6460 visited.resize(cnt);
6461 for (uint32_t i = 0; i < cnt; ++i)
6464 auto& stack = walkData.scratchCurrLevel;
6466 auto& cursorStack = walkData.scratchNextLevel;
6467 cursorStack.clear();
6469 auto append_from_root = [&](uint32_t rootIdx) {
6470 stack.push_back(rootIdx);
6471 cursorStack.push_back(offsets[rootIdx]);
6473 while (!stack.empty()) {
6474 const auto idx = stack.back();
6475 if (visited[idx] == 0) {
6478 ordered.push_back(entities[idx]);
6481 bool pushedChild =
false;
6482 auto& cursor = cursorStack.back();
6483 while (cursor < offsets[idx + 1]) {
6484 const auto childIdx = edges[cursor++];
6485 if (visited[childIdx] != 0)
6488 stack.push_back(childIdx);
6489 cursorStack.push_back(offsets[childIdx]);
6498 ordered.push_back(entities[idx]);
6501 cursorStack.pop_back();
6505 for (uint32_t i = 0; i < cnt; ++i) {
6506 if (indegree[i] == 0 && visited[i] == 0)
6507 append_from_root(i);
6511 for (uint32_t i = 0; i < cnt; ++i) {
6512 if (visited[i] == 0)
6513 append_from_root(i);
6517 const auto orderedCnt = (uint32_t)ordered.size();
6518 for (uint32_t i = 0; i < orderedCnt / 2; ++i)
6519 core::swap(ordered[i], ordered[orderedCnt - i - 1]);
6523 walkData.cachedRelation = relation;
6524 walkData.cachedOrder = order;
6525 walkData.cachedConstraints = constraints;
6526 walkData.cachedRelationVersion = relationVersion;
6527 walkData.cachedEntityVersion = ::gaia::ecs::world_version(world);
6528 walkData.cachedRuns.clear();
6531 const auto orderedCnt = (uint32_t)ordered.size();
6532 if (orderedCnt != 0) {
6533 for (uint32_t i = 0; i < orderedCnt; ++i) {
6534 const auto& ec = ::gaia::ecs::fetch(world, ordered[i]);
6535 if (walkData.cachedRuns.empty()) {
6540 auto& run = walkData.cachedRuns.back();
6541 if (ec.
pChunk == run.pChunk && ec.
row == run.to) {
6542 run.to = (uint16_t)(run.to + 1);
6551 auto& chunks = walkData.scratchChunks;
6553 for (
auto* pArchetype: queryInfo) {
6554 if (pArchetype ==
nullptr || !can_process_archetype(queryInfo, *pArchetype))
6557 for (
const auto* pChunk: pArchetype->chunks()) {
6558 if (pChunk ==
nullptr)
6560 chunks.push_back(pChunk);
6563 walkData.cachedChunks = chunks;
6565 walkData.cachedChunks.clear();
6566 walkData.cacheValid =
true;
6580 template <
typename Func, std::enable_if_t<detail::is_query_walk_core_callback_v<Func>,
int> = 0>
6582 Func func,
Entity relation, TravOrder order = TravOrder::Down,
6583 Constraints constraints = Constraints::EnabledOnly) {
6584 auto& queryInfo =
fetch();
6588 if constexpr (std::is_invocable_v<Func, Iter&>) {
6589 each_direct_entities_iter(queryInfo, ordered, constraints, func);
6590 }
else if constexpr (std::is_invocable_v<Func, const Entity&> || std::is_invocable_v<Func, Entity>) {
6591 for (
const auto entity: ordered)
6596 template <
typename Func, std::enable_if_t<!detail::is_query_walk_core_callback_v<Func>,
int> = 0>
6598 Func func,
Entity relation, TravOrder order = TravOrder::Down,
6599 Constraints constraints = Constraints::EnabledOnly);
6606 auto& queryInfo =
fetch();
6608 if (uses_shared_cache_layer())
6609 GAIA_LOG_N(
"BEG DIAG Query %u.%u [S]",
id(),
gen());
6610 else if (uses_query_cache_storage())
6611 GAIA_LOG_N(
"BEG DIAG Query %u.%u [L]",
id(),
gen());
6613 GAIA_LOG_N(
"BEG DIAG Query [U]");
6614 for (
const auto* pArchetype: queryInfo)
6615 Archetype::diag_basic_info(*m_storage.
world(), *pArchetype);
6616 GAIA_LOG_N(
"END DIAG Query");
6622 auto& queryInfo =
fetch();
6629 if (uses_shared_cache_layer())
6630 GAIA_LOG_N(
"BEG DIAG Query Bytecode %u.%u [S]",
id(),
gen());
6631 else if (uses_query_cache_storage())
6632 GAIA_LOG_N(
"BEG DIAG Query Bytecode %u.%u [L]",
id(),
gen());
6634 GAIA_LOG_N(
"BEG DIAG Query Bytecode [U]");
6635 GAIA_LOG_N(
"%.*s", (
int)dump.size(), dump.data());
6636 GAIA_LOG_N(
"END DIAG Query");