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 | namespace bs |
6 | { |
7 | /** @addtogroup Internal-Utility |
8 | * @{ |
9 | */ |
10 | |
11 | /** @addtogroup Memory-Internal |
12 | * @{ |
13 | */ |
14 | |
15 | /** |
16 | * Static allocator that attempts to perform zero heap (dynamic) allocations by always keeping an active preallocated |
17 | * buffer. The allocator provides a fixed amount of preallocated memory, and if the size of the allocated data goes over |
18 | * that limit the allocator will fall back to dynamic heap allocations using the selected allocator. |
19 | * |
20 | * @note Static allocations can only be freed if memory is deallocated in opposite order it is allocated. |
21 | * Otherwise static memory gets orphaned until a call to clear(). Dynamic memory allocations behave |
22 | * depending on the selected allocator. |
23 | * |
24 | * @tparam BlockSize Size of the initially allocated static block, and minimum size of any dynamically |
25 | * allocated memory. |
26 | * @tparam DynamicAllocator Allocator to fall-back to when static buffer is full. |
27 | */ |
28 | template<int BlockSize = 512, class DynamicAllocator = TFrameAlloc<BlockSize>> |
29 | class StaticAlloc |
30 | { |
31 | private: |
32 | /** A single block of memory within a static allocator. */ |
33 | class MemBlock |
34 | { |
35 | public: |
36 | MemBlock(UINT8* data, UINT32 size) |
37 | :mData(data), mSize(size) |
38 | { } |
39 | |
40 | /** Allocates a piece of memory within the block. Caller must ensure the block has enough empty space. */ |
41 | UINT8* alloc(UINT32 amount) |
42 | { |
43 | UINT8* freePtr = &mData[mFreePtr]; |
44 | mFreePtr += amount; |
45 | |
46 | return freePtr; |
47 | } |
48 | |
49 | /** |
50 | * Frees a piece of memory within the block. If the memory isn't the last allocated memory, no deallocation |
51 | * happens and that memory is instead orphaned. |
52 | */ |
53 | void free(UINT8* data, UINT32 allocSize) |
54 | { |
55 | if((data + allocSize) == (mData + mFreePtr)) |
56 | mFreePtr -= allocSize; |
57 | } |
58 | |
59 | /** Releases all allocations within a block but doesn't actually free the memory. */ |
60 | void clear() |
61 | { |
62 | mFreePtr = 0; |
63 | } |
64 | |
65 | UINT8* mData = nullptr; |
66 | UINT32 mFreePtr = 0; |
67 | UINT32 mSize = 0; |
68 | MemBlock* mNextBlock = nullptr; |
69 | }; |
70 | |
71 | public: |
72 | StaticAlloc() = default; |
73 | ~StaticAlloc() = default; |
74 | |
75 | /** |
76 | * Allocates a new piece of memory of the specified size. |
77 | * |
78 | * @param[in] amount Amount of memory to allocate, in bytes. |
79 | */ |
80 | UINT8* alloc(UINT32 amount) |
81 | { |
82 | if (amount == 0) |
83 | return nullptr; |
84 | |
85 | #if BS_DEBUG_MODE |
86 | amount += sizeof(UINT32); |
87 | #endif |
88 | |
89 | UINT32 freeMem = BlockSize - mFreePtr; |
90 | |
91 | UINT8* data; |
92 | if (amount > freeMem) |
93 | data = mDynamicAlloc.alloc(amount); |
94 | else |
95 | { |
96 | data = &mStaticData[mFreePtr]; |
97 | mFreePtr += amount; |
98 | } |
99 | |
100 | #if BS_DEBUG_MODE |
101 | mTotalAllocBytes += amount; |
102 | |
103 | UINT32* storedSize = reinterpret_cast<UINT32*>(data); |
104 | *storedSize = amount; |
105 | |
106 | return data + sizeof(UINT32); |
107 | #else |
108 | return data; |
109 | #endif |
110 | } |
111 | |
112 | /** Deallocates a previously allocated piece of memory. */ |
113 | void free(void* data, UINT32 allocSize) |
114 | { |
115 | if (data == nullptr) |
116 | return; |
117 | |
118 | UINT8* dataPtr = (UINT8*)data; |
119 | #if BS_DEBUG_MODE |
120 | dataPtr -= sizeof(UINT32); |
121 | |
122 | UINT32* storedSize = (UINT32*)(dataPtr); |
123 | mTotalAllocBytes -= *storedSize; |
124 | #endif |
125 | |
126 | if(data > mStaticData && data < (mStaticData + BlockSize)) |
127 | { |
128 | if((((UINT8*)data) + allocSize) == (mStaticData + mFreePtr)) |
129 | mFreePtr -= allocSize; |
130 | } |
131 | else |
132 | mDynamicAlloc.free(dataPtr); |
133 | } |
134 | |
135 | /** Deallocates a previously allocated piece of memory. */ |
136 | void free(void* data) |
137 | { |
138 | if (data == nullptr) |
139 | return; |
140 | |
141 | UINT8* dataPtr = (UINT8*)data; |
142 | #if BS_DEBUG_MODE |
143 | dataPtr -= sizeof(UINT32); |
144 | |
145 | UINT32* storedSize = (UINT32*)(dataPtr); |
146 | mTotalAllocBytes -= *storedSize; |
147 | #endif |
148 | if(data < mStaticData || data >= (mStaticData + BlockSize)) |
149 | mDynamicAlloc.free(dataPtr); |
150 | } |
151 | |
152 | /** |
153 | * Allocates enough memory to hold the object(s) of specified type using the static allocator, and constructs them. |
154 | */ |
155 | template<class T> |
156 | T* construct(UINT32 count = 0) |
157 | { |
158 | T* data = (T*)alloc(sizeof(T) * count); |
159 | |
160 | for(unsigned int i = 0; i < count; i++) |
161 | new ((void*)&data[i]) T; |
162 | |
163 | return data; |
164 | } |
165 | |
166 | /** |
167 | * Allocates enough memory to hold the object(s) of specified type using the static allocator, and constructs them. |
168 | */ |
169 | template<class T, class... Args> |
170 | T* construct(Args &&...args, UINT32 count = 0) |
171 | { |
172 | T* data = (T*)alloc(sizeof(T) * count); |
173 | |
174 | for(unsigned int i = 0; i < count; i++) |
175 | new ((void*)&data[i]) T(std::forward<Args>(args)...); |
176 | |
177 | return data; |
178 | } |
179 | |
180 | /** Destructs and deallocates an object allocated with the static allocator. */ |
181 | template<class T> |
182 | void destruct(T* data) |
183 | { |
184 | data->~T(); |
185 | |
186 | free(data, sizeof(T)); |
187 | } |
188 | |
189 | /** Destructs and deallocates an array of objects allocated with the static frame allocator. */ |
190 | template<class T> |
191 | void destruct(T* data, UINT32 count) |
192 | { |
193 | for(unsigned int i = 0; i < count; i++) |
194 | data[i].~T(); |
195 | |
196 | free(data, sizeof(T) * count); |
197 | } |
198 | |
199 | /** Frees the internal memory buffers. All external allocations must be freed before calling this. */ |
200 | void clear() |
201 | { |
202 | assert(mTotalAllocBytes == 0); |
203 | |
204 | mFreePtr = 0; |
205 | mDynamicAlloc.clear(); |
206 | } |
207 | |
208 | private: |
209 | UINT8 mStaticData[BlockSize]; |
210 | UINT32 mFreePtr = 0; |
211 | DynamicAllocator mDynamicAlloc; |
212 | |
213 | UINT32 mTotalAllocBytes = 0; |
214 | }; |
215 | |
216 | /** Allocator for the standard library that internally uses a static allocator. */ |
217 | template <int BlockSize, class T> |
218 | class StdStaticAlloc |
219 | { |
220 | public: |
221 | typedef T value_type; |
222 | typedef value_type* pointer; |
223 | typedef const value_type* const_pointer; |
224 | typedef value_type& reference; |
225 | typedef const value_type& const_reference; |
226 | typedef std::size_t size_type; |
227 | typedef std::ptrdiff_t difference_type; |
228 | |
229 | StdStaticAlloc() = default; |
230 | |
231 | StdStaticAlloc(StaticAlloc<BlockSize, FreeAlloc>* alloc) noexcept |
232 | :mStaticAlloc(alloc) |
233 | { } |
234 | |
235 | template<class U> StdStaticAlloc(const StdStaticAlloc<BlockSize, U>& alloc) noexcept |
236 | :mStaticAlloc(alloc.mStaticAlloc) |
237 | { } |
238 | |
239 | template<class U> class rebind { public: typedef StdStaticAlloc<BlockSize, U> other; }; |
240 | |
241 | /** Allocate but don't initialize number elements of type T.*/ |
242 | T* allocate(const size_t num) const |
243 | { |
244 | if (num == 0) |
245 | return nullptr; |
246 | |
247 | if (num > static_cast<size_t>(-1) / sizeof(T)) |
248 | return nullptr; // Error |
249 | |
250 | void* const pv = mStaticAlloc->alloc((UINT32)(num * sizeof(T))); |
251 | if (!pv) |
252 | return nullptr; // Error |
253 | |
254 | return static_cast<T*>(pv); |
255 | } |
256 | |
257 | /** Deallocate storage p of deleted elements. */ |
258 | void deallocate(T* p, size_t num) const noexcept |
259 | { |
260 | mStaticAlloc->free((UINT8*)p, (UINT32)num); |
261 | } |
262 | |
263 | StaticAlloc<BlockSize, FreeAlloc>* mStaticAlloc = nullptr; |
264 | |
265 | size_t max_size() const { return std::numeric_limits<UINT32>::max() / sizeof(T); } |
266 | void construct(pointer p, const_reference t) { new (p) T(t); } |
267 | void destroy(pointer p) { p->~T(); } |
268 | template<class U, class... Args> |
269 | void construct(U* p, Args&&... args) { new(p) U(std::forward<Args>(args)...); } |
270 | |
271 | template <class T1, int N1, class T2, int N2> |
272 | friend bool operator== (const StdStaticAlloc<N1, T1>& a, const StdStaticAlloc<N2, T2>& b) throw(); |
273 | |
274 | }; |
275 | |
276 | /** Return that all specializations of this allocator are interchangeable. */ |
277 | template <class T1, int N1, class T2, int N2> |
278 | bool operator== (const StdStaticAlloc<N1, T1>& a, const StdStaticAlloc<N2, T2>& b) throw() |
279 | { |
280 | return N1 == N2 && a.mStaticAlloc == b.mStaticAlloc; |
281 | } |
282 | |
283 | /** Return that all specializations of this allocator are interchangeable. */ |
284 | template <class T1, int N1, class T2, int N2> |
285 | bool operator!= (const StdStaticAlloc<N1, T1>& a, const StdStaticAlloc<N2, T2>& b) throw() |
286 | { |
287 | return !(a == b); |
288 | } |
289 | |
290 | /** @} */ |
291 | /** @} */ |
292 | |
293 | |
294 | /** @addtogroup Memory |
295 | * @{ |
296 | */ |
297 | |
298 | /** |
299 | * Equivalent to Vector, except it avoids any dynamic allocations until the number of elements exceeds @p Count. |
300 | * Requires allocator to be explicitly provided. |
301 | */ |
302 | template <typename T, int Count> |
303 | using StaticVector = std::vector<T, StdStaticAlloc<sizeof(T) * Count, T>>; |
304 | |
305 | /** @} */ |
306 | } |
307 | |