Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
data_layout_policy.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <tuple>
5#include <type_traits>
6#include <utility>
7
8#include "gaia/core/span.h"
9#include "gaia/core/utility.h"
10#include "gaia/mem/mem_alloc.h"
11#include "gaia/mem/mem_sani.h"
12#include "gaia/meta/reflection.h"
13
14namespace gaia {
15 namespace mem {
16 enum class DataLayout : uint8_t {
17 AoS = 0, //< Array Of Structures
18 SoA = 1, //< Structure Of Arrays, 4 packed items, good for SSE and similar
19 SoA8 = 2, //< Structure Of Arrays, 8 packed items, good for AVX and similar
20 SoA16 = 3, //< Structure Of Arrays, 16 packed items, good for AVX512 and similar
21
22 Count = 4
23 };
24
25#define GAIA_LAYOUT(layout_name) static constexpr auto gaia_Data_Layout = ::gaia::mem::DataLayout::layout_name
26
27 // Helper templates
28 namespace detail {
30 template <typename T>
31 constexpr uint32_t get_alignment() {
32 if constexpr (std::is_empty_v<T>)
33 // Always consider 0 for empty types
34 return 0U;
35 else {
36 // Use at least 4 (32-bit systems) or 8 (64-bit systems) bytes for alignment
37 return (uint32_t)core::get_min(sizeof(uintptr_t), alignof(T));
38 }
39 }
40
41 //----------------------------------------------------------------------
42 // Byte offset of a member of SoA-organized data
43 //----------------------------------------------------------------------
44
45 constexpr size_t get_aligned_byte_offset(uintptr_t address, size_t alig, size_t itemSize, size_t cnt) {
46 const auto padding = mem::padding(address, alig);
47 address += padding + itemSize * cnt;
48 return address;
49 }
50
51 template <typename T, size_t Alignment>
52 constexpr size_t get_aligned_byte_offset(uintptr_t address, size_t cnt) {
53 const auto padding = mem::padding<Alignment>(address);
54 address += padding + sizeof(T) * cnt;
55 return address;
56 }
57 } // namespace detail
58
59 template <DataLayout TDataLayout, typename TItem>
61 template <typename TItem>
62 struct data_layout_properties<DataLayout::AoS, TItem> {
63 constexpr static DataLayout Layout = DataLayout::AoS;
64 constexpr static size_t PackSize = 1;
65 constexpr static size_t Alignment = detail::get_alignment<TItem>();
66 };
67 template <typename TItem>
68 struct data_layout_properties<DataLayout::SoA, TItem> {
69 constexpr static DataLayout Layout = DataLayout::SoA;
70 constexpr static size_t PackSize = 4;
71 constexpr static size_t Alignment = PackSize * 4;
72 };
73 template <typename TItem>
74 struct data_layout_properties<DataLayout::SoA8, TItem> {
75 constexpr static DataLayout Layout = DataLayout::SoA8;
76 constexpr static size_t PackSize = 8;
77 constexpr static size_t Alignment = PackSize * 4;
78 };
79 template <typename TItem>
80 struct data_layout_properties<DataLayout::SoA16, TItem> {
81 constexpr static DataLayout Layout = DataLayout::SoA16;
82 constexpr static size_t PackSize = 16;
83 constexpr static size_t Alignment = PackSize * 4;
84 };
85
86 template <DataLayout TDataLayout, typename TItem>
88
89 template <DataLayout TDataLayout, typename TItem>
91 template <DataLayout TDataLayout, typename TItem>
93
94 template <DataLayout TDataLayout, typename TItem, size_t Ids>
96 template <DataLayout TDataLayout, typename TItem, size_t Ids>
98
111 template <typename ValueType>
113 using TargetCastType = std::add_pointer_t<ValueType>;
114
115 constexpr static DataLayout Layout = data_layout_properties<DataLayout::AoS, ValueType>::Layout;
116 constexpr static size_t Alignment = data_layout_properties<DataLayout::AoS, ValueType>::Alignment;
117
118 GAIA_NODISCARD static constexpr uint32_t get_min_byte_size(uintptr_t addr, size_t cnt) noexcept {
119 const auto offset = detail::get_aligned_byte_offset<ValueType, Alignment>(addr, cnt);
120 return (uint32_t)(offset - addr);
121 }
122
123 template <typename Allocator>
124 GAIA_NODISCARD static uint8_t* alloc(size_t cnt) noexcept {
125 const auto bytes = get_min_byte_size(0, cnt);
126 auto* pData = (ValueType*)mem::AllocHelper::alloc<uint8_t, Allocator>(bytes);
127 core::call_ctor_raw_n(pData, cnt);
128 return (uint8_t*)pData;
129 }
130
131 template <typename Allocator>
132 static void free(void* pData, size_t cap, size_t cnt) noexcept {
133 if (pData == nullptr)
134 return;
135 core::call_dtor_n((ValueType*)pData, cnt);
136 GAIA_MEM_SANI_DEL_BLOCK(sizeof(ValueType), pData, cap, cnt);
137 return mem::AllocHelper::free<Allocator>(pData);
138 }
139
140 template <typename Allocator>
141 static void free(void* pData, size_t cnt) noexcept {
142 if (pData == nullptr)
143 return;
144 core::call_dtor_n((ValueType*)pData, cnt);
145 return mem::AllocHelper::free<Allocator>(pData);
146 }
147
148 GAIA_NODISCARD constexpr static ValueType get_value(std::span<const ValueType> s, size_t idx) noexcept {
149 return s[idx];
150 }
151
152 GAIA_NODISCARD constexpr static const ValueType& get(std::span<const ValueType> s, size_t idx) noexcept {
153 return s[idx];
154 }
155
156 GAIA_NODISCARD constexpr static ValueType& set(std::span<ValueType> s, size_t idx) noexcept {
157 return s[idx];
158 }
159 };
160
161 template <typename ValueType>
162 struct data_view_policy<DataLayout::AoS, ValueType>: data_view_policy_aos<ValueType> {};
163
164 template <typename ValueType>
167
170
171 data_view_policy_aos_get(std::span<uint8_t> data): m_data({(const uint8_t*)data.data(), data.size()}) {}
172 data_view_policy_aos_get(std::span<const uint8_t> data): m_data({(const uint8_t*)data.data(), data.size()}) {}
173 template <typename C>
174 data_view_policy_aos_get(const C& c): m_data({(const uint8_t*)c.data(), c.size()}) {
175 static_assert(!std::is_same_v<C, data_view_policy_aos_get>);
176 }
177
178 GAIA_NODISCARD const ValueType& operator[](size_t idx) const noexcept {
179 GAIA_ASSERT(idx < m_data.size());
180 return ((const ValueType*)m_data.data())[idx];
181 }
182
183 GAIA_NODISCARD decltype(auto) data() const noexcept {
184 return (const uint8_t*)m_data.data();
185 }
186
187 GAIA_NODISCARD auto size() const noexcept {
188 return m_data.size();
189 }
190 };
191
192 template <typename ValueType>
193 struct data_view_policy_get<DataLayout::AoS, ValueType>: data_view_policy_aos_get<ValueType> {};
194
195 template <typename ValueType>
198
201
202 data_view_policy_aos_set(std::span<uint8_t> data): m_data({(uint8_t*)data.data(), data.size()}) {}
203 data_view_policy_aos_set(std::span<const uint8_t> data): m_data({(uint8_t*)data.data(), data.size()}) {}
204 template <typename C>
205 data_view_policy_aos_set(const C& c): m_data({(uint8_t*)c.data(), c.size()}) {
206 static_assert(!std::is_same_v<C, data_view_policy_aos_set>);
207 }
208
209 GAIA_NODISCARD ValueType& operator[](size_t idx) noexcept {
210 GAIA_ASSERT(idx < m_data.size());
211 return ((ValueType*)m_data.data())[idx];
212 }
213
214 GAIA_NODISCARD const ValueType& operator[](size_t idx) const noexcept {
215 GAIA_ASSERT(idx < m_data.size());
216 return ((const ValueType*)m_data.data())[idx];
217 }
218
219 GAIA_NODISCARD auto data() const noexcept {
220 return m_data.data();
221 }
222
223 GAIA_NODISCARD auto size() const noexcept {
224 return m_data.size();
225 }
226 };
227
228 template <typename ValueType>
229 struct data_view_policy_set<DataLayout::AoS, ValueType>: data_view_policy_aos_set<ValueType> {};
230
242 template <DataLayout TDataLayout, typename ValueType>
244 static_assert(std::is_copy_assignable_v<ValueType>);
245
246 using TTuple = decltype(meta::struct_to_tuple(std::declval<ValueType>()));
247 using TargetCastType = uint8_t*;
248
249 constexpr static DataLayout Layout = data_layout_properties<TDataLayout, ValueType>::Layout;
250 constexpr static size_t Alignment = data_layout_properties<TDataLayout, ValueType>::Alignment;
251 constexpr static size_t TTupleItems = std::tuple_size<TTuple>::value;
252 static_assert(Alignment > 0U, "SoA data can't be zero-aligned");
253
254 template <size_t Item>
255 using value_type = typename std::tuple_element<Item, TTuple>::type;
256 template <size_t Item>
257 using const_value_type = typename std::add_const<value_type<Item>>::type;
258
259 GAIA_NODISCARD constexpr static uint32_t get_min_byte_size(uintptr_t addr, size_t cnt) noexcept {
260 const auto offset = get_aligned_byte_offset<TTupleItems>(addr, cnt);
261 return (uint32_t)(offset - addr);
262 }
263
264 template <typename Allocator>
265 GAIA_NODISCARD static uint8_t* alloc(size_t cnt) noexcept {
266 const auto bytes = get_min_byte_size(0, cnt);
267 return mem::AllocHelper::alloc_alig<uint8_t, Allocator>(Alignment, bytes);
268 }
269
270 template <typename Allocator>
271 static void free(void* pData, size_t cap, size_t cnt) noexcept {
272 if (pData == nullptr)
273 return;
274
275 mem_del_block(pData, cap, cnt);
276 return mem::AllocHelper::free_alig<Allocator>(pData);
277 }
278
279 static void mem_add_block(void* pData, size_t cap, size_t count) {
280 meta::each_member(ValueType{}, [&](auto&&... item) {
281 auto address = mem::align<Alignment>((uintptr_t)pData);
282 ((
283 //
284 GAIA_MEM_SANI_ADD_BLOCK(sizeof(item), (void*)address, cap, count),
285 // Skip towards the next element and make sure the address is aligned properly
286 address = mem::align<Alignment>(address + (sizeof(item) * cap))),
287 ...);
288 });
289 }
290
291 static void mem_del_block(void* pData, size_t cap, size_t count) {
292 meta::each_member(ValueType{}, [&](auto&&... item) {
293 auto address = mem::align<Alignment>((uintptr_t)pData);
294 ((
295 //
296 GAIA_MEM_SANI_DEL_BLOCK(sizeof(item), (void*)address, cap, count),
297 // Skip towards the next element and make sure the address is aligned properly
298 address = mem::align<Alignment>(address + (sizeof(item) * cap))),
299 ...);
300 });
301 }
302
303 static void mem_push_block(void* pData, size_t cap, size_t count, size_t n) {
304 meta::each_member(ValueType{}, [&](auto&&... item) {
305 auto address = mem::align<Alignment>((uintptr_t)pData);
306 ((
307 //
308 GAIA_MEM_SANI_PUSH_N(sizeof(item), (void*)address, cap, count, n),
309 // Skip towards the next element and make sure the address is aligned properly
310 address = mem::align<Alignment>(address + (sizeof(item) * cap))),
311 ...);
312 });
313 }
314
315 static void mem_pop_block(void* pData, size_t cap, size_t count, size_t n) {
316 meta::each_member(ValueType{}, [&](auto&&... item) {
317 auto address = mem::align<Alignment>((uintptr_t)pData);
318 ((
319 //
320 GAIA_MEM_SANI_POP_N(sizeof(item), (void*)address, cap, count, n),
321 // Skip towards the next element and make sure the address is aligned properly
322 address = mem::align<Alignment>(address + (sizeof(item) * cap))),
323 ...);
324 });
325 }
326
327 GAIA_NODISCARD constexpr static ValueType get(std::span<const uint8_t> s, size_t idx) noexcept {
328 return get_inter(meta::struct_to_tuple(ValueType{}), s, idx, std::make_index_sequence<TTupleItems>());
329 }
330
331 template <size_t Item>
332 GAIA_NODISCARD constexpr static auto get(std::span<const uint8_t> s, size_t idx = 0) noexcept {
333 const auto offset = get_aligned_byte_offset<Item>((uintptr_t)s.data(), s.size());
334 const auto& ref = get_ref<const value_type<Item>>(reinterpret_cast<const uint8_t*>(offset), idx);
335 return std::span{&ref, s.size() - idx};
336 }
337
338 class accessor {
339 std::span<uint8_t> m_data;
340 size_t m_idx;
341
342 public:
343 constexpr accessor(std::span<uint8_t> data, size_t idx): m_data(data), m_idx(idx) {}
344
345 constexpr void operator=(const ValueType& val) noexcept {
346 set_inter(meta::struct_to_tuple(val), m_data, m_idx, std::make_index_sequence<TTupleItems>());
347 }
348
349 constexpr void operator=(ValueType&& val) noexcept {
350 set_inter(meta::struct_to_tuple(GAIA_MOV(val)), m_data, m_idx, std::make_index_sequence<TTupleItems>());
351 }
352
353 GAIA_NODISCARD constexpr operator ValueType() const noexcept {
354 return get_inter(
355 meta::struct_to_tuple(ValueType{}), {(const uint8_t*)m_data.data(), m_data.size()}, m_idx,
356 std::make_index_sequence<TTupleItems>());
357 }
358 };
359
360 GAIA_NODISCARD constexpr static auto set(std::span<uint8_t> s, size_t idx) noexcept {
361 return accessor(s, idx);
362 }
363
364 template <size_t Item>
365 GAIA_NODISCARD constexpr static auto set(std::span<uint8_t> s, size_t idx = 0) noexcept {
366 const auto offset = get_aligned_byte_offset<Item>((uintptr_t)s.data(), s.size());
367 auto& ref = get_ref<value_type<Item>>((const uint8_t*)offset, idx);
368 return std::span{&ref, s.size() - idx};
369 }
370
371 private:
372 template <size_t... Ids>
373 GAIA_NODISCARD constexpr static size_t
374 get_aligned_byte_offset_seq(uintptr_t address, size_t cnt, std::index_sequence<Ids...> /*no_name*/) {
375 // Align the address for the first type
376 address = mem::align<Alignment>(address);
377 // Offset and align the rest
378 ((address = mem::align<Alignment>(address + (sizeof(value_type<Ids>) * cnt))), ...);
379 return address;
380 }
381
382 template <uint32_t N>
383 GAIA_NODISCARD constexpr static size_t get_aligned_byte_offset(uintptr_t address, size_t cnt) {
384 return get_aligned_byte_offset_seq(address, cnt, std::make_index_sequence<N>());
385 }
386
387 template <typename TMemberType>
388 GAIA_NODISCARD constexpr static TMemberType& get_ref(const uint8_t* data, size_t idx) noexcept {
389 // Write the value directly to the memory address.
390 // Usage of unaligned_ref is not necessary because the memory is aligned.
391 auto* pCastData = (TMemberType*)data;
392 return pCastData[idx];
393 }
394
395 template <typename Tup, size_t... Ids>
396 GAIA_NODISCARD constexpr static ValueType
397 get_inter(Tup&& t, std::span<const uint8_t> s, size_t idx, std::index_sequence<Ids...> /*no_name*/) noexcept {
398 auto address = mem::align<Alignment>((uintptr_t)s.data());
399 ((
400 // Put the value at the address into our tuple. Data is aligned so we can read directly.
401 std::get<Ids>(t) = get_ref<value_type<Ids>>((const uint8_t*)address, idx),
402 // Skip towards the next element and make sure the address is aligned properly
403 address = mem::align<Alignment>(address + (sizeof(value_type<Ids>) * s.size()))),
404 ...);
405 return meta::tuple_to_struct<ValueType, TTuple>(GAIA_FWD(t));
406 }
407
408 template <typename Tup, size_t... Ids>
409 constexpr static void
410 set_inter(Tup&& t, std::span<uint8_t> s, size_t idx, std::index_sequence<Ids...> /*no_name*/) noexcept {
411 auto address = mem::align<Alignment>((uintptr_t)s.data());
412 ((
413 // Set the tuple value. Data is aligned so we can write directly.
414 get_ref<value_type<Ids>>((uint8_t*)address, idx) = std::get<Ids>(t),
415 // Skip towards the next element and make sure the address is aligned properly
416 address = mem::align<Alignment>(address + (sizeof(value_type<Ids>) * s.size()))),
417 ...);
418 }
419 };
420
421 template <typename ValueType>
422 struct data_view_policy<DataLayout::SoA, ValueType>: //
423 data_view_policy_soa<DataLayout::SoA, ValueType> {};
424 template <typename ValueType>
425 struct data_view_policy<DataLayout::SoA8, ValueType>: //
426 data_view_policy_soa<DataLayout::SoA8, ValueType> {};
427 template <typename ValueType>
428 struct data_view_policy<DataLayout::SoA16, ValueType>: //
429 data_view_policy_soa<DataLayout::SoA16, ValueType> {};
430
431 template <DataLayout TDataLayout, typename ValueType>
433 static_assert(std::is_copy_assignable_v<ValueType>);
434
436
437 template <size_t Item>
439 using const_value_type = typename view_policy::template const_value_type<Item>;
440 };
441
444
445 data_view_policy_soa_get(std::span<uint8_t> data): m_data({(const uint8_t*)data.data(), data.size()}) {}
446 data_view_policy_soa_get(std::span<const uint8_t> data): m_data({(const uint8_t*)data.data(), data.size()}) {}
447 template <typename C>
448 data_view_policy_soa_get(const C& c): m_data({(const uint8_t*)c.data(), c.size()}) {
449 static_assert(!std::is_same_v<C, data_view_policy_soa_get>);
450 }
451
452 GAIA_NODISCARD constexpr decltype(auto) operator[](size_t idx) const noexcept {
453 return view_policy::get(m_data, idx);
454 }
455
456 template <size_t Item>
457 GAIA_NODISCARD constexpr auto get() const noexcept {
458 auto s = view_policy::template get<Item>(m_data);
459 return std::span(s.data(), s.size());
460 }
461
462 GAIA_NODISCARD decltype(auto) data() const noexcept {
463 return (const uint8_t*)m_data.data();
464 }
465
466 GAIA_NODISCARD auto size() const noexcept {
467 return m_data.size();
468 }
469 };
470
471 template <typename ValueType>
472 struct data_view_policy_get<DataLayout::SoA, ValueType>: //
473 data_view_policy_soa_get<DataLayout::SoA, ValueType> {};
474 template <typename ValueType>
475 struct data_view_policy_get<DataLayout::SoA8, ValueType>: //
476 data_view_policy_soa_get<DataLayout::SoA8, ValueType> {};
477 template <typename ValueType>
478 struct data_view_policy_get<DataLayout::SoA16, ValueType>: //
479 data_view_policy_soa_get<DataLayout::SoA16, ValueType> {};
480
481 template <DataLayout TDataLayout, typename ValueType>
483 static_assert(std::is_copy_assignable_v<ValueType>);
484
486
487 template <size_t Item>
489 using value_type = typename view_policy::template value_type<Item>;
490 using const_value_type = typename view_policy::template const_value_type<Item>;
491 };
492
495
496 data_view_policy_soa_set(std::span<uint8_t> data): m_data({(uint8_t*)data.data(), data.size()}) {}
497 data_view_policy_soa_set(std::span<const uint8_t> data): m_data({(uint8_t*)data.data(), data.size()}) {}
498 template <typename C>
499 data_view_policy_soa_set(const C& c): m_data({(uint8_t*)c.data(), c.size()}) {
500 static_assert(!std::is_same_v<C, data_view_policy_soa_set>);
501 }
502
503 struct accessor {
504 std::span<uint8_t> m_data;
505 size_t m_idx;
506
507 constexpr void operator=(const ValueType& val) noexcept {
508 view_policy::set(m_data, m_idx) = val;
509 }
510 constexpr void operator=(ValueType&& val) noexcept {
511 view_policy::set(m_data, m_idx) = GAIA_FWD(val);
512 }
513 };
514
515 GAIA_NODISCARD constexpr decltype(auto) operator[](size_t idx) const noexcept {
516 return view_policy::get({(const uint8_t*)m_data.data(), m_data.size()}, idx);
517 }
518 GAIA_NODISCARD constexpr auto operator[](size_t idx) noexcept {
519 return accessor{m_data, idx};
520 }
521
522 template <size_t Item>
523 GAIA_NODISCARD constexpr auto get() const noexcept {
524 auto s = view_policy::template get<Item>(m_data);
525 return std::span(s.data(), s.size());
526 }
527
528 template <size_t Item>
529 GAIA_NODISCARD constexpr auto set() noexcept {
530 auto s = view_policy::template set<Item>(m_data);
531 return std::span(s.data(), s.size());
532 }
533
534 GAIA_NODISCARD auto data() const noexcept {
535 return m_data.data();
536 }
537
538 GAIA_NODISCARD auto size() const noexcept {
539 return m_data.size();
540 }
541 };
542
543 template <typename ValueType>
544 struct data_view_policy_set<DataLayout::SoA, ValueType>: //
545 data_view_policy_soa_set<DataLayout::SoA, ValueType> {};
546 template <typename ValueType>
547 struct data_view_policy_set<DataLayout::SoA8, ValueType>: //
548 data_view_policy_soa_set<DataLayout::SoA8, ValueType> {};
549 template <typename ValueType>
550 struct data_view_policy_set<DataLayout::SoA16, ValueType>: //
551 data_view_policy_soa_set<DataLayout::SoA16, ValueType> {};
552
553 //----------------------------------------------------------------------
554 // Helpers
555 //----------------------------------------------------------------------
556
557 namespace detail {
558 template <typename, typename = void>
560 static constexpr DataLayout data_layout_type = DataLayout::AoS;
561 };
562 template <typename T>
563 struct auto_view_policy_inter<T, std::void_t<decltype(T::gaia_Data_Layout)>> {
564 static constexpr DataLayout data_layout_type = T::gaia_Data_Layout;
565 };
566
567 template <typename, typename = void>
568 struct is_soa_layout: std::false_type {};
569 template <typename T>
570 struct is_soa_layout<T, std::void_t<decltype(T::gaia_Data_Layout)>>:
571 std::bool_constant<!std::is_empty_v<T> && (T::gaia_Data_Layout != DataLayout::AoS)> {};
572 } // namespace detail
573
574 template <typename T>
576 template <typename T>
578 template <typename T>
580
581 template <typename T>
582 inline constexpr bool is_soa_layout_v = detail::is_soa_layout<T>::value;
583
584 } // namespace mem
585} // namespace gaia
Definition span_impl.h:99
Definition data_layout_policy.h:338
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition data_layout_policy.h:60
Definition data_layout_policy.h:165
std::span< const uint8_t > m_data
Raw data pointed to by the view policy.
Definition data_layout_policy.h:169
Definition data_layout_policy.h:196
std::span< uint8_t > m_data
Raw data pointed to by the view policy.
Definition data_layout_policy.h:200
View policy for accessing and storing data in the AoS way. Good for random access and when accessing ...
Definition data_layout_policy.h:112
Definition data_layout_policy.h:95
Definition data_layout_policy.h:90
Definition data_layout_policy.h:97
Definition data_layout_policy.h:92
Definition data_layout_policy.h:432
std::span< const uint8_t > m_data
Raw data pointed to by the view policy.
Definition data_layout_policy.h:443
Definition data_layout_policy.h:503
Definition data_layout_policy.h:482
std::span< uint8_t > m_data
Raw data pointed to by the view policy.
Definition data_layout_policy.h:494
View policy for accessing and storing data in the SoA way. Good for SIMD processing.
Definition data_layout_policy.h:243
Definition data_layout_policy.h:87
Definition data_layout_policy.h:559
Definition data_layout_policy.h:568