Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
query_common.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <type_traits>
5
6#include "gaia/cnt/darray.h"
7#include "gaia/cnt/map.h"
8#include "gaia/cnt/sarray.h"
9#include "gaia/cnt/sarray_ext.h"
10#include "gaia/core/bit_utils.h"
11#include "gaia/core/hashing_policy.h"
12#include "gaia/core/utility.h"
13#include "gaia/ecs/api.h"
14#include "gaia/ecs/component.h"
15#include "gaia/ecs/id.h"
16#include "gaia/ecs/query_fwd.h"
17#include "gaia/ecs/query_mask.h"
18#include "gaia/ecs/ser_binary.h"
19
20namespace gaia {
21 namespace ecs {
22 class World;
23 class Archetype;
24
26 static constexpr uint32_t MAX_ITEMS_IN_QUERY = 8U;
27
28 GAIA_GCC_WARNING_PUSH()
29 // GCC is unnecessarily too strict about shadowing.
30 // We have a globally defined entity All and thinks QueryOpKind::All shadows it.
31 GAIA_GCC_WARNING_DISABLE("-Wshadow")
32
34 enum class QueryOpKind : uint8_t { All, Any, Not, Count };
36 enum class QueryAccess : uint8_t { None, Read, Write };
38 enum class QueryInputFlags : uint8_t { None, Variable };
39
40 GAIA_GCC_WARNING_POP()
41
42 using QueryLookupHash = core::direct_hash_key<uint64_t>;
43 using QueryEntityArray = cnt::sarray<Entity, MAX_ITEMS_IN_QUERY>;
44 using QueryArchetypeCacheIndexMap = cnt::map<EntityLookupKey, uint32_t>;
45 using QuerySerMap = cnt::map<QueryId, QuerySerBuffer>;
46
47 static constexpr QueryId QueryIdBad = (QueryId)-1;
48 static constexpr GroupId GroupIdMax = ((GroupId)-1) - 1;
49
50 struct QueryHandle {
51 static constexpr uint32_t IdMask = QueryIdBad;
52
53 private:
54 struct HandleData {
55 QueryId id;
56 uint32_t gen;
57 };
58
59 union {
60 HandleData data;
61 uint64_t val;
62 };
63
64 public:
65 constexpr QueryHandle() noexcept: val((uint64_t)-1) {};
66
67 QueryHandle(QueryId id, uint32_t gen) {
68 data.id = id;
69 data.gen = gen;
70 }
71 ~QueryHandle() = default;
72
73 QueryHandle(QueryHandle&&) noexcept = default;
74 QueryHandle(const QueryHandle&) = default;
75 QueryHandle& operator=(QueryHandle&&) noexcept = default;
76 QueryHandle& operator=(const QueryHandle&) = default;
77
78 GAIA_NODISCARD constexpr bool operator==(const QueryHandle& other) const noexcept {
79 return val == other.val;
80 }
81 GAIA_NODISCARD constexpr bool operator!=(const QueryHandle& other) const noexcept {
82 return val != other.val;
83 }
84
85 GAIA_NODISCARD auto id() const {
86 return data.id;
87 }
88 GAIA_NODISCARD auto gen() const {
89 return data.gen;
90 }
91 GAIA_NODISCARD auto value() const {
92 return val;
93 }
94 };
95
96 inline static const QueryHandle QueryHandleBad = QueryHandle();
97
101
102 private:
104 QueryHandle m_handle;
106 LookupHash m_hash;
107
108 static LookupHash calc(QueryHandle handle) {
109 return {core::calculate_hash64(handle.value())};
110 }
111
112 public:
113 static constexpr bool IsDirectHashKey = true;
114
115 QueryHandleLookupKey() = default;
116 explicit QueryHandleLookupKey(QueryHandle handle): m_handle(handle), m_hash(calc(handle)) {}
117 ~QueryHandleLookupKey() = default;
118
121 QueryHandleLookupKey& operator=(const QueryHandleLookupKey&) = default;
122 QueryHandleLookupKey& operator=(QueryHandleLookupKey&&) = default;
123
124 QueryHandle handle() const {
125 return m_handle;
126 }
127
128 size_t hash() const {
129 return (size_t)m_hash.hash;
130 }
131
132 bool operator==(const QueryHandleLookupKey& other) const {
133 if GAIA_LIKELY (m_hash != other.m_hash)
134 return false;
135
136 return m_handle == other.m_handle;
137 }
138
139 bool operator!=(const QueryHandleLookupKey& other) const {
140 return !operator==(other);
141 }
142 };
143
144 inline static const QueryHandleLookupKey QueryHandleBadLookupKey = QueryHandleLookupKey(QueryHandleBad);
145
147 struct QueryInput {
149 QueryOpKind op = QueryOpKind::All;
151 QueryAccess access = QueryAccess::Read;
157 Entity src = EntityBad;
158 };
159
161 struct QueryTerm {
169 QueryOpKind op;
170
171 bool operator==(const QueryTerm& other) const {
172 return id == other.id && src == other.src && op == other.op;
173 }
174 bool operator!=(const QueryTerm& other) const {
175 return !operator==(other);
176 }
177 };
178
179 using QueryTermArray = cnt::sarray_ext<QueryTerm, MAX_ITEMS_IN_QUERY>;
180 using QueryTermSpan = std::span<QueryTerm>;
181 using QueryRemappingArray = cnt::sarray_ext<uint8_t, MAX_ITEMS_IN_QUERY>;
182
187 QueryId serId = QueryIdBad;
188
189 GAIA_NODISCARD QuerySerBuffer& ser_buffer(World* world) {
190 return query_buffer(*world, serId);
191 }
192 void ser_buffer_reset(World* world) {
193 query_buffer_reset(*world, serId);
194 }
195 };
196
197 struct QueryCtx {
198 // World
199 const World* w{};
206
207 enum QueryFlags : uint8_t {
208 Empty = 0x00,
209 // Entities need sorting
210 SortEntities = 0x01,
211 // Groups need sorting
212 SortGroups = 0x02,
213 // Complex query
214 Complex = 0x04,
215 // Recompilation requested
216 Recompile = 0x08,
217 };
218
219 struct Data {
228 QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_Any;
229 QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_Not;
230 uint8_t idsCnt = 0;
231 uint8_t changedCnt = 0;
233 GroupId groupIdSet;
239 TSortByFunc sortByFunc;
243 TGroupByFunc groupByFunc;
245 QueryMask queryMask;
248 uint32_t as_mask_0;
251 uint32_t as_mask_1;
253 uint8_t firstNot;
255 uint8_t firstAny;
260 uint8_t flags;
261
262 GAIA_NODISCARD std::span<const Entity> ids_view() const {
263 return {_ids.data(), idsCnt};
264 }
265
266 GAIA_NODISCARD std::span<const Entity> changed_view() const {
267 return {_changed.data(), changedCnt};
268 }
269
270 GAIA_NODISCARD std::span<QueryTerm> terms_view_mut() {
271 return {_terms.data(), idsCnt};
272 }
273 GAIA_NODISCARD std::span<const QueryTerm> terms_view() const {
274 return {_terms.data(), idsCnt};
275 }
276 } data{};
277 // Make sure that MAX_ITEMS_IN_QUERY can fit into data.readWriteMask
278 static_assert(MAX_ITEMS_IN_QUERY == 8);
279
280 void init(World* pWorld) {
281 w = pWorld;
282 cc = &comp_cache_mut(*pWorld);
283 }
284
285 void refresh() {
286 const auto mask0_old = data.as_mask_0;
287 const auto mask1_old = data.as_mask_1;
288 const auto isComplex_old = data.flags & QueryFlags::Complex;
289
290 // Update masks
291 {
292 uint32_t as_mask_0 = 0;
293 uint32_t as_mask_1 = 0;
294 bool isComplex = false;
295
296 auto ids = data.ids_view();
297 const auto cnt = (uint32_t)ids.size();
298 GAIA_FOR(cnt) {
299 const auto id = ids[i];
300
301 // Build the Is mask.
302 // We will use it to identify entities with an Is relationship quickly.
303 if (!id.pair()) {
304 const auto j = (uint32_t)i; // data.remapping[i];
305 const auto has_as = (uint32_t)is_base(*w, id);
306 as_mask_0 |= (has_as << j);
307 } else {
308 const bool idIsWildcard = is_wildcard(id.id());
309 const bool isGenWildcard = is_wildcard(id.gen());
310 isComplex |= (idIsWildcard || isGenWildcard);
311
312 if (!idIsWildcard) {
313 const auto j = (uint32_t)i; // data.remapping[i];
314 const auto e = entity_from_id(*w, id.id());
315 const auto has_as = (uint32_t)is_base(*w, e);
316 as_mask_0 |= (has_as << j);
317 }
318
319 if (!isGenWildcard) {
320 const auto j = (uint32_t)i; // data.remapping[i];
321 const auto e = entity_from_id(*w, id.gen());
322 const auto has_as = (uint32_t)is_base(*w, e);
323 as_mask_1 |= (has_as << j);
324 }
325 }
326 }
327
328 // Update the mask
329 data.as_mask_0 = as_mask_0;
330 data.as_mask_1 = as_mask_1;
331
332 // Calculate the component mask for simple queries
333 isComplex |= ((data.as_mask_0 + data.as_mask_1) != 0);
334 if (isComplex) {
335 data.queryMask = {};
336 data.flags |= QueryCtx::QueryFlags::Complex;
337 } else {
338 data.queryMask = build_entity_mask(ids);
339 data.flags &= ~QueryCtx::QueryFlags::Complex;
340 }
341 }
342
343 // Request recompilation of the query if the mask has changed
344 if (mask0_old != data.as_mask_0 || mask1_old != data.as_mask_1 ||
345 isComplex_old != (data.flags & QueryFlags::Complex))
346 data.flags |= QueryCtx::QueryFlags::Recompile;
347 }
348
349 GAIA_NODISCARD bool operator==(const QueryCtx& other) const noexcept {
350 // Comparison expected to be done only the first time the query is set up
351 GAIA_ASSERT(q.handle.id() == QueryIdBad);
352 // Fast path when cache ids are set
353 // if (queryId != QueryIdBad && queryId == other.queryId)
354 // return true;
355
356 // Lookup hash must match
357 if (hashLookup != other.hashLookup)
358 return false;
359
360 const auto& left = data;
361 const auto& right = other.data;
362
363 // Check array sizes first
364 if (left.idsCnt != right.idsCnt)
365 return false;
366 if (left.changedCnt != right.changedCnt)
367 return false;
368 if (left.readWriteMask != right.readWriteMask)
369 return false;
370
371 // Components need to be the same
372 {
373 const auto cnt = left.idsCnt;
374 GAIA_FOR(cnt) {
375 if (left._terms[i] != right._terms[i])
376 return false;
377 }
378 }
379
380 // Filters need to be the same
381 {
382 const auto cnt = left.changedCnt;
383 GAIA_FOR(cnt) {
384 if (left._changed[i] != right._changed[i])
385 return false;
386 }
387 }
388
389 // SortBy data need to match
390 if (left.sortBy != right.sortBy)
391 return false;
392 if (left.sortByFunc != right.sortByFunc)
393 return false;
394
395 // Grouping data need to match
396 if (left.groupBy != right.groupBy)
397 return false;
398 if (left.groupByFunc != right.groupByFunc)
399 return false;
400
401 return true;
402 }
403
404 GAIA_NODISCARD bool operator!=(const QueryCtx& other) const noexcept {
405 return !operator==(other);
406 }
407 };
408
411 constexpr bool operator()(const QueryTerm& lhs, const QueryTerm& rhs) const {
412 // Smaller ops first.
413 if (lhs.op != rhs.op)
414 return lhs.op < rhs.op;
415
416 // Smaller ids second.
417 if (lhs.id != rhs.id)
418 return SortComponentCond()(lhs.id, rhs.id);
419
420 // Sources go last. Note, sources are never a pair.
421 // We want to do it this way because it would be expensive to build cache for
422 // the entire tree. Rather, we only cache fixed parts of the query without
423 // variables.
424 // TODO: In theory, there might be a better way to sort sources.
425 // E.g. depending on the number of archetypes we'd have to traverse
426 // it might be beneficial to do a different ordering which is impossible
427 // to do at this point.
428 return SortComponentCond()(lhs.src, rhs.src);
429 }
430 };
431
433 inline void sort(QueryCtx& ctx) {
434 const uint32_t idsCnt = ctx.data.idsCnt;
435
436 auto& ctxData = ctx.data;
437 auto remappingCopy = ctxData._remapping;
438
439 // Sort data. Necessary for correct hash calculation.
440 // Without sorting query.all<XXX, YYY> would be different than query.all<YYY, XXX>.
441 // Also makes sure data is in optimal order for query processing.
442 core::sort(
443 ctxData._terms.data(), ctxData._terms.data() + ctxData.idsCnt, query_sort_cond{}, //
444 [&](uint32_t left, uint32_t right) {
445 core::swap(ctxData._ids[left], ctxData._ids[right]);
446 core::swap(ctxData._terms[left], ctxData._terms[right]);
447 core::swap(remappingCopy[left], remappingCopy[right]);
448
449 // Make sure masks remains correct after sorting
450 core::swap_bits(ctxData.readWriteMask, left, right);
451 core::swap_bits(ctxData.as_mask_0, left, right);
452 core::swap_bits(ctxData.as_mask_1, left, right);
453 });
454
455 // Update remapping indices.
456 // E.g., let us have ids 0, 14, 15, with indices 0, 1, 2.
457 // After sorting they become 14, 15, 0, with indices 1, 2, 0.
458 // So indices mapping is as follows: 0 -> 1, 1 -> 2, 2 -> 0.
459 // After remapping update, indices become 0 -> 2, 1 -> 0, 2 -> 1.
460 // Therefore, if we want to see where 15 was located originally (curr index 1), we do look at index 2 and get 1.
461
462 GAIA_FOR(idsCnt) {
463 const auto idxBeforeRemapping = (uint8_t)core::get_index_unsafe(remappingCopy, (uint8_t)i);
464 ctxData._remapping[i] = idxBeforeRemapping;
465 }
466
467 if (idsCnt > 0) {
468 uint32_t i = 0;
469 while (i < idsCnt && ctxData._terms[i].op == QueryOpKind::All)
470 ++i;
471 ctxData.firstAny = (uint8_t)i;
472 while (i < idsCnt && ctxData._terms[i].op == QueryOpKind::Any)
473 ++i;
474 ctxData.firstNot = (uint8_t)i;
475 }
476 }
477
478 inline void calc_lookup_hash(QueryCtx& ctx) {
479 GAIA_ASSERT(ctx.cc != nullptr);
480 // Make sure we don't calculate the hash twice
481 GAIA_ASSERT(ctx.hashLookup.hash == 0);
482
483 QueryLookupHash::Type hashLookup = 0;
484
485 const auto& ctxData = ctx.data;
486
487 // Ids & ops
488 {
489 QueryLookupHash::Type hash = 0;
490
491 auto terms = ctxData.terms_view();
492 for (const auto& pair: terms) {
493 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.op);
494 hash = core::hash_combine(hash, (QueryLookupHash::Type)pair.id.value());
495 }
496 hash = core::hash_combine(hash, (QueryLookupHash::Type)terms.size());
497 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.readWriteMask);
498
499 hashLookup = hash;
500 }
501
502 // Filters
503 {
504 QueryLookupHash::Type hash = 0;
505
506 auto changed = ctxData.changed_view();
507 for (auto id: changed)
508 hash = core::hash_combine(hash, (QueryLookupHash::Type)id.value());
509 hash = core::hash_combine(hash, (QueryLookupHash::Type)changed.size());
510
511 hashLookup = core::hash_combine(hashLookup, hash);
512 }
513
514 // Grouping
515 {
516 QueryLookupHash::Type hash = 0;
517
518 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.groupBy.value());
519 hash = core::hash_combine(hash, (QueryLookupHash::Type)ctxData.groupByFunc);
520
521 hashLookup = core::hash_combine(hashLookup, hash);
522 }
523
524 ctx.hashLookup = {core::calculate_hash64(hashLookup)};
525 }
526
533 template <uint32_t MAX_COMPONENTS>
534 GAIA_NODISCARD inline uint32_t comp_idx(const QueryTerm* pTerms, Entity entity, Entity src) {
535 // We let the compiler know the upper iteration bound at compile-time.
536 // This way it can optimize better (e.g. loop unrolling, vectorization).
537 GAIA_FOR(MAX_COMPONENTS) {
538 if (pTerms[i].id == entity && pTerms[i].src == src)
539 return i;
540 }
541
542 GAIA_ASSERT(false);
543 return BadIndex;
544 }
545 } // namespace ecs
546} // namespace gaia
Array of elements of type.
Definition sarray_impl.h:26
Definition span_impl.h:99
Definition archetype.h:82
Cache for compile-time defined components.
Definition component_cache.h:23
Definition world.h:48
Definition ser_buffer_binary.h:154
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition utility.h:966
Definition id.h:225
Definition query_common.h:219
Entity sortBy
Entity to sort the archetypes by. EntityBad for no sorting.
Definition query_common.h:237
TGroupByFunc groupByFunc
Function to use to perform the grouping.
Definition query_common.h:243
uint32_t as_mask_0
Mask for items with Is relationship pair. If the id is a pair, the first part (id) is written here.
Definition query_common.h:248
Entity groupBy
Entity to group the archetypes by. EntityBad for no grouping.
Definition query_common.h:241
uint8_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:258
uint32_t as_mask_1
Mask for items with Is relationship pair. If the id is a pair, the second part (gen) is written here.
Definition query_common.h:251
cnt::sarray< uint8_t, MAX_ITEMS_IN_QUERY > _remapping
Mapping of the original indices to the new ones after sorting.
Definition query_common.h:225
QueryMask queryMask
Component mask used for faster matching of simple queries.
Definition query_common.h:245
uint8_t firstAny
First ANY record in pairs/ids/ops.
Definition query_common.h:255
QueryArchetypeCacheIndexMap lastMatchedArchetypeIdx_All
Index of the last checked archetype in the component-to-archetype map.
Definition query_common.h:227
QueryEntityArray _ids
Array of queried ids.
Definition query_common.h:221
GroupId groupIdSet
Iteration will be restricted only to target Group.
Definition query_common.h:233
QueryEntityArray _changed
Array of filtered components.
Definition query_common.h:235
TSortByFunc sortByFunc
Function to use to perform sorting.
Definition query_common.h:239
uint8_t flags
Query flags.
Definition query_common.h:260
uint8_t firstNot
First NOT record in pairs/ids/ops.
Definition query_common.h:253
cnt::sarray< QueryTerm, MAX_ITEMS_IN_QUERY > _terms
Array of terms.
Definition query_common.h:223
Definition query_common.h:197
ComponentCache * cc
Component cache.
Definition query_common.h:201
QueryIdentity q
Query identity.
Definition query_common.h:205
QueryLookupHash hashLookup
Lookup hash for this query.
Definition query_common.h:203
Hashmap lookup structure used for Entity.
Definition query_common.h:99
Definition query_common.h:50
Definition query_common.h:183
QueryHandle handle
Query id.
Definition query_common.h:185
QueryId serId
Serialization id.
Definition query_common.h:187
User-provided query input.
Definition query_common.h:147
Entity id
Entity/Component/Pair to query.
Definition query_common.h:153
QueryAccess access
Access type.
Definition query_common.h:151
Entity src
Source entity to query the id on. If id==EntityBad the source is fixed. If id!=src the source is vari...
Definition query_common.h:157
QueryOpKind op
Operation to perform with the input.
Definition query_common.h:149
Internal representation of QueryInput.
Definition query_common.h:161
Entity id
Queried id.
Definition query_common.h:163
Archetype * srcArchetype
Archetype of the src entity.
Definition query_common.h:167
QueryOpKind op
Operation to perform with the term.
Definition query_common.h:169
Entity src
Source of where the queried id is looked up at.
Definition query_common.h:165
Functor for sorting terms in a query before compilation.
Definition query_common.h:410