Gaia-ECS v0.9.3
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/darray.h"
10#include "gaia/cnt/map.h"
11#include "gaia/core/hashing_string.h"
12#include "gaia/ecs/component.h"
13#include "gaia/ecs/component_cache_item.h"
14#include "gaia/ecs/component_desc.h"
15#include "gaia/ecs/id.h"
16#include "gaia/meta/type_info.h"
17#include "gaia/util/logging.h"
18#include "gaia/util/str.h"
19
20namespace gaia {
21 namespace ecs {
22 class World;
23
25 class GAIA_API ComponentCache final {
26 friend class World;
27
28 static constexpr uint32_t FastComponentCacheSize = 512;
29
34
46 detail::ComponentDescId m_nextRuntimeCompDescId = 0x80000000u;
47
54 void clear() {
55 for (const auto* pItem: m_itemArr)
56 ComponentCacheItem::destroy(const_cast<ComponentCacheItem*>(pItem));
57 for (auto [componentId, pItem]: m_itemByDescId)
58 ComponentCacheItem::destroy(const_cast<ComponentCacheItem*>(pItem));
59
60 m_itemArr.clear();
61 m_itemByDescId.clear();
62 m_compBySymbol.clear();
63 m_compByPath.clear();
64 m_compByShortSymbol.clear();
65 m_compByEntity.clear();
66 }
67
68 template <typename Func>
69 void for_each_item(Func&& func) {
70 for (auto* pItem: m_itemArr) {
71 if (pItem != nullptr)
72 func(*const_cast<ComponentCacheItem*>(pItem));
73 }
74
75 for (auto& [componentId, pItem]: m_itemByDescId) {
76 (void)componentId;
77 func(*const_cast<ComponentCacheItem*>(pItem));
78 }
79 }
80
81 template <typename Func>
82 void for_each_item(Func&& func) const {
83 for (const auto* pItem: m_itemArr) {
84 if (pItem != nullptr)
85 func(*pItem);
86 }
87
88 for (const auto& [componentId, pItem]: m_itemByDescId) {
89 (void)componentId;
90 func(*pItem);
91 }
92 }
93
94 GAIA_NODISCARD static bool is_internal_symbol(util::str_view symbol) noexcept {
95 constexpr char InternalPrefix[] = "gaia::ecs::";
96 constexpr uint32_t InternalPrefixLen = (uint32_t)(sizeof(InternalPrefix) - 1);
97 return symbol.size() >= InternalPrefixLen && memcmp(symbol.data(), InternalPrefix, InternalPrefixLen) == 0;
98 }
99
100 static ComponentCacheItem::SymbolLookupKey short_name_key(const ComponentCacheItem& item) noexcept {
101 const auto* pName = item.name.str();
102 const auto len = item.name.len();
103 for (uint32_t i = len; i > 1; --i) {
104 const auto idx = i - 1;
105 if (pName[idx] != ':' || pName[idx - 1] != ':')
106 continue;
107 return ComponentCacheItem::SymbolLookupKey(pName + idx + 1, len - idx - 1, 0);
108 }
109
111 }
112
113 GAIA_NODISCARD static ComponentCacheItem::SymbolLookupKey lookup_key(util::str_view value) noexcept {
115 : ComponentCacheItem::SymbolLookupKey(value.data(), value.size(), 0);
116 }
117
118 static bool build_default_path(util::str& out, util::str_view symbol) {
119 out.clear();
120 bool changed = false;
121 out.reserve(symbol.size());
122 for (uint32_t i = 0; i < symbol.size(); ++i) {
123 if (i + 1 < symbol.size() && symbol.data()[i] == ':' && symbol.data()[i + 1] == ':') {
124 out.append('.');
125 ++i;
126 changed = true;
127 continue;
128 }
129
130 out.append(symbol.data()[i]);
131 }
132
133 if (!changed)
134 out.clear();
135
136 return changed;
137 }
138
144 static bool build_scoped_path(util::str& out, util::str_view scopePath, util::str_view leaf) {
145 out.clear();
146 if (scopePath.empty() || leaf.empty())
147 return false;
148
149 out.reserve(scopePath.size() + 1 + leaf.size());
150 out.append(scopePath);
151 out.append('.');
152 out.append(leaf);
153 return true;
154 }
155
159 static void initialize_names(ComponentCacheItem& item, util::str_view scopePath = {}) {
160 item.path.clear();
161 const auto symbol = util::str_view{item.name.str(), item.name.len()};
162 if (is_internal_symbol(symbol))
163 return;
164
165 const auto shortName = short_name_key(item);
166 const bool hasShortName =
167 shortName.str() != nullptr && shortName.len() != 0 && shortName.len() != item.name.len();
168 const auto leaf = hasShortName ? util::str_view{shortName.str(), shortName.len()} : symbol;
169
170 if (!build_scoped_path(item.path, scopePath, leaf))
171 (void)build_default_path(item.path, symbol);
172 }
173
174 template <typename ViewFunc>
175 void rebuild_lookup_map(
177 map.clear();
178 for_each_item([&](const ComponentCacheItem& item) {
179 const auto view = getView(item);
180 if (view.empty())
181 return;
182
183 const auto key = lookup_key(view);
184 const auto it = map.find(key);
185 if (it == map.end()) {
186 map.emplace(key, &item);
187 return;
188 }
189
190 if (it->second != &item)
191 it->second = nullptr;
192 });
193 }
194
195 void rebuild_resolved_name_maps() {
196 rebuild_lookup_map(m_compByPath, [](const ComponentCacheItem& item) {
197 return item.path.view();
198 });
199 rebuild_lookup_map(m_compByShortSymbol, [&](const ComponentCacheItem& item) {
200 if (is_internal_symbol(symbol_name(item)))
201 return util::str_view{};
202
203 const auto shortName = short_name_key(item);
204 return shortName.str() != nullptr && shortName.len() != 0 ? util::str_view(shortName.str(), shortName.len())
205 : util::str_view{};
206 });
207 }
208
212 void add_name_mappings(ComponentCacheItem& item, util::str_view scopePath) {
213 m_compBySymbol.emplace(item.name, &item);
214 initialize_names(item, scopePath);
215 rebuild_resolved_name_maps();
216 }
217
218 public:
220 // Reserve enough storage space for most use-cases
221 m_itemArr.reserve(FastComponentCacheSize);
222 }
223
225 clear();
226 }
227
228 ComponentCache(ComponentCache&&) = delete;
229 ComponentCache(const ComponentCache&) = delete;
230 ComponentCache& operator=(ComponentCache&&) = delete;
231 ComponentCache& operator=(const ComponentCache&) = delete;
232
235 template <typename T>
236 GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem& add(Entity entity, util::str_view scopePath = {}) {
237 static_assert(!is_pair<T>::value);
238 GAIA_ASSERT(!entity.pair());
239
240 const auto compDescId = detail::ComponentDesc<T>::id();
241
242 // Fast path for small component ids - use the array storage
243 if (compDescId < FastComponentCacheSize) {
244 auto createDesc = [&]() -> const ComponentCacheItem& {
245 const auto* pItem = ComponentCacheItem::create<T>(entity);
246 GAIA_ASSERT(compDescId == pItem->comp.id());
247 m_itemArr[compDescId] = pItem;
248 add_name_mappings(*const_cast<ComponentCacheItem*>(pItem), scopePath);
249 m_compByEntity.emplace(pItem->entity, pItem);
250 return *pItem;
251 };
252
253 if GAIA_UNLIKELY (compDescId >= m_itemArr.size()) {
254 const auto newSize = compDescId + 1U;
255
256 // Increase the capacity by multiples of CapacityIncreaseSize
257 constexpr uint32_t CapacityIncreaseSize = 128;
258 const auto newCapacity = ((newSize / CapacityIncreaseSize) * CapacityIncreaseSize) + CapacityIncreaseSize;
259 m_itemArr.reserve(newCapacity);
260
261 // Update the size
262 m_itemArr.resize(newSize, nullptr);
263
264 return createDesc();
265 }
266
267 if GAIA_UNLIKELY (m_itemArr[compDescId] == nullptr) {
268 return createDesc();
269 }
270
271 return *m_itemArr[compDescId];
272 }
273
274 // Generic path for large component ids - use the map storage
275 {
276 auto createDesc = [&]() -> const ComponentCacheItem& {
277 const auto* pItem = ComponentCacheItem::create<T>(entity);
278 GAIA_ASSERT(compDescId == pItem->comp.id());
279 m_itemByDescId.emplace(compDescId, pItem);
280 add_name_mappings(*const_cast<ComponentCacheItem*>(pItem), scopePath);
281 m_compByEntity.emplace(pItem->entity, pItem);
282 return *pItem;
283 };
284
285 const auto it = m_itemByDescId.find(compDescId);
286 if (it == m_itemByDescId.end())
287 return createDesc();
288
289 return *it->second;
290 }
291 }
292
305 GAIA_NODISCARD const ComponentCacheItem&
306 add(Entity entity, const char* name, uint32_t len, uint32_t size, DataStorageType storageType, uint32_t alig = 1,
307 uint32_t soa = 0, const uint8_t* pSoaSizes = nullptr, ComponentLookupHash hashLookup = {},
308 util::str_view scopePath = {}) {
309 GAIA_ASSERT(!entity.pair());
310 GAIA_ASSERT(name != nullptr);
311
312 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
313 GAIA_ASSERT(l > 0 && l < ComponentCacheItem::MaxNameLength);
314
315 {
316 const auto* pExisting = symbol(name, l);
317 if (pExisting != nullptr)
318 return *pExisting;
319 }
320
321 detail::ComponentDescId compDescId = m_nextRuntimeCompDescId;
322 while (find(compDescId) != nullptr) {
323 ++compDescId;
324 }
325 m_nextRuntimeCompDescId = compDescId + 1;
326
327 ComponentCacheItem::ComponentCacheItemCtx ctx{};
328 ctx.compDescId = compDescId;
329 ctx.nameStr = name;
330 ctx.nameLen = l;
331 ctx.size = size;
332 ctx.alig = alig;
333 ctx.storageType = storageType;
334 ctx.soa = soa;
335 ctx.pSoaSizes = pSoaSizes;
336 ctx.hashLookup = hashLookup;
337
338 const auto* pItem = ComponentCacheItem::create(entity, ctx);
339 if (compDescId < FastComponentCacheSize) {
340 if (compDescId >= m_itemArr.size())
341 m_itemArr.resize(compDescId + 1U);
342 m_itemArr[compDescId] = pItem;
343 } else {
344 m_itemByDescId.emplace(compDescId, pItem);
345 }
346
347 add_name_mappings(*const_cast<ComponentCacheItem*>(pItem), scopePath);
348 m_compByEntity.emplace(pItem->entity, pItem);
349 return *pItem;
350 }
351
355 GAIA_NODISCARD const ComponentCacheItem* find(detail::ComponentDescId compDescId) const noexcept {
356 // Fast path - array storage
357 if (compDescId < FastComponentCacheSize) {
358 if (compDescId >= m_itemArr.size())
359 return nullptr;
360
361 return m_itemArr[compDescId];
362 }
363
364 // Generic path - map storage
365 const auto it = m_itemByDescId.find(compDescId);
366 return it != m_itemByDescId.end() ? it->second : nullptr;
367 }
368
373 GAIA_NODISCARD const ComponentCacheItem& get(detail::ComponentDescId compDescId) const noexcept {
374 // Fast path - array storage
375 if (compDescId < FastComponentCacheSize) {
376 GAIA_ASSERT(compDescId < m_itemArr.size());
377 return *m_itemArr[compDescId];
378 }
379
380 // Generic path - map storage
381 GAIA_ASSERT(m_itemByDescId.contains(compDescId));
382 return *m_itemByDescId.find(compDescId)->second;
383 }
384
388 GAIA_NODISCARD const ComponentCacheItem* find(Entity entity) const noexcept {
389 if (entity.pair())
390 return nullptr;
391
392 const auto it = m_compByEntity.find(EntityLookupKey(entity));
393 if (it != m_compByEntity.end())
394 return it->second;
395
396 return nullptr;
397 }
398
402 GAIA_NODISCARD ComponentCacheItem* find(Entity entity) noexcept {
403 return const_cast<ComponentCacheItem*>(const_cast<const ComponentCache*>(this)->find(entity));
404 }
405
410 GAIA_NODISCARD const ComponentCacheItem& get(Entity entity) const noexcept {
411 GAIA_ASSERT(!entity.pair());
412 const auto* pItem = find(entity);
413 GAIA_ASSERT(pItem != nullptr);
414 return *pItem;
415 }
416
421 GAIA_NODISCARD ComponentCacheItem& get(Entity entity) noexcept {
422 auto* pItem = find(entity);
423 GAIA_ASSERT(pItem != nullptr);
424 return *pItem;
425 }
426
427 private:
431 GAIA_NODISCARD util::str_view symbol_name(const ComponentCacheItem& item) const noexcept {
432 return {item.name.str(), item.name.len()};
433 }
434
438 GAIA_NODISCARD util::str_view path_name(const ComponentCacheItem& item) const noexcept {
439 return item.path.view();
440 }
441
448 GAIA_NODISCARD util::str_view display_name(const ComponentCacheItem& item) const noexcept {
449 const auto symbol = symbol_name(item);
450 if (is_internal_symbol(symbol))
451 return symbol;
452
453 const auto path = path_name(item);
454 if (!path.empty()) {
455 const auto pathIt = m_compByPath.find(lookup_key(path));
456 const auto symbolIt = m_compBySymbol.find(lookup_key(path));
457 const bool pathIsUnique = pathIt != m_compByPath.end() && pathIt->second == &item;
458 const bool pathShadowed = symbolIt != m_compBySymbol.end() && symbolIt->second != &item;
459 if (pathIsUnique && !pathShadowed)
460 return path;
461 }
462
463 return symbol;
464 }
465
472 bool path(ComponentCacheItem& item, const char* name, uint32_t len = 0) noexcept {
473 if (name == nullptr || name[0] == 0) {
474 item.path.clear();
475 rebuild_resolved_name_maps();
476 return true;
477 }
478
479 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
480 if (l == 0 || l >= ComponentCacheItem::MaxNameLength)
481 return false;
482
483 item.path.assign(name, l);
484 rebuild_resolved_name_maps();
485 return true;
486 }
487
492 GAIA_NODISCARD const ComponentCacheItem* symbol(const char* name, uint32_t len = 0) const noexcept {
493 GAIA_ASSERT(name != nullptr);
494
495 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
496 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
497
498 const auto it = m_compBySymbol.find(ComponentCacheItem::SymbolLookupKey(name, l, 0));
499 return it != m_compBySymbol.end() ? it->second : nullptr;
500 }
501
506 GAIA_NODISCARD const ComponentCacheItem* path(const char* name, uint32_t len = 0) const noexcept {
507 GAIA_ASSERT(name != nullptr);
508
509 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
510 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
511
512 const auto it = m_compByPath.find(ComponentCacheItem::SymbolLookupKey(name, l, 0));
513 return it != m_compByPath.end() ? it->second : nullptr;
514 }
515
521 GAIA_NODISCARD const ComponentCacheItem* short_symbol(const char* name, uint32_t len = 0) const noexcept {
522 GAIA_ASSERT(name != nullptr);
523
524 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
525 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
526
527 const auto it = m_compByShortSymbol.find(ComponentCacheItem::SymbolLookupKey(name, l, 0));
528 return it != m_compByShortSymbol.end() ? it->second : nullptr;
529 }
530
531 GAIA_NODISCARD ComponentCacheItem* symbol(const char* name, uint32_t len = 0) noexcept {
532 return const_cast<ComponentCacheItem*>(const_cast<const ComponentCache*>(this)->symbol(name, len));
533 }
534
535 GAIA_NODISCARD ComponentCacheItem* path(const char* name, uint32_t len = 0) noexcept {
536 return const_cast<ComponentCacheItem*>(const_cast<const ComponentCache*>(this)->path(name, len));
537 }
538
539 GAIA_NODISCARD ComponentCacheItem* short_symbol(const char* name, uint32_t len = 0) noexcept {
540 return const_cast<ComponentCacheItem*>(const_cast<const ComponentCache*>(this)->short_symbol(name, len));
541 }
542
550 GAIA_NODISCARD const ComponentCacheItem* resolve(const char* name, uint32_t len = 0) const noexcept {
551 if (const auto* pItem = symbol(name, len); pItem != nullptr)
552 return pItem;
553 if (const auto* pItem = path(name, len); pItem != nullptr)
554 return pItem;
555 if (const auto* pItem = short_symbol(name, len); pItem != nullptr)
556 return pItem;
557 return nullptr;
558 }
559
560 GAIA_NODISCARD ComponentCacheItem* resolve(const char* name, uint32_t len = 0) noexcept {
561 return const_cast<ComponentCacheItem*>(const_cast<const ComponentCache*>(this)->resolve(name, len));
562 }
563
570 void resolve(cnt::darray<const ComponentCacheItem*>& out, const char* name, uint32_t len = 0) const {
571 GAIA_ASSERT(name != nullptr);
572 out.clear();
573
574 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
575 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
576 const auto needle = util::str_view(name, l);
577
578 auto push_unique = [&](const ComponentCacheItem* pItem) {
579 if (pItem == nullptr)
580 return;
581 for (const auto* pExisting: out) {
582 if (pExisting == pItem)
583 return;
584 }
585 out.push_back(pItem);
586 };
587
588 push_unique(symbol(name, l));
589 push_unique(short_symbol(name, l));
590
591 if (const auto* pItem = path(name, l); pItem != nullptr) {
592 push_unique(pItem);
593 } else {
594 for_each_item([&](const ComponentCacheItem& item) {
595 if (item.path.view() == needle)
596 push_unique(&item);
597 });
598 }
599 }
600
607 GAIA_NODISCARD const ComponentCacheItem& get(const char* name, uint32_t len = 0) const noexcept {
608 const auto* pItem = resolve(name, len);
609 GAIA_ASSERT(pItem != nullptr);
610 return *pItem;
611 }
612
619 GAIA_NODISCARD ComponentCacheItem& get(const char* name, uint32_t len = 0) noexcept {
620 auto* pItem = resolve(name, len);
621 GAIA_ASSERT(pItem != nullptr);
622 return *pItem;
623 }
624
625 public:
629 template <typename T>
630 GAIA_NODISCARD const ComponentCacheItem* find() const noexcept {
631 static_assert(!is_pair<T>::value);
632 const auto compDescId = detail::ComponentDesc<T>::id();
633 return find(compDescId);
634 }
635
639 template <typename T>
640 GAIA_NODISCARD const ComponentCacheItem& get() const noexcept {
641 static_assert(!is_pair<T>::value);
642 const auto compDescId = detail::ComponentDesc<T>::id();
643 return get(compDescId);
644 }
645
646#if GAIA_ENABLE_HOOKS
647
651 static ComponentCacheItem::Hooks& hooks(const ComponentCacheItem& cacheItem) noexcept {
652 return const_cast<ComponentCacheItem&>(cacheItem).hooks();
653 }
654
655#endif
656
657 void diag() const {
658 const auto registeredTypes = m_itemArr.size();
659 GAIA_LOG_N("Registered components: %u", registeredTypes);
660
661 auto logDesc = [](const ComponentCacheItem& item) {
662 GAIA_LOG_N(
663 " hash:%016" PRIx64 ", size:%3u B, align:%3u B, [%u:%u] %s [%s]", item.hashLookup.hash,
664 item.comp.size(), item.comp.alig(), item.entity.id(), item.entity.gen(), item.name.str(),
665 EntityKindString[item.entity.kind()]);
666 };
667 for (const auto* pItem: m_itemArr) {
668 if (pItem == nullptr)
669 continue;
670 logDesc(*pItem);
671 }
672 for (auto [componentId, pItem]: m_itemByDescId)
673 logDesc(*pItem);
674 }
675 };
676 } // namespace ecs
677} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Cache for compile-time defined components.
Definition component_cache.h:25
GAIA_NODISCARD const ComponentCacheItem & add(Entity entity, const char *name, uint32_t len, uint32_t size, DataStorageType storageType, uint32_t alig=1, uint32_t soa=0, const uint8_t *pSoaSizes=nullptr, ComponentLookupHash hashLookup={}, util::str_view scopePath={})
Registers a runtime-defined component.
Definition component_cache.h:306
GAIA_NODISCARD ComponentCacheItem & get(Entity entity) noexcept
Returns the component cache item.
Definition component_cache.h:421
GAIA_NODISCARD const ComponentCacheItem & get(Entity entity) const noexcept
Returns the component cache item.
Definition component_cache.h:410
GAIA_NODISCARD const ComponentCacheItem & get() const noexcept
Returns the component item for.
Definition component_cache.h:640
GAIA_NODISCARD ComponentCacheItem * find(Entity entity) noexcept
Searches for the component cache item.
Definition component_cache.h:402
GAIA_NODISCARD const ComponentCacheItem * find(detail::ComponentDescId compDescId) const noexcept
Searches for the component cache item given the compDescId.
Definition component_cache.h:355
GAIA_NODISCARD const ComponentCacheItem * find(Entity entity) const noexcept
Searches for the component cache item.
Definition component_cache.h:388
GAIA_NODISCARD const ComponentCacheItem * find() const noexcept
Searches for the component item for.
Definition component_cache.h:630
GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem & add(Entity entity, util::str_view scopePath={})
Registers the component item for.
Definition component_cache.h:236
GAIA_NODISCARD const ComponentCacheItem & get(detail::ComponentDescId compDescId) const noexcept
Returns the component cache item given the compDescId.
Definition component_cache.h:373
Definition world.h:88
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition component_cache_item.h:25
SymbolLookupKey name
Component name.
Definition component_cache_item.h:84
util::str path
User-facing path name, e.g. "Gameplay.Device".
Definition component_cache_item.h:86
Hashmap lookup structure used for Entity.
Definition id.h:439
Definition id.h:241
Definition component_desc.h:24
Definition id.h:233
Lightweight non-owning string view over a character sequence.
Definition str.h:13
GAIA_NODISCARD constexpr uint32_t size() const
Returns the number of characters in the view.
Definition str.h:41
GAIA_NODISCARD constexpr bool empty() const
Checks whether the view contains no characters.
Definition str.h:47
GAIA_NODISCARD constexpr const char * data() const
Returns the underlying character pointer.
Definition str.h:35
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331
void append(const char *data, uint32_t size)
Appends size characters from data.
Definition str.h:389
void clear()
Removes all characters from the string.
Definition str.h:352
GAIA_NODISCARD str_view view() const
Returns a non-owning view over the current contents.
Definition str.h:443
void reserve(uint32_t len)
Reserves capacity for at least len characters.
Definition str.h:358