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/ser/ser_common.h"
16#include "gaia/ser/ser_rt.h"
17#include "gaia/util/str.h"
18
19namespace gaia {
20 namespace ecs {
21 class World;
22 class Chunk;
23 struct ComponentRecord;
24
25 struct GAIA_API ComponentCacheItem final {
26 static constexpr uint32_t MaxNameLength = 256;
27
29
30 using FuncCtor = void(void*, uint32_t);
31 using FuncDtor = void(void*, uint32_t);
32 using FuncFrom = void(void*, void*, uint32_t, uint32_t, uint32_t, uint32_t);
33 using FuncCopy = void(void*, const void*, uint32_t, uint32_t, uint32_t, uint32_t);
34 using FuncMove = void(void*, void*, uint32_t, uint32_t, uint32_t, uint32_t);
35 using FuncSwap = void(void*, void*, uint32_t, uint32_t, uint32_t, uint32_t);
36 using FuncCmp = bool(const void*, const void*);
37
38 using FuncSave = void(ser::serializer&, const void*, uint32_t, uint32_t, uint32_t);
39 using FuncLoad = void(ser::serializer&, void*, uint32_t, uint32_t, uint32_t);
40
41 using FuncOnAdd = void(const World& world, const ComponentCacheItem&, Entity);
42 using FuncOnDel = void(const World& world, const ComponentCacheItem&, Entity);
43 using FuncOnSet = void(const World& world, const ComponentRecord&, Chunk& chunk);
44
45 struct SchemaField {
46 char name[MaxNameLength]{};
47 ser::serialization_type_id type = ser::serialization_type_id::u8;
48 uint32_t offset = 0;
49 uint32_t size = 0;
50 };
51
53 uint32_t compDescId = 0;
54 const char* nameStr = nullptr;
55 uint32_t nameLen = 0;
56 uint32_t size = 0;
57 uint32_t alig = 0;
58 DataStorageType storageType = DataStorageType::Table;
59 uint32_t soa = 0;
60 const uint8_t* pSoaSizes = nullptr;
61 ComponentLookupHash hashLookup = {};
62 FuncCtor* funcCtor = nullptr;
63 FuncMove* funcMoveCtor = nullptr;
64 FuncCopy* funcCopyCtor = nullptr;
65 FuncDtor* funcDtor = nullptr;
66 FuncCopy* funcCopy = nullptr;
67 FuncMove* funcMove = nullptr;
68 FuncSwap* funcSwap = nullptr;
69 FuncCmp* funcCmp = nullptr;
70 FuncSave* funcSave = nullptr;
71 FuncLoad* funcLoad = nullptr;
72 };
73
81 uint8_t soaSizes[meta::StructToTupleMaxTypes];
82
88 FuncCtor* func_ctor{};
90 FuncMove* func_move_ctor{};
92 FuncCopy* func_copy_ctor{};
94 FuncDtor* func_dtor{};
96 FuncCopy* func_copy{};
98 FuncMove* func_move{};
100 FuncSwap* func_swap{};
102 FuncCmp* func_cmp{};
104 FuncSave* func_save{};
105 // !Function to call when loading component from a buffer
106 FuncLoad* func_load{};
107
108#if GAIA_ENABLE_HOOKS
109 struct Hooks {
110 #if GAIA_ENABLE_ADD_DEL_HOOKS
112 FuncOnAdd* func_add{};
114 FuncOnDel* func_del{};
115 #endif
116 #if GAIA_ENABLE_SET_HOOKS
118 FuncOnSet* func_set{};
119 #endif
120 };
121 Hooks comp_hooks;
122#endif
123 cnt::darray<SchemaField> schema;
124
125 private:
126 ComponentCacheItem() = default;
127 ~ComponentCacheItem() = default;
128
129 public:
130 ComponentCacheItem(const ComponentCacheItem&) = delete;
131 ComponentCacheItem(ComponentCacheItem&&) = delete;
132 ComponentCacheItem& operator=(const ComponentCacheItem&) = delete;
133 ComponentCacheItem& operator=(ComponentCacheItem&&) = delete;
134
135 void
136 ctor_move(void* pDst, void* pSrc, uint32_t idxDst, uint32_t idxSrc, uint32_t sizeDst, uint32_t sizeSrc) const {
137 GAIA_ASSERT(pSrc != nullptr && pDst != nullptr);
138 GAIA_ASSERT(pSrc != pDst || idxSrc != idxDst);
139 if (func_move_ctor != nullptr) {
140 func_move_ctor(pDst, pSrc, idxDst, idxSrc, sizeDst, sizeSrc);
141 return;
142 }
143
144 if (comp.soa() != 0 || comp.size() == 0)
145 return;
146
147 auto* pD = (uint8_t*)pDst + ((uintptr_t)comp.size() * idxDst);
148 auto* pS = (uint8_t*)pSrc + ((uintptr_t)comp.size() * idxSrc);
149 memmove((void*)pD, (const void*)pS, comp.size());
150 }
151
152 void ctor_copy(
153 void* pDst, const void* pSrc, uint32_t idxDst, uint32_t idxSrc, uint32_t sizeDst, uint32_t sizeSrc) const {
154 GAIA_ASSERT(pSrc != nullptr && pDst != nullptr);
155 GAIA_ASSERT(pSrc != pDst || idxSrc != idxDst);
156 if (func_copy_ctor != nullptr) {
157 func_copy_ctor(pDst, pSrc, idxDst, idxSrc, sizeDst, sizeSrc);
158 return;
159 }
160
161 if (comp.soa() != 0 || comp.size() == 0)
162 return;
163
164 auto* pD = (uint8_t*)pDst + ((uintptr_t)comp.size() * idxDst);
165 auto* pS = (const uint8_t*)pSrc + ((uintptr_t)comp.size() * idxSrc);
166 memcpy((void*)pD, (const void*)pS, comp.size());
167 }
168
169 void dtor(void* pSrc) const {
170 if (func_dtor != nullptr)
171 func_dtor(pSrc, 1);
172 }
173
174 void
175 copy(void* pDst, const void* pSrc, uint32_t idxDst, uint32_t idxSrc, uint32_t sizeDst, uint32_t sizeSrc) const {
176 GAIA_ASSERT(pSrc != nullptr && pDst != nullptr);
177 GAIA_ASSERT(pSrc != pDst || idxSrc != idxDst);
178 if (func_copy != nullptr) {
179 func_copy(pDst, pSrc, idxDst, idxSrc, sizeDst, sizeSrc);
180 return;
181 }
182
183 if (comp.soa() != 0 || comp.size() == 0)
184 return;
185
186 auto* pD = (uint8_t*)pDst + ((uintptr_t)comp.size() * idxDst);
187 auto* pS = (const uint8_t*)pSrc + ((uintptr_t)comp.size() * idxSrc);
188 memcpy((void*)pD, (const void*)pS, comp.size());
189 }
190
191 void move(void* pDst, void* pSrc, uint32_t idxDst, uint32_t idxSrc, uint32_t sizeDst, uint32_t sizeSrc) const {
192 GAIA_ASSERT(pSrc != nullptr && pDst != nullptr);
193 GAIA_ASSERT(pSrc != pDst || idxSrc != idxDst);
194 if (func_move != nullptr) {
195 func_move(pDst, pSrc, idxDst, idxSrc, sizeDst, sizeSrc);
196 return;
197 }
198
199 if (comp.soa() != 0 || comp.size() == 0)
200 return;
201
202 auto* pD = (uint8_t*)pDst + ((uintptr_t)comp.size() * idxDst);
203 auto* pS = (uint8_t*)pSrc + ((uintptr_t)comp.size() * idxSrc);
204 memmove((void*)pD, (const void*)pS, comp.size());
205 }
206
207 void
208 swap(void* pLeft, void* pRight, uint32_t idxLeft, uint32_t idxRight, uint32_t sizeDst, uint32_t sizeSrc) const {
209 GAIA_ASSERT(pLeft != nullptr && pRight != nullptr);
210 if (func_swap != nullptr) {
211 func_swap(pLeft, pRight, idxLeft, idxRight, sizeDst, sizeSrc);
212 return;
213 }
214
215 if (comp.soa() != 0 || comp.size() == 0)
216 return;
217
218 auto* l = (uint8_t*)pLeft + ((uintptr_t)comp.size() * idxLeft);
219 auto* r = (uint8_t*)pRight + ((uintptr_t)comp.size() * idxRight);
220 GAIA_FOR(comp.size()) {
221 const auto tmp = l[i];
222 l[i] = r[i];
223 r[i] = tmp;
224 }
225 }
226
227 bool cmp(const void* pLeft, const void* pRight) const {
228 GAIA_ASSERT(pLeft != pRight);
229 if (func_cmp != nullptr)
230 return func_cmp(pLeft, pRight);
231
232 if (comp.soa() != 0 || comp.size() == 0)
233 return true;
234
235 return memcmp(pLeft, pRight, comp.size()) == 0;
236 }
237
238 void save(ser::serializer& serializer, const void* pSrc, uint32_t from, uint32_t to, uint32_t cap) const {
239 GAIA_ASSERT(serializer.valid() && pSrc != nullptr && from < to && to <= cap);
240 if (func_save != nullptr) {
241 func_save(serializer, pSrc, from, to, cap);
242 return;
243 }
244
245 if (comp.soa() != 0 || comp.size() == 0)
246 return;
247
248 const auto* pBase = (const uint8_t*)pSrc;
249 GAIA_FOR2(from, to) {
250 const auto* p = pBase + ((uintptr_t)comp.size() * i);
251 serializer.save_raw((const void*)p, comp.size(), ser::serialization_type_id::trivial_wrapper);
252 }
253 }
254
255 void load(ser::serializer& serializer, void* pDst, uint32_t from, uint32_t to, uint32_t cap) const {
256 GAIA_ASSERT(serializer.valid() && pDst != nullptr && from < to && to <= cap);
257 if (func_load != nullptr) {
258 func_load(serializer, pDst, from, to, cap);
259 return;
260 }
261
262 if (comp.soa() != 0 || comp.size() == 0)
263 return;
264
265 auto* pBase = (uint8_t*)pDst;
266 GAIA_FOR2(from, to) {
267 auto* p = pBase + ((uintptr_t)comp.size() * i);
268 serializer.load_raw((void*)p, comp.size(), ser::serialization_type_id::trivial_wrapper);
269 }
270 }
271
272 GAIA_NODISCARD bool set_field(
273 const char* fieldName, uint32_t len, ser::serialization_type_id type, uint32_t fieldOffset,
274 uint32_t fieldSize) {
275 if (fieldName == nullptr || fieldName[0] == 0)
276 return false;
277
278 const auto fieldLen = len == 0 ? (uint32_t)GAIA_STRLEN(fieldName, MaxNameLength) : len;
279 if (fieldLen == 0 || fieldLen >= MaxNameLength)
280 return false;
281
282 for (auto& f: schema) {
283 if (strncmp(f.name, fieldName, fieldLen) == 0 && f.name[fieldLen] == 0) {
284 f.type = type;
285 f.offset = fieldOffset;
286 f.size = fieldSize;
287 return true;
288 }
289 }
290
291 SchemaField f{};
292 memcpy((void*)f.name, (const void*)fieldName, fieldLen);
293 f.name[fieldLen] = 0;
294 f.type = type;
295 f.offset = fieldOffset;
296 f.size = fieldSize;
297 schema.push_back(f);
298 return true;
299 }
300
301 void clear_fields() {
302 schema.clear();
303 }
304
305 GAIA_NODISCARD bool has_fields() const {
306 return !schema.empty();
307 }
308
309#if GAIA_ENABLE_HOOKS
310 Hooks& hooks() {
311 return comp_hooks;
312 }
313
314 const Hooks& hooks() const {
315 return comp_hooks;
316 }
317
318#endif
319
320 GAIA_NODISCARD uint32_t calc_new_mem_offset(uint32_t addr, size_t cnt) const noexcept {
321 if (comp.soa() == 0) {
322 addr = (uint32_t)mem::detail::get_aligned_byte_offset(addr, comp.alig(), comp.size(), cnt);
323 } else {
324 GAIA_FOR(comp.soa()) {
325 addr = (uint32_t)mem::detail::get_aligned_byte_offset(addr, comp.alig(), soaSizes[i], cnt);
326 }
327 // TODO: Magic offset. Otherwise, SoA data might leak past the chunk boundary when accessing
328 // the last element. By faking the memory offset we can bypass this is issue for now.
329 // Obviously, this needs fixing at some point.
330 addr += comp.soa() * 12;
331 }
332 return addr;
333 }
334
335 private:
336 template <typename T>
337 GAIA_NODISCARD static uint32_t init_type_name(char (&nameTmp)[MaxNameLength]) {
338 auto ct_name = detail::ComponentDesc<T>::name();
339
340 // Allocate enough memory for the name string + the null-terminating character (
341 // the compile time string returned by ComponentDesc<T>::name is not null-terminated).
342 // Different compilers will give a bit different strings, e.g.:
343 // Clang/GCC: gaia::ecs::uni<Position>
344 // MSVC : gaia::ecs::uni<struct Position>
345 // Therefore, we first copy the compile-time string and then tweak it so it is
346 // the same on all supported compilers.
347 auto nameTmpLen = (uint32_t)ct_name.size();
348 GAIA_ASSERT(nameTmpLen < MaxNameLength);
349 memcpy((void*)nameTmp, (const void*)ct_name.data(), nameTmpLen);
350 nameTmp[ct_name.size()] = 0;
351
352 auto strip_prefix = [&](const char* prefix, uint32_t prefixLen) {
353 if (nameTmpLen <= prefixLen || strncmp(nameTmp, prefix, prefixLen) != 0)
354 return;
355
356 memmove(nameTmp, nameTmp + prefixLen, nameTmpLen - prefixLen + 1);
357 nameTmpLen -= prefixLen;
358 };
359
360 strip_prefix("const ", 6);
361
362 const uint32_t NSubstrings = 3;
363 const char* to_remove[NSubstrings] = {"class ", "struct ", "enum "};
364 const uint32_t to_remove_len[NSubstrings] = {6, 7, 5};
365 GAIA_FOR(NSubstrings) {
366 strip_prefix(to_remove[i], to_remove_len[i]);
367 }
368
369 while (nameTmpLen > 0) {
370 const auto ch = nameTmp[nameTmpLen - 1];
371 if (ch != ' ' && ch != '&' && ch != '*')
372 break;
373
374 nameTmp[--nameTmpLen] = 0;
375 }
376
377 if (nameTmpLen > 6 && strncmp(nameTmp + nameTmpLen - 6, " const", 6) == 0) {
378 nameTmpLen -= 6;
379 nameTmp[nameTmpLen] = 0;
380 }
381
382 // Normalization template names by removing keywords when they appear as template argument
383 // prefixes instead of as part of a longer identifier.
384 GAIA_FOR(NSubstrings) {
385 const auto* str = to_remove[i];
386 const auto len = to_remove_len[i];
387
388 auto* pos = nameTmp;
389 while ((pos = strstr(pos, str)) != nullptr) {
390 const bool isBoundary = pos == nameTmp || pos[-1] == '<' || pos[-1] == ',' || pos[-1] == ' ';
391 if (!isBoundary) {
392 ++pos;
393 continue;
394 }
395
396 const auto tailMaxLen = (size_t)(MaxNameLength - (uint32_t)(pos + len - nameTmp));
397 memmove(pos, pos + len, GAIA_STRLEN(pos + len, tailMaxLen) + 1);
398 nameTmpLen -= len;
399 }
400 }
401
402 return nameTmpLen;
403 }
404
405 static void init_name(SymbolLookupKey& nameOut, const char* nameStr, uint32_t nameLen) {
406 char* name = mem::AllocHelper::alloc<char>(nameLen + 1);
407 memcpy((void*)name, (const void*)nameStr, nameLen);
408 name[nameLen] = 0;
409 nameOut = SymbolLookupKey(name, nameLen, 1);
410 }
411
412 public:
413 template <typename T>
414 GAIA_NODISCARD static ComponentCacheItem* create(Entity entity) {
415 static_assert(core::is_raw_v<T>);
416
417 constexpr auto componentSize = detail::ComponentDesc<T>::size();
418 static_assert(
419 componentSize < Component::MaxComponentSizeInBytes,
420 "Trying to register a component larger than the maximum allowed component size! In the future this "
421 "restriction won't apply to components not stored inside archetype chunks.");
422
423 char nameTmp[MaxNameLength];
424 const auto nameTmpLen = init_type_name<T>(nameTmp);
425
426 ComponentCacheItemCtx ctx{};
427 ctx.compDescId = detail::ComponentDesc<T>::id();
428 ctx.nameStr = nameTmp;
429 ctx.nameLen = nameTmpLen;
430 ctx.size = componentSize;
431 ctx.alig = detail::ComponentDesc<T>::alig();
432 ctx.storageType = DataStorageType::Table;
433 uint8_t soaSizes[meta::StructToTupleMaxTypes]{};
434 ctx.soa = detail::ComponentDesc<T>::soa(soaSizes);
435 ctx.pSoaSizes = soaSizes;
436 ctx.hashLookup = detail::ComponentDesc<T>::hash_lookup();
437 ctx.funcCtor = detail::ComponentDesc<T>::func_ctor();
438 ctx.funcMoveCtor = detail::ComponentDesc<T>::func_move_ctor();
439 ctx.funcCopyCtor = detail::ComponentDesc<T>::func_copy_ctor();
440 ctx.funcDtor = detail::ComponentDesc<T>::func_dtor();
441 ctx.funcCopy = detail::ComponentDesc<T>::func_copy();
442 ctx.funcMove = detail::ComponentDesc<T>::func_move();
443 ctx.funcSwap = detail::ComponentDesc<T>::func_swap();
444 ctx.funcCmp = detail::ComponentDesc<T>::func_cmp();
445 ctx.funcSave = detail::ComponentDesc<T>::func_save();
446 ctx.funcLoad = detail::ComponentDesc<T>::func_load();
447 return create(entity, ctx);
448 }
449
450 GAIA_NODISCARD static ComponentCacheItem* create(Entity entity, const ComponentCacheItemCtx& ctx) {
451 GAIA_ASSERT(ctx.nameStr != nullptr && ctx.nameLen > 0 && ctx.nameLen < MaxNameLength);
452 GAIA_ASSERT(ctx.size < Component::MaxComponentSizeInBytes);
453 GAIA_ASSERT((ctx.size == 0 && ctx.alig == 0) || (ctx.alig > 0 && ctx.alig < Component::MaxAlignment));
454 GAIA_ASSERT(ctx.soa <= meta::StructToTupleMaxTypes);
455
456 auto* cci = mem::AllocHelper::alloc<ComponentCacheItem>("ComponentCacheItem");
457 (void)new (cci) ComponentCacheItem();
458 cci->entity = entity;
459 cci->comp = Component(ctx.compDescId, ctx.soa, ctx.size, ctx.alig, ctx.storageType);
460 cci->hashLookup = ctx.hashLookup.hash != 0
461 ? ctx.hashLookup
462 : ComponentLookupHash{core::calculate_hash64(ctx.nameStr, ctx.nameLen)};
463
464 if (ctx.soa > 0 && ctx.pSoaSizes != nullptr) {
465 GAIA_FOR(ctx.soa) cci->soaSizes[i] = ctx.pSoaSizes[i];
466 }
467
468 init_name(cci->name, ctx.nameStr, ctx.nameLen);
469
470 cci->func_ctor = ctx.funcCtor;
471 cci->func_move_ctor = ctx.funcMoveCtor;
472 cci->func_copy_ctor = ctx.funcCopyCtor;
473 cci->func_dtor = ctx.funcDtor;
474 cci->func_copy = ctx.funcCopy;
475 cci->func_move = ctx.funcMove;
476 cci->func_swap = ctx.funcSwap;
477 cci->func_cmp = ctx.funcCmp;
478 cci->func_save = ctx.funcSave;
479 cci->func_load = ctx.funcLoad;
480
481 return cci;
482 }
483
484 static void destroy(ComponentCacheItem* pItem) {
485 if (pItem == nullptr)
486 return;
487
488 if (pItem->name.str() != nullptr && pItem->name.owned()) {
489 mem::AllocHelper::free((void*)pItem->name.str());
490 pItem->name = {};
491 }
492
493 pItem->~ComponentCacheItem();
494 mem::AllocHelper::free("ComponentCacheItem", pItem);
495 }
496 };
497 } // namespace ecs
498} // namespace gaia
Definition chunk.h:35
Definition world.h:88
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition component_cache_item.h:52
Definition component_cache_item.h:45
Definition component_cache_item.h:25
Entity entity
Component entity.
Definition component_cache_item.h:75
ComponentLookupHash hashLookup
Complex hash used for look-ups.
Definition component_cache_item.h:79
Component comp
Unique component identifier.
Definition component_cache_item.h:77
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
Definition chunk_header.h:30
Definition id.h:34
Definition id.h:241
Runtime serializer type-erased handle. Traversal logic is shared with compile-time serialization,...
Definition ser_rt.h:79
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331