MxEngine
PoolAllocator.h
1 #pragma once
2 
3 #include <cassert>
4 #include <cstdint>
5 #include <ostream>
6 #include <memory>
7 
8 #include "Core/Macro/Macro.h"
9 
10 namespace MxEngine
11 {
12  /*
13  PoolAllocator class by #Momo
14  accepts chunk of memory and its size, do NOT allocate or free memory by itself
15  allocates objects of type T, each can be freed in any order
16  object constructor is called automatically, object destructor is called on Free() or allocator destruction
17  */
18  template<typename T>
20  {
21  public:
25  struct Block
26  {
30  T data;
34  size_t next;
35 
36  static constexpr size_t LastBit = 1ULL << (8 * sizeof(size_t) - 1);
37 
38  void MarkBusy()
39  {
40  next |= LastBit;
41  }
42 
43  bool IsFree()
44  {
45  return !(next & LastBit);
46  }
47  };
48 
49  private:
53  Block* storage = nullptr;
57  size_t free = InvalidOffset;
61  size_t count = 0;
62 
66  constexpr static size_t InvalidOffset = std::numeric_limits<size_t>::max() - Block::LastBit;
67  public:
68  using DataPointer = uint8_t*;
69 
74 
80  PoolAllocator(DataPointer data, size_t bytes)
81  {
82  this->Init(data, bytes);
83  }
84 
90  void Init(DataPointer data, size_t bytes)
91  {
92  MX_ASSERT(data != nullptr);
93  MX_ASSERT(bytes >= sizeof(Block));
94 
95  this->storage = reinterpret_cast<Block*>(data);
96  this->free = 0;
97  this->count = bytes / sizeof(Block);
98 
99  Block* last = this->storage + count - 1;
100  size_t offset = 1;
101  for (Block* begin = this->storage; begin != last; begin++, offset++)
102  {
103  begin->next = offset;
104  }
105  last->next = InvalidOffset;
106  }
107 
114  void Transfer(DataPointer newData, size_t newBytes)
115  {
116  if (this->storage == nullptr)
117  {
118  this->Init(newData, newBytes);
119  return;
120  }
121 
122  size_t newCount = newBytes / sizeof(Block); // calc new number of blocks
123  MX_ASSERT(newData != nullptr);
124  MX_ASSERT(this->count <= newCount);
125 
126  std::memcpy(newData, this->storage, this->count * sizeof(Block)); // copy old blocks to new memory chunk
127 
128  // some objects in MxEngine tend to invoke clean up on destruction, even if they are moved.
129  // to avoid this we put restriction on pool allocator objects to not to refer to themselves. Its better to use
130  // macros like offsetof() than to store raw pointers to data which can be easily invalidated by rellocator
131 
132  // for (size_t i = 0; i < this->count; i++)
133  // {
134  // auto& oldBlock = *(this->storage + i);
135  // auto* _ = new((Block*)newData + i) Block(std::move(oldBlock)); //-V799
136  // oldBlock.~Block();
137  // }
138 
139  this->storage = (Block*)newData;
140  Block* last = this->storage + newCount - 1; // calculate last block to chain new ones
141  size_t offset = this->count + 1;
142  for(Block* begin = this->storage + this->count; begin != last; begin++, offset++)
143  {
144  begin->next = offset;
145  }
146  last->next = this->free; // make last of new blocks point to the first free block of old
147  this->free = this->count; // chain all new blocks to the free list
148  this->count = newCount;
149  }
150 
155  {
156  constexpr size_t marked = InvalidOffset - 1;
157  size_t offset = this->free;
158  while (offset != InvalidOffset)
159  {
160  Block* block = this->storage + offset;
161  offset = block->next;
162  block->next = marked;
163  }
164  for (Block* block = this->storage; block != this->storage + this->count; block++)
165  {
166  if (block->next != marked)
167  {
168  block->data.~T();
169  }
170  }
171  }
172 
177  DataPointer GetBase()
178  {
179  return this->storage;
180  }
181 
187  template<typename... Args>
188  [[nodiscard]] T* Alloc(Args&&... args)
189  {
190  MX_ASSERT(this->free != InvalidOffset);
191  Block* res = this->storage + this->free;
192  T* ptr = &res->data;
193  this->free = res->next;
194  res->MarkBusy();
195  return new (ptr) T(std::forward<Args>(args)...);
196  }
197 
202  void Free(T* object)
203  {
204  object->~T();
205  Block* block = reinterpret_cast<Block*>(object);
206  MX_ASSERT(block >= this->storage && block < this->storage + this->count);
207  block->next = this->free;
208  this->free = static_cast<size_t>(block - this->storage);
209  }
210 
216  template<typename... Args>
217  [[nodiscard]] auto StackAlloc(Args&&... args)
218  {
219  auto deleter = [this](T* ptr) { this->Free(ptr); };
220  using SmartPtr = std::unique_ptr<T, decltype(deleter)>;
221  return SmartPtr(this->Alloc(std::forward<Args>(args)...), std::move(deleter));
222  }
223 
228  void Dump(std::ostream& out) const
229  {
230  for (size_t i = 0; i < this->count * sizeof(Block); i++)
231  {
232  out << std::hex << (int)((DataPointer)this->storage)[i];
233  }
234  out << std::dec << "\n --- dumped " << this->count * sizeof(Block) << " bytes --- \n";
235  }
236  };
237 }
void Transfer(DataPointer newData, size_t newBytes)
Definition: PoolAllocator.h:114
~PoolAllocator()
Definition: PoolAllocator.h:154
T data
Definition: PoolAllocator.h:30
void Dump(std::ostream &out) const
Definition: PoolAllocator.h:228
DataPointer GetBase()
Definition: PoolAllocator.h:177
Definition: PoolAllocator.h:25
Definition: PoolAllocator.h:19
PoolAllocator()
Definition: PoolAllocator.h:73
PoolAllocator(DataPointer data, size_t bytes)
Definition: PoolAllocator.h:80
T * Alloc(Args &&... args)
Definition: PoolAllocator.h:188
size_t next
Definition: PoolAllocator.h:34
void Init(DataPointer data, size_t bytes)
Definition: PoolAllocator.h:90
auto StackAlloc(Args &&... args)
Definition: PoolAllocator.h:217
Definition: Application.cpp:49
void Free(T *object)
Definition: PoolAllocator.h:202