Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
component_cache.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cinttypes>
5#include <cstdint>
6#include <cstring>
7#include <type_traits>
8
9#include "gaia/cnt/map.h"
10#include "gaia/core/hashing_string.h"
11#include "gaia/ecs/component.h"
12#include "gaia/ecs/component_cache_item.h"
13#include "gaia/ecs/component_desc.h"
14#include "gaia/ecs/id.h"
15#include "gaia/meta/type_info.h"
16#include "gaia/util/logging.h"
17#include "gaia/util/str.h"
18
20namespace gaia {
21 namespace ecs {
22 class World;
23
25 class GAIA_API ComponentCache final {
26 friend class World;
27
28 struct ResolvedLookupEntry {
29 const ComponentCacheItem* pItem = nullptr;
30 uint32_t matches = 0;
31 };
32
34 cnt::map<uint32_t, const ComponentCacheItem*> m_compByEntityId;
36 SymbolTable m_symbols;
38 cnt::map<ComponentLookupHash, const ComponentCacheItem*> m_compByTypeHash;
39
41 cnt::map<ComponentCacheItem::SymbolLookupKey, const ComponentCacheItem*> m_compBySymbol;
44 cnt::map<ComponentCacheItem::SymbolLookupKey, ResolvedLookupEntry> m_compByPath;
47 cnt::map<ComponentCacheItem::SymbolLookupKey, ResolvedLookupEntry> m_compByShortSymbol;
48
55 void clear() {
56 m_compByTypeHash.clear();
57 m_compBySymbol.clear();
58 m_compByPath.clear();
59 m_compByShortSymbol.clear();
60
61 for (auto [entityId, pItem]: m_compByEntityId) {
62 (void)entityId;
63 ComponentCacheItem::destroy(const_cast<ComponentCacheItem*>(pItem));
64 }
65
66 m_compByEntityId.clear();
67 m_symbols.clear();
68 }
69
70 GAIA_NODISCARD static bool is_internal_symbol(util::str_view symbol) noexcept {
71 constexpr char InternalPrefix[] = "gaia::ecs::";
72 constexpr uint32_t InternalPrefixLen = (uint32_t)(sizeof(InternalPrefix) - 1);
73 return symbol.size() >= InternalPrefixLen && memcmp(symbol.data(), InternalPrefix, InternalPrefixLen) == 0;
74 }
75
76 static ComponentCacheItem::SymbolLookupKey short_name_key(const ComponentCacheItem& item) noexcept {
77 const auto symbol = item.symbol_name();
78 const auto* pName = symbol.data();
79 const auto len = symbol.size();
80 for (uint32_t i = len; i > 1; --i) {
81 const auto idx = i - 1;
82 if (pName[idx] != ':' || pName[idx - 1] != ':')
83 continue;
84
85 return ComponentCacheItem::SymbolLookupKey(pName + idx + 1, len - idx - 1, 0);
86 }
87
88 return ComponentCacheItem::SymbolLookupKey();
89 }
90
91 GAIA_NODISCARD static util::str_view short_symbol_view(const ComponentCacheItem& item) noexcept {
92 const auto symbol = item.symbol_name();
93 if (is_internal_symbol(symbol))
94 return {};
95
96 const auto shortName = short_name_key(item);
97 if (shortName.str() == nullptr || shortName.len() == 0)
98 return {};
99
100 return util::str_view(shortName.str(), shortName.len());
101 }
102
103 GAIA_NODISCARD static ComponentCacheItem::SymbolLookupKey lookup_key(util::str_view value) noexcept {
104 return value.empty() ? ComponentCacheItem::SymbolLookupKey()
105 : ComponentCacheItem::SymbolLookupKey(value.data(), value.size(), 0);
106 }
107
108 GAIA_NODISCARD static util::str_view normalize_name_view(const char* name, uint32_t len = 0) noexcept {
109 if (name == nullptr)
110 return {};
111
112 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
113 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
114 return util::str_view(name, l);
115 }
116
117#if GAIA_ASSERT_ENABLED
120 void validate_runtime_type(const ecs::ComponentDesc& desc) const noexcept {
121 const auto& runtimeType = desc.runtimeType;
122 ser::serialization_type_id type = ser::serialization_type_id::ignore;
123 if (runtimeType.typeKind == RuntimeTypeKind::Enum || runtimeType.typeKind == RuntimeTypeKind::Bitmask) {
124 GAIA_ASSERT(runtime_primitive_serialization_type(runtimeType.underlyingType, type));
125 GAIA_ASSERT(runtimeType.elementType == EntityBad);
126 GAIA_ASSERT(runtimeType.elementCount == 0);
127 GAIA_ASSERT(runtimeType.opaqueAsType == EntityBad);
128 return;
129 }
130
131 if (runtimeType.typeKind == RuntimeTypeKind::Array) {
132 GAIA_ASSERT(runtimeType.underlyingType == EntityBad);
133 GAIA_ASSERT(runtimeType.elementType != EntityBad);
134 GAIA_ASSERT(runtimeType.elementCount > 0);
135 GAIA_ASSERT(runtimeType.fields == nullptr);
136 GAIA_ASSERT(runtimeType.fieldCount == 0);
137 GAIA_ASSERT(runtimeType.constants == nullptr);
138 GAIA_ASSERT(runtimeType.constantCount == 0);
139 GAIA_ASSERT(runtimeType.opaqueAsType == EntityBad);
140 const auto* pElementType = find(runtimeType.elementType);
141 GAIA_ASSERT(pElementType != nullptr);
142 const auto elementSize = pElementType != nullptr ? pElementType->comp.size() : 0;
143 const auto elementAlignment = pElementType != nullptr ? pElementType->comp.alig() : 0;
144 GAIA_ASSERT(elementSize > 0);
145 if (elementSize > 0) {
146 GAIA_ASSERT(runtimeType.elementCount <= UINT32_MAX / elementSize);
147 GAIA_ASSERT(desc.size == elementSize * runtimeType.elementCount);
148 }
149 GAIA_ASSERT(desc.alig == elementAlignment);
150 return;
151 }
152
153 if (runtimeType.typeKind == RuntimeTypeKind::Vector) {
154 GAIA_ASSERT(runtimeType.underlyingType == EntityBad);
155 GAIA_ASSERT(runtimeType.elementType != EntityBad);
156 GAIA_ASSERT(runtimeType.elementCount == 0);
157 GAIA_ASSERT(runtimeType.fields == nullptr);
158 GAIA_ASSERT(runtimeType.fieldCount == 0);
159 GAIA_ASSERT(runtimeType.constants == nullptr);
160 GAIA_ASSERT(runtimeType.constantCount == 0);
161 GAIA_ASSERT(runtimeType.opaqueAsType == EntityBad);
162 if (runtimeType.sequenceAdapter == nullptr) {
163 GAIA_ASSERT(desc.size == 0);
164 GAIA_ASSERT(desc.soa == 0);
165 } else {
166 GAIA_ASSERT(runtimeType.sequenceAdapter->count != nullptr);
167 GAIA_ASSERT(runtimeType.sequenceAdapter->element != nullptr);
168 }
169 GAIA_ASSERT(find(runtimeType.elementType) != nullptr);
170 #if GAIA_JSON_ENABLED
171 GAIA_ASSERT(runtimeType.jsonEncoding != RuntimeJsonEncoding::Utf8String || runtimeType.elementType == Char8);
172 #endif
173 return;
174 }
175 #if GAIA_JSON_ENABLED
176 GAIA_ASSERT(runtimeType.jsonEncoding == RuntimeJsonEncoding::Default);
177 #endif
178
179 if (runtimeType.typeKind == RuntimeTypeKind::Opaque) {
180 GAIA_ASSERT(runtimeType.underlyingType == EntityBad);
181 GAIA_ASSERT(runtimeType.elementType == EntityBad);
182 GAIA_ASSERT(runtimeType.elementCount == 0);
183 GAIA_ASSERT(runtimeType.fields == nullptr);
184 GAIA_ASSERT(runtimeType.fieldCount == 0);
185 GAIA_ASSERT(runtimeType.constants == nullptr);
186 GAIA_ASSERT(runtimeType.constantCount == 0);
187 GAIA_ASSERT(runtimeType.opaqueAsType != EntityBad);
188 if (runtimeType.opaqueAdapter != nullptr)
189 GAIA_ASSERT(runtimeType.opaqueAdapter->project != nullptr);
190 GAIA_ASSERT(find(runtimeType.opaqueAsType) != nullptr);
191 return;
192 }
193
194 GAIA_ASSERT(runtimeType.underlyingType == EntityBad);
195 GAIA_ASSERT(runtimeType.elementType == EntityBad);
196 GAIA_ASSERT(runtimeType.elementCount == 0);
197 GAIA_ASSERT(runtimeType.opaqueAsType == EntityBad);
198 GAIA_ASSERT(runtimeType.sequenceAdapter == nullptr);
199 GAIA_ASSERT(runtimeType.opaqueAdapter == nullptr);
200 }
201
204 void validate_runtime_fields(const ecs::ComponentDesc& desc) const noexcept {
205 const auto& runtimeType = desc.runtimeType;
206 if (runtimeType.fieldCount == 0)
207 return;
208
209 GAIA_ASSERT(runtimeType.fields != nullptr);
210 if (runtimeType.fields == nullptr)
211 return;
212
213 GAIA_FOR(runtimeType.fieldCount) {
214 const auto& field = runtimeType.fields[i];
215 GAIA_ASSERT(!field.name.empty());
216 GAIA_ASSERT(field.name.size() < ComponentCacheItem::MaxNameLength);
217 GAIA_ASSERT(field.unit.size() < RuntimeFieldDesc::MaxUnitLength);
218 GAIA_ASSERT(field.type != EntityBad);
219 GAIA_ASSERT(
220 (field.flags & (RuntimeFieldFlag_HasMinimum | RuntimeFieldFlag_HasMaximum)) !=
221 (RuntimeFieldFlag_HasMinimum | RuntimeFieldFlag_HasMaximum) ||
222 field.minimum <= field.maximum);
223 GAIA_ASSERT((field.flags & RuntimeFieldFlag_HasStep) == 0 || field.step > 0.0);
224 #if GAIA_JSON_ENABLED
225 GAIA_ASSERT(
226 field.jsonEncoding != RuntimeJsonEncoding::Utf8String || (field.type == Char8 && field.count != 0));
227 #endif
228
229 const auto* pType = find(field.type);
230 GAIA_ASSERT(pType != nullptr);
231 GAIA_ASSERT(pType == nullptr || pType->typeKind != RuntimeTypeKind::Array || field.count == 0);
232 const auto elementSize = pType != nullptr ? pType->comp.size() : 0;
233 GAIA_ASSERT(elementSize > 0);
234 const auto elementCount = ComponentCacheItem::field_element_count(field);
235 GAIA_ASSERT(elementCount > 0);
236 GAIA_ASSERT(field.offset <= desc.size);
237 const auto availableSize = field.offset <= desc.size ? desc.size - field.offset : 0;
238 GAIA_ASSERT(elementSize <= availableSize);
239 GAIA_ASSERT(elementCount <= availableSize / elementSize);
240
241 GAIA_FOR2_(i + 1, runtimeType.fieldCount, otherIdx) {
242 const auto& other = runtimeType.fields[otherIdx];
243 const bool sameLen = field.name.size() == other.name.size();
244 const bool sameName = sameLen && field.name.data() != nullptr && other.name.data() != nullptr &&
245 strncmp(field.name.data(), other.name.data(), field.name.size()) == 0;
246 GAIA_ASSERT(!sameName);
247 }
248 }
249 }
250
253 static void validate_runtime_constants(const ecs::ComponentDesc& desc) noexcept {
254 const auto& runtimeType = desc.runtimeType;
255 if (runtimeType.constantCount == 0)
256 return;
257
258 GAIA_ASSERT(runtimeType.typeKind == RuntimeTypeKind::Enum || runtimeType.typeKind == RuntimeTypeKind::Bitmask);
259 GAIA_ASSERT(runtimeType.constants != nullptr);
260 if (runtimeType.constants == nullptr)
261 return;
262
263 GAIA_FOR(runtimeType.constantCount) {
264 const auto& constant = runtimeType.constants[i];
265 GAIA_ASSERT(!constant.name.empty());
266 GAIA_ASSERT(constant.name.size() < ComponentCacheItem::MaxNameLength);
267
268 GAIA_FOR2_(i + 1, runtimeType.constantCount, otherIdx) {
269 const auto& other = runtimeType.constants[otherIdx];
270 const bool sameLen = constant.name.size() == other.name.size();
271 const bool sameName = sameLen && constant.name.data() != nullptr && other.name.data() != nullptr &&
272 strncmp(constant.name.data(), other.name.data(), constant.name.size()) == 0;
273 GAIA_ASSERT(!sameName);
274 GAIA_ASSERT(constant.value != other.value);
275 }
276 }
277 }
278
282 void
283 validate_typed_runtime_type(const ComponentCacheItem& item, const RuntimeTypeDesc& runtimeType) const noexcept {
284 ecs::ComponentDesc desc{};
285 desc.size = item.comp.size();
286 desc.alig = item.comp.alig();
287 desc.storageType = item.comp.storage_type();
288 desc.soa = item.comp.soa();
289 desc.pSoaSizes = item.soaSizes;
290 desc.runtimeType = runtimeType;
291 validate_runtime_type(desc);
292 validate_runtime_fields(desc);
293 validate_runtime_constants(desc);
294 }
295#endif
296
297 static bool build_default_path(util::str& out, util::str_view symbol) {
298 out.clear();
299 bool changed = false;
300 out.reserve(symbol.size());
301 for (uint32_t i = 0; i < symbol.size(); ++i) {
302 if (i + 1 < symbol.size() && symbol.data()[i] == ':' && symbol.data()[i + 1] == ':') {
303 out.append('.');
304 ++i;
305 changed = true;
306 continue;
307 }
308
309 out.append(symbol.data()[i]);
310 }
311
312 if (!changed)
313 out.clear();
314
315 return changed;
316 }
317
323 static bool build_scoped_path(util::str& out, util::str_view scopePath, util::str_view leaf) {
324 out.clear();
325 if (scopePath.empty() || leaf.empty())
326 return false;
327
328 out.reserve(scopePath.size() + 1 + leaf.size());
329 out.append(scopePath);
330 out.append('.');
331 out.append(leaf);
332 return true;
333 }
334
338 static void initialize_names(ComponentCacheItem& item, util::str_view scopePath = {}) {
339 item.path.clear();
340 const auto symbol = item.symbol_name();
341 if (is_internal_symbol(symbol))
342 return;
343
344 const auto shortName = short_name_key(item);
345 const bool hasShortName =
346 shortName.str() != nullptr && shortName.len() != 0 && shortName.len() != symbol.size();
347 const auto leaf = hasShortName ? util::str_view{shortName.str(), shortName.len()} : symbol;
348
349 if (!build_scoped_path(item.path, scopePath, leaf))
350 (void)build_default_path(item.path, symbol);
351 }
352
353 static void add_lookup_mapping(
354 cnt::map<ComponentCacheItem::SymbolLookupKey, ResolvedLookupEntry>& map, util::str_view view,
355 const ComponentCacheItem& item) {
356 if (view.empty())
357 return;
358
359 const auto key = lookup_key(view);
360 const auto it = map.find(key);
361 if (it == map.end()) {
362 map.emplace(key, ResolvedLookupEntry{&item, 1});
363 return;
364 }
365
366 auto& entry = it->second;
367 GAIA_ASSERT(entry.matches != 0);
368 ++entry.matches;
369 }
370
371 void add_path_mapping(const ComponentCacheItem& item) {
372 add_lookup_mapping(m_compByPath, item.path.view(), item);
373 }
374
375 static void push_unique_entity(cnt::darray<Entity>& out, Entity entity) {
376 if (entity == EntityBad)
377 return;
378
379 for (const auto existing: out) {
380 if (existing == entity)
381 return;
382 }
383
384 out.push_back(entity);
385 }
386
387 void remove_path_mapping(const ComponentCacheItem& item) {
388 const auto pathValue = item.path.view();
389 if (pathValue.empty())
390 return;
391
392 const auto key = lookup_key(pathValue);
393 const auto it = m_compByPath.find(key);
394 if (it == m_compByPath.end())
395 return;
396
397 auto& entry = it->second;
398 GAIA_ASSERT(entry.matches != 0);
399
400 if (entry.matches == 1) {
401 GAIA_ASSERT(entry.pItem == &item);
402 m_compByPath.erase(it);
403 return;
404 }
405
406 const auto remainingMatches = entry.matches - 1;
407 if (entry.pItem != &item) {
408 entry.matches = remainingMatches;
409 return;
410 }
411
412 const ComponentCacheItem* pReplacement = nullptr;
413 for (const auto& [entityId, pItem]: m_compByEntityId) {
414 (void)entityId;
415 GAIA_ASSERT(pItem != nullptr);
416 if (pItem == &item)
417 continue;
418
419 if (pItem->path.view() != pathValue)
420 continue;
421
422 pReplacement = pItem;
423 break;
424 }
425
426 GAIA_ASSERT(pReplacement != nullptr);
427 // The map key borrows the representative path storage, so replace it before the old path changes.
428 m_compByPath.erase(it);
429 if (pReplacement != nullptr)
430 m_compByPath.emplace(
431 lookup_key(pReplacement->path.view()), ResolvedLookupEntry{pReplacement, remainingMatches});
432 }
433
437 void add_name_mappings(ComponentCacheItem& item, util::str_view scopePath) {
438 m_compBySymbol.emplace(lookup_key(item.symbol_name()), &item);
439 initialize_names(item, scopePath);
440 add_path_mapping(item);
441 add_lookup_mapping(m_compByShortSymbol, short_symbol_view(item), item);
442 }
443
448 ComponentCacheItem& add_item(const ComponentCacheItem* pItem, util::str_view scopePath) {
449 GAIA_ASSERT(pItem != nullptr);
450 GAIA_ASSERT(pItem->entity.id() == pItem->comp.id());
451 m_compByEntityId.emplace(pItem->entity.id(), pItem);
452
453 auto& item = *const_cast<ComponentCacheItem*>(pItem);
454 item.m_ownerCache = this;
455 add_name_mappings(item, scopePath);
456 return item;
457 }
458
462 GAIA_NODISCARD const ComponentCacheItem* find_item(ComponentLookupHash hash) const noexcept {
463 const auto it = m_compByTypeHash.find(hash);
464 return it != m_compByTypeHash.end() ? it->second : nullptr;
465 }
466
470 template <typename T>
471 void add_hash_mapping(const ComponentCacheItem& item) {
472 const auto hash = detail::ComponentDesc<T>::hash_lookup();
473 const auto it = m_compByTypeHash.find(hash);
474 if (it == m_compByTypeHash.end()) {
475 m_compByTypeHash.emplace(hash, &item);
476 return;
477 }
478
479 GAIA_ASSERT(it->second == &item);
480 }
481
482 public:
483 ComponentCache() = default;
484
485 ~ComponentCache() {
486 clear();
487 }
488
489 ComponentCache(ComponentCache&&) = delete;
490 ComponentCache(const ComponentCache&) = delete;
491 ComponentCache& operator=(ComponentCache&&) = delete;
492 ComponentCache& operator=(const ComponentCache&) = delete;
493
496 template <typename T>
497 GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem& add(Entity entity, util::str_view scopePath = {}) {
498 static_assert(!is_pair<T>::value);
499 GAIA_ASSERT(!entity.pair());
500
501 const auto* pItem = find_item(detail::ComponentDesc<T>::hash_lookup());
502 if (pItem != nullptr)
503 return *pItem;
504
505 const auto* pNewItem = ComponentCacheItem::create<T>(entity, m_symbols);
506 GAIA_ASSERT(entity.id() == pNewItem->comp.id());
507 const auto& item = add_item(pNewItem, scopePath);
508 add_hash_mapping<T>(item);
509 return item;
510 }
511
519 template <typename T>
520 GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem&
521 add(Entity entity, const RuntimeTypeDesc& runtimeType, util::str_view scopePath = {}) {
522 static_assert(!is_pair<T>::value);
523 GAIA_ASSERT(!entity.pair());
524
525 const auto* pItem = find_item(detail::ComponentDesc<T>::hash_lookup());
526 if (pItem != nullptr)
527 return *pItem;
528
529 const auto* pNewItem = ComponentCacheItem::create<T>(entity, m_symbols, runtimeType);
530 GAIA_ASSERT(entity.id() == pNewItem->comp.id());
531#if GAIA_ASSERT_ENABLED
532 validate_typed_runtime_type(*pNewItem, runtimeType);
533#endif
534 const auto& item = add_item(pNewItem, scopePath);
535 add_hash_mapping<T>(item);
536 return item;
537 }
538
544 GAIA_NODISCARD ComponentCacheItem&
545 add(Entity entity, const ecs::ComponentDesc& desc, util::str_view scopePath = {}) {
546 GAIA_ASSERT(entity != EntityBad);
547 GAIA_ASSERT(!entity.pair());
548 GAIA_ASSERT(!desc.name.empty());
549 GAIA_ASSERT(desc.name.size() < ComponentCacheItem::MaxNameLength);
550
551 {
552 const auto* pExisting = symbol(desc.name);
553 if (pExisting != nullptr)
554 return *find(pExisting->entity);
555 }
556
557#if GAIA_ASSERT_ENABLED
558 validate_runtime_type(desc);
559 validate_runtime_fields(desc);
560 validate_runtime_constants(desc);
561#endif
562
563 const auto* pItem = ComponentCacheItem::create(entity, m_symbols, desc);
564 GAIA_ASSERT(entity.id() == pItem->comp.id());
565 return add_item(pItem, scopePath);
566 }
567
571 GAIA_NODISCARD const ComponentCacheItem* find(Entity entity) const noexcept {
572 if (entity.pair())
573 return nullptr;
574
575 const auto it = m_compByEntityId.find(entity.id());
576 return it != m_compByEntityId.end() ? it->second : nullptr;
577 }
578
583 GAIA_NODISCARD const ComponentCacheItem* find_pair_payload(Entity pair) const noexcept {
584 if (!pair.pair())
585 return nullptr;
586
587 const auto relationIt = m_compByEntityId.find(pair.id());
588 const auto* pRelation = relationIt != m_compByEntityId.end() ? relationIt->second : nullptr;
589 if (pRelation != nullptr && pRelation->comp.size() != 0U)
590 return pRelation;
591
592 const auto targetIt = m_compByEntityId.find(pair.gen());
593 const auto* pTarget = targetIt != m_compByEntityId.end() ? targetIt->second : nullptr;
594 return pTarget != nullptr && pTarget->comp.size() != 0U ? pTarget : pRelation;
595 }
596
600 GAIA_NODISCARD ComponentCacheItem* find(Entity entity) noexcept {
601 return const_cast<ComponentCacheItem*>(const_cast<const ComponentCache*>(this)->find(entity));
602 }
603
608 GAIA_NODISCARD const ComponentCacheItem& get(Entity entity) const noexcept {
609 GAIA_ASSERT(!entity.pair());
610 const auto* pItem = find(entity);
611 GAIA_ASSERT(pItem != nullptr);
612 return *pItem;
613 }
614
619 GAIA_NODISCARD ComponentCacheItem& get(Entity entity) noexcept {
620 auto* pItem = find(entity);
621 GAIA_ASSERT(pItem != nullptr);
622 return *pItem;
623 }
624
625 private:
629 GAIA_NODISCARD util::str_view symbol_name(const ComponentCacheItem& item) const noexcept {
630 return item.symbol_name();
631 }
632
636 GAIA_NODISCARD util::str_view path_name(const ComponentCacheItem& item) const noexcept {
637 return item.path.view();
638 }
639
645 bool path(ComponentCacheItem& item, util::str_view name) noexcept {
646 if (path_name(item) == name)
647 return true;
648
649 if (name.empty()) {
650 remove_path_mapping(item);
651 item.path.clear();
652 return true;
653 }
654
655 if (name.size() >= ComponentCacheItem::MaxNameLength)
656 return false;
657
658 remove_path_mapping(item);
659 item.path.assign(name);
660 add_path_mapping(item);
661 return true;
662 }
663
670 bool path(ComponentCacheItem& item, const char* name, uint32_t len = 0) noexcept {
671 return path(item, normalize_name_view(name, len));
672 }
673
677 GAIA_NODISCARD const ComponentCacheItem* symbol(util::str_view name) const noexcept {
678 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
679 const auto it = m_compBySymbol.find(lookup_key(name));
680 return it != m_compBySymbol.end() ? it->second : nullptr;
681 }
682
687 GAIA_NODISCARD const ComponentCacheItem* symbol(const char* name, uint32_t len = 0) const noexcept {
688 GAIA_ASSERT(name != nullptr);
689 return symbol(normalize_name_view(name, len));
690 }
691
695 GAIA_NODISCARD const ComponentCacheItem* path(util::str_view name) const noexcept {
696 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
697 const auto it = m_compByPath.find(lookup_key(name));
698 if (it == m_compByPath.end())
699 return nullptr;
700
701 return it->second.matches == 1 ? it->second.pItem : nullptr;
702 }
703
708 GAIA_NODISCARD const ComponentCacheItem* path(const char* name, uint32_t len = 0) const noexcept {
709 GAIA_ASSERT(name != nullptr);
710 return path(normalize_name_view(name, len));
711 }
712
717 GAIA_NODISCARD const ComponentCacheItem* short_symbol(util::str_view name) const noexcept {
718 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
719 const auto it = m_compByShortSymbol.find(lookup_key(name));
720 if (it == m_compByShortSymbol.end())
721 return nullptr;
722
723 return it->second.matches == 1 ? it->second.pItem : nullptr;
724 }
725
731 GAIA_NODISCARD const ComponentCacheItem* short_symbol(const char* name, uint32_t len = 0) const noexcept {
732 GAIA_ASSERT(name != nullptr);
733 return short_symbol(normalize_name_view(name, len));
734 }
735
740 void add_path_matches(cnt::darray<Entity>& out, util::str_view name) const {
741 if (name.empty())
742 return;
743
744 const auto it = m_compByPath.find(lookup_key(name));
745 if (it == m_compByPath.end())
746 return;
747
748 const auto& entry = it->second;
749 GAIA_ASSERT(entry.matches != 0);
750
751 if (entry.matches == 1) {
752 GAIA_ASSERT(entry.pItem != nullptr);
753 push_unique_entity(out, entry.pItem->entity);
754 return;
755 }
756
757 for (const auto& [entityId, pItem]: m_compByEntityId) {
758 (void)entityId;
759 GAIA_ASSERT(pItem != nullptr);
760 if (pItem->path.view() == name)
761 push_unique_entity(out, pItem->entity);
762 }
763 }
764
765 public:
769 template <typename T>
770 GAIA_NODISCARD const ComponentCacheItem* find() const noexcept {
771 static_assert(!is_pair<T>::value);
772 return find_item(detail::ComponentDesc<T>::hash_lookup());
773 }
774
778 template <typename T>
779 GAIA_NODISCARD const ComponentCacheItem& get() const noexcept {
780 static_assert(!is_pair<T>::value);
781 const auto* pItem = find<T>();
782 GAIA_ASSERT(pItem != nullptr);
783 return *pItem;
784 }
785
786#if GAIA_ENABLE_HOOKS
787
791 static ComponentCacheItem::Hooks& hooks(const ComponentCacheItem& cacheItem) noexcept {
792 return const_cast<ComponentCacheItem&>(cacheItem).hooks();
793 }
794
795#endif
796
797 void diag() const {
798 const auto registeredTypes = m_compByEntityId.size();
799 GAIA_LOG_N("Registered components: %u", registeredTypes);
800
801 auto logDesc = [](const ComponentCacheItem& item) {
802 const auto symbol = item.symbol_name();
803 GAIA_LOG_N(
804 " hash:%016" PRIx64 ", size:%3u B, align:%3u B, [%u:%u] %.*s [%s]", item.hashLookup.hash,
805 item.comp.size(), item.comp.alig(), item.entity.id(), item.entity.gen(), (int)symbol.size(),
806 symbol.data(), EntityKindString[item.entity.kind()]);
807 };
808 for (const auto& [entityId, pItem]: m_compByEntityId) {
809 (void)entityId;
810 GAIA_ASSERT(pItem != nullptr);
811 logDesc(*pItem);
812 }
813 }
814 };
815 } // namespace ecs
816} // namespace gaia