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#if GAIA_ENABLE_HOOKS
50 bool m_hooksAccessed = false;
51#endif
52
59 void clear() {
60 m_compByTypeHash.clear();
61 m_compBySymbol.clear();
62 m_compByPath.clear();
63 m_compByShortSymbol.clear();
64
65 for (auto [entityId, pItem]: m_compByEntityId) {
66 (void)entityId;
67 ComponentCacheItem::destroy(const_cast<ComponentCacheItem*>(pItem));
68 }
69
70 m_compByEntityId.clear();
71 m_symbols.clear();
72#if GAIA_ENABLE_HOOKS
73 m_hooksAccessed = false;
74#endif
75 }
76
77#if GAIA_ENABLE_HOOKS
80 GAIA_NODISCARD bool hooks_accessed() const noexcept {
81 return m_hooksAccessed;
82 }
83#endif
84
85 GAIA_NODISCARD static bool is_internal_symbol(util::str_view symbol) noexcept {
86 constexpr char InternalPrefix[] = "gaia::ecs::";
87 constexpr uint32_t InternalPrefixLen = (uint32_t)(sizeof(InternalPrefix) - 1);
88 return symbol.size() >= InternalPrefixLen && memcmp(symbol.data(), InternalPrefix, InternalPrefixLen) == 0;
89 }
90
91 static ComponentCacheItem::SymbolLookupKey short_name_key(const ComponentCacheItem& item) noexcept {
92 const auto symbol = item.symbol_name();
93 const auto* pName = symbol.data();
94 const auto len = symbol.size();
95 for (uint32_t i = len; i > 1; --i) {
96 const auto idx = i - 1;
97 if (pName[idx] != ':' || pName[idx - 1] != ':')
98 continue;
99
100 return ComponentCacheItem::SymbolLookupKey(pName + idx + 1, len - idx - 1, 0);
101 }
102
103 return ComponentCacheItem::SymbolLookupKey();
104 }
105
106 GAIA_NODISCARD static util::str_view short_symbol_view(const ComponentCacheItem& item) noexcept {
107 const auto symbol = item.symbol_name();
108 if (is_internal_symbol(symbol))
109 return {};
110
111 const auto shortName = short_name_key(item);
112 if (shortName.str() == nullptr || shortName.len() == 0)
113 return {};
114
115 return util::str_view(shortName.str(), shortName.len());
116 }
117
118 GAIA_NODISCARD static ComponentCacheItem::SymbolLookupKey lookup_key(util::str_view value) noexcept {
119 return value.empty() ? ComponentCacheItem::SymbolLookupKey()
120 : ComponentCacheItem::SymbolLookupKey(value.data(), value.size(), 0);
121 }
122
123 GAIA_NODISCARD static util::str_view normalize_name_view(const char* name, uint32_t len = 0) noexcept {
124 if (name == nullptr)
125 return {};
126
127 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
128 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
129 return util::str_view(name, l);
130 }
131
132#if GAIA_ASSERT_ENABLED
135 void validate_runtime_type(const ecs::ComponentDesc& desc) const noexcept {
136 const auto& runtimeType = desc.runtimeType;
137 ser::serialization_type_id type = ser::serialization_type_id::ignore;
138 if (runtimeType.typeKind == RuntimeTypeKind::Enum || runtimeType.typeKind == RuntimeTypeKind::Bitmask) {
139 GAIA_ASSERT(runtime_primitive_serialization_type(runtimeType.underlyingType, type));
140 GAIA_ASSERT(runtimeType.elementType == EntityBad);
141 GAIA_ASSERT(runtimeType.elementCount == 0);
142 GAIA_ASSERT(runtimeType.opaqueAsType == EntityBad);
143 return;
144 }
145
146 if (runtimeType.typeKind == RuntimeTypeKind::Array) {
147 GAIA_ASSERT(runtimeType.underlyingType == EntityBad);
148 GAIA_ASSERT(runtimeType.elementType != EntityBad);
149 GAIA_ASSERT(runtimeType.elementCount > 0);
150 GAIA_ASSERT(runtimeType.fields == nullptr);
151 GAIA_ASSERT(runtimeType.fieldCount == 0);
152 GAIA_ASSERT(runtimeType.constants == nullptr);
153 GAIA_ASSERT(runtimeType.constantCount == 0);
154 GAIA_ASSERT(runtimeType.opaqueAsType == EntityBad);
155 const auto* pElementType = find(runtimeType.elementType);
156 GAIA_ASSERT(pElementType != nullptr);
157 const auto elementSize = pElementType != nullptr ? pElementType->comp.size() : 0;
158 const auto elementAlignment = pElementType != nullptr ? pElementType->comp.alig() : 0;
159 GAIA_ASSERT(elementSize > 0);
160 if (elementSize > 0) {
161 GAIA_ASSERT(runtimeType.elementCount <= UINT32_MAX / elementSize);
162 GAIA_ASSERT(desc.size == elementSize * runtimeType.elementCount);
163 }
164 GAIA_ASSERT(desc.alig == elementAlignment);
165 return;
166 }
167
168 if (runtimeType.typeKind == RuntimeTypeKind::Vector) {
169 GAIA_ASSERT(runtimeType.underlyingType == EntityBad);
170 GAIA_ASSERT(runtimeType.elementType != EntityBad);
171 GAIA_ASSERT(runtimeType.elementCount == 0);
172 GAIA_ASSERT(runtimeType.fields == nullptr);
173 GAIA_ASSERT(runtimeType.fieldCount == 0);
174 GAIA_ASSERT(runtimeType.constants == nullptr);
175 GAIA_ASSERT(runtimeType.constantCount == 0);
176 GAIA_ASSERT(runtimeType.opaqueAsType == EntityBad);
177 if (runtimeType.sequenceAdapter == nullptr) {
178 GAIA_ASSERT(desc.size == 0);
179 GAIA_ASSERT(desc.soa == 0);
180 } else {
181 GAIA_ASSERT(runtimeType.sequenceAdapter->count != nullptr);
182 GAIA_ASSERT(runtimeType.sequenceAdapter->element != nullptr);
183 }
184 GAIA_ASSERT(find(runtimeType.elementType) != nullptr);
185 #if GAIA_JSON_ENABLED
186 GAIA_ASSERT(runtimeType.jsonEncoding != RuntimeJsonEncoding::Utf8String || runtimeType.elementType == Char8);
187 #endif
188 return;
189 }
190 #if GAIA_JSON_ENABLED
191 GAIA_ASSERT(runtimeType.jsonEncoding == RuntimeJsonEncoding::Default);
192 #endif
193
194 if (runtimeType.typeKind == RuntimeTypeKind::Opaque) {
195 GAIA_ASSERT(runtimeType.underlyingType == EntityBad);
196 GAIA_ASSERT(runtimeType.elementType == EntityBad);
197 GAIA_ASSERT(runtimeType.elementCount == 0);
198 GAIA_ASSERT(runtimeType.fields == nullptr);
199 GAIA_ASSERT(runtimeType.fieldCount == 0);
200 GAIA_ASSERT(runtimeType.constants == nullptr);
201 GAIA_ASSERT(runtimeType.constantCount == 0);
202 GAIA_ASSERT(runtimeType.opaqueAsType != EntityBad);
203 if (runtimeType.opaqueAdapter != nullptr)
204 GAIA_ASSERT(runtimeType.opaqueAdapter->project != nullptr);
205 GAIA_ASSERT(find(runtimeType.opaqueAsType) != nullptr);
206 return;
207 }
208
209 GAIA_ASSERT(runtimeType.underlyingType == EntityBad);
210 GAIA_ASSERT(runtimeType.elementType == EntityBad);
211 GAIA_ASSERT(runtimeType.elementCount == 0);
212 GAIA_ASSERT(runtimeType.opaqueAsType == EntityBad);
213 GAIA_ASSERT(runtimeType.sequenceAdapter == nullptr);
214 GAIA_ASSERT(runtimeType.opaqueAdapter == nullptr);
215 }
216
219 void validate_runtime_fields(const ecs::ComponentDesc& desc) const noexcept {
220 const auto& runtimeType = desc.runtimeType;
221 if (runtimeType.fieldCount == 0)
222 return;
223
224 GAIA_ASSERT(runtimeType.fields != nullptr);
225 if (runtimeType.fields == nullptr)
226 return;
227
228 GAIA_FOR(runtimeType.fieldCount) {
229 const auto& field = runtimeType.fields[i];
230 GAIA_ASSERT(!field.name.empty());
231 GAIA_ASSERT(field.name.size() < ComponentCacheItem::MaxNameLength);
232 GAIA_ASSERT(field.unit.size() < RuntimeFieldDesc::MaxUnitLength);
233 GAIA_ASSERT(field.type != EntityBad);
234 GAIA_ASSERT(
235 (field.flags & (RuntimeFieldFlag_HasMinimum | RuntimeFieldFlag_HasMaximum)) !=
236 (RuntimeFieldFlag_HasMinimum | RuntimeFieldFlag_HasMaximum) ||
237 field.minimum <= field.maximum);
238 GAIA_ASSERT((field.flags & RuntimeFieldFlag_HasStep) == 0 || field.step > 0.0);
239 #if GAIA_JSON_ENABLED
240 GAIA_ASSERT(
241 field.jsonEncoding != RuntimeJsonEncoding::Utf8String || (field.type == Char8 && field.count != 0));
242 #endif
243
244 const auto* pType = find(field.type);
245 GAIA_ASSERT(pType != nullptr);
246 GAIA_ASSERT(pType == nullptr || pType->typeKind != RuntimeTypeKind::Array || field.count == 0);
247 const auto elementSize = pType != nullptr ? pType->comp.size() : 0;
248 GAIA_ASSERT(elementSize > 0);
249 const auto elementCount = ComponentCacheItem::field_element_count(field);
250 GAIA_ASSERT(elementCount > 0);
251 GAIA_ASSERT(field.offset <= desc.size);
252 const auto availableSize = field.offset <= desc.size ? desc.size - field.offset : 0;
253 GAIA_ASSERT(elementSize <= availableSize);
254 GAIA_ASSERT(elementCount <= availableSize / elementSize);
255
256 GAIA_FOR2_(i + 1, runtimeType.fieldCount, otherIdx) {
257 const auto& other = runtimeType.fields[otherIdx];
258 const bool sameLen = field.name.size() == other.name.size();
259 const bool sameName = sameLen && field.name.data() != nullptr && other.name.data() != nullptr &&
260 strncmp(field.name.data(), other.name.data(), field.name.size()) == 0;
261 GAIA_ASSERT(!sameName);
262 }
263 }
264 }
265
268 static void validate_runtime_constants(const ecs::ComponentDesc& desc) noexcept {
269 const auto& runtimeType = desc.runtimeType;
270 if (runtimeType.constantCount == 0)
271 return;
272
273 GAIA_ASSERT(runtimeType.typeKind == RuntimeTypeKind::Enum || runtimeType.typeKind == RuntimeTypeKind::Bitmask);
274 GAIA_ASSERT(runtimeType.constants != nullptr);
275 if (runtimeType.constants == nullptr)
276 return;
277
278 GAIA_FOR(runtimeType.constantCount) {
279 const auto& constant = runtimeType.constants[i];
280 GAIA_ASSERT(!constant.name.empty());
281 GAIA_ASSERT(constant.name.size() < ComponentCacheItem::MaxNameLength);
282
283 GAIA_FOR2_(i + 1, runtimeType.constantCount, otherIdx) {
284 const auto& other = runtimeType.constants[otherIdx];
285 const bool sameLen = constant.name.size() == other.name.size();
286 const bool sameName = sameLen && constant.name.data() != nullptr && other.name.data() != nullptr &&
287 strncmp(constant.name.data(), other.name.data(), constant.name.size()) == 0;
288 GAIA_ASSERT(!sameName);
289 GAIA_ASSERT(constant.value != other.value);
290 }
291 }
292 }
293
297 void
298 validate_typed_runtime_type(const ComponentCacheItem& item, const RuntimeTypeDesc& runtimeType) const noexcept {
299 ecs::ComponentDesc desc{};
300 desc.size = item.comp.size();
301 desc.alig = item.comp.alig();
302 desc.storageType = item.comp.storage_type();
303 desc.soa = item.comp.soa();
304 desc.pSoaSizes = item.soaSizes;
305 desc.runtimeType = runtimeType;
306 validate_runtime_type(desc);
307 validate_runtime_fields(desc);
308 validate_runtime_constants(desc);
309 }
310#endif
311
312 static bool build_default_path(util::str& out, util::str_view symbol) {
313 out.clear();
314 bool changed = false;
315 out.reserve(symbol.size());
316 for (uint32_t i = 0; i < symbol.size(); ++i) {
317 if (i + 1 < symbol.size() && symbol.data()[i] == ':' && symbol.data()[i + 1] == ':') {
318 out.append('.');
319 ++i;
320 changed = true;
321 continue;
322 }
323
324 out.append(symbol.data()[i]);
325 }
326
327 if (!changed)
328 out.clear();
329
330 return changed;
331 }
332
338 static bool build_scoped_path(util::str& out, util::str_view scopePath, util::str_view leaf) {
339 out.clear();
340 if (scopePath.empty() || leaf.empty())
341 return false;
342
343 out.reserve(scopePath.size() + 1 + leaf.size());
344 out.append(scopePath);
345 out.append('.');
346 out.append(leaf);
347 return true;
348 }
349
353 static void initialize_names(ComponentCacheItem& item, util::str_view scopePath = {}) {
354 item.path.clear();
355 const auto symbol = item.symbol_name();
356 if (is_internal_symbol(symbol))
357 return;
358
359 const auto shortName = short_name_key(item);
360 const bool hasShortName =
361 shortName.str() != nullptr && shortName.len() != 0 && shortName.len() != symbol.size();
362 const auto leaf = hasShortName ? util::str_view{shortName.str(), shortName.len()} : symbol;
363
364 if (!build_scoped_path(item.path, scopePath, leaf))
365 (void)build_default_path(item.path, symbol);
366 }
367
368 static void add_lookup_mapping(
369 cnt::map<ComponentCacheItem::SymbolLookupKey, ResolvedLookupEntry>& map, util::str_view view,
370 const ComponentCacheItem& item) {
371 if (view.empty())
372 return;
373
374 const auto key = lookup_key(view);
375 const auto it = map.find(key);
376 if (it == map.end()) {
377 map.emplace(key, ResolvedLookupEntry{&item, 1});
378 return;
379 }
380
381 auto& entry = it->second;
382 GAIA_ASSERT(entry.matches != 0);
383 ++entry.matches;
384 }
385
386 void add_path_mapping(const ComponentCacheItem& item) {
387 add_lookup_mapping(m_compByPath, item.path.view(), item);
388 }
389
390 static void push_unique_entity(cnt::darray<Entity>& out, Entity entity) {
391 if (entity == EntityBad)
392 return;
393
394 for (const auto existing: out) {
395 if (existing == entity)
396 return;
397 }
398
399 out.push_back(entity);
400 }
401
402 void remove_path_mapping(const ComponentCacheItem& item) {
403 const auto pathValue = item.path.view();
404 if (pathValue.empty())
405 return;
406
407 const auto key = lookup_key(pathValue);
408 const auto it = m_compByPath.find(key);
409 if (it == m_compByPath.end())
410 return;
411
412 auto& entry = it->second;
413 GAIA_ASSERT(entry.matches != 0);
414
415 if (entry.matches == 1) {
416 GAIA_ASSERT(entry.pItem == &item);
417 m_compByPath.erase(it);
418 return;
419 }
420
421 const auto remainingMatches = entry.matches - 1;
422 if (entry.pItem != &item) {
423 entry.matches = remainingMatches;
424 return;
425 }
426
427 const ComponentCacheItem* pReplacement = nullptr;
428 for (const auto& [entityId, pItem]: m_compByEntityId) {
429 (void)entityId;
430 GAIA_ASSERT(pItem != nullptr);
431 if (pItem == &item)
432 continue;
433
434 if (pItem->path.view() != pathValue)
435 continue;
436
437 pReplacement = pItem;
438 break;
439 }
440
441 GAIA_ASSERT(pReplacement != nullptr);
442 // The map key borrows the representative path storage, so replace it before the old path changes.
443 m_compByPath.erase(it);
444 if (pReplacement != nullptr)
445 m_compByPath.emplace(
446 lookup_key(pReplacement->path.view()), ResolvedLookupEntry{pReplacement, remainingMatches});
447 }
448
452 void add_name_mappings(ComponentCacheItem& item, util::str_view scopePath) {
453 m_compBySymbol.emplace(lookup_key(item.symbol_name()), &item);
454 initialize_names(item, scopePath);
455 add_path_mapping(item);
456 add_lookup_mapping(m_compByShortSymbol, short_symbol_view(item), item);
457 }
458
463 ComponentCacheItem& add_item(const ComponentCacheItem* pItem, util::str_view scopePath) {
464 GAIA_ASSERT(pItem != nullptr);
465 GAIA_ASSERT(pItem->entity.id() == pItem->comp.id());
466 m_compByEntityId.emplace(pItem->entity.id(), pItem);
467
468 auto& item = *const_cast<ComponentCacheItem*>(pItem);
469 item.m_ownerCache = this;
470#if GAIA_ENABLE_HOOKS
471 item.m_pHooksAccessed = &m_hooksAccessed;
472#endif
473 add_name_mappings(item, scopePath);
474 return item;
475 }
476
480 GAIA_NODISCARD const ComponentCacheItem* find_item(ComponentLookupHash hash) const noexcept {
481 const auto it = m_compByTypeHash.find(hash);
482 return it != m_compByTypeHash.end() ? it->second : nullptr;
483 }
484
488 template <typename T>
489 void add_hash_mapping(const ComponentCacheItem& item) {
490 const auto hash = detail::ComponentDesc<T>::hash_lookup();
491 const auto it = m_compByTypeHash.find(hash);
492 if (it == m_compByTypeHash.end()) {
493 m_compByTypeHash.emplace(hash, &item);
494 return;
495 }
496
497 GAIA_ASSERT(it->second == &item);
498 }
499
500 public:
501 ComponentCache() = default;
502
503 ~ComponentCache() {
504 clear();
505 }
506
507 ComponentCache(ComponentCache&&) = delete;
508 ComponentCache(const ComponentCache&) = delete;
509 ComponentCache& operator=(ComponentCache&&) = delete;
510 ComponentCache& operator=(const ComponentCache&) = delete;
511
514 template <typename T>
515 GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem& add(Entity entity, util::str_view scopePath = {}) {
516 static_assert(!is_pair<T>::value);
517 GAIA_ASSERT(!entity.pair());
518
519 const auto* pItem = find_item(detail::ComponentDesc<T>::hash_lookup());
520 if (pItem != nullptr)
521 return *pItem;
522
523 const auto* pNewItem = ComponentCacheItem::create<T>(entity, m_symbols);
524 GAIA_ASSERT(entity.id() == pNewItem->comp.id());
525 const auto& item = add_item(pNewItem, scopePath);
526 add_hash_mapping<T>(item);
527 return item;
528 }
529
537 template <typename T>
538 GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem&
539 add(Entity entity, const RuntimeTypeDesc& runtimeType, util::str_view scopePath = {}) {
540 static_assert(!is_pair<T>::value);
541 GAIA_ASSERT(!entity.pair());
542
543 const auto* pItem = find_item(detail::ComponentDesc<T>::hash_lookup());
544 if (pItem != nullptr)
545 return *pItem;
546
547 const auto* pNewItem = ComponentCacheItem::create<T>(entity, m_symbols, runtimeType);
548 GAIA_ASSERT(entity.id() == pNewItem->comp.id());
549#if GAIA_ASSERT_ENABLED
550 validate_typed_runtime_type(*pNewItem, runtimeType);
551#endif
552 const auto& item = add_item(pNewItem, scopePath);
553 add_hash_mapping<T>(item);
554 return item;
555 }
556
562 GAIA_NODISCARD ComponentCacheItem&
563 add(Entity entity, const ecs::ComponentDesc& desc, util::str_view scopePath = {}) {
564 GAIA_ASSERT(entity != EntityBad);
565 GAIA_ASSERT(!entity.pair());
566 GAIA_ASSERT(!desc.name.empty());
567 GAIA_ASSERT(desc.name.size() < ComponentCacheItem::MaxNameLength);
568
569 {
570 const auto* pExisting = symbol(desc.name);
571 if (pExisting != nullptr)
572 return *find(pExisting->entity);
573 }
574
575#if GAIA_ASSERT_ENABLED
576 validate_runtime_type(desc);
577 validate_runtime_fields(desc);
578 validate_runtime_constants(desc);
579#endif
580
581 const auto* pItem = ComponentCacheItem::create(entity, m_symbols, desc);
582 GAIA_ASSERT(entity.id() == pItem->comp.id());
583 return add_item(pItem, scopePath);
584 }
585
589 GAIA_NODISCARD const ComponentCacheItem* find(Entity entity) const noexcept {
590 if (entity.pair())
591 return nullptr;
592
593 const auto it = m_compByEntityId.find(entity.id());
594 return it != m_compByEntityId.end() ? it->second : nullptr;
595 }
596
601 GAIA_NODISCARD const ComponentCacheItem* find_pair_payload(Entity pair) const noexcept {
602 if (!pair.pair())
603 return nullptr;
604
605 const auto relationIt = m_compByEntityId.find(pair.id());
606 const auto* pRelation = relationIt != m_compByEntityId.end() ? relationIt->second : nullptr;
607 if (pRelation != nullptr && pRelation->comp.size() != 0U)
608 return pRelation;
609
610 const auto targetIt = m_compByEntityId.find(pair.gen());
611 const auto* pTarget = targetIt != m_compByEntityId.end() ? targetIt->second : nullptr;
612 return pTarget != nullptr && pTarget->comp.size() != 0U ? pTarget : pRelation;
613 }
614
618 GAIA_NODISCARD ComponentCacheItem* find(Entity entity) noexcept {
619 return const_cast<ComponentCacheItem*>(const_cast<const ComponentCache*>(this)->find(entity));
620 }
621
626 GAIA_NODISCARD const ComponentCacheItem& get(Entity entity) const noexcept {
627 GAIA_ASSERT(!entity.pair());
628 const auto* pItem = find(entity);
629 GAIA_ASSERT(pItem != nullptr);
630 return *pItem;
631 }
632
637 GAIA_NODISCARD ComponentCacheItem& get(Entity entity) noexcept {
638 auto* pItem = find(entity);
639 GAIA_ASSERT(pItem != nullptr);
640 return *pItem;
641 }
642
643 private:
647 GAIA_NODISCARD util::str_view symbol_name(const ComponentCacheItem& item) const noexcept {
648 return item.symbol_name();
649 }
650
654 GAIA_NODISCARD util::str_view path_name(const ComponentCacheItem& item) const noexcept {
655 return item.path.view();
656 }
657
663 bool path(ComponentCacheItem& item, util::str_view name) noexcept {
664 if (path_name(item) == name)
665 return true;
666
667 if (name.empty()) {
668 remove_path_mapping(item);
669 item.path.clear();
670 return true;
671 }
672
673 if (name.size() >= ComponentCacheItem::MaxNameLength)
674 return false;
675
676 remove_path_mapping(item);
677 item.path.assign(name);
678 add_path_mapping(item);
679 return true;
680 }
681
688 bool path(ComponentCacheItem& item, const char* name, uint32_t len = 0) noexcept {
689 return path(item, normalize_name_view(name, len));
690 }
691
695 GAIA_NODISCARD const ComponentCacheItem* symbol(util::str_view name) const noexcept {
696 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
697 const auto it = m_compBySymbol.find(lookup_key(name));
698 return it != m_compBySymbol.end() ? it->second : nullptr;
699 }
700
705 GAIA_NODISCARD const ComponentCacheItem* symbol(const char* name, uint32_t len = 0) const noexcept {
706 GAIA_ASSERT(name != nullptr);
707 return symbol(normalize_name_view(name, len));
708 }
709
713 GAIA_NODISCARD const ComponentCacheItem* path(util::str_view name) const noexcept {
714 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
715 const auto it = m_compByPath.find(lookup_key(name));
716 if (it == m_compByPath.end())
717 return nullptr;
718
719 return it->second.matches == 1 ? it->second.pItem : nullptr;
720 }
721
726 GAIA_NODISCARD const ComponentCacheItem* path(const char* name, uint32_t len = 0) const noexcept {
727 GAIA_ASSERT(name != nullptr);
728 return path(normalize_name_view(name, len));
729 }
730
735 GAIA_NODISCARD const ComponentCacheItem* short_symbol(util::str_view name) const noexcept {
736 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
737 const auto it = m_compByShortSymbol.find(lookup_key(name));
738 if (it == m_compByShortSymbol.end())
739 return nullptr;
740
741 return it->second.matches == 1 ? it->second.pItem : nullptr;
742 }
743
749 GAIA_NODISCARD const ComponentCacheItem* short_symbol(const char* name, uint32_t len = 0) const noexcept {
750 GAIA_ASSERT(name != nullptr);
751 return short_symbol(normalize_name_view(name, len));
752 }
753
758 void add_path_matches(cnt::darray<Entity>& out, util::str_view name) const {
759 if (name.empty())
760 return;
761
762 const auto it = m_compByPath.find(lookup_key(name));
763 if (it == m_compByPath.end())
764 return;
765
766 const auto& entry = it->second;
767 GAIA_ASSERT(entry.matches != 0);
768
769 if (entry.matches == 1) {
770 GAIA_ASSERT(entry.pItem != nullptr);
771 push_unique_entity(out, entry.pItem->entity);
772 return;
773 }
774
775 for (const auto& [entityId, pItem]: m_compByEntityId) {
776 (void)entityId;
777 GAIA_ASSERT(pItem != nullptr);
778 if (pItem->path.view() == name)
779 push_unique_entity(out, pItem->entity);
780 }
781 }
782
783 public:
787 template <typename T>
788 GAIA_NODISCARD const ComponentCacheItem* find() const noexcept {
789 static_assert(!is_pair<T>::value);
790 return find_item(detail::ComponentDesc<T>::hash_lookup());
791 }
792
796 template <typename T>
797 GAIA_NODISCARD const ComponentCacheItem& get() const noexcept {
798 static_assert(!is_pair<T>::value);
799 const auto* pItem = find<T>();
800 GAIA_ASSERT(pItem != nullptr);
801 return *pItem;
802 }
803
804#if GAIA_ENABLE_HOOKS
805
809 static ComponentCacheItem::Hooks& hooks(const ComponentCacheItem& cacheItem) noexcept {
810 return const_cast<ComponentCacheItem&>(cacheItem).hooks();
811 }
812
813#endif
814
815 void diag() const {
816 const auto registeredTypes = m_compByEntityId.size();
817 GAIA_LOG_N("Registered components: %u", registeredTypes);
818
819 auto logDesc = [](const ComponentCacheItem& item) {
820 const auto symbol = item.symbol_name();
821 GAIA_LOG_N(
822 " hash:%016" PRIx64 ", size:%3u B, align:%3u B, [%u:%u] %.*s", item.hashLookup.hash,
823 item.comp.size(), item.comp.alig(), item.entity.id(), item.entity.gen(), (int)symbol.size(),
824 symbol.data());
825 };
826 for (const auto& [entityId, pItem]: m_compByEntityId) {
827 (void)entityId;
828 GAIA_ASSERT(pItem != nullptr);
829 logDesc(*pItem);
830 }
831 }
832 };
833 } // namespace ecs
834} // namespace gaia