231 lines
6.3 KiB
C++
231 lines
6.3 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <malloc.h>
|
|
#include <new>
|
|
|
|
// Fixed block allocator reconstructed from the repeated PDB specializations
|
|
// and the AllocNewBlock/Alloc/Free/Shutdown bodies in the Hex-Rays dump.
|
|
template<class T, int blockSize, int memTag = 0>
|
|
class idBlockAlloc {
|
|
public:
|
|
static_assert(blockSize > 0, "idBlockAlloc requires a positive block size");
|
|
|
|
static constexpr std::size_t ELEMENT_SIZE =
|
|
sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);
|
|
|
|
union element_t {
|
|
T* data;
|
|
element_t* next;
|
|
alignas(T) unsigned char buffer[ELEMENT_SIZE];
|
|
|
|
element_t() {}
|
|
~element_t() {}
|
|
};
|
|
|
|
class idBlock {
|
|
public:
|
|
element_t elements[blockSize];
|
|
idBlock* next;
|
|
element_t* free;
|
|
int freeCount;
|
|
|
|
idBlock() : next(nullptr), free(nullptr), freeCount(0) {}
|
|
};
|
|
|
|
idBlock* blocks;
|
|
element_t* free;
|
|
int total;
|
|
int active;
|
|
bool allowAllocs;
|
|
bool clearAllocs;
|
|
|
|
explicit idBlockAlloc(const bool clear = false)
|
|
: blocks(nullptr), free(nullptr), total(0), active(0),
|
|
allowAllocs(true), clearAllocs(clear) {
|
|
}
|
|
|
|
~idBlockAlloc() {
|
|
Shutdown();
|
|
}
|
|
|
|
idBlockAlloc(const idBlockAlloc&) = delete;
|
|
idBlockAlloc& operator=(const idBlockAlloc&) = delete;
|
|
|
|
std::size_t Allocated() const {
|
|
return static_cast<std::size_t>(total) * sizeof(T);
|
|
}
|
|
|
|
std::size_t Size() const {
|
|
return sizeof(*this) + Allocated();
|
|
}
|
|
|
|
void Shutdown() {
|
|
while (blocks != nullptr) {
|
|
idBlock* const block = blocks;
|
|
blocks = block->next;
|
|
block->~idBlock();
|
|
_aligned_free(block);
|
|
}
|
|
blocks = nullptr;
|
|
free = nullptr;
|
|
active = 0;
|
|
total = 0;
|
|
}
|
|
|
|
void SetFixedBlocks(const int numBlocks) {
|
|
const int count = numBlocks > 0 ? numBlocks : 0;
|
|
while (total < count * blockSize && AllocNewBlock()) {
|
|
}
|
|
allowAllocs = false;
|
|
}
|
|
|
|
void SetAllocAllowed(const bool allowed) {
|
|
allowAllocs = allowed;
|
|
}
|
|
|
|
void SetClear(const bool clear) {
|
|
clearAllocs = clear;
|
|
}
|
|
|
|
void FreeEmptyBlocks() {
|
|
for (idBlock* block = blocks; block != nullptr; block = block->next) {
|
|
block->free = nullptr;
|
|
block->freeCount = 0;
|
|
}
|
|
|
|
element_t* element = free;
|
|
while (element != nullptr) {
|
|
element_t* const next = element->next;
|
|
const std::uintptr_t address = reinterpret_cast<std::uintptr_t>(element);
|
|
for (idBlock* block = blocks; block != nullptr; block = block->next) {
|
|
const std::uintptr_t begin =
|
|
reinterpret_cast<std::uintptr_t>(&block->elements[0]);
|
|
const std::uintptr_t end =
|
|
reinterpret_cast<std::uintptr_t>(&block->elements[blockSize]);
|
|
if (address >= begin && address < end) {
|
|
element->next = block->free;
|
|
block->free = element;
|
|
++block->freeCount;
|
|
break;
|
|
}
|
|
}
|
|
element = next;
|
|
}
|
|
|
|
free = nullptr;
|
|
idBlock** link = &blocks;
|
|
while (*link != nullptr) {
|
|
idBlock* const block = *link;
|
|
if (block->freeCount == blockSize) {
|
|
*link = block->next;
|
|
total -= blockSize;
|
|
block->~idBlock();
|
|
_aligned_free(block);
|
|
continue;
|
|
}
|
|
element_t* blockFree = block->free;
|
|
while (blockFree != nullptr) {
|
|
element_t* const next = blockFree->next;
|
|
blockFree->next = free;
|
|
free = blockFree;
|
|
blockFree = next;
|
|
}
|
|
link = &block->next;
|
|
}
|
|
}
|
|
|
|
T* Alloc() {
|
|
if (free == nullptr && (!allowAllocs || !AllocNewBlock())) {
|
|
return nullptr;
|
|
}
|
|
|
|
element_t* const element = free;
|
|
free = element->next;
|
|
++active;
|
|
if (clearAllocs) {
|
|
std::memset(element->buffer, 0, sizeof(element->buffer));
|
|
}
|
|
return new (element->buffer) T();
|
|
}
|
|
|
|
void Free(T* value) {
|
|
if (value == nullptr) {
|
|
return;
|
|
}
|
|
value->~T();
|
|
element_t* const element = reinterpret_cast<element_t*>(value);
|
|
element->next = free;
|
|
free = element;
|
|
--active;
|
|
}
|
|
|
|
int GetTotalCount() const { return total; }
|
|
int GetAllocCount() const { return active; }
|
|
int GetFreeCount() const { return total - active; }
|
|
|
|
private:
|
|
bool AllocNewBlock() {
|
|
constexpr std::size_t alignment = alignof(T) > 16 ? alignof(T) : 16;
|
|
void* const memory = _aligned_malloc(sizeof(idBlock), alignment);
|
|
if (memory == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
idBlock* const block = new (memory) idBlock();
|
|
block->next = blocks;
|
|
blocks = block;
|
|
for (int index = 0; index < blockSize; ++index) {
|
|
block->elements[index].next = free;
|
|
free = &block->elements[index];
|
|
}
|
|
total += blockSize;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template<class Object, class Key>
|
|
class idBTreeNode;
|
|
|
|
template<class Object, class Key, int maxChildren>
|
|
class idBTree;
|
|
|
|
// The recovered idDynamicBlock specializations all have this four-field,
|
|
// sixteen-byte Win32 layout. A negative size marks the first block in a base
|
|
// allocation; membership in the free tree marks whether a block is free.
|
|
template<class T, int memTag = 0>
|
|
class idDynamicBlock {
|
|
public:
|
|
int size;
|
|
idDynamicBlock* prev;
|
|
idDynamicBlock* next;
|
|
idBTreeNode<idDynamicBlock<T, memTag>, int>* node;
|
|
|
|
T* GetMemory() {
|
|
return reinterpret_cast<T*>(this + 1);
|
|
}
|
|
|
|
const T* GetMemory() const {
|
|
return reinterpret_cast<const T*>(this + 1);
|
|
}
|
|
|
|
int GetSize() const {
|
|
return size < 0 ? -size : size;
|
|
}
|
|
|
|
void SetSize(const int newSize, const bool isBaseBlock) {
|
|
size = isBaseBlock ? -newSize : newSize;
|
|
}
|
|
|
|
bool IsBaseBlock() const {
|
|
return size < 0;
|
|
}
|
|
};
|
|
|
|
#if INTPTR_MAX == INT32_MAX
|
|
static_assert(sizeof(idDynamicBlock<unsigned char, 0>) == 16,
|
|
"Recovered idDynamicBlock ABI changed");
|
|
#endif
|