Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
component_cache_item.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdint>
5#include <cstring>
6#include <type_traits>
7
8#include "gaia/cnt/darray.h"
9#include "gaia/core/hashing_string.h"
10#include "gaia/ecs/component.h"
11#include "gaia/ecs/component_desc.h"
12#include "gaia/ecs/id.h"
13#include "gaia/mem/mem_alloc.h"
14#include "gaia/mem/mem_utils.h"
15#include "gaia/mem/smallblock_allocator.h"
16#include "gaia/ser/ser_common.h"
17#include "gaia/ser/ser_rt.h"
18#include "gaia/util/str.h"
19
20namespace gaia {
21 namespace ecs {
22 class World;
23 class Chunk;
24 struct ComponentRecord;
25
26 struct GAIA_API ComponentCacheItem final {
27 GAIA_USE_SMALLBLOCK(ComponentCacheItem);
28
29 static constexpr uint32_t MaxNameLength = 256;
30
32
33 using FuncCtor = void(void*, uint32_t);
34 using FuncDtor = void(void*, uint32_t);
35 using FuncFrom = void(void*, void*, uint32_t, uint32_t, uint32_t, uint32_t);
36 using FuncCopy = void(void*, const void*, uint32_t, uint32_t, uint32_t, uint32_t);
37 using FuncMove = void(void*, void*, uint32_t, uint32_t, uint32_t, uint32_t);
38 using FuncSwap = void(void*, void*, uint32_t, uint32_t, uint32_t, uint32_t);
39 using FuncCmp = bool(const void*, const void*);
40
41 using FuncSave = void(ser::serializer&, const void*, uint32_t, uint32_t, uint32_t);
42 using FuncLoad = void(ser::serializer&, void*, uint32_t, uint32_t, uint32_t);
43
44 using FuncOnAdd = void(const World& world, const ComponentCacheItem&, Entity);
45 using FuncOnDel = void(const World& world, const ComponentCacheItem&, Entity);
46 using FuncOnSet = void(const World& world, const ComponentRecord&, Chunk& chunk);
47
49
55 uint32_t size = 0;
57 uint32_t alig = 0;
59 DataStorageType storageType = DataStorageType::Table;
61 uint32_t soa = 0;
63 const uint8_t* pSoaSizes = nullptr;
65 ComponentLookupHash hashLookup{};
67 RuntimeTypeKind typeKind = RuntimeTypeKind::Struct;
69 RuntimePrimitiveKind primitiveKind = RuntimePrimitiveKind::None;
71 FuncCtor* funcCtor = nullptr;
72 FuncMove* funcMoveCtor = nullptr;
73 FuncCopy* funcCopyCtor = nullptr;
74 FuncDtor* funcDtor = nullptr;
75 FuncCopy* funcCopy = nullptr;
76 FuncMove* funcMove = nullptr;
77 FuncSwap* funcSwap = nullptr;
78 FuncCmp* funcCmp = nullptr;
79 FuncSave* funcSave = nullptr;
80 FuncLoad* funcLoad = nullptr;
81 };
82
90 uint8_t soaSizes[meta::StructToTupleMaxTypes];
91
97 FuncCtor* func_ctor{};
99 FuncMove* func_move_ctor{};
101 FuncCopy* func_copy_ctor{};
103 FuncDtor* func_dtor{};
105 FuncCopy* func_copy{};
107 FuncMove* func_move{};
109 FuncSwap* func_swap{};
111 FuncCmp* func_cmp{};
113 FuncSave* func_save{};
114 // !Function to call when loading component from a buffer
115 FuncLoad* func_load{};
117 RuntimeTypeKind typeKind = RuntimeTypeKind::Struct;
119 RuntimePrimitiveKind primitiveKind = RuntimePrimitiveKind::None;
120
121#if GAIA_ENABLE_HOOKS
122 struct Hooks {
123 #if GAIA_ENABLE_ADD_DEL_HOOKS
125 FuncOnAdd* func_add{};
127 FuncOnDel* func_del{};
128 #endif
129 #if GAIA_ENABLE_SET_HOOKS
131 FuncOnSet* func_set{};
132 #endif
133 };
134 Hooks comp_hooks;
135#endif
137
138 private:
139 ComponentCacheItem() = default;
140 ~ComponentCacheItem() = default;
141
142 public:
143 ComponentCacheItem(const ComponentCacheItem&) = delete;
145 ComponentCacheItem& operator=(const ComponentCacheItem&) = delete;
146 ComponentCacheItem& operator=(ComponentCacheItem&&) = delete;
147
148 void
149 ctor_move(void* pDst, void* pSrc, uint32_t idxDst, uint32_t idxSrc, uint32_t sizeDst, uint32_t sizeSrc) const {
150 GAIA_ASSERT(pSrc != nullptr && pDst != nullptr);
151 GAIA_ASSERT(pSrc != pDst || idxSrc != idxDst);
152 if (func_move_ctor != nullptr) {
153 func_move_ctor(pDst, pSrc, idxDst, idxSrc, sizeDst, sizeSrc);
154 return;
155 }
156
157 if (comp.soa() != 0 || comp.size() == 0)
158 return;
159
160 auto* pD = (uint8_t*)pDst + ((uintptr_t)comp.size() * idxDst);
161 auto* pS = (uint8_t*)pSrc + ((uintptr_t)comp.size() * idxSrc);
162 memmove((void*)pD, (const void*)pS, comp.size());
163 }
164
165 void ctor_copy(
166 void* pDst, const void* pSrc, uint32_t idxDst, uint32_t idxSrc, uint32_t sizeDst, uint32_t sizeSrc) const {
167 GAIA_ASSERT(pSrc != nullptr && pDst != nullptr);
168 GAIA_ASSERT(pSrc != pDst || idxSrc != idxDst);
169 if (func_copy_ctor != nullptr) {
170 func_copy_ctor(pDst, pSrc, idxDst, idxSrc, sizeDst, sizeSrc);
171 return;
172 }
173
174 if (comp.soa() != 0 || comp.size() == 0)
175 return;
176
177 auto* pD = (uint8_t*)pDst + ((uintptr_t)comp.size() * idxDst);
178 auto* pS = (const uint8_t*)pSrc + ((uintptr_t)comp.size() * idxSrc);
179 memcpy((void*)pD, (const void*)pS, comp.size());
180 }
181
182 void dtor(void* pSrc) const {
183 if (func_dtor != nullptr)
184 func_dtor(pSrc, 1);
185 }
186
187 void
188 copy(void* pDst, const void* pSrc, uint32_t idxDst, uint32_t idxSrc, uint32_t sizeDst, uint32_t sizeSrc) const {
189 GAIA_ASSERT(pSrc != nullptr && pDst != nullptr);
190 GAIA_ASSERT(pSrc != pDst || idxSrc != idxDst);
191 if (func_copy != nullptr) {
192 func_copy(pDst, pSrc, idxDst, idxSrc, sizeDst, sizeSrc);
193 return;
194 }
195
196 if (comp.soa() != 0 || comp.size() == 0)
197 return;
198
199 auto* pD = (uint8_t*)pDst + ((uintptr_t)comp.size() * idxDst);
200 auto* pS = (const uint8_t*)pSrc + ((uintptr_t)comp.size() * idxSrc);
201 memcpy((void*)pD, (const void*)pS, comp.size());
202 }
203
204 void move(void* pDst, void* pSrc, uint32_t idxDst, uint32_t idxSrc, uint32_t sizeDst, uint32_t sizeSrc) const {
205 GAIA_ASSERT(pSrc != nullptr && pDst != nullptr);
206 GAIA_ASSERT(pSrc != pDst || idxSrc != idxDst);
207 if (func_move != nullptr) {
208 func_move(pDst, pSrc, idxDst, idxSrc, sizeDst, sizeSrc);
209 return;
210 }
211
212 if (comp.soa() != 0 || comp.size() == 0)
213 return;
214
215 auto* pD = (uint8_t*)pDst + ((uintptr_t)comp.size() * idxDst);
216 auto* pS = (uint8_t*)pSrc + ((uintptr_t)comp.size() * idxSrc);
217 memmove((void*)pD, (const void*)pS, comp.size());
218 }
219
220 void
221 swap(void* pLeft, void* pRight, uint32_t idxLeft, uint32_t idxRight, uint32_t sizeDst, uint32_t sizeSrc) const {
222 GAIA_ASSERT(pLeft != nullptr && pRight != nullptr);
223 if (func_swap != nullptr) {
224 func_swap(pLeft, pRight, idxLeft, idxRight, sizeDst, sizeSrc);
225 return;
226 }
227
228 if (comp.soa() != 0 || comp.size() == 0)
229 return;
230
231 auto* l = (uint8_t*)pLeft + ((uintptr_t)comp.size() * idxLeft);
232 auto* r = (uint8_t*)pRight + ((uintptr_t)comp.size() * idxRight);
233 GAIA_FOR(comp.size()) {
234 const auto tmp = l[i];
235 l[i] = r[i];
236 r[i] = tmp;
237 }
238 }
239
240 bool cmp(const void* pLeft, const void* pRight) const {
241 GAIA_ASSERT(pLeft != pRight);
242 if (func_cmp != nullptr)
243 return func_cmp(pLeft, pRight);
244
245 if (comp.soa() != 0 || comp.size() == 0)
246 return true;
247
248 return memcmp(pLeft, pRight, comp.size()) == 0;
249 }
250
251 void save(ser::serializer& serializer, const void* pSrc, uint32_t from, uint32_t to, uint32_t cap) const {
252 GAIA_ASSERT(serializer.valid() && pSrc != nullptr && from < to && to <= cap);
253 if (func_save != nullptr) {
254 func_save(serializer, pSrc, from, to, cap);
255 return;
256 }
257
258 if (comp.soa() != 0 || comp.size() == 0)
259 return;
260
261 const auto* pBase = (const uint8_t*)pSrc;
262 GAIA_FOR2(from, to) {
263 const auto* p = pBase + ((uintptr_t)comp.size() * i);
264 serializer.save_raw((const void*)p, comp.size(), ser::serialization_type_id::trivial_wrapper);
265 }
266 }
267
268 void load(ser::serializer& serializer, void* pDst, uint32_t from, uint32_t to, uint32_t cap) const {
269 GAIA_ASSERT(serializer.valid() && pDst != nullptr && from < to && to <= cap);
270 if (func_load != nullptr) {
271 func_load(serializer, pDst, from, to, cap);
272 return;
273 }
274
275 if (comp.soa() != 0 || comp.size() == 0)
276 return;
277
278 auto* pBase = (uint8_t*)pDst;
279 GAIA_FOR2(from, to) {
280 auto* p = pBase + ((uintptr_t)comp.size() * i);
281 serializer.load_raw((void*)p, comp.size(), ser::serialization_type_id::trivial_wrapper);
282 }
283 }
284
285 void clear_fields() {
286 fields.clear();
287 }
288
289 GAIA_NODISCARD bool has_fields() const {
290 return !fields.empty();
291 }
292
293 GAIA_NODISCARD static uint32_t field_element_count(const RuntimeFieldDesc& field) noexcept {
294 return field.count == 0 ? 1U : field.count;
295 }
296
297 GAIA_NODISCARD static uint32_t field_element_count(const RuntimeField& field) noexcept {
298 return field.count == 0 ? 1U : field.count;
299 }
300
301 GAIA_NODISCARD static uint32_t primitive_type_size(Entity type) noexcept {
302 ser::serialization_type_id id = ser::serialization_type_id::ignore;
303 if (!runtime_primitive_serialization_type(type, id))
304 return 0;
305 return ser::serialization_type_size(id, 0);
306 }
307
308 GAIA_NODISCARD bool add_field(const RuntimeFieldDesc& desc, uint32_t typeSize = 0) {
309 if (desc.name.empty() || desc.name.size() >= MaxNameLength)
310 return false;
311 if (desc.type == EntityBad)
312 return false;
313
314 const auto elemSize = typeSize != 0 ? typeSize : primitive_type_size(desc.type);
315 if (elemSize == 0)
316 return false;
317
318 const auto elemCount = field_element_count(desc);
319 const auto end = (uint64_t)desc.offset + (uint64_t)elemSize * (uint64_t)elemCount;
320 if (end > comp.size())
321 return false;
322
323 for (auto& field: fields) {
324 if (strncmp(field.name, desc.name.data(), desc.name.size()) == 0 && field.name[desc.name.size()] == 0) {
325 field.type = desc.type;
326 field.offset = desc.offset;
327 field.count = desc.count;
328 return true;
329 }
330 }
331
332 RuntimeField field{};
333 memcpy((void*)field.name, (const void*)desc.name.data(), desc.name.size());
334 field.name[desc.name.size()] = 0;
335 field.type = desc.type;
336 field.offset = desc.offset;
337 field.count = desc.count;
338 fields.push_back(field);
339 return true;
340 }
341
342 GAIA_NODISCARD bool field(util::str_view fieldName, const RuntimeField** ppField) const {
343 GAIA_ASSERT(ppField != nullptr);
344 *ppField = nullptr;
345 if (fieldName.empty() || fieldName.size() >= MaxNameLength)
346 return false;
347
348 for (const auto& field: fields) {
349 if (strncmp(field.name, fieldName.data(), fieldName.size()) == 0 && field.name[fieldName.size()] == 0) {
350 *ppField = &field;
351 return true;
352 }
353 }
354 return false;
355 }
356
357 GAIA_NODISCARD uint32_t field_count() const noexcept {
358 return (uint32_t)fields.size();
359 }
360
361 void clear_runtime_fields() {
362 fields.clear();
363 }
364
365#if GAIA_ENABLE_HOOKS
366 Hooks& hooks() {
367 return comp_hooks;
368 }
369
370 const Hooks& hooks() const {
371 return comp_hooks;
372 }
373
374#endif
375
376 GAIA_NODISCARD uint32_t calc_new_mem_offset(uint32_t addr, size_t cnt) const noexcept {
377 if (comp.soa() == 0) {
378 addr = (uint32_t)mem::detail::get_aligned_byte_offset(addr, comp.alig(), comp.size(), cnt);
379 } else {
380 GAIA_FOR(comp.soa()) {
381 addr = (uint32_t)mem::detail::get_aligned_byte_offset(addr, comp.alig(), soaSizes[i], cnt);
382 }
383 // TODO: Magic offset. Otherwise, SoA data might leak past the chunk boundary when accessing
384 // the last element. By faking the memory offset we can bypass this is issue for now.
385 // Obviously, this needs fixing at some point.
386 addr += comp.soa() * 12;
387 }
388 return addr;
389 }
390
391 private:
392 template <typename T>
393 GAIA_NODISCARD static uint32_t init_type_name(char (&nameTmp)[MaxNameLength]) {
394 auto ct_name = detail::ComponentDesc<T>::name();
395
396 // Allocate enough memory for the name string + the null-terminating character (
397 // the compile time string returned by ComponentDesc<T>::name is not null-terminated).
398 // Different compilers will give a bit different strings, e.g.:
399 // Clang/GCC: gaia::ecs::uni<Position>
400 // MSVC : gaia::ecs::uni<struct Position>
401 // Therefore, we first copy the compile-time string and then tweak it so it is
402 // the same on all supported compilers.
403 auto nameTmpLen = (uint32_t)ct_name.size();
404 GAIA_ASSERT(nameTmpLen < MaxNameLength);
405 memcpy((void*)nameTmp, (const void*)ct_name.data(), nameTmpLen);
406 nameTmp[ct_name.size()] = 0;
407
408 auto strip_prefix = [&](const char* prefix, uint32_t prefixLen) {
409 if (nameTmpLen <= prefixLen || strncmp(nameTmp, prefix, prefixLen) != 0)
410 return;
411
412 memmove(nameTmp, nameTmp + prefixLen, nameTmpLen - prefixLen + 1);
413 nameTmpLen -= prefixLen;
414 };
415
416 strip_prefix("const ", 6);
417
418 const uint32_t NSubstrings = 3;
419 const char* to_remove[NSubstrings] = {"class ", "struct ", "enum "};
420 const uint32_t to_remove_len[NSubstrings] = {6, 7, 5};
421 GAIA_FOR(NSubstrings) {
422 strip_prefix(to_remove[i], to_remove_len[i]);
423 }
424
425 while (nameTmpLen > 0) {
426 const auto ch = nameTmp[nameTmpLen - 1];
427 if (ch != ' ' && ch != '&' && ch != '*')
428 break;
429
430 nameTmp[--nameTmpLen] = 0;
431 }
432
433 if (nameTmpLen > 6 && strncmp(nameTmp + nameTmpLen - 6, " const", 6) == 0) {
434 nameTmpLen -= 6;
435 nameTmp[nameTmpLen] = 0;
436 }
437
438 // Normalization template names by removing keywords when they appear as template argument
439 // prefixes instead of as part of a longer identifier.
440 GAIA_FOR(NSubstrings) {
441 const auto* str = to_remove[i];
442 const auto len = to_remove_len[i];
443
444 auto* pos = nameTmp;
445 while ((pos = strstr(pos, str)) != nullptr) {
446 const bool isBoundary = pos == nameTmp || pos[-1] == '<' || pos[-1] == ',' || pos[-1] == ' ';
447 if (!isBoundary) {
448 ++pos;
449 continue;
450 }
451
452 const auto tailMaxLen = (size_t)(MaxNameLength - (uint32_t)(pos + len - nameTmp));
453 memmove(pos, pos + len, GAIA_STRLEN(pos + len, tailMaxLen) + 1);
454 nameTmpLen -= len;
455 }
456 }
457
458 return nameTmpLen;
459 }
460
461 static void init_name(SymbolLookupKey& nameOut, util::str_view nameView) {
462 char* name = mem::AllocHelper::alloc<char>(nameView.size() + 1);
463 memcpy((void*)name, (const void*)nameView.data(), nameView.size());
464 name[nameView.size()] = 0;
465 nameOut = SymbolLookupKey(name, nameView.size(), 1);
466 }
467
468 public:
469 template <typename T>
470 GAIA_NODISCARD static ComponentCacheItem* create(Entity entity) {
471 static_assert(core::is_raw_v<T>);
472
473 constexpr auto componentSize = detail::ComponentDesc<T>::size();
474 static_assert(
475 componentSize < Component::MaxComponentSizeInBytes,
476 "Trying to register a component larger than the maximum allowed component size! In the future this "
477 "restriction won't apply to components not stored inside archetype chunks.");
478
479 char nameTmp[MaxNameLength];
480 const auto nameTmpLen = init_type_name<T>(nameTmp);
481
482 uint8_t soaSizes[meta::StructToTupleMaxTypes]{};
483 const auto desc = detail::ComponentDesc<T>::make(
484 util::str_view(nameTmp, nameTmpLen), std::span<uint8_t, meta::StructToTupleMaxTypes>{soaSizes});
485 return create(entity, desc);
486 }
487
488 GAIA_NODISCARD static ComponentCacheItem* create(Entity entity, const ecs::ComponentDesc& desc) {
489 GAIA_ASSERT(!desc.name.empty());
490 GAIA_ASSERT(desc.name.size() < MaxNameLength);
491 GAIA_ASSERT(desc.size < Component::MaxComponentSizeInBytes);
492 GAIA_ASSERT((desc.size == 0 && desc.alig == 0) || (desc.alig > 0 && desc.alig < Component::MaxAlignment));
493 GAIA_ASSERT(desc.soa <= meta::StructToTupleMaxTypes);
494
495 auto* cci = new ComponentCacheItem();
496 cci->entity = entity;
497 cci->comp = Component(entity.id(), desc.soa, desc.size, desc.alig, desc.storageType);
498 cci->hashLookup = desc.hashLookup.hash != 0
499 ? desc.hashLookup
500 : ComponentLookupHash{core::calculate_hash64(desc.name.data(), desc.name.size())};
501
502 if (desc.soa > 0 && desc.pSoaSizes != nullptr) {
503 GAIA_FOR(desc.soa) cci->soaSizes[i] = desc.pSoaSizes[i];
504 }
505
506 init_name(cci->name, desc.name);
507
508 cci->func_ctor = desc.funcCtor;
509 cci->func_move_ctor = desc.funcMoveCtor;
510 cci->func_copy_ctor = desc.funcCopyCtor;
511 cci->func_dtor = desc.funcDtor;
512 cci->func_copy = desc.funcCopy;
513 cci->func_move = desc.funcMove;
514 cci->func_swap = desc.funcSwap;
515 cci->func_cmp = desc.funcCmp;
516 cci->func_save = desc.funcSave;
517 cci->func_load = desc.funcLoad;
518 cci->typeKind = desc.typeKind;
519 cci->primitiveKind = desc.primitiveKind;
520
521 return cci;
522 }
523
524 GAIA_NODISCARD static ComponentCacheItem* create(Entity entity, const ComponentCacheItemCtx& ctx) {
525 ecs::ComponentDesc desc{};
526 desc.name = ctx.name;
527 desc.size = ctx.size;
528 desc.alig = ctx.alig;
529 desc.storageType = ctx.storageType;
530 desc.soa = ctx.soa;
531 desc.pSoaSizes = ctx.pSoaSizes;
532 desc.hashLookup = ctx.hashLookup;
533 desc.typeKind = ctx.typeKind;
534 desc.primitiveKind = ctx.primitiveKind;
535 desc.funcCtor = ctx.funcCtor;
536 desc.funcMoveCtor = ctx.funcMoveCtor;
537 desc.funcCopyCtor = ctx.funcCopyCtor;
538 desc.funcDtor = ctx.funcDtor;
539 desc.funcCopy = ctx.funcCopy;
540 desc.funcMove = ctx.funcMove;
541 desc.funcSwap = ctx.funcSwap;
542 desc.funcCmp = ctx.funcCmp;
543 desc.funcSave = ctx.funcSave;
544 desc.funcLoad = ctx.funcLoad;
545 return create(entity, desc);
546 }
547
548 static void destroy(ComponentCacheItem* pItem) {
549 if (pItem == nullptr)
550 return;
551
552 if (pItem->name.str() != nullptr && pItem->name.owned()) {
553 mem::AllocHelper::free((void*)pItem->name.str());
554 pItem->name = {};
555 }
556
557 delete pItem;
558 }
559 };
560 } // namespace ecs
561} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Definition span_impl.h:99
Definition chunk.h:35
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
Definition component_cache_item.h:26
Entity entity
Component entity.
Definition component_cache_item.h:84
ComponentLookupHash hashLookup
Complex hash used for look-ups.
Definition component_cache_item.h:88
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
Definition chunk_header.h:30
Definition id.h:35
Definition id.h:247
Stored runtime field metadata.
Definition component_desc.h:65
Runtime serializer type-erased handle. Traversal logic is shared with compile-time serialization,...
Definition ser_rt.h:79
Lightweight non-owning string view over a character sequence.
Definition str.h:13
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331