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/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
19namespace gaia {
20 namespace ecs {
21 class World;
22
24 class GAIA_API ComponentCache final {
25 friend class World;
26
27 struct ResolvedLookupEntry {
28 const ComponentCacheItem* pItem = nullptr;
29 uint32_t matches = 0;
30 };
31
36
45
52 void clear() {
53 for (auto [entityId, pItem]: m_compByEntityId) {
54 (void)entityId;
55 ComponentCacheItem::destroy(const_cast<ComponentCacheItem*>(pItem));
56 }
57
58 m_compByEntityId.clear();
59 m_compByTypeHash.clear();
60 m_compBySymbol.clear();
61 m_compByPath.clear();
62 m_compByShortSymbol.clear();
63 }
64
65 GAIA_NODISCARD static bool is_internal_symbol(util::str_view symbol) noexcept {
66 constexpr char InternalPrefix[] = "gaia::ecs::";
67 constexpr uint32_t InternalPrefixLen = (uint32_t)(sizeof(InternalPrefix) - 1);
68 return symbol.size() >= InternalPrefixLen && memcmp(symbol.data(), InternalPrefix, InternalPrefixLen) == 0;
69 }
70
71 static ComponentCacheItem::SymbolLookupKey short_name_key(const ComponentCacheItem& item) noexcept {
72 const auto* pName = item.name.str();
73 const auto len = item.name.len();
74 for (uint32_t i = len; i > 1; --i) {
75 const auto idx = i - 1;
76 if (pName[idx] != ':' || pName[idx - 1] != ':')
77 continue;
78
79 return ComponentCacheItem::SymbolLookupKey(pName + idx + 1, len - idx - 1, 0);
80 }
81
83 }
84
85 GAIA_NODISCARD static util::str_view short_symbol_view(const ComponentCacheItem& item) noexcept {
86 const auto symbol = util::str_view{item.name.str(), item.name.len()};
87 if (is_internal_symbol(symbol))
88 return {};
89
90 const auto shortName = short_name_key(item);
91 if (shortName.str() == nullptr || shortName.len() == 0)
92 return {};
93
94 return util::str_view(shortName.str(), shortName.len());
95 }
96
97 GAIA_NODISCARD static ComponentCacheItem::SymbolLookupKey lookup_key(util::str_view value) noexcept {
99 : ComponentCacheItem::SymbolLookupKey(value.data(), value.size(), 0);
100 }
101
102 GAIA_NODISCARD static util::str_view normalize_name_view(const char* name, uint32_t len = 0) noexcept {
103 if (name == nullptr)
104 return {};
105
106 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, ComponentCacheItem::MaxNameLength) : len;
107 GAIA_ASSERT(l < ComponentCacheItem::MaxNameLength);
108 return util::str_view(name, l);
109 }
110
111 static bool build_default_path(util::str& out, util::str_view symbol) {
112 out.clear();
113 bool changed = false;
114 out.reserve(symbol.size());
115 for (uint32_t i = 0; i < symbol.size(); ++i) {
116 if (i + 1 < symbol.size() && symbol.data()[i] == ':' && symbol.data()[i + 1] == ':') {
117 out.append('.');
118 ++i;
119 changed = true;
120 continue;
121 }
122
123 out.append(symbol.data()[i]);
124 }
125
126 if (!changed)
127 out.clear();
128
129 return changed;
130 }
131
137 static bool build_scoped_path(util::str& out, util::str_view scopePath, util::str_view leaf) {
138 out.clear();
139 if (scopePath.empty() || leaf.empty())
140 return false;
141
142 out.reserve(scopePath.size() + 1 + leaf.size());
143 out.append(scopePath);
144 out.append('.');
145 out.append(leaf);
146 return true;
147 }
148
152 static void initialize_names(ComponentCacheItem& item, util::str_view scopePath = {}) {
153 item.path.clear();
154 const auto symbol = util::str_view{item.name.str(), item.name.len()};
155 if (is_internal_symbol(symbol))
156 return;
157
158 const auto shortName = short_name_key(item);
159 const bool hasShortName =
160 shortName.str() != nullptr && shortName.len() != 0 && shortName.len() != item.name.len();
161 const auto leaf = hasShortName ? util::str_view{shortName.str(), shortName.len()} : symbol;
162
163 if (!build_scoped_path(item.path, scopePath, leaf))
164 (void)build_default_path(item.path, symbol);
165 }
166
167 static void add_lookup_mapping(
169 const ComponentCacheItem& item) {
170 if (view.empty())
171 return;
172
173 const auto key = lookup_key(view);
174 const auto it = map.find(key);
175 if (it == map.end()) {
176 map.emplace(key, ResolvedLookupEntry{&item, 1});
177 return;
178 }
179
180 auto& entry = it->second;
181 GAIA_ASSERT(entry.matches != 0);
182 ++entry.matches;
183 }
184
185 void add_path_mapping(const ComponentCacheItem& item) {
186 add_lookup_mapping(m_compByPath, item.path.view(), item);
187 }
188
189 static void push_unique_entity(cnt::darray<Entity>& out, Entity entity) {
190 if (entity == EntityBad)
191 return;
192
193 for (const auto existing: out) {
194 if (existing == entity)
195 return;
196 }
197
198 out.push_back(entity);
199 }
200
201 void remove_path_mapping(const ComponentCacheItem& item) {
202 const auto pathValue = item.path.view();
203 if (pathValue.empty())
204 return;
205
206 const auto key = lookup_key(pathValue);
207 const auto it = m_compByPath.find(key);
208 if (it == m_compByPath.end())
209 return;
210
211 auto& entry = it->second;
212 GAIA_ASSERT(entry.matches != 0);
213
214 if (entry.matches == 1) {
215 GAIA_ASSERT(entry.pItem == &item);
216 m_compByPath.erase(it);
217 return;
218 }
219
220 --entry.matches;
221 if (entry.pItem != &item)
222 return;
223
224 entry.pItem = nullptr;
225 for (const auto& [entityId, pItem]: m_compByEntityId) {
226 (void)entityId;
227 GAIA_ASSERT(pItem != nullptr);
228 if (pItem == &item)
229 continue;
230
231 if (pItem->path.view() != pathValue)
232 continue;
233
234 entry.pItem = pItem;
235 return;
236 }
237
238 GAIA_ASSERT(false);
239 }
240
244 void add_name_mappings(ComponentCacheItem& item, util::str_view scopePath) {
245 m_compBySymbol.emplace(item.name, &item);
246 initialize_names(item, scopePath);
247 add_path_mapping(item);
248 add_lookup_mapping(m_compByShortSymbol, short_symbol_view(item), item);
249 }
250
255 const ComponentCacheItem& add_item(const ComponentCacheItem* pItem, util::str_view scopePath) {
256 GAIA_ASSERT(pItem != nullptr);
257 GAIA_ASSERT(pItem->entity.id() == pItem->comp.id());
258 m_compByEntityId.emplace(pItem->entity.id(), pItem);
259
260 add_name_mappings(*const_cast<ComponentCacheItem*>(pItem), scopePath);
261 return *pItem;
262 }
263
267 GAIA_NODISCARD const ComponentCacheItem* find_item(ComponentLookupHash hash) const noexcept {
268 const auto it = m_compByTypeHash.find(hash);
269 return it != m_compByTypeHash.end() ? it->second : nullptr;
270 }
271
275 template <typename T>
276 void add_hash_mapping(const ComponentCacheItem& item) {
277 const auto hash = detail::ComponentDesc<T>::hash_lookup();
278 const auto it = m_compByTypeHash.find(hash);
279 if (it == m_compByTypeHash.end()) {
280 m_compByTypeHash.emplace(hash, &item);
281 return;
282 }
283
284 GAIA_ASSERT(it->second == &item);
285 }
286
287 public:
288 ComponentCache() = default;
289
291 clear();
292 }
293
294 ComponentCache(ComponentCache&&) = delete;
295 ComponentCache(const ComponentCache&) = delete;
296 ComponentCache& operator=(ComponentCache&&) = delete;
297 ComponentCache& operator=(const ComponentCache&) = delete;
298
301 template <typename T>
302 GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem& add(Entity entity, util::str_view scopePath = {}) {
303 static_assert(!is_pair<T>::value);
304 GAIA_ASSERT(!entity.pair());
305
306 const auto* pItem = find_item(detail::ComponentDesc<T>::hash_lookup());
307 if (pItem != nullptr)
308 return *pItem;
309
310 const auto* pNewItem = ComponentCacheItem::create<T>(entity);
311 GAIA_ASSERT(entity.id() == pNewItem->comp.id());
312 const auto& item = add_item(pNewItem, scopePath);
313 add_hash_mapping<T>(item);
314 return item;
315 }
316
322 GAIA_NODISCARD const ComponentCacheItem&
323 add(Entity entity, const ecs::ComponentDesc& desc, util::str_view scopePath = {}) {
324 GAIA_ASSERT(entity != EntityBad);
325 GAIA_ASSERT(!entity.pair());
326 GAIA_ASSERT(!desc.name.empty());
327 GAIA_ASSERT(desc.name.size() < ComponentCacheItem::MaxNameLength);
328
329 {
330 const auto* pExisting = symbol(desc.name);
331 if (pExisting != nullptr)
332 return *pExisting;
333 }
334
335 const auto* pItem = ComponentCacheItem::create(entity, desc);
336 GAIA_ASSERT(entity.id() == pItem->comp.id());
337 return add_item(pItem, scopePath);
338 }
339
345 GAIA_NODISCARD const ComponentCacheItem&
347 ecs::ComponentDesc desc{};
348 desc.name = item.name;
349 desc.size = item.size;
350 desc.alig = item.alig;
351 desc.storageType = item.storageType;
352 desc.soa = item.soa;
353 desc.pSoaSizes = item.pSoaSizes;
354 desc.hashLookup = item.hashLookup;
355 desc.typeKind = item.typeKind;
356 desc.primitiveKind = item.primitiveKind;
357 desc.funcCtor = item.funcCtor;
358 desc.funcMoveCtor = item.funcMoveCtor;
359 desc.funcCopyCtor = item.funcCopyCtor;
360 desc.funcDtor = item.funcDtor;
361 desc.funcCopy = item.funcCopy;
362 desc.funcMove = item.funcMove;
363 desc.funcSwap = item.funcSwap;
364 desc.funcCmp = item.funcCmp;
365 desc.funcSave = item.funcSave;
366 desc.funcLoad = item.funcLoad;
367 return add(entity, desc, scopePath);
368 }
369
374 GAIA_NODISCARD bool add_field(Entity component, const RuntimeFieldDesc& field) {
375 auto* pItem = find(component);
376 if (pItem == nullptr)
377 return false;
378
379 const auto* pType = find(field.type);
380 if (pType == nullptr)
381 return false;
382
383 return pItem->add_field(field, pType->comp.size());
384 }
385
389 GAIA_NODISCARD const ComponentCacheItem* find(Entity entity) const noexcept {
390 if (entity.pair())
391 return nullptr;
392
393 const auto it = m_compByEntityId.find(entity.id());
394 return it != m_compByEntityId.end() ? it->second : nullptr;
395 }
396
400 GAIA_NODISCARD ComponentCacheItem* find(Entity entity) noexcept {
401 return const_cast<ComponentCacheItem*>(const_cast<const ComponentCache*>(this)->find(entity));
402 }
403
408 GAIA_NODISCARD const ComponentCacheItem& get(Entity entity) const noexcept {
409 GAIA_ASSERT(!entity.pair());
410 const auto* pItem = find(entity);
411 GAIA_ASSERT(pItem != nullptr);
412 return *pItem;
413 }
414
419 GAIA_NODISCARD ComponentCacheItem& get(Entity entity) noexcept {
420 auto* pItem = find(entity);
421 GAIA_ASSERT(pItem != nullptr);
422 return *pItem;
423 }
424
425 private:
429 GAIA_NODISCARD util::str_view symbol_name(const ComponentCacheItem& item) const noexcept {
430 return {item.name.str(), item.name.len()};
431 }
432
436 GAIA_NODISCARD util::str_view path_name(const ComponentCacheItem& item) const noexcept {
437 return item.path.view();
438 }
439
445 bool path(ComponentCacheItem& item, util::str_view name) noexcept {
446 if (path_name(item) == name)
447 return true;
448
449 if (name.empty()) {
450 remove_path_mapping(item);
451 item.path.clear();
452 return true;
453 }
454
455 if (name.size() >= ComponentCacheItem::MaxNameLength)
456 return false;
457
458 remove_path_mapping(item);
459 item.path.assign(name);
460 add_path_mapping(item);
461 return true;
462 }
463
470 bool path(ComponentCacheItem& item, const char* name, uint32_t len = 0) noexcept {
471 return path(item, normalize_name_view(name, len));
472 }
473
477 GAIA_NODISCARD const ComponentCacheItem* symbol(util::str_view name) const noexcept {
478 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
479 const auto it = m_compBySymbol.find(lookup_key(name));
480 return it != m_compBySymbol.end() ? it->second : nullptr;
481 }
482
487 GAIA_NODISCARD const ComponentCacheItem* symbol(const char* name, uint32_t len = 0) const noexcept {
488 GAIA_ASSERT(name != nullptr);
489 return symbol(normalize_name_view(name, len));
490 }
491
495 GAIA_NODISCARD const ComponentCacheItem* path(util::str_view name) const noexcept {
496 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
497 const auto it = m_compByPath.find(lookup_key(name));
498 if (it == m_compByPath.end())
499 return nullptr;
500
501 return it->second.matches == 1 ? it->second.pItem : nullptr;
502 }
503
508 GAIA_NODISCARD const ComponentCacheItem* path(const char* name, uint32_t len = 0) const noexcept {
509 GAIA_ASSERT(name != nullptr);
510 return path(normalize_name_view(name, len));
511 }
512
517 GAIA_NODISCARD const ComponentCacheItem* short_symbol(util::str_view name) const noexcept {
518 GAIA_ASSERT(name.size() < ComponentCacheItem::MaxNameLength);
519 const auto it = m_compByShortSymbol.find(lookup_key(name));
520 if (it == m_compByShortSymbol.end())
521 return nullptr;
522
523 return it->second.matches == 1 ? it->second.pItem : nullptr;
524 }
525
531 GAIA_NODISCARD const ComponentCacheItem* short_symbol(const char* name, uint32_t len = 0) const noexcept {
532 GAIA_ASSERT(name != nullptr);
533 return short_symbol(normalize_name_view(name, len));
534 }
535
540 void add_path_matches(cnt::darray<Entity>& out, util::str_view name) const {
541 if (name.empty())
542 return;
543
544 const auto it = m_compByPath.find(lookup_key(name));
545 if (it == m_compByPath.end())
546 return;
547
548 const auto& entry = it->second;
549 GAIA_ASSERT(entry.matches != 0);
550
551 if (entry.matches == 1) {
552 GAIA_ASSERT(entry.pItem != nullptr);
553 push_unique_entity(out, entry.pItem->entity);
554 return;
555 }
556
557 for (const auto& [entityId, pItem]: m_compByEntityId) {
558 (void)entityId;
559 GAIA_ASSERT(pItem != nullptr);
560 if (pItem->path.view() == name)
561 push_unique_entity(out, pItem->entity);
562 }
563 }
564
565 public:
569 template <typename T>
570 GAIA_NODISCARD const ComponentCacheItem* find() const noexcept {
571 static_assert(!is_pair<T>::value);
572 return find_item(detail::ComponentDesc<T>::hash_lookup());
573 }
574
578 template <typename T>
579 GAIA_NODISCARD const ComponentCacheItem& get() const noexcept {
580 static_assert(!is_pair<T>::value);
581 const auto* pItem = find<T>();
582 GAIA_ASSERT(pItem != nullptr);
583 return *pItem;
584 }
585
586#if GAIA_ENABLE_HOOKS
587
591 static ComponentCacheItem::Hooks& hooks(const ComponentCacheItem& cacheItem) noexcept {
592 return const_cast<ComponentCacheItem&>(cacheItem).hooks();
593 }
594
595#endif
596
597 void diag() const {
598 const auto registeredTypes = m_compByEntityId.size();
599 GAIA_LOG_N("Registered components: %u", registeredTypes);
600
601 auto logDesc = [](const ComponentCacheItem& item) {
602 GAIA_LOG_N(
603 " hash:%016" PRIx64 ", size:%3u B, align:%3u B, [%u:%u] %s [%s]", item.hashLookup.hash,
604 item.comp.size(), item.comp.alig(), item.entity.id(), item.entity.gen(), item.name.str(),
605 EntityKindString[item.entity.kind()]);
606 };
607 for (const auto& [entityId, pItem]: m_compByEntityId) {
608 (void)entityId;
609 GAIA_ASSERT(pItem != nullptr);
610 logDesc(*pItem);
611 }
612 }
613 };
614 } // namespace ecs
615} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
iterator erase(iterator pos) noexcept
Removes the element at pos.
Definition darray_impl.h:331
Cache for compile-time defined components.
Definition component_cache.h:24
GAIA_NODISCARD bool add_field(Entity component, const RuntimeFieldDesc &field)
Adds runtime field metadata to a registered component.
Definition component_cache.h:374
GAIA_NODISCARD ComponentCacheItem & get(Entity entity) noexcept
Returns the component cache item.
Definition component_cache.h:419
GAIA_NODISCARD const ComponentCacheItem & get(Entity entity) const noexcept
Returns the component cache item.
Definition component_cache.h:408
GAIA_NODISCARD const ComponentCacheItem & get() const noexcept
Returns the component item for.
Definition component_cache.h:579
GAIA_NODISCARD ComponentCacheItem * find(Entity entity) noexcept
Searches for the component cache item.
Definition component_cache.h:400
GAIA_NODISCARD const ComponentCacheItem * find(Entity entity) const noexcept
Searches for the component cache item.
Definition component_cache.h:389
GAIA_NODISCARD const ComponentCacheItem * find() const noexcept
Searches for the component item for.
Definition component_cache.h:570
GAIA_NODISCARD const ComponentCacheItem & add(Entity entity, const ComponentCacheItem::ComponentCacheItemCtx &item, util::str_view scopePath={})
Registers a runtime-defined component.
Definition component_cache.h:346
GAIA_NODISCARD GAIA_FORCEINLINE const ComponentCacheItem & add(Entity entity, util::str_view scopePath={})
Registers the component item for.
Definition component_cache.h:302
GAIA_NODISCARD const ComponentCacheItem & add(Entity entity, const ecs::ComponentDesc &desc, util::str_view scopePath={})
Registers a runtime-defined component.
Definition component_cache.h:323
Definition world.h:174
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Component item registration context.
Definition component_cache_item.h:51
ComponentLookupHash hashLookup
Optional explicit lookup hash. When empty, the symbol hash is used.
Definition component_cache_item.h:65
RuntimePrimitiveKind primitiveKind
Runtime primitive kind. Only valid when typeKind is Primitive.
Definition component_cache_item.h:69
uint32_t size
Component payload size in bytes.
Definition component_cache_item.h:55
uint32_t alig
Component payload alignment in bytes.
Definition component_cache_item.h:57
DataStorageType storageType
Component storage mode.
Definition component_cache_item.h:59
FuncCtor * funcCtor
Optional lifecycle and serialization callbacks.
Definition component_cache_item.h:71
util::str_view name
Registered component symbol.
Definition component_cache_item.h:53
RuntimeTypeKind typeKind
Runtime reflection type kind.
Definition component_cache_item.h:67
uint32_t soa
Number of SoA elements, 0 means AoS.
Definition component_cache_item.h:61
const uint8_t * pSoaSizes
Per-element SoA sizes when soa is non-zero.
Definition component_cache_item.h:63
Definition component_cache_item.h:26
Entity entity
Component entity.
Definition component_cache_item.h:84
Component comp
Unique component identifier.
Definition component_cache_item.h:86
SymbolLookupKey name
Component name.
Definition component_cache_item.h:93
util::str path
User-facing path name, e.g. "Gameplay.Device".
Definition component_cache_item.h:95
Plain component registration descriptor shared by typed and runtime component paths....
Definition component_desc.h:75
FuncCtor * funcCtor
Optional lifecycle and serialization callbacks.
Definition component_desc.h:105
const uint8_t * pSoaSizes
Per-element SoA sizes when soa is non-zero.
Definition component_desc.h:97
RuntimeTypeKind typeKind
Runtime reflection type kind.
Definition component_desc.h:101
uint32_t soa
Number of SoA elements, 0 means AoS.
Definition component_desc.h:95
DataStorageType storageType
Component storage mode.
Definition component_desc.h:93
RuntimePrimitiveKind primitiveKind
Runtime primitive kind. Only valid when typeKind is Primitive.
Definition component_desc.h:103
uint32_t alig
Component payload alignment in bytes.
Definition component_desc.h:91
uint32_t size
Component payload size in bytes.
Definition component_desc.h:89
ComponentLookupHash hashLookup
Optional explicit lookup hash. When empty, the symbol hash is used.
Definition component_desc.h:99
util::str_view name
Registered component symbol.
Definition component_desc.h:87
Definition id.h:247
User-authored runtime field descriptor. A count of 0 means scalar; positive values describe a fixed i...
Definition component_desc.h:53
Entity type
Entity describing the field type.
Definition component_desc.h:57
Definition component_desc.h:119
Definition id.h:239
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