Cogs.Core
PoolBase.cpp
1#include "PoolBase.h"
2
3#include <OsoMemoryProfiler/OsoMemoryProfiler.h>
4
5#include <limits>
6
7// Uncomment to put pool elements onto the heap instead of in the pool,
8// which allows memory debugging tools to identify problems.
9//
10//#define COGS_HEAP_DEBUG 1
11
12namespace
13{
15 struct PoolHeader
16 {
18 void * next;
19 };
20}
21
22Cogs::Collections::PoolBase::PoolBase(size_t elementSize, size_t initialCapacity, size_t pageSize, Memory::Allocator * allocator, MemBlockType memType) :
23 elementSize(elementSize),
24 pageSize(pageSize),
25 allocator(allocator),
26 memType(memType)
27{
28 assert(elementSize >= sizeof(PoolHeader) && "Element size must be at least enough to store a pointer.");
29 assert(pageSize <= std::numeric_limits<ElementOffset>::max() && "Page size must be smaller or equal to max element offset.");
30
31 resize(initialCapacity);
32}
33
34void Cogs::Collections::PoolBase::resize(size_t desiredCapacity)
35{
36 assert(desiredCapacity >= capacity && "Pool does not support shrinkage.");
37
38 const size_t numPages = (desiredCapacity / pageSize) + ((desiredCapacity % pageSize) ? 1 : 0);
39
40 assert(numPages <= std::numeric_limits<ElementOffset>::max() && "Page count must be smaller or equal to max element offset.");
41
43 while (numPages > pages.size()) {
44 addPage(pageSize);
45 }
46
47 capacity = pages.size() * pageSize;
48}
49
51{
52 // Add a single page to the list of pages.
53 pages.emplace_back(pageSize * elementSize, allocator, memType);
54
55 // Retrieve the new page.
56 auto data = static_cast<uint8_t *>(pages.back().data());
57
58 // Initialize the linked list of elements chaining all elements in the page together.
59 for (size_t i = 0; i < pageSize; ++i) {
60 PoolHeader * block = reinterpret_cast<PoolHeader *>(data + i * elementSize);
61
62 block->next = reinterpret_cast<PoolHeader *>(data + (i + 1) * elementSize);
63 }
64
65 // Terminate the linked list at the last element.
66 reinterpret_cast<PoolHeader *>(data + (pageSize - 1) * elementSize)->next = nullptr;
67
68 if (!freeHead) {
69 // If there are currently no free elements in the pool, we simply insert our new page of free elements
70 // as the first available.
71 freeHead = data;
72 } else {
73 // Walk to the last free element in the pool and insert our new linked list of free elements from the
74 // page there.
75 PoolHeader * last = static_cast<PoolHeader *>(freeHead);
76
77 while (last->next) {
78 last = static_cast<PoolHeader *>(last->next);
79 }
80
81 last->next = data;
82 }
83}
84
86{
87 // If there are no elements in our free list, we need to grow by one page.
88 if (!freeHead) resize(capacity + pageSize);
89
90 // Pick the first available element in the free list.
91 auto element = static_cast<PoolHeader *>(freeHead);
92
93 // Set the first free element to the next element in the free list. This element might be nullptr, and
94 // in that case the pool will need to grow if further allocations are made.
95 freeHead = element->next;
96
97 ++numAllocated;
98
99#ifdef COGS_HEAP_DEBUG
100 uint8_t* mem = static_cast<uint8_t*>(std::malloc(sizeof(void*) + elementSize));
101 *reinterpret_cast<PoolHeader**>(mem) = element;
102 element->next = mem + sizeof(void*);
103 return element->next;
104#else
105 return element;
106#endif
107}
108
110{
111#ifdef COGS_HEAP_DEBUG
112 void * foo = static_cast<char*>(element) - sizeof(void*);
113 element = *static_cast<PoolHeader**>(foo);
114 std::free(foo);
115#endif
116
117 assert(numAllocated && "No available elements to free.");
118
119 --numAllocated;
120
121 static_cast<PoolHeader *>(element)->next = freeHead;
122 freeHead = element;
123}
124
126{
127#ifdef COGS_HEAP_DEBUG
128 element = *reinterpret_cast<PoolHeader**>(static_cast<char*>(element) - sizeof(void*));
129#endif
130
131 for (size_t i = 0; i < pages.size(); ++i) {
132 auto & page = pages[i];
133 auto data = static_cast<const uint8_t *>(page.data());
134
135 const auto pageBegin = data;
136 const auto pageEnd = data + pageSize * elementSize;
137
138 if (element >= pageBegin && element < pageEnd) {
139 const size_t offsetInBytes = static_cast<uint8_t *>(element) - pageBegin;
140 const ElementOffset offset = static_cast<ElementOffset>(offsetInBytes / elementSize);
141 const size_t pageIndex = (i << (sizeof(ElementOffset) * 8));
142 const ElementHandle handle = static_cast<ElementHandle>(pageIndex) + offset;
143
144 return handle;
145 }
146 }
147
148 assert(false && "Element not part of any allocated pages.");
149
150 return 0;
151}
152
154{
155 const auto pageIndex = getPageIndex(handle);
156
157 assert(pageIndex < pages.size() && "Page index out of range.");
158
159 const auto elementIndex = getElementOffset(handle);
160
161 assert(elementIndex < pageSize && "Element index out of range.");
162
163 auto elementPtr = reinterpret_cast<uint8_t *>(pages[pageIndex].data()) + elementIndex * elementSize;
164
165#ifdef COGS_HEAP_DEBUG
166 return reinterpret_cast<PoolHeader *>(elementPtr)->next;
167#else
168 return elementPtr;
169#endif
170}
171
173{
174 if (!isValid(handle)) return false;
175 const auto pageIndex = getPageIndex(handle);
176 const auto elementIndex = getElementOffset(handle);
177 const void* elementPtr = static_cast<const void*>(reinterpret_cast<const uint8_t*>(pages[pageIndex].data()) + elementIndex * elementSize);
178
179 PoolHeader* last = static_cast<PoolHeader*>(freeHead);
180
181 while (last->next) {
182 if (last == elementPtr)
183 return false;
184 last = static_cast<PoolHeader*>(last->next);
185 }
186
187 return true;
188}
Base allocator implementation.
Definition: Allocator.h:30
uint16_t ElementOffset
Offset type used to index elements in resource pools.
Definition: PoolBase.h:13
uint32_t ElementHandle
Handle type for elements.
Definition: PoolBase.h:16
void addPage(size_t pageSize)
Adds a new page to the pool, with the given size in number of elements.
Definition: PoolBase.cpp:50
void * allocate()
Allocate storage for a single new element, returning the pointer to the uninitialized storage.
Definition: PoolBase.cpp:85
ElementHandle getHandle(void *element) const
Get a handle to the given element.
Definition: PoolBase.cpp:125
void resize(size_t capacity)
Resize the pool to have storage for the given capacity number of elements.
Definition: PoolBase.cpp:34
bool isAllocated(ElementHandle handle) const
Checks if the given handle is a valid handle to an allocated element.
Definition: PoolBase.cpp:172
void deallocate(void *element)
Deallocate the given element, returning the memory to be managed by the pool.
Definition: PoolBase.cpp:109
PoolBase(size_t elementSize, size_t initialCapacity, size_t pageSize, Memory::Allocator *allocator=Memory::Allocator::defaultAllocator(), MemBlockType memType=MemBlockType::Bucket)
Construct a pool base with the given parameters.
Definition: PoolBase.cpp:22
void * getElement(ElementHandle handle)
Get a pointer to the element indexed by the given handle.
Definition: PoolBase.cpp:153