1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4
5#include <limits>
6#include <new> /* For 'placement new' */
7
8#include "Prerequisites/BsPlatformDefines.h"
9#include "Prerequisites/BsTypes.h"
10#include "Prerequisites/BsStdHeaders.h"
11#include "Threading/BsThreading.h"
12
13namespace bs
14{
15 /** @addtogroup Internal-Utility
16 * @{
17 */
18
19 /** @addtogroup Memory-Internal
20 * @{
21 */
22
23 /**
24 * Frame allocator. Performs very fast allocations but can only free all of its memory at once. Perfect for allocations
25 * that last just a single frame.
26 *
27 * @note Not thread safe with an exception. alloc() and clear() methods need to be called from the same thread.
28 * dealloc() is thread safe and can be called from any thread.
29 */
30 class BS_UTILITY_EXPORT FrameAlloc
31 {
32 private:
33 /** A single block of memory within a frame allocator. */
34 class MemBlock
35 {
36 public:
37 MemBlock(UINT32 size) :mSize(size) { }
38
39 ~MemBlock() = default;
40
41 /** Allocates a piece of memory within the block. Caller must ensure the block has enough empty space. */
42 UINT8* alloc(UINT32 amount);
43
44 /** Releases all allocations within a block but doesn't actually free the memory. */
45 void clear();
46
47 UINT8* mData = nullptr;
48 UINT32 mFreePtr = 0;
49 UINT32 mSize;
50 };
51
52 public:
53 FrameAlloc(UINT32 blockSize = 1024 * 1024);
54 ~FrameAlloc();
55
56 /**
57 * Allocates a new block of memory of the specified size.
58 *
59 * @param[in] amount Amount of memory to allocate, in bytes.
60 *
61 * @note Not thread safe.
62 */
63 UINT8* alloc(UINT32 amount);
64
65 /**
66 * Allocates a new block of memory of the specified size aligned to the specified boundary. If the aligment is less
67 * or equal to 16 it is more efficient to use the allocAligned16() alternative of this method.
68 *
69 * @param[in] amount Amount of memory to allocate, in bytes.
70 * @param[in] alignment Alignment of the allocated memory. Must be power of two.
71 *
72 * @note Not thread safe.
73 */
74 UINT8* allocAligned(UINT32 amount, UINT32 alignment);
75
76 /**
77 * Allocates and constructs a new object.
78 *
79 * @note Not thread safe.
80 */
81 template<class T, class... Args>
82 T* construct(Args &&...args)
83 {
84 return new ((T*)alloc(sizeof(T))) T(std::forward<Args>(args)...);
85 }
86
87 /**
88 * Destructs and deallocates an object.
89 *
90 * @note Not thread safe.
91 */
92 template<class T>
93 void destruct(T* data)
94 {
95 data->~T();
96 free((UINT8*)data);
97 }
98
99 /**
100 * Deallocates a previously allocated block of memory.
101 *
102 * @note
103 * No deallocation is actually done here. This method is only used for debug purposes so it is easier to track
104 * down memory leaks and corruption.
105 * @note
106 * Thread safe.
107 */
108 void free(UINT8* data);
109
110 /**
111 * Deallocates and destructs a previously allocated object.
112 *
113 * @note
114 * No deallocation is actually done here. This method is only used to call the destructor and for debug purposes
115 * so it is easier to track down memory leaks and corruption.
116 * @note
117 * Thread safe.
118 */
119 template<class T>
120 void free(T* obj)
121 {
122 if (obj != nullptr)
123 obj->~T();
124
125 free((UINT8*)obj);
126 }
127
128 /** Starts a new frame. Next call to clear() will only clear memory allocated past this point. */
129 void markFrame();
130
131 /**
132 * Deallocates all allocated memory since the last call to markFrame() (or all the memory if there was no call
133 * to markFrame()).
134 *
135 * @note Not thread safe.
136 */
137 void clear();
138
139 /**
140 * Changes the frame allocator owner thread. After the owner thread has changed only allocations from that thread
141 * can be made.
142 */
143 void setOwnerThread(ThreadId thread);
144
145 private:
146 UINT32 mBlockSize;
147 Vector<MemBlock*> mBlocks;
148 MemBlock* mFreeBlock;
149 UINT32 mNextBlockIdx;
150 std::atomic<UINT32> mTotalAllocBytes;
151 void* mLastFrame;
152
153#if BS_DEBUG_MODE
154 ThreadId mOwnerThread;
155#endif
156
157 /**
158 * Allocates a dynamic block of memory of the wanted size. The exact allocation size might be slightly higher in
159 * order to store block meta data.
160 */
161 MemBlock* allocBlock(UINT32 wantedSize);
162
163 /** Frees a memory block. */
164 void deallocBlock(MemBlock* block);
165 };
166
167 /**
168 * Version of FrameAlloc that allows blocks size to be provided through the template argument instead of the
169 * constructor. */
170 template<int BlockSize>
171 class TFrameAlloc : public FrameAlloc
172 {
173 public:
174 TFrameAlloc()
175 :FrameAlloc(BlockSize)
176 { }
177 };
178
179 /** Allocator for the standard library that internally uses a frame allocator. */
180 template <class T>
181 class StdFrameAlloc
182 {
183 public:
184 typedef T value_type;
185 typedef value_type* pointer;
186 typedef const value_type* const_pointer;
187 typedef value_type& reference;
188 typedef const value_type& const_reference;
189 typedef std::size_t size_type;
190 typedef std::ptrdiff_t difference_type;
191
192 StdFrameAlloc() noexcept = default;
193
194 StdFrameAlloc(FrameAlloc* alloc) noexcept
195 :mFrameAlloc(alloc)
196 { }
197
198 template<class U> StdFrameAlloc(const StdFrameAlloc<U>& alloc) noexcept
199 :mFrameAlloc(alloc.mFrameAlloc)
200 { }
201
202 template<class U> bool operator==(const StdFrameAlloc<U>&) const noexcept { return true; }
203 template<class U> bool operator!=(const StdFrameAlloc<U>&) const noexcept { return false; }
204 template<class U> class rebind { public: typedef StdFrameAlloc<U> other; };
205
206 /** Allocate but don't initialize number elements of type T.*/
207 T* allocate(const size_t num) const
208 {
209 if (num == 0)
210 return nullptr;
211
212 if (num > static_cast<size_t>(-1) / sizeof(T))
213 return nullptr; // Error
214
215 void* const pv = mFrameAlloc->alloc((UINT32)(num * sizeof(T)));
216 if (!pv)
217 return nullptr; // Error
218
219 return static_cast<T*>(pv);
220 }
221
222 /** Deallocate storage p of deleted elements. */
223 void deallocate(T* p, size_t num) const noexcept
224 {
225 mFrameAlloc->free((UINT8*)p);
226 }
227
228 FrameAlloc* mFrameAlloc = nullptr;
229
230 size_t max_size() const { return std::numeric_limits<size_type>::max() / sizeof(T); }
231 void construct(pointer p, const_reference t) { new (p) T(t); }
232 void destroy(pointer p) { p->~T(); }
233 template<class U, class... Args>
234 void construct(U* p, Args&&... args) { new(p) U(std::forward<Args>(args)...); }
235 };
236
237 /** Return that all specializations of this allocator are interchangeable. */
238 template <class T1, class T2>
239 bool operator== (const StdFrameAlloc<T1>&,
240 const StdFrameAlloc<T2>&) throw() {
241 return true;
242 }
243
244 /** Return that all specializations of this allocator are interchangeable. */
245 template <class T1, class T2>
246 bool operator!= (const StdFrameAlloc<T1>&,
247 const StdFrameAlloc<T2>&) throw() {
248 return false;
249 }
250
251 /** @} */
252 /** @} */
253
254 /** @addtogroup Memory
255 * @{
256 */
257
258 /**
259 * Returns a global, application wide FrameAlloc. Each thread gets its own frame allocator.
260 *
261 * @note Thread safe.
262 */
263 BS_UTILITY_EXPORT FrameAlloc& gFrameAlloc();
264
265 /**
266 * Allocates some memory using the global frame allocator.
267 *
268 * @param[in] numBytes Number of bytes to allocate.
269 */
270 BS_UTILITY_EXPORT UINT8* bs_frame_alloc(UINT32 numBytes);
271
272 /**
273 * Allocates the specified number of bytes aligned to the provided boundary, using the global frame allocator. Boundary
274 * is in bytes and must be a power of two.
275 */
276 BS_UTILITY_EXPORT UINT8* bs_frame_alloc_aligned(UINT32 count, UINT32 align);
277
278 /**
279 * Deallocates memory allocated with the global frame allocator.
280 *
281 * @note Must be called on the same thread the memory was allocated on.
282 */
283 BS_UTILITY_EXPORT void bs_frame_free(void* data);
284
285 /**
286 * Frees memory previously allocated with bs_frame_alloc_aligned().
287 *
288 * @note Must be called on the same thread the memory was allocated on.
289 */
290 BS_UTILITY_EXPORT void bs_frame_free_aligned(void* data);
291
292 /**
293 * Allocates enough memory to hold the object of specified type using the global frame allocator, but does not
294 * construct the object.
295 */
296 template<class T>
297 T* bs_frame_alloc()
298 {
299 return (T*)bs_frame_alloc(sizeof(T));
300 }
301
302 /**
303 * Allocates enough memory to hold N objects of specified type using the global frame allocator, but does not
304 * construct the object.
305 */
306 template<class T>
307 T* bs_frame_alloc(UINT32 count)
308 {
309 return (T*)bs_frame_alloc(sizeof(T) * count);
310 }
311
312 /**
313 * Allocates enough memory to hold the object(s) of specified type using the global frame allocator,
314 * and constructs them.
315 */
316 template<class T>
317 T* bs_frame_new(UINT32 count = 0)
318 {
319 T* data = bs_frame_alloc<T>(count);
320
321 for(unsigned int i = 0; i < count; i++)
322 new ((void*)&data[i]) T;
323
324 return data;
325 }
326
327 /**
328 * Allocates enough memory to hold the object(s) of specified type using the global frame allocator, and constructs them.
329 */
330 template<class T, class... Args>
331 T* bs_frame_new(Args &&...args, UINT32 count = 0)
332 {
333 T* data = bs_frame_alloc<T>(count);
334
335 for(unsigned int i = 0; i < count; i++)
336 new ((void*)&data[i]) T(std::forward<Args>(args)...);
337
338 return data;
339 }
340
341 /**
342 * Destructs and deallocates an object allocated with the global frame allocator.
343 *
344 * @note Must be called on the same thread the memory was allocated on.
345 */
346 template<class T>
347 void bs_frame_delete(T* data)
348 {
349 data->~T();
350
351 bs_frame_free((UINT8*)data);
352 }
353
354 /**
355 * Destructs and deallocates an array of objects allocated with the global frame allocator.
356 *
357 * @note Must be called on the same thread the memory was allocated on.
358 */
359 template<class T>
360 void bs_frame_delete(T* data, UINT32 count)
361 {
362 for(unsigned int i = 0; i < count; i++)
363 data[i].~T();
364
365 bs_frame_free((UINT8*)data);
366 }
367
368 /** @copydoc FrameAlloc::markFrame */
369 BS_UTILITY_EXPORT void bs_frame_mark();
370
371 /** @copydoc FrameAlloc::clear */
372 BS_UTILITY_EXPORT void bs_frame_clear();
373
374 /** String allocated with a frame allocator. */
375 typedef std::basic_string<char, std::char_traits<char>, StdAlloc<char, FrameAlloc>> FrameString;
376
377 /** WString allocated with a frame allocator. */
378 typedef std::basic_string<wchar_t, std::char_traits<wchar_t>, StdAlloc<wchar_t, FrameAlloc>> FrameWString;
379
380 /** Vector allocated with a frame allocator. */
381 template <typename T, typename A = StdAlloc<T, FrameAlloc>>
382 using FrameVector = std::vector < T, A > ;
383
384 /** Stack allocated with a frame allocator. */
385 template <typename T, typename A = StdAlloc<T, FrameAlloc>>
386 using FrameStack = std::stack < T, std::deque<T, A> > ;
387
388 /** Queue allocated with a frame allocator. */
389 template <typename T, typename A = StdAlloc<T, FrameAlloc>>
390 using FrameQueue = std::queue<T, std::deque<T, A>>;
391
392 /** Set allocated with a frame allocator. */
393 template <typename T, typename P = std::less<T>, typename A = StdAlloc<T, FrameAlloc>>
394 using FrameSet = std::set < T, P, A > ;
395
396 /** Map allocated with a frame allocator. */
397 template <typename K, typename V, typename P = std::less<K>, typename A = StdAlloc<std::pair<const K, V>, FrameAlloc>>
398 using FrameMap = std::map < K, V, P, A >;
399
400 /** UnorderedSet allocated with a frame allocator. */
401 template <typename T, typename H = std::hash<T>, typename C = std::equal_to<T>, typename A = StdAlloc<T, FrameAlloc>>
402 using FrameUnorderedSet = std::unordered_set < T, H, C, A >;
403
404 /** UnorderedMap allocated with a frame allocator. */
405 template <typename K, typename V, typename H = std::hash<K>, typename C = std::equal_to<K>, typename A = StdAlloc<std::pair<const K, V>, FrameAlloc>>
406 using FrameUnorderedMap = std::unordered_map < K, V, H, C, A >;
407
408 /** @} */
409 /** @addtogroup Internal-Utility
410 * @{
411 */
412
413 /** @addtogroup Memory-Internal
414 * @{
415 */
416
417 extern BS_THREADLOCAL FrameAlloc* _GlobalFrameAlloc;
418
419 /**
420 * Specialized memory allocator implementations that allows use of a global frame allocator in normal
421 * new/delete/free/dealloc operators.
422 */
423 template<>
424 class MemoryAllocator<FrameAlloc> : public MemoryAllocatorBase
425 {
426 public:
427 /** @copydoc MemoryAllocator::allocate */
428 static void* allocate(size_t bytes)
429 {
430 return bs_frame_alloc((UINT32)bytes);
431 }
432
433 /** @copydoc MemoryAllocator::allocateAligned */
434 static void* allocateAligned(size_t bytes, size_t alignment)
435 {
436#if BS_PROFILING_ENABLED
437 incAllocCount();
438#endif
439
440 return bs_frame_alloc_aligned((UINT32)bytes, (UINT32)alignment);
441 }
442
443 /** @copydoc MemoryAllocator::allocateAligned16 */
444 static void* allocateAligned16(size_t bytes)
445 {
446#if BS_PROFILING_ENABLED
447 incAllocCount();
448#endif
449
450 return bs_frame_alloc_aligned((UINT32)bytes, 16);
451 }
452
453 /** @copydoc MemoryAllocator::free */
454 static void free(void* ptr)
455 {
456 bs_frame_free(ptr);
457 }
458
459 /** @copydoc MemoryAllocator::freeAligned */
460 static void freeAligned(void* ptr)
461 {
462#if BS_PROFILING_ENABLED
463 incFreeCount();
464#endif
465
466 bs_frame_free_aligned(ptr);
467 }
468
469 /** @copydoc MemoryAllocator::freeAligned16 */
470 static void freeAligned16(void* ptr)
471 {
472#if BS_PROFILING_ENABLED
473 incFreeCount();
474#endif
475
476 bs_frame_free_aligned(ptr);
477 }
478 };
479
480 /** @} */
481 /** @} */
482}
483