Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
query_typed.inl
1#pragma once
2
3#include "gaia/ecs/query_adapter_typed.inl"
4
5namespace gaia {
6 namespace ecs {
7 namespace detail {
8 inline TypedQueryExecState build_typed_query_exec_state(
9 [[maybe_unused]] QueryImpl& query, World& world, const QueryInfo& queryInfo, const TypedQueryArgMeta* pMetas,
10 uint32_t argCount) {
11 TypedQueryExecState state{};
12 QueryImpl::DirectChunkArgEvalDesc directChunkDescs[MAX_ITEMS_IN_QUERY]{};
13 state.argCount = argCount;
14 GAIA_FOR(argCount) {
15 state.argIds[i] = pMetas[i].termId;
16 state.writeFlags[i] = pMetas[i].isWrite;
17 if (pMetas[i].isWrite) {
18 state.hasWriteArgs = true;
19 if (state.firstWriteArg == MAX_ITEMS_IN_QUERY)
20 state.firstWriteArg = (uint32_t)i;
21 }
22 state.needsInheritedArgIds = state.needsInheritedArgIds || !pMetas[i].isEntity;
23 directChunkDescs[i] = {pMetas[i].termId, pMetas[i].isEntity, pMetas[i].isPair};
24 }
25
26 state.canUseDirectChunkEval =
27 QueryImpl::can_use_direct_chunk_term_eval_descs(world, queryInfo, directChunkDescs, argCount);
28 if (state.needsInheritedArgIds)
29 state.hasInheritedTerms = queryInfo.has_potential_inherited_id_terms();
30 return state;
31 }
32
33#if GAIA_ECS_TEST_HOOKS
34 template <typename Func>
35 inline QueryImpl::QueryPlan QueryImpl::test_typed_plan(Func func) {
36 auto& queryInfo = fetch();
37 match_all(queryInfo);
38
39 using InputArgs = decltype(core::func_args(&Func::operator()));
40 #if GAIA_ASSERT_ENABLED
41 GAIA_ASSERT(typed_query_args_match_query(queryInfo, InputArgs{}));
42 #endif
43 auto& world = *const_cast<World*>(queryInfo.world());
44 TypedQueryArgMeta metas[MAX_ITEMS_IN_QUERY]{};
45 const auto argCount = init_typed_query_arg_metas(metas, world, InputArgs{});
46 const auto state = build_typed_query_exec_state(*this, world, queryInfo, metas, argCount);
47 (void)func;
48 return prepare_query_plan(queryInfo, state);
49 }
50
51 inline QueryImpl::QueryPlan QueryImpl::test_iter_plan(Constraints constraints) {
52 auto& queryInfo = fetch();
53 match_all(queryInfo);
54 return prepare_query_plan(queryInfo, constraints);
55 }
56#endif
57
58 inline void finish_typed_chunk_state(
59 QueryImpl& query, World& world, Chunk* pChunk, uint16_t from, uint16_t to, const TypedQueryExecState& state);
60
61 inline void finish_typed_iter_state(QueryImpl& query, Iter& it, const TypedQueryExecState& state);
62
63 inline void noop_row_done(uint32_t) {}
64
65 template <typename Func, typename ViewsTuple, typename... T>
66 inline void invoke_typed_query_row_erased(
67 void* pFunc, Iter& it, ViewsTuple& dataPointerTuple, uint32_t row, bool directLocalIndex);
68
69 template <typename OnRowDone, typename... T>
70 inline void run_typed_chunk_views(
71 const QueryInfo* pQueryInfo, Iter& it, void* pFunc, bool directViews, OnRowDone&& onRowDone,
72 void (*invokeDirectRow)(
73 void*, Iter&, std::tuple<decltype(std::declval<Iter&>().template sview_auto<T>())...>&, uint32_t, bool),
74 void (*invokeMappedRow)(
75 void*, Iter&, std::tuple<decltype(std::declval<Iter&>().template view_auto_any<T>())...>&, uint32_t,
76 bool),
77 core::func_type_list<T...>);
78
79 template <typename Func, typename... T>
80 inline void run_typed_chunk_direct_iter_fast_cb(QueryImpl&, Iter& it, void* pFunc, const TypedQueryExecState&);
81
82 template <typename Func, typename... T>
83 inline void
84 run_typed_chunk_direct_iter_cb(QueryImpl& query, Iter& it, void* pFunc, const TypedQueryExecState& state);
85
86 template <typename Func, typename... T>
87 inline void run_typed_chunk_mapped_iter_cb(
88 QueryImpl& query, const QueryInfo& queryInfo, Iter& it, void* pFunc, const TypedQueryExecState& state);
89
90 template <typename Func, typename... T>
91 GAIA_NODISCARD inline auto typed_run_direct_fast_chunk_ptr(core::func_type_list<T...>) {
92 return &run_typed_chunk_direct_iter_fast_cb<Func, T...>;
93 }
94
95 template <typename Func, typename... T>
96 GAIA_NODISCARD inline auto typed_run_direct_chunk_ptr(core::func_type_list<T...>) {
97 return &run_typed_chunk_direct_iter_cb<Func, T...>;
98 }
99
100 template <typename Func, typename... T>
101 GAIA_NODISCARD inline auto typed_run_mapped_chunk_ptr(core::func_type_list<T...>) {
102 return &run_typed_chunk_mapped_iter_cb<Func, T...>;
103 }
104
105 template <typename Func, typename... T>
106 inline void run_typed_chunk_direct_iter_fast_cb(QueryImpl&, Iter& it, void* pFunc, const TypedQueryExecState&) {
107 run_typed_chunk_views(
108 nullptr, it, pFunc, true, noop_row_done,
109 &invoke_typed_query_row_erased<
110 Func, std::tuple<decltype(std::declval<Iter&>().template sview_auto<T>())...>, T...>,
111 &invoke_typed_query_row_erased<
112 Func, std::tuple<decltype(std::declval<Iter&>().template view_auto_any<T>())...>, T...>,
113 core::func_type_list<T...>{});
114 }
115
116 template <typename Func, typename... T>
117 inline void
118 run_typed_chunk_direct_iter_cb(QueryImpl& query, Iter& it, void* pFunc, const TypedQueryExecState& state) {
119 auto& func = *static_cast<Func*>(pFunc);
120 run_typed_chunk_direct_finish(query, it, func, state, core::func_type_list<T...>{});
121 }
122
123 template <typename Func, typename... T>
124 inline void run_typed_chunk_mapped_iter_cb(
125 QueryImpl& query, const QueryInfo& queryInfo, Iter& it, void* pFunc, const TypedQueryExecState& state) {
126 auto& func = *static_cast<Func*>(pFunc);
127 run_typed_chunk_mapped_finish(query, queryInfo, it, func, state, core::func_type_list<T...>{});
128 }
129
130 template <typename Func, typename... T>
131 inline void each_iter_dispatch(
132 QueryImpl& query, const QueryInfo& queryInfo, Iter& it, Func& func, const TypedQueryExecState& state,
133 core::func_type_list<T...>) {
134 if (state.canUseDirectChunkEval) {
135 run_typed_chunk_views(
136 nullptr, it, &func, true, noop_row_done,
137 &invoke_typed_query_row_erased<
138 Func, std::tuple<decltype(std::declval<Iter&>().template sview_auto<T>())...>, T...>,
139 &invoke_typed_query_row_erased<
140 Func, std::tuple<decltype(std::declval<Iter&>().template view_auto_any<T>())...>, T...>,
141 core::func_type_list<T...>{});
142 finish_typed_chunk_state(
143 query, *it.world(), const_cast<Chunk*>(it.chunk()), it.row_begin(), it.row_end(), state);
144 } else
145 run_typed_chunk_unmapped(query, queryInfo, it, func, state, core::func_type_list<T...>{});
146 }
147
148 template <typename Func, typename... T>
149 inline void run_typed_chunk_direct_finish(
150 QueryImpl& query, Iter& it, Func& func, const TypedQueryExecState& state, core::func_type_list<T...>) {
151 run_typed_chunk_views(
152 nullptr, it, &func, true, noop_row_done,
153 &invoke_typed_query_row_erased<
154 Func, std::tuple<decltype(std::declval<Iter&>().template sview_auto<T>())...>, T...>,
155 &invoke_typed_query_row_erased<
156 Func, std::tuple<decltype(std::declval<Iter&>().template view_auto_any<T>())...>, T...>,
157 core::func_type_list<T...>{});
158 finish_typed_iter_state(query, it, state);
159 }
160
161 template <typename Func, typename... T>
162 inline void run_typed_chunk_mapped_finish(
163 QueryImpl& query, const QueryInfo& queryInfo, Iter& it, Func& func, const TypedQueryExecState& state,
164 core::func_type_list<T...>) {
165 run_typed_chunk_views(
166 &queryInfo, it, &func, false, noop_row_done,
167 &invoke_typed_query_row_erased<
168 Func, std::tuple<decltype(std::declval<Iter&>().template sview_auto<T>())...>, T...>,
169 &invoke_typed_query_row_erased<
170 Func, std::tuple<decltype(std::declval<Iter&>().template view_auto_any<T>())...>, T...>,
171 core::func_type_list<T...>{});
172 finish_typed_iter_state(query, it, state);
173 }
174
175 template <typename Func, typename... T>
176 inline void run_typed_chunk_direct_walk_cb(
177 QueryImpl& query, const QueryInfo&, Iter& it, void* pFunc, const TypedQueryExecState& state) {
178 run_typed_chunk_direct_finish(query, it, *static_cast<Func*>(pFunc), state, core::func_type_list<T...>{});
179 }
180
181 template <typename Func, typename... T>
182 inline void run_typed_chunk_mapped_walk_cb(
183 QueryImpl& query, const QueryInfo& queryInfo, Iter& it, void* pFunc, const TypedQueryExecState& state) {
184 run_typed_chunk_mapped_finish(
185 query, queryInfo, it, *static_cast<Func*>(pFunc), state, core::func_type_list<T...>{});
186 }
187
188 template <typename Func, typename... T>
189 inline void each_walk_dispatch_direct(
190 QueryImpl& query, QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, Func& func,
191 const TypedQueryExecState& state, core::func_type_list<T...>) {
192 query.each_walk_inter(
193 queryInfo, entities, constraints, &func, state, &run_typed_chunk_direct_walk_cb<Func, T...>);
194 }
195
196 template <typename Func, typename... T>
197 inline void each_walk_dispatch_mapped(
198 QueryImpl& query, QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, Func& func,
199 const TypedQueryExecState& state, core::func_type_list<T...>) {
200 query.each_walk_inter(
201 queryInfo, entities, constraints, &func, state, &run_typed_chunk_mapped_walk_cb<Func, T...>);
202 }
203
204 template <typename Func, typename... T>
205 inline void run_typed_chunk_unmapped(
206 QueryImpl& query, const QueryInfo& queryInfo, Iter& it, Func& func, const TypedQueryExecState& state,
207 core::func_type_list<T...> types) {
208 auto& world = *const_cast<World*>(queryInfo.world());
209 auto* pChunk = const_cast<Chunk*>(it.chunk());
210 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
211
212 if (!hasEntityFilters) {
213 run_typed_chunk_views(
214 &queryInfo, it, &func, false, noop_row_done,
215 &invoke_typed_query_row_erased<
216 Func, std::tuple<decltype(std::declval<Iter&>().template sview_auto<T>())...>, T...>,
217 &invoke_typed_query_row_erased<
218 Func, std::tuple<decltype(std::declval<Iter&>().template view_auto_any<T>())...>, T...>,
219 types);
220 finish_typed_chunk_state(query, world, pChunk, it.row_begin(), it.row_end(), state);
221 } else {
222 run_typed_chunk_views(
223 &queryInfo, it, &func, false,
224 [&](uint32_t row) {
225 finish_typed_chunk_state(
226 query, world, pChunk, (uint16_t)(it.row_begin() + row), (uint16_t)(it.row_begin() + row + 1),
227 state);
228 },
229 &invoke_typed_query_row_erased<
230 Func, std::tuple<decltype(std::declval<Iter&>().template sview_auto<T>())...>, T...>,
231 &invoke_typed_query_row_erased<
232 Func, std::tuple<decltype(std::declval<Iter&>().template view_auto_any<T>())...>, T...>,
233 types);
234 }
235
236 it.clear_touched_writes();
237 }
238
239 template <typename Func, typename... T>
240 inline void run_typed_cached_seed_runs_basic(
241 QueryImpl& query, World& world, Constraints constraints, Func& func, std::span<const BfsChunkRun> runs,
242 const TypedQueryExecState& state) {
243 Iter it;
244 it.init_query_state(&world, constraints, false);
245 const Archetype* pLastArchetype = nullptr;
246 for (const auto& run: runs) {
247 if (run.pArchetype != pLastArchetype) {
248 it.set_archetype(run.pArchetype);
249 pLastArchetype = run.pArchetype;
250 }
251
252 it.set_chunk(run.pChunk, run.from, run.to);
253 it.set_group_id(0);
254 run_typed_chunk_direct_finish(query, it, func, state, core::func_type_list<T...>{});
255 }
256 }
257
258 template <typename Func, typename... T>
259 inline void run_typed_cached_seed_runs_entity_init(
260 QueryImpl& query, QueryInfo& queryInfo, World& world, Constraints constraints, Func& func,
261 std::span<const BfsChunkRun> runs, const TypedQueryExecState& state) {
262 Iter it;
263 it.init_query_state(&world, constraints, false);
264 const Archetype* pLastArchetype = nullptr;
265 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
266 Entity termIds[ChunkHeader::MAX_COMPONENTS];
267 for (const auto& run: runs) {
268 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
269 query.init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
270 it.set_chunk(run.pChunk, run.from, run.to);
271 it.set_group_id(0);
272 run_typed_chunk_direct_finish(query, it, func, state, core::func_type_list<T...>{});
273 }
274 }
275
276 template <typename Func, typename... T>
277 inline void run_typed_cached_seed_runs(
278 QueryImpl& query, QueryInfo& queryInfo, World& world, Constraints constraints, Func& func,
279 std::span<const BfsChunkRun> runs, bool canUseBasicInit, const TypedQueryExecState& state) {
280 if (canUseBasicInit)
281 run_typed_cached_seed_runs_basic<Func, T...>(query, world, constraints, func, runs, state);
282 else
283 run_typed_cached_seed_runs_entity_init<Func, T...>(query, queryInfo, world, constraints, func, runs, state);
284 }
285
286 inline void QueryImpl::each_walk_inter(
287 QueryInfo& queryInfo, std::span<const Entity> entities, Constraints constraints, void* pFunc,
288 const TypedQueryExecState& state,
289 void (*runChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&)) {
290 auto& world = *queryInfo.world();
291 auto& walkData = ensure_each_walk_data();
292 Iter it;
293 it.init_query_state(&world, constraints, false);
294 if (!walkData.cachedRuns.empty()) {
295 const auto& runs = walkData.cachedRuns;
296 const Archetype* pLastArchetype = nullptr;
297 for (const auto& run: runs) {
298 if (run.pArchetype != pLastArchetype) {
299 it.set_archetype(run.pArchetype);
300 pLastArchetype = run.pArchetype;
301 }
302
303 it.set_chunk(run.pChunk, run.from, run.to);
304 it.set_group_id(0);
305 runChunk(*this, queryInfo, it, pFunc, state);
306 }
307 return;
308 }
309
310 const Archetype* pLastArchetype = nullptr;
311 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
312 Entity termIds[ChunkHeader::MAX_COMPONENTS];
313 for (const auto entity: entities) {
314 const auto& ec = ::gaia::ecs::fetch(world, entity);
315 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
316 runChunk(*this, queryInfo, it, pFunc, state);
317 }
318 }
319
320 inline void finish_typed_iter_state(QueryImpl& query, Iter& it, const TypedQueryExecState& state) {
321 (void)query;
322 query.finish_typed_iter_writes_runtime(it, state.argIds, state.writeFlags, state.argCount, state.firstWriteArg);
323 it.clear_touched_writes();
324 }
325
326 inline void finish_typed_chunk_state(
327 QueryImpl& query, World& world, Chunk* pChunk, uint16_t from, uint16_t to, const TypedQueryExecState& state) {
328 (void)query;
329 query.finish_typed_chunk_writes_runtime(
330 world, pChunk, from, to, state.argIds, state.writeFlags, state.argCount, state.firstWriteArg);
331 }
332
333 template <typename InvokeRow, typename OnRowDone>
334 inline void run_typed_query_rows_runtime(
335 const QueryInfo* pQueryInfo, Iter& it, InvokeRow&& invokeRow, OnRowDone&& onRowDone) {
336 const auto cnt = it.size();
337 const bool hasEntityFilters = pQueryInfo != nullptr && pQueryInfo->has_entity_filter_terms();
338
339 if (!hasEntityFilters) {
340 GAIA_FOR(cnt) {
341 invokeRow((uint32_t)i);
342 onRowDone((uint32_t)i);
343 }
344 return;
345 }
346
347 const auto entities = it.template view<Entity>();
348 GAIA_FOR(cnt) {
349 if (!QueryImpl::match_entity_filters(*pQueryInfo->world(), entities[i], *pQueryInfo))
350 continue;
351 invokeRow((uint32_t)i);
352 onRowDone((uint32_t)i);
353 }
354 }
355
356 template <typename Func, typename ViewsTuple, typename... T>
357 inline void invoke_typed_query_row(
358 Iter& it, Func& func, ViewsTuple& dataPointerTuple, uint32_t row, bool directLocalIndex,
359 core::func_type_list<T...>) {
360 if constexpr (sizeof...(T) > 0) {
361 if (directLocalIndex) {
362 std::apply(
363 [&](auto&... views) {
364 func(views[row]...);
365 },
366 dataPointerTuple);
367 } else {
368 std::apply(
369 [&](auto&... views) {
370 func(views[it.template acc_index<T>(row)]...);
371 },
372 dataPointerTuple);
373 }
374 } else
375 func();
376 }
377
378 template <typename Func, typename ViewsTuple, typename... T>
379 inline void invoke_typed_query_row_erased(
380 void* pFunc, Iter& it, ViewsTuple& dataPointerTuple, uint32_t row, bool directLocalIndex) {
381 auto& func = *static_cast<Func*>(pFunc);
382 invoke_typed_query_row(it, func, dataPointerTuple, row, directLocalIndex, core::func_type_list<T...>{});
383 }
384
385 template <typename OnRowDone, typename ViewsTuple>
386 inline void run_typed_tuple_rows(
387 const QueryInfo* pQueryInfo, Iter& it, void* pFunc, ViewsTuple& dataPointerTuple, bool directLocalIndex,
388 void (*invokeRow)(void*, Iter&, ViewsTuple&, uint32_t, bool), OnRowDone&& onRowDone) {
389 run_typed_query_rows_runtime(
390 pQueryInfo, it,
391 [&](uint32_t row) {
392 invokeRow(pFunc, it, dataPointerTuple, row, directLocalIndex);
393 },
394 GAIA_FWD(onRowDone));
395 }
396
397 template <typename OnRowDone, typename... T>
398 inline void run_typed_chunk_views(
399 const QueryInfo* pQueryInfo, Iter& it, void* pFunc, bool directViews, OnRowDone&& onRowDone,
400 void (*invokeDirectRow)(
401 void*, Iter&, std::tuple<decltype(std::declval<Iter&>().template sview_auto<T>())...>&, uint32_t, bool),
402 void (*invokeMappedRow)(
403 void*, Iter&, std::tuple<decltype(std::declval<Iter&>().template view_auto_any<T>())...>&, uint32_t,
404 bool),
405 core::func_type_list<T...>) {
406 if constexpr (sizeof...(T) > 0) {
407 if (directViews) {
408 auto dataPointerTuple = std::make_tuple(it.template sview_auto<T>()...);
409 run_typed_tuple_rows(pQueryInfo, it, pFunc, dataPointerTuple, true, invokeDirectRow, GAIA_FWD(onRowDone));
410 } else {
411 auto dataPointerTuple = std::make_tuple(it.template view_auto_any<T>()...);
412 run_typed_tuple_rows(pQueryInfo, it, pFunc, dataPointerTuple, false, invokeMappedRow, GAIA_FWD(onRowDone));
413 }
414 } else {
415 auto dataPointerTuple = std::tuple<>{};
416 run_typed_tuple_rows(
417 pQueryInfo, it, pFunc, dataPointerTuple, directViews, directViews ? invokeDirectRow : invokeMappedRow,
418 GAIA_FWD(onRowDone));
419 }
420 }
421
422 template <typename ContainerOut>
423 inline void typed_arr_push(World& world, Entity entity, ContainerOut& outArray) {
424 using ContainerItemType = typename ContainerOut::value_type;
425 if constexpr (std::is_same_v<ContainerItemType, Entity>)
426 outArray.push_back(entity);
427 else {
428 auto tmp = world_direct_entity_arg<ContainerItemType>(world, entity);
429 outArray.push_back(tmp);
430 }
431 }
432
433 template <bool UseFilters, typename ContainerOut>
434 inline void run_typed_arr_rows(
435 QueryImpl& query, const QueryInfo& queryInfo, Iter& it, ContainerOut& outArray, uint32_t changedWorldVersion,
436 uint32_t archetypeIdx, const Archetype* pArchetype, Chunk* pChunk, uint16_t from, uint16_t to,
437 bool needsBarrierCache, bool canUseDirectChunkEval) {
438 using ContainerItemType = typename ContainerOut::value_type;
439 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(archetypeIdx);
440 if GAIA_UNLIKELY (!query.can_process_archetype_inter(queryInfo, *pArchetype, it.constraints(), barrierPasses))
441 return;
442 if GAIA_UNLIKELY (from == to)
443 return;
444
445 GAIA_PROF_SCOPE(query::arr);
446
447 it.set_archetype(pArchetype);
448 it.set_chunk(pChunk, from, to);
449 it.set_group_id(0);
450
451 const auto cnt = it.size();
452 if (cnt == 0)
453 return;
454
455 if constexpr (UseFilters) {
456 if (!QueryImpl::match_filters(*pChunk, queryInfo, changedWorldVersion))
457 return;
458 }
459
460 const bool hasEntityFilters = queryInfo.has_entity_filter_terms();
461 if (canUseDirectChunkEval) {
462 const auto dataView = it.template sview_auto<ContainerItemType>();
463 GAIA_FOR(cnt) {
464 auto tmp = dataView[i];
465 outArray.push_back(tmp);
466 }
467 return;
468 }
469
470 const auto dataView = it.template view<ContainerItemType>();
471 if (!hasEntityFilters) {
472 GAIA_FOR(cnt) {
473 const auto idx = it.template acc_index<ContainerItemType>(i);
474 auto tmp = dataView[idx];
475 outArray.push_back(tmp);
476 }
477 return;
478 }
479
480 const auto entities = it.template view<Entity>();
481 GAIA_FOR(cnt) {
482 if (!QueryImpl::match_entity_filters(*queryInfo.world(), entities[i], queryInfo))
483 continue;
484 const auto idx = it.template acc_index<ContainerItemType>(i);
485 auto tmp = dataView[idx];
486 outArray.push_back(tmp);
487 }
488 }
489
490 inline void QueryImpl::finish_typed_chunk_writes_runtime(
491 World& world, Chunk* pChunk, uint16_t from, uint16_t to, const Entity* pArgIds, const bool* pWriteFlags,
492 uint32_t argCnt, uint32_t firstWriteArg) {
493 if (firstWriteArg >= argCnt || from >= to)
494 return;
495
496 Entity seenTerms[ChunkHeader::MAX_COMPONENTS]{};
497 uint32_t seenCnt = 0;
498 const auto entities = pChunk->entity_view();
499 const auto finish_term = [&](Entity term) {
500 GAIA_FOR(seenCnt) {
501 if (seenTerms[i] == term)
502 return;
503 }
504
505 seenTerms[seenCnt++] = term;
506 if (!world_is_out_of_line_component(world, term)) {
507 const auto compIdx = core::get_index(pChunk->ids_view(), term);
508 if (compIdx != BadIndex) {
509 pChunk->finish_write(compIdx, from, to);
510 return;
511 }
512 }
513
514 for (uint16_t row = from; row < to; ++row)
515 world_finish_write(world, term, entities[row]);
516 };
517
518 for (uint32_t i = firstWriteArg; i < argCnt; ++i) {
519 if (!pWriteFlags[i])
520 continue;
521 const auto term = pArgIds[i];
522 if (term != EntityBad)
523 finish_term(term);
524 }
525 }
526
527 template <typename... T>
528 inline void QueryImpl::finish_typed_chunk_writes(World& world, Chunk* pChunk, uint16_t from, uint16_t to) {
529 TypedQueryArgMeta metas[MAX_ITEMS_IN_QUERY]{};
530 const auto argCount = init_typed_query_arg_metas(metas, world, core::func_type_list<T...>{});
531 Entity argIds[MAX_ITEMS_IN_QUERY]{};
532 bool writeFlags[MAX_ITEMS_IN_QUERY]{};
533 uint32_t firstWriteArg = argCount;
534 GAIA_FOR(argCount) {
535 argIds[i] = metas[i].termId;
536 writeFlags[i] = metas[i].isWrite;
537 if (metas[i].isWrite && firstWriteArg == argCount)
538 firstWriteArg = (uint32_t)i;
539 }
540 finish_typed_chunk_writes_runtime(world, pChunk, from, to, argIds, writeFlags, argCount, firstWriteArg);
541 }
542
543 inline void QueryImpl::finish_typed_iter_writes_runtime(
544 Iter& it, const Entity* pArgIds, const bool* pWriteFlags, uint32_t argCnt, uint32_t firstWriteArg) {
545 if (firstWriteArg >= argCnt)
546 return;
547
548 auto* pChunk = const_cast<Chunk*>(it.chunk());
549 if (pChunk == nullptr || it.row_begin() >= it.row_end())
550 return;
551
552 auto& world = *it.world();
553 auto entities = it.entity_rows();
554 Entity seenTerms[ChunkHeader::MAX_COMPONENTS]{};
555 uint32_t seenCnt = 0;
556 const auto finish_term = [&](Entity term, uint32_t fieldIdx) {
557 GAIA_FOR(seenCnt) {
558 if (seenTerms[i] == term)
559 return;
560 }
561
562 seenTerms[seenCnt++] = term;
563 const bool isOutOfLine = world_is_out_of_line_component(world, term);
564 auto compIdx = uint8_t(0xFF);
565 if (it.comp_indices() != nullptr && fieldIdx < ChunkHeader::MAX_COMPONENTS)
566 compIdx = it.comp_indices()[fieldIdx];
567 else if (!isOutOfLine)
568 compIdx = (uint8_t)pChunk->comp_idx(term);
569
570 if (compIdx != 0xFF && !isOutOfLine) {
571 pChunk->finish_write(compIdx, it.row_begin(), it.row_end());
572 return;
573 }
574
575 GAIA_FOR(entities.size()) {
576 world_finish_write(world, term, entities[i]);
577 }
578 };
579
580 for (uint32_t i = firstWriteArg; i < argCnt; ++i) {
581 if (!pWriteFlags[i])
582 continue;
583
584 Entity term = pArgIds[i];
585 if (it.term_ids() != nullptr && i < ChunkHeader::MAX_COMPONENTS && it.term_ids()[i] != EntityBad)
586 term = it.term_ids()[i];
587 if (term == EntityBad)
588 continue;
589
590 finish_term(term, i);
591 }
592 }
593
603 template <typename T, typename View>
604 GAIA_NODISCARD inline decltype(auto) typed_direct_chunk_arg_at(View& view, uint32_t row, uint16_t from) {
605 using U = typename actual_type_t<T>::Type;
606 if constexpr (mem::is_soa_layout_v<U>)
607 return view[from + row];
608 else
609 return view[row];
610 }
611
622 template <typename Func, typename ViewsTuple, typename... T, size_t... I>
623 inline void invoke_typed_direct_chunk_row(
624 Func& func, ViewsTuple& views, uint32_t row, uint16_t from, core::func_type_list<T...>,
625 std::index_sequence<I...>) {
626 func(typed_direct_chunk_arg_at<T>(std::get<I>(views), row, from)...);
627 }
628
637 template <typename Func, typename... T>
638 inline void
639 run_typed_direct_chunk_rows(Chunk* pChunk, uint16_t from, uint16_t to, Func& func, core::func_type_list<T...>) {
640 if constexpr (sizeof...(T) > 0) {
641 auto views = std::make_tuple(pChunk->template sview_auto<T>(from, to)...);
642 const auto cnt = (uint32_t)(to - from);
643 GAIA_FOR(cnt)
644 invoke_typed_direct_chunk_row(
645 func, views, (uint32_t)i, from, core::func_type_list<T...>{}, std::index_sequence_for<T...>{});
646 } else {
647 const auto cnt = (uint32_t)(to - from);
648 GAIA_FOR(cnt)
649 func();
650 }
651 }
652
657 inline QueryImpl::QueryPlan
658 QueryImpl::prepare_query_plan(const QueryInfo& queryInfo, const TypedQueryExecState& state) const {
659 QueryPlan plan{};
660 plan.payloadKind = exec_payload_kind(queryInfo, Constraints::EnabledOnly);
661
662 const bool hasFilters = queryInfo.has_filters();
663 const bool hasSortedPayload = queryInfo.has_sorted_payload();
664 const bool hasDepthOrderBarrier = has_depth_order_hierarchy_enabled_barrier(queryInfo);
665 const bool depthOrderBarrierPrunes = hasDepthOrderBarrier && depth_order_hierarchy_barrier_prunes(queryInfo);
666 if (hasFilters)
667 plan.flags |= QueryPlanFlag_Filtered;
668 if (queryInfo.has_entity_filter_terms())
669 plan.flags |= QueryPlanFlag_EntityFilter;
670 if (queryInfo.has_inherited_data_payload())
671 plan.flags |= QueryPlanFlag_InheritedPayload;
672 if (queryInfo.has_grouped_payload())
673 plan.flags |= QueryPlanFlag_Grouped;
674 if (hasSortedPayload || hasDepthOrderBarrier)
675 plan.flags |= QueryPlanFlag_Sorted;
676 if (hasDepthOrderBarrier && !depthOrderBarrierPrunes)
677 plan.payloadKind = ExecPayloadKind::Grouped;
678 if (depthOrderBarrierPrunes)
679 plan.flags |= QueryPlanFlag_BarrierCache;
680
681 const bool canDirectEntitySeed = !hasFilters && can_use_direct_entity_seed_eval(queryInfo);
682 const bool canDirectChunks = can_use_direct_chunk_iteration_fastpath(queryInfo);
683
684 auto setDenseRange = [&]() -> bool {
685 const auto cacheRange = selected_query_cache_range(queryInfo);
686 plan.idxFrom = cacheRange.idxFrom;
687 plan.idxTo = cacheRange.idxTo;
688 if (cacheRange.hasSelectedGroup) {
689 plan.flags |= QueryPlanFlag_Grouped;
690 plan.payloadKind = ExecPayloadKind::Grouped;
691 if (!cacheRange.valid)
692 return false;
693 }
694 return plan.idxFrom < plan.idxTo;
695 };
696
697 if (state.canUseDirectChunkEval && !canDirectEntitySeed && canDirectChunks) {
698 if (!setDenseRange()) {
699 plan.mode = QueryPlanMode::Empty;
700 plan.idxFrom = 0;
701 plan.idxTo = 0;
702 return plan;
703 }
704
705 plan.mode = QueryPlanMode::DirectDense;
706 return plan;
707 }
708
709 if (canDirectEntitySeed) {
710 plan.mode = QueryPlanMode::EntitySeed;
711 return plan;
712 }
713
714 if (!setDenseRange()) {
715 plan.mode = QueryPlanMode::Empty;
716 plan.idxFrom = 0;
717 plan.idxTo = 0;
718 return plan;
719 }
720
721 if (hasSortedPayload) {
722 plan.mode = QueryPlanMode::Sorted;
723 plan.payloadKind = ExecPayloadKind::NonTrivial;
724 return plan;
725 }
726
727 if (hasDepthOrderBarrier && depthOrderBarrierPrunes) {
728 plan.mode = QueryPlanMode::Traversal;
729 plan.payloadKind = ExecPayloadKind::NonTrivial;
730 return plan;
731 }
732
733 if ((plan.flags & QueryPlanFlag_InheritedPayload) != 0) {
734 plan.mode = QueryPlanMode::Traversal;
735 plan.payloadKind = ExecPayloadKind::NonTrivial;
736 return plan;
737 }
738
739 if (hasDepthOrderBarrier) {
740 plan.payloadKind = ExecPayloadKind::Grouped;
741 return plan;
742 }
743
744 if (!canDirectChunks) {
745 plan.mode = QueryPlanMode::Sorted;
746 return plan;
747 }
748
749 if (!state.canUseDirectChunkEval) {
750 plan.mode = QueryPlanMode::MappedDense;
751 return plan;
752 }
753
754 return plan;
755 }
756
767 template <bool HasFilters, typename Func, typename... T>
769 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, Func& func,
771 auto& world = *queryInfo.world();
772 if constexpr (HasFilters) {
773 GAIA_ASSERT(plan.mode == QueryPlanMode::DirectDense);
774 GAIA_ASSERT((plan.flags & QueryPlanFlag_Filtered) != 0);
775 } else {
776 GAIA_ASSERT(plan.mode == QueryPlanMode::DirectDense);
777 }
778
779 auto cacheView = queryInfo.cache_archetype_view();
780 if (plan.idxFrom >= plan.idxTo)
781 return;
782
783 if (state.hasWriteArgs)
784 ::gaia::ecs::update_version(*m_worldVersion);
785
786 lock(*m_storage.world());
787 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
788 const auto* pArchetype = cacheView[i];
789 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, Constraints::EnabledOnly))
790 continue;
791
792 std::span<const uint8_t> indicesView;
793 if constexpr (HasFilters)
794 indicesView = queryInfo.indices_mapping_view(i);
795
796 const auto& chunks = pArchetype->chunks();
797 for (auto* pChunk: chunks) {
798 const auto from = Iter::start_index(pChunk);
799 const auto to = Iter::end_index(pChunk);
800 if GAIA_UNLIKELY (from == to)
801 continue;
802
803 if constexpr (HasFilters) {
804 if GAIA_UNLIKELY (!match_filters(*pChunk, queryInfo, m_changedWorldVersion, indicesView))
805 continue;
806 }
807
808 GAIA_PROF_SCOPE(query_func);
809 run_typed_direct_chunk_rows(pChunk, from, to, func, types);
810 finish_typed_chunk_state(*this, world, pChunk, from, to, state);
811 }
812 }
813
814 unlock(*m_storage.world());
815 commit_cmd_buffer_st(*m_storage.world());
816 commit_cmd_buffer_mt(*m_storage.world());
817 m_changedWorldVersion = *m_worldVersion;
818 }
819
820 inline void QueryImpl::run_query_on_chunks_direct_iter(
821 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, void* pFunc,
822 void (*runChunk)(QueryImpl&, Iter& it, void*, const TypedQueryExecState&)) {
823 auto& world = *queryInfo.world();
824 GAIA_ASSERT(plan.mode == QueryPlanMode::DirectDense);
825 GAIA_ASSERT(!queryInfo.has_filters());
826 auto cacheView = queryInfo.cache_archetype_view();
827 if (plan.idxFrom >= plan.idxTo)
828 return;
829
830 if (state.hasWriteArgs)
831 ::gaia::ecs::update_version(*m_worldVersion);
832
833 lock(*m_storage.world());
834 Iter it;
835 it.init_query_state(queryInfo.world(), Constraints::EnabledOnly, false);
836 const Archetype* pLastArchetype = nullptr;
837 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
838 const auto* pArchetype = cacheView[i];
839 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, Constraints::EnabledOnly))
840 continue;
841
842 const auto& chunks = pArchetype->chunks();
843 for (auto* pChunk: chunks) {
844 const auto from = Iter::start_index(pChunk);
845 const auto to = Iter::end_index(pChunk);
846 if GAIA_UNLIKELY (from == to)
847 continue;
848
849 GAIA_PROF_SCOPE(query_func);
850 if (pArchetype != pLastArchetype) {
851 it.set_archetype(pArchetype);
852 pLastArchetype = pArchetype;
853 }
854 it.set_chunk(pChunk, from, to);
855 it.set_group_id(0);
856 it.ctx(m_ctx);
857 runChunk(*this, it, pFunc, state);
858 finish_typed_chunk_state(*this, world, pChunk, from, to, state);
859 }
860 }
861
862 unlock(*m_storage.world());
863 commit_cmd_buffer_st(*m_storage.world());
864 commit_cmd_buffer_mt(*m_storage.world());
865 m_changedWorldVersion = *m_worldVersion;
866 }
867
868 inline void QueryImpl::run_query_on_chunks_direct(
869 QueryInfo& queryInfo, const QueryPlan& plan, const TypedQueryExecState& state, void* pFunc,
870 void (*runChunk)(QueryImpl&, Iter& it, void*, const TypedQueryExecState&)) {
871 auto& world = *queryInfo.world();
872 auto cacheView = queryInfo.cache_archetype_view();
873 if (plan.idxFrom >= plan.idxTo)
874 return;
875
876 if (state.hasWriteArgs)
877 ::gaia::ecs::update_version(*m_worldVersion);
878
879 GAIA_ASSERT(plan.mode == QueryPlanMode::DirectDense);
880 const bool hasFilters = (plan.flags & QueryPlanFlag_Filtered) != 0;
881
882 lock(*m_storage.world());
883 Iter it;
884 it.init_query_state(queryInfo.world(), Constraints::EnabledOnly, false);
885 const Archetype* pLastArchetype = nullptr;
886
887 for (uint32_t i = plan.idxFrom; i < plan.idxTo; ++i) {
888 const auto* pArchetype = cacheView[i];
889 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, Constraints::EnabledOnly))
890 continue;
891
892 std::span<const uint8_t> indicesView;
893 if (hasFilters)
894 indicesView = queryInfo.indices_mapping_view(i);
895
896 const auto& chunks = pArchetype->chunks();
897 for (auto* pChunk: chunks) {
898 const auto from = Iter::start_index(pChunk);
899 const auto to = Iter::end_index(pChunk);
900 if GAIA_UNLIKELY (from == to)
901 continue;
902
903 if (hasFilters) {
904 if GAIA_UNLIKELY (!match_filters(*pChunk, queryInfo, m_changedWorldVersion, indicesView))
905 continue;
906 }
907
908 GAIA_PROF_SCOPE(query_func);
909 if (pArchetype != pLastArchetype) {
910 it.set_archetype(pArchetype);
911 pLastArchetype = pArchetype;
912 }
913 it.set_chunk(pChunk, from, to);
914 it.set_group_id(0);
915 it.ctx(m_ctx);
916 runChunk(*this, it, pFunc, state);
917 finish_typed_chunk_state(*this, world, pChunk, from, to, state);
918 }
919 }
920
921 unlock(*m_storage.world());
922 commit_cmd_buffer_st(*m_storage.world());
923 commit_cmd_buffer_mt(*m_storage.world());
924 m_changedWorldVersion = *m_worldVersion;
925 }
926
927 template <QueryExecType ExecType>
928 inline void QueryImpl::each_inter(
929 QueryInfo& queryInfo, const QueryPlan& plan, void* pFunc, const TypedQueryExecState& state,
930 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
931 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
932 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&),
933 bool needsInheritedArgIds, void (*invokeInherited)(World&, Entity, const Entity*, void*)) {
934 if (plan.mode == QueryPlanMode::Empty)
935 return;
936
937 if (plan.mode == QueryPlanMode::EntitySeed) {
938 GAIA_PROF_SCOPE(query_func);
940 queryInfo, Constraints::EnabledOnly, pFunc, state, runDirectChunk, needsInheritedArgIds, invokeInherited);
941 return;
942 }
943
944 if (state.canUseDirectChunkEval) {
945 if constexpr (ExecType == QueryExecType::Default) {
946 if (plan.mode == QueryPlanMode::DirectDense) {
947 run_query_on_chunks_direct(queryInfo, plan, state, pFunc, runDirectFastChunk);
948 return;
949 }
950 }
951 TypedDirectChunkCallback cb{this, pFunc, &state, runDirectChunk};
952 run_query_on_chunks<ExecType, IterModeEnabled>(queryInfo, cb);
953 } else {
954 TypedMappedChunkCallback cb{this, &queryInfo, pFunc, &state, runMappedChunk};
955 run_query_on_chunks<ExecType, IterModeEnabled>(queryInfo, cb);
956 }
957 }
958
959 template <QueryExecType ExecType, typename Func>
960 inline void QueryImpl::each_typed_inter(QueryInfo& queryInfo, Func func) {
961 using InputArgs = decltype(core::func_args(&Func::operator()));
962
963#if GAIA_ASSERT_ENABLED
964 GAIA_ASSERT(typed_query_args_match_query(queryInfo, InputArgs{}));
965#endif
966 auto& world = *const_cast<World*>(queryInfo.world());
967 TypedQueryArgMeta metas[MAX_ITEMS_IN_QUERY]{};
968 const auto argCount = init_typed_query_arg_metas(metas, world, InputArgs{});
969 const auto state = build_typed_query_exec_state(*this, world, queryInfo, metas, argCount);
970 const auto plan = prepare_query_plan(queryInfo, state);
971 if constexpr (ExecType == QueryExecType::Default) {
972 if (plan.mode == QueryPlanMode::DirectDense) {
973 if ((plan.flags & QueryPlanFlag_Filtered) != 0)
974 run_query_on_chunks_direct_typed<true>(queryInfo, plan, state, func, InputArgs{});
975 else
976 run_query_on_chunks_direct_typed<false>(queryInfo, plan, state, func, InputArgs{});
977 return;
978 }
979 }
980
981 const auto runDirectFastChunk = typed_run_direct_fast_chunk_ptr<Func>(InputArgs{});
982 const auto runDirectChunk = typed_run_direct_chunk_ptr<Func>(InputArgs{});
983 const auto runMappedChunk = typed_run_mapped_chunk_ptr<Func>(InputArgs{});
984 const auto invokeInherited = typed_invoke_inherited_ptr<Func>(InputArgs{});
985 each_inter<ExecType>(
986 queryInfo, plan, &func, state, runDirectFastChunk, runDirectChunk, runMappedChunk,
987 state.needsInheritedArgIds, invokeInherited);
988 }
989
990 inline void QueryImpl::each_typed_erased(
991 QueryExecType execType, void* pFunc, const TypedQueryExecState& state,
992 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
993 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
994 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&),
995 bool needsInheritedArgIds, void (*invokeInherited)(World&, Entity, const Entity*, void*)) {
996 auto& queryInfo = fetch();
997 match_all(queryInfo);
998 const auto plan = prepare_query_plan(queryInfo, state);
999
1000 switch (execType) {
1001 case QueryExecType::Parallel:
1002 each_inter<QueryExecType::Parallel>(
1003 queryInfo, plan, pFunc, state, runDirectFastChunk, runDirectChunk, runMappedChunk, needsInheritedArgIds,
1004 invokeInherited);
1005 break;
1006 case QueryExecType::ParallelPerf:
1007 each_inter<QueryExecType::ParallelPerf>(
1008 queryInfo, plan, pFunc, state, runDirectFastChunk, runDirectChunk, runMappedChunk, needsInheritedArgIds,
1009 invokeInherited);
1010 break;
1011 case QueryExecType::ParallelEff:
1012 each_inter<QueryExecType::ParallelEff>(
1013 queryInfo, plan, pFunc, state, runDirectFastChunk, runDirectChunk, runMappedChunk, needsInheritedArgIds,
1014 invokeInherited);
1015 break;
1016 default:
1017 each_inter<QueryExecType::Default>(
1018 queryInfo, plan, pFunc, state, runDirectFastChunk, runDirectChunk, runMappedChunk, needsInheritedArgIds,
1019 invokeInherited);
1020 break;
1021 }
1022 }
1023
1024 template <typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>, int>>
1025 inline void QueryImpl::each(Func func) {
1026 each(func, QueryExecType::Default);
1027 }
1028
1029 template <typename Func, std::enable_if_t<!detail::is_query_iter_callback_v<Func>, int>>
1030 inline void QueryImpl::each(Func func, QueryExecType execType) {
1031 auto& queryInfo = fetch();
1032 match_all(queryInfo);
1033
1034 switch (execType) {
1035 case QueryExecType::Parallel:
1036 each_typed_inter<QueryExecType::Parallel>(queryInfo, func);
1037 break;
1038 case QueryExecType::ParallelPerf:
1039 each_typed_inter<QueryExecType::ParallelPerf>(queryInfo, func);
1040 break;
1041 case QueryExecType::ParallelEff:
1042 each_typed_inter<QueryExecType::ParallelEff>(queryInfo, func);
1043 break;
1044 default:
1045 each_typed_inter<QueryExecType::Default>(queryInfo, func);
1046 break;
1047 }
1048 }
1049
1050 template <QueryExecType ExecType>
1051 inline void QueryImpl::each_iter_inter_erased(
1052 QueryInfo& queryInfo, const QueryPlan& plan, void* pFunc, const TypedQueryExecState& state,
1053 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
1054 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&)) {
1055 TypedIterErasedCallback cb{this, pFunc, &state, runDirectFastChunk, runMappedChunk};
1056
1057 if (plan.mode == QueryPlanMode::Empty)
1058 return;
1059
1060 if (plan.mode == QueryPlanMode::EntitySeed) {
1061 each_direct_iter_inter(queryInfo, Constraints::EnabledOnly, cb);
1062 return;
1063 }
1064
1065 if constexpr (ExecType == QueryExecType::Default) {
1066 if (plan.mode == QueryPlanMode::DirectDense) {
1067 run_query_on_chunks_direct_iter(queryInfo, plan, state, pFunc, runDirectFastChunk);
1068 return;
1069 }
1070 }
1071
1072 run_query_on_chunks<ExecType, IterModeEnabled>(queryInfo, cb);
1073 }
1074
1075 inline void QueryImpl::each_iter_erased(
1076 QueryExecType execType, void* pFunc, const TypedQueryExecState& state,
1077 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
1078 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&)) {
1079 auto& queryInfo = fetch();
1080 match_all(queryInfo);
1081 const auto plan = prepare_query_plan(queryInfo, state);
1082
1083 switch (execType) {
1084 case QueryExecType::Parallel:
1085 each_iter_inter_erased<QueryExecType::Parallel>(
1086 queryInfo, plan, pFunc, state, runDirectFastChunk, runMappedChunk);
1087 break;
1088 case QueryExecType::ParallelPerf:
1089 each_iter_inter_erased<QueryExecType::ParallelPerf>(
1090 queryInfo, plan, pFunc, state, runDirectFastChunk, runMappedChunk);
1091 break;
1092 case QueryExecType::ParallelEff:
1093 each_iter_inter_erased<QueryExecType::ParallelEff>(
1094 queryInfo, plan, pFunc, state, runDirectFastChunk, runMappedChunk);
1095 break;
1096 default:
1097 each_iter_inter_erased<QueryExecType::Default>(
1098 queryInfo, plan, pFunc, state, runDirectFastChunk, runMappedChunk);
1099 break;
1100 }
1101 }
1102
1103 template <typename Func>
1104 inline void QueryImpl::each_iter(Iter& it, Func func) {
1105 using InputArgs = decltype(core::func_args(&Func::operator()));
1106 auto& queryInfo = fetch();
1107
1108#if GAIA_ASSERT_ENABLED
1109 GAIA_ASSERT(typed_query_args_match_query(queryInfo, InputArgs{}));
1110#endif
1111 TypedQueryArgMeta metas[MAX_ITEMS_IN_QUERY]{};
1112 const auto argCount = init_typed_query_arg_metas(metas, *it.world(), InputArgs{});
1113 const auto state = build_typed_query_exec_state(*this, *it.world(), queryInfo, metas, argCount);
1114 it.ctx(m_ctx);
1115 each_iter_dispatch(*this, queryInfo, it, func, state, InputArgs{});
1116 }
1117
1118 inline void QueryImpl::each_iter_erased(
1119 Iter& it, void* pFunc, const TypedQueryExecState& state,
1120 void (*runDirectFastChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&),
1121 void (*runMappedChunk)(QueryImpl&, const QueryInfo&, Iter&, void*, const TypedQueryExecState&)) {
1122 auto& queryInfo = fetch();
1123 it.ctx(m_ctx);
1124 if (state.canUseDirectChunkEval) {
1125 runDirectFastChunk(*this, it, pFunc, state);
1126 finish_typed_chunk_state(
1127 *this, *it.world(), const_cast<Chunk*>(it.chunk()), it.row_begin(), it.row_end(), state);
1128 } else
1129 runMappedChunk(*this, queryInfo, it, pFunc, state);
1130 }
1131
1133 QueryInfo& queryInfo, Constraints constraints, void* pFunc, const TypedQueryExecState& state,
1134 void (*runDirectChunk)(QueryImpl&, Iter&, void*, const TypedQueryExecState&), bool needsInheritedArgIds,
1135 void (*invokeInherited)(World&, Entity, const Entity*, void*)) {
1136 auto& world = *queryInfo.world();
1137 const auto plan = direct_entity_seed_plan(world, queryInfo);
1138 const bool hasWriteTerms = queryInfo.ctx().data.readWriteMask != 0;
1139
1140 auto exec_direct_entity = [&](Entity entity) {
1141 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
1143 Iter it;
1144 it.set_constraints(constraints);
1145 init_direct_entity_iter(queryInfo, world, entity, it, indices, termIds);
1146 it.ctx(m_ctx);
1147 runDirectChunk(*this, it, pFunc, state);
1148 };
1149
1150 auto exec_entity = [&](Entity entity) {
1151 if (needsInheritedArgIds && state.hasInheritedTerms) {
1152 invokeInherited(world, entity, state.argIds, pFunc);
1153 finish_typed_query_args_by_id(world, entity, state.argIds, state.writeFlags, state.argCount);
1154 return;
1155 }
1156
1157 exec_direct_entity(entity);
1158 };
1159
1160 if (!hasWriteTerms && !plan.preferOrSeed) {
1161 const auto* pSeedTerm = find_direct_all_seed_term(queryInfo, plan);
1162 if (pSeedTerm != nullptr && can_use_direct_seed_run_cache(world, queryInfo, *pSeedTerm)) {
1163 DirectEntitySeedInfo seedInfo{};
1164 seedInfo.seededAllTerm = pSeedTerm->id;
1165 seedInfo.seededAllMatchKind = pSeedTerm->matchKind;
1166 seedInfo.seededFromAll = true;
1167 if (!state.hasInheritedTerms) {
1168 const auto runs = cached_direct_seed_runs(queryInfo, *pSeedTerm, seedInfo, constraints);
1169 if (state.canUseDirectChunkEval) {
1170 Iter it;
1171 it.init_query_state(&world, constraints, false);
1172 const Archetype* pLastArchetype = nullptr;
1173 for (const auto& run: runs) {
1174 if (run.pArchetype != pLastArchetype) {
1175 it.set_archetype(run.pArchetype);
1176 pLastArchetype = run.pArchetype;
1177 }
1178 it.set_chunk(run.pChunk, run.from, run.to);
1179 it.set_group_id(0);
1180 it.ctx(m_ctx);
1181 runDirectChunk(*this, it, pFunc, state);
1182 }
1183 } else {
1184 Iter it;
1185 it.init_query_state(&world, constraints, false);
1186 const Archetype* pLastArchetype = nullptr;
1187 uint8_t indices[ChunkHeader::MAX_COMPONENTS];
1189 for (const auto& run: runs) {
1190 const auto& ec = ::gaia::ecs::fetch(world, run.pChunk->entity_view()[run.from]);
1191 init_direct_entity_iter(queryInfo, world, ec, it, indices, termIds, pLastArchetype);
1192 it.set_chunk(run.pChunk, run.from, run.to);
1193 it.set_group_id(0);
1194 it.ctx(m_ctx);
1195 runDirectChunk(*this, it, pFunc, state);
1196 }
1197 }
1198 } else {
1199 const auto entities = cached_direct_seed_chunk_entities(queryInfo, *pSeedTerm, seedInfo, constraints);
1200 for (const auto entity: entities)
1201 exec_entity(entity);
1202 }
1203 return;
1204 }
1205 }
1206
1207 auto walk_entities = [&](auto&& execEntity) {
1208 if (hasWriteTerms) {
1209 auto& scratch = direct_query_scratch();
1210 const auto seedInfo = build_direct_entity_seed(world, queryInfo, scratch.entities);
1211 for (const auto entity: scratch.entities) {
1212 if (!match_direct_entity_constraints(world, queryInfo, entity, constraints))
1213 continue;
1214 if (!match_direct_entity_terms(world, entity, queryInfo, seedInfo))
1215 continue;
1216 execEntity(entity);
1217 }
1218 return;
1219 }
1220
1221 if (plan.preferOrSeed) {
1222 for_each_direct_or_union(world, queryInfo, constraints, [&](Entity entity) {
1223 execEntity(entity);
1224 return true;
1225 });
1226 return;
1227 }
1228
1229 (void)for_each_direct_all_seed(world, queryInfo, plan, constraints, [&](Entity entity) {
1230 execEntity(entity);
1231 return true;
1232 });
1233 };
1234
1235 walk_entities(exec_entity);
1236 }
1237
1238 template <typename Func, std::enable_if_t<!detail::is_query_walk_core_callback_v<Func>, int>>
1239 inline void QueryImpl::each_walk(Func func, Entity relation, TravOrder order, Constraints constraints) {
1240 auto& queryInfo = fetch();
1241 match_all(queryInfo);
1242 const auto ordered = ordered_entities_walk(queryInfo, relation, order, constraints);
1243
1244 using InputArgs = decltype(core::func_args(&Func::operator()));
1245 GAIA_ASSERT(typed_query_args_match_query(queryInfo, InputArgs{}));
1246 GAIA_ASSERT(can_use_direct_target_eval(queryInfo));
1247 if (!can_use_direct_target_eval(queryInfo))
1248 return;
1249
1250 auto& world = *queryInfo.world();
1251 TypedQueryArgMeta metas[MAX_ITEMS_IN_QUERY]{};
1252 const auto argCount = init_typed_query_arg_metas(metas, world, InputArgs{});
1253 const auto state = build_typed_query_exec_state(*this, world, queryInfo, metas, argCount);
1254 if (state.canUseDirectChunkEval) {
1255 each_walk_dispatch_direct(*this, queryInfo, ordered, constraints, func, state, InputArgs{});
1256 } else {
1257 each_walk_dispatch_mapped(*this, queryInfo, ordered, constraints, func, state, InputArgs{});
1258 }
1259 }
1260
1261 template <bool UseFilters, typename ContainerOut>
1262 inline void QueryImpl::arr_inter(QueryInfo& queryInfo, ContainerOut& outArray, Constraints constraints) {
1263 using ContainerItemType = typename ContainerOut::value_type;
1264 if constexpr (!UseFilters) {
1265 if (can_use_direct_entity_seed_eval(queryInfo)) {
1266 auto& world = *queryInfo.world();
1267 const auto plan = direct_entity_seed_plan(world, queryInfo);
1268 if (plan.preferOrSeed) {
1269 for_each_direct_or_union(world, queryInfo, constraints, [&](Entity entity) {
1270 typed_arr_push(world, entity, outArray);
1271 });
1272 return;
1273 }
1274
1275 (void)for_each_direct_all_seed(world, queryInfo, plan, constraints, [&](Entity entity) {
1276 typed_arr_push(world, entity, outArray);
1277 return true;
1278 });
1279
1280 return;
1281 }
1282 }
1283
1284 auto& world = *queryInfo.world();
1285 const auto meta = typed_query_arg_meta<ContainerItemType>(world);
1286 const DirectChunkArgEvalDesc desc{meta.termId, meta.isEntity, meta.isPair};
1287 Iter it;
1288 it.init_query_state(queryInfo.world(), constraints, false);
1289 const bool canUseDirectChunkEval = !UseFilters && !queryInfo.has_entity_filter_terms() &&
1290 can_use_direct_chunk_term_eval_descs(world, queryInfo, &desc, 1) &&
1292 const auto cacheView = queryInfo.cache_archetype_view();
1293 const bool needsBarrierCache = needs_depth_order_hierarchy_barrier_cache(queryInfo, constraints);
1294 const bool hasSortedArrayPayload = queryInfo.has_sorted_payload() || needsBarrierCache;
1295 const auto sortView =
1296 hasSortedArrayPayload ? queryInfo.cache_sort_view() : decltype(queryInfo.cache_sort_view()){};
1297 if (needsBarrierCache)
1298 queryInfo.ensure_depth_order_hierarchy_barrier_cache();
1299 const auto cacheRange = selected_query_cache_range(queryInfo);
1300 if (!cacheRange.valid)
1301 return;
1302 const auto idxFrom = cacheRange.idxFrom;
1303 const auto idxTo = cacheRange.idxTo;
1304
1305 if (!sortView.empty()) {
1306 for (const auto& view: sortView) {
1307 if (view.archetypeIdx < idxFrom || view.archetypeIdx >= idxTo)
1308 continue;
1309
1310 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(view.archetypeIdx);
1311 if GAIA_UNLIKELY (!can_process_archetype_inter(
1312 queryInfo, *cacheView[view.archetypeIdx], constraints, barrierPasses))
1313 continue;
1314
1315 const auto viewFrom = view.startRow;
1316 const auto viewTo = (uint16_t)(view.startRow + view.count);
1317 uint16_t minStartRow = 0;
1318 uint16_t minEndRow = 0;
1319 chunk_effective_range(view.pChunk, constraints, needsBarrierCache, barrierPasses, minStartRow, minEndRow);
1320 const auto startRow = core::get_max(minStartRow, viewFrom);
1321 const auto endRow = core::get_min(minEndRow, viewTo);
1322 if (startRow == endRow)
1323 continue;
1324
1325 run_typed_arr_rows<UseFilters>(
1326 *this, queryInfo, it, outArray, m_changedWorldVersion, view.archetypeIdx, cacheView[view.archetypeIdx],
1327 view.pChunk, startRow, endRow, needsBarrierCache, canUseDirectChunkEval);
1328 }
1329 return;
1330 }
1331
1332 for (uint32_t i = idxFrom; i < idxTo; ++i) {
1333 auto* pArchetype = cacheView[i];
1334 const bool barrierPasses = !needsBarrierCache || queryInfo.barrier_passes(i);
1335 if GAIA_UNLIKELY (!can_process_archetype_inter(queryInfo, *pArchetype, constraints, barrierPasses))
1336 continue;
1337
1338 const auto& chunks = pArchetype->chunks();
1339 for (auto* pChunk: chunks) {
1340 uint16_t from = 0;
1341 uint16_t to = 0;
1342 chunk_effective_range(pChunk, constraints, needsBarrierCache, barrierPasses, from, to);
1343 run_typed_arr_rows<UseFilters>(
1344 *this, queryInfo, it, outArray, m_changedWorldVersion, i, pArchetype, pChunk, from, to,
1345 needsBarrierCache, canUseDirectChunkEval);
1346 }
1347 }
1348 }
1349
1350 template <typename Container>
1351 inline void QueryImpl::arr(Container& outArray, Constraints constraints) {
1352 const auto entCnt = count(constraints);
1353 if (entCnt == 0)
1354 return;
1355
1356 outArray.reserve(entCnt);
1357 auto& queryInfo = fetch();
1358 match_all(queryInfo);
1359
1360 const bool hasFilters = queryInfo.has_filters();
1361 if (hasFilters) {
1362 arr_inter<true>(queryInfo, outArray, constraints);
1363 } else {
1364 arr_inter<false>(queryInfo, outArray, constraints);
1365 }
1366 }
1367 } // namespace detail
1368 } // namespace ecs
1369} // namespace gaia
Definition span_impl.h:99
Definition archetype.h:83
Definition chunk.h:35
Iterator for iterating entity subsets selected by Constraints. Disabled entities always precede enabl...
Definition chunk_iterator.h:1469
Definition query_info.h:100
GAIA_NODISCARD std::span< const Archetype * > cache_archetype_view() const
Returns the cached result archetypes as a span.
Definition query_info.h:2307
GAIA_NODISCARD bool has_inherited_data_payload() const
Returns true when query iteration needs cached inherited data payloads.
Definition query_info.h:1651
GAIA_NODISCARD bool has_filters() const
Returns true when the query has per-entity changed/filter terms.
Definition query_info.h:2116
GAIA_NODISCARD bool has_grouped_payload() const
Returns true when grouped-query payloads are active for this query.
Definition query_info.h:969
GAIA_NODISCARD World * world()
Returns the mutable world owning this query.
Definition query_info.h:2072
GAIA_NODISCARD bool has_sorted_payload() const
Returns true when sorted-query payloads are active for this query.
Definition query_info.h:974
GAIA_NODISCARD bool has_entity_filter_terms() const
Returns true when direct non-fragmenting terms must be rechecked per entity.
Definition query_info.h:2122
GAIA_NODISCARD QueryCtx & ctx()
Returns the mutable compiled query context.
Definition query_info.h:2092
std::span< const uint8_t > indices_mapping_view(uint32_t archetypeIdx) const
Returns a view of indices mapping for component entities in a given archetype.
Definition query_info.h:2199
Definition world.h:174
void init_query_state(const World *pWorld, Constraints constraints, bool writeIm)
Initializes stable query execution state stored by the iterator.
Definition chunk_iterator.h:998
GAIA_NODISCARD uint16_t row_end() const noexcept
Returns one-past-the-end row covered by the iterator in the current chunk.
Definition chunk_iterator.h:1438
void ctx(void *pCtx)
Sets the user-owned context pointer visible through ctx().
Definition chunk_iterator.h:1095
GAIA_NODISCARD uint16_t row_begin() const noexcept
Returns the first row covered by the iterator in the current chunk.
Definition chunk_iterator.h:1433
Definition query.h:500
static void chunk_effective_range(Chunk *pChunk, Constraints constraints, bool needsBarrierCache, bool barrierPasses, uint16_t &from, uint16_t &to) noexcept
Calculates the row range of a chunk after applying row constraints and depth-order barrier state.
Definition query.h:1914
void arr(Container &outArray, Constraints constraints=Constraints::EnabledOnly)
Appends all components or entities matching the query to the output array.
Definition query_typed.inl:1351
static GAIA_NODISCARD bool needs_depth_order_hierarchy_barrier_cache(const QueryInfo &queryInfo, Constraints constraints)
Checks whether the current row constraints require the depth-order hierarchy barrier cache.
Definition query.h:1903
GAIA_NODISCARD QueryPlan prepare_query_plan(const QueryInfo &queryInfo, const TypedQueryExecState &state) const
Selects the prepared execution plan for typed callbacks.
Definition query_typed.inl:658
static GAIA_NODISCARD bool match_direct_entity_constraints(const World &world, const QueryInfo &queryInfo, Entity entity, Constraints constraints)
Applies iterator-specific entity state constraints to the direct seeded path.
Definition query.h:4294
GAIA_NODISCARD bool can_use_direct_chunk_iteration_fastpath(const QueryInfo &queryInfo) const
Checks whether typed callbacks can use dense chunk iteration while preserving required cache ordering...
Definition query.h:3449
static GAIA_NODISCARD bool can_use_direct_entity_seed_eval(const QueryInfo &queryInfo)
Detects queries that can skip archetype seeding and start directly from entity-backed term indices.
Definition query.h:3941
@ Grouped
Batches carry group ids but do not require sorted slices or inherited/barrier metadata.
@ NonTrivial
Batches require non-trivial side payload such as sorted slices, inherited data, or barriers.
void for_each_direct_or_union(World &world, const QueryInfo &queryInfo, Constraints constraints, Func &&func)
Visits the deduplicated OR union for direct-seeded queries without materializing an entity seed array...
Definition query.h:4531
@ QueryPlanFlag_Sorted
The plan may need sorted cache slices; runners use them only with non-trivial payload.
Definition query.h:2090
@ QueryPlanFlag_Filtered
The query has per-chunk filters such as changed terms.
Definition query.h:2082
@ QueryPlanFlag_InheritedPayload
The query carries inherited component data into iterator payloads.
Definition query.h:2086
@ QueryPlanFlag_BarrierCache
The plan must use the depth-order hierarchy barrier cache when checking archetype/row ranges.
Definition query.h:2092
@ QueryPlanFlag_Grouped
The query uses grouped payload/ranges or grouped cache ordering.
Definition query.h:2088
@ QueryPlanFlag_EntityFilter
The query has entity-filter terms that require per-entity rechecks.
Definition query.h:2084
void each_direct_iter_inter(QueryInfo &queryInfo, Constraints constraints, Func func)
Runs an iterator-based each() callback over directly seeded entities using one-row chunk views.
Definition query.h:5037
static GAIA_NODISCARD bool match_entity_filters(const World &world, Entity entity, const QueryInfo &queryInfo)
Evaluates the entity-level terms that are not fully represented by archetype membership.
Definition query.h:4656
void each_iter(Iter &it, Func func)
Runs a typed callback against an already prepared iterator. This is used by higher-level adapters tha...
Definition query_typed.inl:1104
static GAIA_NODISCARD ExecPayloadKind exec_payload_kind(const QueryInfo &queryInfo, Constraints constraints)
Classifies the generic batch payload needed for a matched query under row constraints.
Definition query.h:2041
void each(Func func)
Iterates query matches using the default execution mode.
Definition query.h:5925
static GAIA_NODISCARD bool depth_order_hierarchy_barrier_prunes(const QueryInfo &queryInfo)
Checks whether cached depth-order barrier results can prune any matched archetype.
Definition query.h:1930
GAIA_NODISCARD QueryCacheRange selected_query_cache_range(const QueryInfo &queryInfo) const
Selects the cache range visible to this query, applying a selected group id when present.
Definition query.h:3458
static GAIA_NODISCARD bool has_depth_order_hierarchy_enabled_barrier(const QueryInfo &queryInfo)
Checks whether depth-order grouping can prune disabled hierarchy subtrees.
Definition query.h:1892
void run_query_on_chunks_direct_typed(QueryInfo &queryInfo, const QueryPlan &plan, const TypedQueryExecState &state, Func &func, core::func_type_list< T... >)
Runs the prepared direct typed row path for simple cached queries.
Definition query_typed.inl:768
static DirectEntitySeedInfo build_direct_entity_seed(const World &world, const QueryInfo &queryInfo, cnt::darray< Entity > &out)
Builds the best direct entity seed set from the smallest positive ALL term or the OR union fallback.
Definition query.h:4461
void match_all(QueryInfo &queryInfo)
Matches the query against all relevant archetypes.
Definition query.h:943
static GAIA_NODISCARD bool can_use_direct_target_eval(const QueryInfo &queryInfo)
Detects queries whose terms can be evaluated directly against concrete target entities.
Definition query.h:3959
GAIA_NODISCARD std::span< const Entity > ordered_entities_walk(QueryInfo &queryInfo, Entity relation, TravOrder order, Constraints constraints=Constraints::EnabledOnly)
Builds and caches relation traversal order for the current query result.
Definition query.h:6245
uint32_t count(Constraints constraints=Constraints::EnabledOnly)
Calculates the number of entities matching the query.
Definition query.h:6041
static GAIA_NODISCARD bool match_filters(const Chunk &chunk, const QueryInfo &queryInfo, uint32_t changedWorldVersion, std::span< const uint8_t > compIndices)
Returns whether a chunk passes the query's changed filters.
Definition query.h:1781
void each_walk(Func func, Entity relation, TravOrder order=TravOrder::Down, Constraints constraints=Constraints::EnabledOnly)
Iterates entities matching the query in a requested relation traversal order. For relation R this tre...
Definition query.h:6581
@ Sorted
Sorted payload execution that must preserve cache-provided chunk order.
@ DirectDense
Direct cached archetype/chunk iteration with query-term indices matching storage layout.
@ EntitySeed
Direct entity-seed evaluation over explicitly selected entities.
@ MappedDense
Typed dense cached archetype/chunk iteration using mapped component access. Public Iter callbacks use...
@ Empty
The selected group/range has no matching archetypes, so execution can return immediately.
@ Traversal
Traversal or inherited payload execution that requires the mapped generic path.
static GAIA_NODISCARD bool match_direct_entity_terms(const World &world, Entity entity, const QueryInfo &queryInfo, const DirectEntitySeedInfo &seedInfo)
Evaluates the remaining direct terms for a single seeded entity after the seed term itself was consum...
Definition query.h:4069
QueryInfo & fetch()
Fetches the QueryInfo object. Creates or refreshes the backing QueryInfo if needed.
Definition query.h:889
static GAIA_NODISCARD bool can_use_direct_seed_run_cache(const World &world, const QueryInfo &queryInfo, const QueryTerm &seedTerm)
Returns whether a repeated semantic or inherited seed can be cached as chunk runs.
Definition query.h:4151
GAIA_NODISCARD bool can_process_archetype_inter(const QueryInfo &queryInfo, const Archetype &archetype, Constraints constraints, int8_t barrierPasses=-1) const
Checks whether a matched archetype can be processed for the current row constraints.
Definition query.h:1970
void each_direct_inter(QueryInfo &queryInfo, Constraints constraints, void *pFunc, const TypedQueryExecState &state, void(*runDirectChunk)(QueryImpl &, Iter &, void *, const TypedQueryExecState &), bool needsInheritedArgIds, void(*invokeInherited)(World &, Entity, const Entity *, void *))
Runs a typed each() callback over directly seeded entities.
Definition query_typed.inl:1132
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
constexpr uint32_t BadIndex
Sentinel index value returned by helpers when a lookup fails.
Definition utility.h:20
Lightweight carrier for a function argument type pack.
Definition utility.h:643
static constexpr uint32_t MAX_COMPONENTS
Maximum number of components on archetype.
Definition chunk_header.h:53
Definition id.h:247
uint16_t readWriteMask
Read-write mask. Bit 0 stands for component 0 in component arrays. A set bit means write access is re...
Definition query_common.h:770
Definition query_adapter_typed.inl:16
Definition query_adapter_typed.inl:5
GAIA_NODISCARD World * world()
Returns the world associated with this query storage.
Definition query.h:389
Prepared query execution metadata shared by typed callbacks and public Iter callbacks.
Definition query.h:2096
uint32_t idxTo
One-past-the-end cached archetype index to process.
Definition query.h:2106
QueryPlanMode mode
Runner family selected for the current matched query cache.
Definition query.h:2098
uint32_t idxFrom
First cached archetype index to process.
Definition query.h:2104
ExecPayloadKind payloadKind
Payload layout required by generic chunk-batch runners independent of sorted-cache availability.
Definition query.h:2102
uint8_t flags
Orthogonal plan properties such as filtering, entity filters, grouping, or payload requirements.
Definition query.h:2100