Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
mem_alloc.h
1#pragma once
2#include "gaia/config/config.h"
3#include "gaia/config/profiler.h"
4
5#include <cstdint>
6#include <cstring>
7#include <stdlib.h>
8#include <type_traits>
9#include <utility>
10
11#if GAIA_ALLOC_ARENA_LOCK || GAIA_ASSERT_ENABLED
12 #include <atomic>
13#endif
14#if !GAIA_ALLOC_ARENA_LOCK && GAIA_ASSERT_ENABLED
15 #include <thread>
16#endif
17
18#if GAIA_PLATFORM_WINDOWS
19 #define GAIA_MEM_ALLC(size) ::malloc(size)
20 #define GAIA_MEM_FREE(ptr) ::free(ptr)
21
22 // Clang with MSVC codegen needs some remapping
23 #if !defined(aligned_alloc)
24 #define GAIA_MEM_ALLC_A(size, alig) ::_aligned_malloc(size, alig)
25 #define GAIA_MEM_FREE_A(ptr) ::_aligned_free(ptr)
26 #else
27 #define GAIA_MEM_ALLC_A(size, alig) ::aligned_alloc(alig, size)
28 #define GAIA_MEM_FREE_A(ptr) ::aligned_free(ptr)
29 #endif
30#else
31 #define GAIA_MEM_ALLC(size) ::malloc(size)
32 #define GAIA_MEM_ALLC_A(size, alig) ::aligned_alloc(alig, size)
33 #define GAIA_MEM_FREE(ptr) ::free(ptr)
34 #define GAIA_MEM_FREE_A(ptr) ::free(ptr)
35#endif
36
37namespace gaia {
38 namespace mem {
40 namespace detail {
53 class ArenaLock final {
54#if GAIA_ALLOC_ARENA_LOCK
55 inline static std::atomic_int32_t s_lock{0};
56#elif GAIA_ASSERT_ENABLED
57 inline static std::atomic_flag s_entered = ATOMIC_FLAG_INIT;
58 inline static std::atomic<std::thread::id> s_owner{};
59 #if GAIA_ECS_TEST_HOOKS
60 inline static std::atomic_uint32_t s_testViolations = 0;
61 #endif
62#endif
63
64 public:
65 ArenaLock() noexcept {
66#if GAIA_ALLOC_ARENA_LOCK
67 while (s_lock.exchange(1, std::memory_order_acquire) != 0) {
68 while (s_lock.load(std::memory_order_relaxed) != 0)
69 #if defined(__aarch64__)
70 __asm__ volatile("yield");
71 #else
72 ;
73 #endif
74 }
75#elif GAIA_ASSERT_ENABLED
76 const auto me = std::this_thread::get_id();
77 if (s_entered.test_and_set(std::memory_order_acquire)) {
78 const auto other = s_owner.load(std::memory_order_relaxed);
79 #if GAIA_ECS_TEST_HOOKS
80 if (other != me)
81 ++s_testViolations;
82 #else
83 // Same-thread re-entry is tolerated; a different thread means two Worlds
84 // are allocating or freeing concurrently through the same process-wide arena.
85 GAIA_ASSERT(
86 other == me && "Allocation arena entered concurrently from two "
87 "threads. Define GAIA_ALLOC_ARENA_LOCK=1 to serialize the arenas, or "
88 "keep one mutating World per process.");
89 #endif
90 } else {
91 s_owner.store(me, std::memory_order_relaxed);
92 }
93#endif
94 }
95
96 ~ArenaLock() noexcept {
97#if GAIA_ALLOC_ARENA_LOCK
98 s_lock.store(0, std::memory_order_release);
99#elif GAIA_ASSERT_ENABLED
100 s_entered.clear(std::memory_order_release);
101#endif
102 }
103
104 ArenaLock(const ArenaLock&) = delete;
105 ArenaLock& operator=(const ArenaLock&) = delete;
106
110 GAIA_NODISCARD static uint32_t test_violations() noexcept {
111#if !GAIA_ALLOC_ARENA_LOCK && GAIA_ASSERT_ENABLED && GAIA_ECS_TEST_HOOKS
112 return s_testViolations.load(std::memory_order_relaxed);
113#else
114 return 0;
115#endif
116 }
117 };
118 } // namespace detail
120
126 GAIA_NODISCARD static void* alloc(size_t size) {
127 GAIA_ASSERT(size > 0);
128
129 void* ptr = GAIA_MEM_ALLC(size);
130 GAIA_ASSERT(ptr != nullptr);
131 GAIA_PROF_ALLOC(ptr, size);
132 return ptr;
133 }
134
139 GAIA_NODISCARD static void* alloc([[maybe_unused]] const char* name, size_t size) {
140 GAIA_ASSERT(size > 0);
141
142 void* ptr = GAIA_MEM_ALLC(size);
143 GAIA_ASSERT(ptr != nullptr);
144 GAIA_PROF_ALLOC2(ptr, size, name);
145 return ptr;
146 }
147
152 GAIA_NODISCARD static void* alloc_alig(size_t size, size_t alig) {
153 GAIA_ASSERT(size > 0);
154 GAIA_ASSERT(alig > 0);
155
156 // Make sure size is a multiple of the alignment
157 size = (size + alig - 1) & ~(alig - 1);
158 void* ptr = GAIA_MEM_ALLC_A(size, alig);
159 GAIA_ASSERT(ptr != nullptr);
160 GAIA_PROF_ALLOC(ptr, size);
161 return ptr;
162 }
163
169 GAIA_NODISCARD static void* alloc_alig([[maybe_unused]] const char* name, size_t size, size_t alig) {
170 GAIA_ASSERT(size > 0);
171 GAIA_ASSERT(alig > 0);
172
173 // Make sure size is a multiple of the alignment
174 size = (size + alig - 1) & ~(alig - 1);
175 void* ptr = GAIA_MEM_ALLC_A(size, alig);
176 GAIA_ASSERT(ptr != nullptr);
177 GAIA_PROF_ALLOC2(ptr, size, name);
178 return ptr;
179 }
180
183 static void free(void* ptr) {
184 GAIA_ASSERT(ptr != nullptr);
185
186 GAIA_MEM_FREE(ptr);
187 GAIA_PROF_FREE(ptr);
188 }
189
193 static void free([[maybe_unused]] const char* name, void* ptr) {
194 GAIA_ASSERT(ptr != nullptr);
195
196 GAIA_MEM_FREE(ptr);
197 GAIA_PROF_FREE2(ptr, name);
198 }
199
202 static void free_alig(void* ptr) {
203 GAIA_ASSERT(ptr != nullptr);
204
205 GAIA_MEM_FREE_A(ptr);
206 GAIA_PROF_FREE(ptr);
207 }
208
212 static void free_alig([[maybe_unused]] const char* name, void* ptr) {
213 GAIA_ASSERT(ptr != nullptr);
214
215 GAIA_MEM_FREE_A(ptr);
216 GAIA_PROF_FREE2(ptr, name);
217 }
218 };
219
225 static DefaultAllocator s_allocator;
226 return s_allocator;
227 }
228 };
229
231 struct AllocHelper {
237 template <typename T, typename Adaptor = DefaultAllocatorAdaptor>
238 GAIA_NODISCARD static T* alloc(uint32_t cnt = 1) {
239 return (T*)Adaptor::get().alloc(sizeof(T) * cnt);
240 }
247 template <typename T, typename Adaptor = DefaultAllocatorAdaptor>
248 GAIA_NODISCARD static T* alloc(const char* name, uint32_t cnt = 1) {
249 return (T*)Adaptor::get().alloc(name, sizeof(T) * cnt);
250 }
257 template <typename T, typename Adaptor = DefaultAllocatorAdaptor>
258 GAIA_NODISCARD static T* alloc_alig(size_t alig, uint32_t cnt = 1) {
259 return (T*)Adaptor::get().alloc_alig(sizeof(T) * cnt, alig);
260 }
268 template <typename T, typename Adaptor = DefaultAllocatorAdaptor>
269 GAIA_NODISCARD static T* alloc_alig(const char* name, size_t alig, uint32_t cnt = 1) {
270 return (T*)Adaptor::get().alloc_alig(name, sizeof(T) * cnt, alig);
271 }
275 template <typename Adaptor = DefaultAllocatorAdaptor>
276 static void free(void* ptr) {
277 Adaptor::get().free(ptr);
278 }
283 template <typename Adaptor = DefaultAllocatorAdaptor>
284 static void free(const char* name, void* ptr) {
285 Adaptor::get().free(name, ptr);
286 }
290 template <typename Adaptor = DefaultAllocatorAdaptor>
291 static void free_alig(void* ptr) {
292 Adaptor::get().free_alig(ptr);
293 }
298 template <typename Adaptor = DefaultAllocatorAdaptor>
299 static void free_alig(const char* name, void* ptr) {
300 Adaptor::get().free_alig(name, ptr);
301 }
302 };
303
307 GAIA_NODISCARD inline void* mem_alloc(size_t size) {
308 return DefaultAllocatorAdaptor::get().alloc(size);
309 }
310
315 GAIA_NODISCARD inline void* mem_alloc(const char* name, size_t size) {
316 return DefaultAllocatorAdaptor::get().alloc(name, size);
317 }
318
324 GAIA_NODISCARD inline void* mem_alloc_alig(size_t size, size_t alig) {
325 return DefaultAllocatorAdaptor::get().alloc_alig(size, alig);
326 }
327
334 GAIA_NODISCARD inline void* mem_alloc_alig(const char* name, size_t size, size_t alig) {
335 return DefaultAllocatorAdaptor::get().alloc_alig(name, size, alig);
336 }
337
340 inline void mem_free(void* ptr) {
342 }
343
347 inline void mem_free(const char* name, void* ptr) {
349 }
350
353 inline void mem_free_alig(void* ptr) {
355 }
356
360 inline void mem_free_alig(const char* name, void* ptr) {
362 }
363
368 template <typename T, typename V>
369 GAIA_NODISCARD constexpr T align(T num, V alignment) {
370 return alignment == 0 ? num : ((num + (alignment - 1)) / alignment) * alignment;
371 }
372
378 template <size_t alignment, typename T>
379 GAIA_NODISCARD constexpr T align(T num) {
380 return ((num + (alignment - 1)) & ~(alignment - 1));
381 }
382
387 template <typename T, typename V>
388 GAIA_NODISCARD constexpr uint32_t padding(T num, V alignment) {
389 return (uint32_t)(align(num, alignment) - num);
390 }
391
396 template <size_t alignment, typename T>
397 GAIA_NODISCARD constexpr uint32_t padding(T num) {
398 return (uint32_t)(align<alignment>(num) - num);
399 }
400
406 template <typename Dst, typename Src>
407 GAIA_NODISCARD Dst bit_cast(const Src& src) {
408 static_assert(sizeof(Dst) == sizeof(Src));
409 static_assert(std::is_trivially_copyable_v<Src>);
410 static_assert(std::is_trivially_copyable_v<Dst>);
411
412 // int i = {};
413 // float f = *(*float)&i; // undefined behavior
414 // memcpy(&f, &i, sizeof(float)); // okay
415 Dst dst;
416 memmove((void*)&dst, (const void*)&src, sizeof(Dst));
417 return dst;
418 }
419
422 template <typename T>
424 void* m_p;
425
426 public:
429 unaligned_ref(void* p): m_p(p) {}
430
434 unaligned_ref& operator=(const T& value) {
435 memmove(m_p, (const void*)&value, sizeof(T));
436 return *this;
437 }
438
441 GAIA_NODISCARD operator T() const {
442 T tmp;
443 memmove((void*)&tmp, (const void*)m_p, sizeof(T));
444 return tmp;
445 }
446 };
447 } // namespace mem
448} // namespace gaia
Pointer wrapper for writing memory in defined way (not causing undefined behavior)
Definition mem_alloc.h:423
unaligned_ref & operator=(const T &value)
Stores a value using byte-wise copying.
Definition mem_alloc.h:434
unaligned_ref(void *p)
Creates a reference to potentially unaligned storage.
Definition mem_alloc.h:429
Typed allocation facade for allocator adaptors.
Definition mem_alloc.h:231
static GAIA_NODISCARD T * alloc_alig(const char *name, size_t alig, uint32_t cnt=1)
Allocates named aligned storage for objects without constructing them.
Definition mem_alloc.h:269
static GAIA_NODISCARD T * alloc_alig(size_t alig, uint32_t cnt=1)
Allocates aligned storage for objects without constructing them.
Definition mem_alloc.h:258
static void free_alig(void *ptr)
Releases aligned storage allocated through an adaptor.
Definition mem_alloc.h:291
static GAIA_NODISCARD T * alloc(const char *name, uint32_t cnt=1)
Allocates named storage for objects without constructing them.
Definition mem_alloc.h:248
static GAIA_NODISCARD T * alloc(uint32_t cnt=1)
Allocates storage for objects without constructing them.
Definition mem_alloc.h:238
static void free_alig(const char *name, void *ptr)
Releases named aligned storage allocated through an adaptor.
Definition mem_alloc.h:299
static void free(void *ptr)
Releases storage allocated through an adaptor.
Definition mem_alloc.h:276
static void free(const char *name, void *ptr)
Releases named storage allocated through an adaptor.
Definition mem_alloc.h:284
Provides the shared default allocator instance expected by AllocHelper.
Definition mem_alloc.h:221
static DefaultAllocator & get()
Returns the process-local default allocator.
Definition mem_alloc.h:224
Stateless allocator backed by the platform heap.
Definition mem_alloc.h:122
static GAIA_NODISCARD void * alloc_alig(size_t size, size_t alig)
Allocates an aligned memory region.
Definition mem_alloc.h:152
static void free_alig(void *ptr)
Releases an aligned memory region.
Definition mem_alloc.h:202
static void free(void *ptr)
Releases an unaligned memory region.
Definition mem_alloc.h:183
static GAIA_NODISCARD void * alloc(const char *name, size_t size)
Allocates a named unaligned memory region.
Definition mem_alloc.h:139
static GAIA_NODISCARD void * alloc(size_t size)
Allocates an unaligned memory region.
Definition mem_alloc.h:126
static void free_alig(const char *name, void *ptr)
Releases a named aligned memory region.
Definition mem_alloc.h:212
static void free(const char *name, void *ptr)
Releases a named unaligned memory region.
Definition mem_alloc.h:193
static GAIA_NODISCARD void * alloc_alig(const char *name, size_t size, size_t alig)
Allocates a named aligned memory region.
Definition mem_alloc.h:169