/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
///////////////////////////////////////////////////////////////////////////////
// FastAllocator.h
//
// Implements simple and fast memory allocators. Includes sample code for
// STL and overriding of specific or global new/delete.
//
// Allocators are very fast and prevent memory fragmentation. They use up
// a little more space on average than generic new/malloc free/delete.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef FASTALLOCATOR_H
#define FASTALLOCATOR_H
#if defined(_MSC_VER)
#pragma once
#endif
//#define MEMORY_OVERWRITE_TEST
///////////////////////////////////////////////////////////////////////////////
// Include files
//
#include "always.h"
#include "wwdebug.h"
#include "mutex.h"
#include
#include //size_t & ptrdiff_t definition
#include
///////////////////////////////////////////////////////////////////////////////
// Forward Declarations
//
class FastFixedAllocator; //Allocates and deletes items of a fixed size.
class FastAllocatorGeneral; //Allocates and deletes items of any size. Can use as a fast replacement for global new/delete.
///////////////////////////////////////////////////////////////////////////////
// StackAllocator
//
// Introduction:
// This templated class allows you to allocate a dynamically sized temporary
// block of memory local to a function in a way that causes it to use some
// stack space instead of using the (very slow) malloc function. You don't
// want to call malloc/new to allocate temporary blocks of memory when speed
// is important. This class is only practical if you are allocating arrays of
// objects (especially smaller objects). If you have a class whose size is
// 40K, you aren't going to be able to put even one of them on the stack.
// If you don't need an array but instead just need a single object, then you
// can simply create your object on the stack and be done with it. However,
// there are some times when you need a temporary array of objects (or just
// an array of chars or pointers) but don't know the size beforehand. That's
// where this class is useful.
//
// Parameters:
// The class takes three templated parameters: T, nStackCount, bConstruct.
// T -- The type of object being allocated. Examples include char,
// cRZString, int*, cRZRect*.
// nStackCount -- How many items to allocate on the stack before switching
// to a heap allocation. Note that if you want to write
// portable code, you need to use no more than about 2K
// of stack space. So make sure nStackCount*sizeof(T) < 2048.
// bConstruct -- This is normally 1, but setting it to 0 is a hint to the
// compiler that you are constructing a type that has no
// constructor. Testing showed that the VC++ compiler wasn't
// completely able to optimize on its own. In particular, it
// was smart enough to remove the while loop, but not smart
// enough to remore the pTArrayEnd assignment.
//
// Benchmarks:
// Testing one one machine showed that allocating and freeing a block of
// 2048 bytes via malloc/free took 8700 and 8400 clock ticks respectively.
// Using this stack allocator to do the same thing tooko 450 and 375 clock
// ticks respectively.
//
// Usage and examples:
// Since we are using a local allocator and not overloading global or class
// operator new, you have to directory call StackAllocator::New instead
// of operator new. So instead of saying:
// SomeStruct* pStructArray = new SomeStruct[13];
// you say:
// SomeStruct* pStructArray = stackAllocator.New(13);
//
// void Example(int nSize){
// StackAllocator stackAllocator; //Create an instance
// char* pArray = stackAllocator.New(nSize); //Allocate memory, it will auto-freed.
// memset(pArray, 0, nSize*sizeof(char)); //Do something with the memory.
// }
//
// void Example(int nSize){
// StackAllocator stackAllocator; //Create an instance. We use the 'construct' hint feature here.
// int** pArray = stackAllocator.New(nSize); //Allocate memory.
// memset(pArray, 0, nSize*sizeof(int*)); //Do something with the memory.
// stackAllocator.Delete(pArray); //In this example, we explicity free the memory.
// }
//
// void Example(int nSize){
// StackAllocator stackAllocator; //Create an instance
// cRZRect* pRectArray = stackAllocator.New(nSize); //Allocate memory, it will auto-freed.
// pRectArray[0].SetRect(0,0,1,1); //Do something with the memory.
// }
//
// void Example(int nSize){
// StackAllocator stackAllocator; //Create an instance
// char* pArray = stackAllocator.New(nSize); //Allocate memory.
// char* pArray2 = stackAllocator.New(nSize); //Allocate some additional memory.
// memset(pArray, 0, nSize*sizeof(char)); //Do something with the memory.
// stackAllocator.Delete(pArray); //Delete the memory
// pArray = stackAllocator.New(nSize*2); //Allocate some new memory. We'll let the allocator free it.
// memset(pArray, 0, nSize*2*sizeof(char)); //Do something with the new memory.
// stackAllocator.Delete(pArray2); //Delete the additional memory.
// }
//
template
class StackAllocator{
public:
StackAllocator() : mnAllocCount(-1), mpTHeap(NULL){}
~StackAllocator(){
if(mnAllocCount != -1){ //If there is anything to do...
if(mpTHeap)
delete mpTHeap;
else{
if(bConstruct){ //Since this constant, the comparison gets optimized away.
T* pTArray = (T*)mTArray;
const T* const pTArrayEnd = pTArray + mnAllocCount;
while(pTArray < pTArrayEnd){
pTArray->~T(); //Call the destructor on the object directly.
++pTArray;
}
}
}
}
}
T* New(unsigned nCount){
if(mnAllocCount == -1){
mnAllocCount = nCount;
if(nCount < nStackCount){ //If the request is small enough to come from the stack...
//We call the constructors of all the objects here.
if(bConstruct){ //Since this constant, the comparison gets optimized away.
T* pTArray = (T*)mTArray;
const T* const pTArrayEnd = pTArray + nCount;
while(pTArray < pTArrayEnd){
new(pTArray)T; //Use the placement operator new. This simply calls the constructor
++pTArray; //of T with 'this' set to the input address. Note that we don't put
} //a '()' after the T this is because () causes trivial types like int
} //and class* to be assigned zero/NULL. We don't want that.
return (T*)mTArray;
} //Else the request is too big. So let's use (the slower) operator new.
return (mpTHeap = new T[nCount]); //The compiler will call the constructors here.
} //Else we are being used. Let's be nice and allocate something anyway.
return new T[nCount];
}
void Delete(T* pT){
if(pT == (T*)mTArray){ //If the allocation came from our stack...
if(bConstruct){ //Since this constant, the comparison gets optimized away.
T* pTArray = (T*)mTArray;
const T* const pTArrayEnd = pTArray + mnAllocCount;
while(pTArray < pTArrayEnd){
pTArray->~T(); //Call the destructor on the object directly.
++pTArray;
}
}
mnAllocCount = -1;
}
else if(pT == mpTHeap){ //If the allocation came from our heap...
delete[] mpTHeap; //The compiler will call the destructors here.
mpTHeap = NULL; //We clear these out so that we can possibly
mnAllocCount = -1; // use the allocator again.
}
else //Else the allocation came from the external heap.
delete[] pT;
}
protected:
int mnAllocCount; //Count of objects allocated. -1 means that nothing is allocated. We don't use zero because zero is a legal allocation count in C++.
T* mpTHeap; //This is normally NULL, but gets used of the allocation request is too high.
char mTArray[nStackCount*sizeof(T)]; //This is our stack memory.
};
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// class FastFixedAllocator
//
class FastFixedAllocator
{
public:
FastFixedAllocator(unsigned int n=0);
~FastFixedAllocator();
void Init(unsigned int n); //Useful for setting allocation size *after* construction,
//but before first use.
void* Alloc();
void Free(void* pAlloc);
unsigned Get_Heap_Size() const { return TotalHeapSize; }
unsigned Get_Allocated_Size() const { return TotalAllocatedSize; }
unsigned Get_Allocation_Count() const { return TotalAllocationCount; }
protected:
struct Link
{
Link* next;
};
struct Chunk
{
enum {
size = 8*1024-16
};
Chunk* next;
char mem[size];
};
Chunk* chunks;
unsigned int esize;
unsigned TotalHeapSize;
unsigned TotalAllocatedSize;
unsigned TotalAllocationCount;
Link* head;
void grow();
};
// ----------------------------------------------------------------------------
//
//
//
// ----------------------------------------------------------------------------
WWINLINE void* FastFixedAllocator::Alloc()
{
TotalAllocationCount++;
TotalAllocatedSize+=esize;
if (head==0) {
grow();
}
Link* p = head;
head = p->next;
return p;
}
// ----------------------------------------------------------------------------
//
//
//
// ----------------------------------------------------------------------------
WWINLINE void FastFixedAllocator::Free(void* pAlloc)
{
TotalAllocationCount--;
TotalAllocatedSize-=esize;
Link* p = static_cast(pAlloc);
p->next = head;
head = p;
}
// ----------------------------------------------------------------------------
//
//
//
// ----------------------------------------------------------------------------
WWINLINE FastFixedAllocator::FastFixedAllocator(unsigned int n) : esize(1), TotalHeapSize(0), TotalAllocatedSize(0), TotalAllocationCount(0)
{
head = 0;
chunks = 0;
Init(n);
}
// ----------------------------------------------------------------------------
//
//
//
// ----------------------------------------------------------------------------
WWINLINE FastFixedAllocator::~FastFixedAllocator()
{
Chunk* n = chunks;
while(n){
Chunk* p = n;
n = n->next;
delete p;
}
}
// ----------------------------------------------------------------------------
//
//
//
// ----------------------------------------------------------------------------
WWINLINE void FastFixedAllocator::Init(unsigned int n)
{
esize = (nnext = chunks;
chunks = n;
TotalHeapSize+=sizeof(Chunk);
const int nelem = Chunk::size/esize;
char* start = n->mem;
char* last = &start[(nelem-1)*esize];
for(char* p = start; p(p)->next = reinterpret_cast(p+esize);
reinterpret_cast(last)->next = 0;
head = reinterpret_cast(start);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// class FastAllocatorGeneral
//
// This class works by putting sizes into fixed size buckets. Each fixed size
// bucket is a FastFixedAllocator.
//
class FastAllocatorGeneral
{
enum {
MAX_ALLOC_SIZE=2048,
ALLOC_STEP=16
};
public:
FastAllocatorGeneral();
void* Alloc(unsigned int n);
void Free(void* pAlloc);
void* Realloc(void* pAlloc, unsigned int n);
unsigned Get_Total_Heap_Size();
unsigned Get_Total_Allocated_Size();
unsigned Get_Total_Allocation_Count();
unsigned Get_Total_Actual_Memory_Usage() { return ActualMemoryUsage; }
static FastAllocatorGeneral* Get_Allocator();
protected:
FastFixedAllocator allocators[MAX_ALLOC_SIZE/ALLOC_STEP];
FastCriticalSectionClass CriticalSections[MAX_ALLOC_SIZE/ALLOC_STEP];
bool MemoryLeakLogEnabled;
unsigned AllocatedWithMalloc;
unsigned AllocatedWithMallocCount;
unsigned ActualMemoryUsage;
};
///////////////////////////////////////////////////////////////////////////////
WWINLINE unsigned FastAllocatorGeneral::Get_Total_Heap_Size()
{
int size=AllocatedWithMalloc;
for (int i=0;i
struct FastSTLAllocator{
typedef size_t size_type; //basically, "unsigned int"
typedef ptrdiff_t difference_type; //basically, "int"
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
T* address(T& t) const { return (&t); } //These two are slightly strange but
const T* address(const T& t) const { return (&t); } //required functions. Just do it.
static T* allocate(size_t n, const void* =NULL) { return (T*)generalAllocator.Alloc(n*sizeof(T)); }
static void construct(T* ptr, const T& value) { new(ptr) T(value); }
static void deallocate(void* ptr, size_t /*n*/) { generalAllocator.Free(ptr); }
static void destroy(T* ptr) { ptr->~T(); }
static size_t max_size() { return (size_t)-1; }
//This _Charalloc is required by VC++5 since it VC++5 predates
//the language standardization. Allocator behaviour is one of the
//last things to have been hammered out. Important note: If you
//decide to write your own fast allocator, containers will allocate
//random objects through this function but delete them through
//the above delallocate() function. So your version of deallocate
//should *not* assume that it will only be given T objects to delete.
char* _Charalloc(size_t n){ return (char*)::generalAllocator.Alloc(n*sizeof(char)); }
};
#else
//This is a C++ language standard allocator. Most C++ compilers after 1999
//other than Microsoft C++ compile this fine. Otherwise. you might be able
//to use the same allocator as VC++ uses above.
template
class FastSTLAllocator{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
template struct rebind {
typedef FastSTLAllocator other;
};
FastSTLAllocator() {}
FastSTLAllocator(const FastSTLAllocator&) {}
template FastSTLAllocator(const FastSTLAllocator&) {}
~FastSTLAllocator() {}
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const { return &x; }
T* allocate(size_type n, const void* = NULL) { return n != 0 ? static_cast(generalAllocator.Alloc(n*sizeof(T))) : NULL; }
void deallocate(pointer p, size_type n) { generalAllocator.Free(p); }
size_type max_size() const { return size_t(-1) / sizeof(T); }
void construct(pointer p, const T& val) { new(p) T(val); }
void destroy(pointer p) { p->~T(); }
};
#endif
template
WWINLINE bool operator==(const FastSTLAllocator&, const FastSTLAllocator&) { return true; }
template
WWINLINE bool operator!=(const FastSTLAllocator&, const FastSTLAllocator&) { return false; }
///////////////////////////////////////////////////////////////////////////////
/*
///////////////////////////////////////////////////////////////////////////////
// Example usage of fast allocators in STL.
//
///////////////////////////////////////////////////////////////////////////////
#include
#include
#include
#include
#include
#include
#include
#include