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 | |
13 | namespace 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 | |