(* Copyright (C) Doom 2D: Forever Developers
*
* 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, version 3 of the License ONLY.
*
* 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 .
*)
{$INCLUDE a_modes.inc}
{$DEFINE MEM_DISABLE_ACCOUNTING}
unit mempool;
interface
{$IFDEF USE_MEMPOOL}
uses
SysUtils;
type
PMemPool = ^TMemPool;
TMemPool = record
private
mName: ShortString;
mObjSize: Integer; // not a limit, just a recommendation
mFirstPage: Pointer;
mLastPage: Pointer;
mAllocTotal: Integer;
mAllocCount: Integer;
public
constructor Create (const aname: AnsiString; aobjsize: Integer);
procedure setCapacity (acount: Integer); // ensure capacity for at least `acount` objects
procedure release (); // release all pool memory
function alloc (len: Integer): Pointer; // throws on OOM
procedure free (ptr: Pointer); // currently it is noop
public
property name: ShortString read mName;
property allocCount: Integer read mAllocCount;
property allocTotal: Integer read mAllocTotal;
end;
TPoolObject = class
{$IF DEFINED(D2F_DEBUG) and NOT DEFINED(MEM_DISABLE_ACCOUNTING)}
public class function NewInstance (): TObject; override;
public procedure FreeInstance (); override;
{$ENDIF}
end;
{$ENDIF}
(* Simple "mark/release" allocator *)
type
PoolMark = Integer;
PPoolMarkRelease = ^TPoolMarkRelease;
TPoolMarkRelease = record
private
mMemory: Pointer;
mSize: Integer;
mUsed: Integer;
public
constructor Create (aInitSize: Integer);
// free all allocated memory
procedure kill ();
// forget everything
procedure reset ();
// mark current position
function mark (): PoolMark; inline;
// forget everything from the given mark
procedure release (amark: PoolMark); inline;
// allocate some memory
// WARNING! pool can realloc it's internal storage and invalidate all previous pointers!
function alloc (size: Integer): Pointer; inline;
// get pointer for the given mark
// WARNING! pointer can become invalid after next call to `alloc()`!
function getPtr (amark: PoolMark): Pointer; inline;
function curPtr (): Pointer; inline;
end;
type
generic PoolIter = record
public
type Ptr = ^T;
type MyType = specialize PoolIter;
private
mPool: PPoolMarkRelease;
mMark: PoolMark;
mCount: Integer;
mCurrent: Integer;
mFinished: Boolean;
public
constructor Create (var apool: TPoolMarkRelease); // idiotic FPC doesn't support arg-less ctors for rectord
procedure finishIt (); inline; // sets count
procedure rewind (); inline;
function length (): Integer; inline;
procedure release (); inline; // reset pool
function moveNext (): Boolean; inline;
function getCurrent (): Ptr; inline;
function getEnumerator (): MyType; inline;
function first (): Ptr; inline;
public
property current: Ptr read getCurrent;
end;
var
framePool: TPoolMarkRelease; // temporary per-frame allocation pool
implementation
uses
SysUtils
{$IFDEF USE_MEMPOOL}
, hashtable
{$ENDIF}
;
// ////////////////////////////////////////////////////////////////////////// //
constructor TPoolMarkRelease.Create (aInitSize: Integer);
begin
if (aInitSize > 0) then
begin
mSize := aInitSize;
GetMem(mMemory, mSize);
end
else
begin
mMemory := nil;
mSize := 0;
end;
mUsed := 0;
end;
// free all allocated memory
procedure TPoolMarkRelease.kill ();
begin
if (mMemory <> nil) then FreeMem(mMemory);
mMemory := nil;
mSize := 0;
mUsed := 0;
end;
// forget everything
procedure TPoolMarkRelease.reset ();
begin
mUsed := 0;
end;
// mark current position
function TPoolMarkRelease.mark (): PoolMark; inline;
begin
result := mUsed;
end;
// forget everything from the given mark
procedure TPoolMarkRelease.release (amark: PoolMark); inline;
begin
if (amark < 0) or (amark > mUsed) then raise Exception.Create('MarkReleasePool is fucked (release)');
mUsed := amark;
end;
// allocate some memory
// WARNING! pool can realloc it's internal storage and invalidate all previous pointers!
function TPoolMarkRelease.alloc (size: Integer): Pointer; inline;
begin
if (size < 0) then raise Exception.Create('MarkReleasePool: cannot allocate negative amount of bytes');
if (size > 1024*1024) then raise Exception.Create('MarkReleasePool: why do you need to allocate more than 1MB?');
// do we need to get more memory?
if (mUsed+size > mSize) then
begin
if (mUsed+size > 1024*1024*64) then raise Exception.Create('MarkReleasePool: more than 64MB in MarkReleasePool is insanity!');
while (mUsed+size > mSize) do
begin
// less than 256KB: 64KB steps
if (mSize < 256*1024) then mSize += 64*1024
// less than 1MB: 128KB steps
else if (mSize < 1024*1024) then mSize += 128*1024
// otherwise, 1MB steps
else mSize += 1024*1024;
end;
ReallocMem(mMemory, mSize);
if (mMemory = nil) then raise Exception.Create('MarkReleasePool: out of memory!');
end;
result := Pointer(PAnsiChar(mMemory)+mUsed);
mUsed += size;
assert(mUsed <= mSize);
end;
// get pointer for the given mark
// WARNING! pointer can become invalid after next call to `alloc()`!
function TPoolMarkRelease.getPtr (amark: PoolMark): Pointer; inline;
begin
if (amark < 0) or (amark > mUsed) then raise Exception.Create('MarkReleasePool is fucked (getPtr)');
result := Pointer(PAnsiChar(mMemory)+amark);
end;
function TPoolMarkRelease.curPtr (): Pointer; inline;
begin
result := Pointer(PAnsiChar(mMemory)+mUsed);
end;
// ////////////////////////////////////////////////////////////////////////// //
constructor PoolIter.Create (var apool: TPoolMarkRelease);
begin
mPool := @apool;
mMark := mPool^.mark();
mCount := 0;
mCurrent := -1;
mFinished := false;
end;
procedure PoolIter.finishIt (); inline; // sets count
begin
if (mFinished) then raise Exception.Create('double fatality');
if (mPool = nil) then raise Exception.Create('void fatality');
mFinished := true;
mCount := Integer(PtrUInt(mPool^.curPtr)-PtrUInt(mPool^.getPtr(mMark))) div Integer(sizeof(T));
if (mCount < 0) then raise Exception.Create('wutafu?');
end;
procedure PoolIter.rewind (); inline;
begin
if (mPool = nil) then raise Exception.Create('void rewind');
mCurrent := -1;
end;
function PoolIter.length (): Integer; inline;
begin
//if (mCurrent+1 >= 0) and (mCurrent+1 < mCount) then result := mCount-(mCurrent+1) else result := 0;
result := mCount;
end;
procedure PoolIter.release (); inline; // reset pool
begin
if (mPool = nil) then raise Exception.Create('double release');
mPool^.release(mMark);
mPool := nil;
mCount := 0;
mCurrent := -1;
mFinished := false;
end;
function PoolIter.moveNext (): Boolean; inline;
begin
if (mPool = nil) then raise Exception.Create('void moveNext()');
if (not mFinished) then raise Exception.Create('moveNext() on unfinished');
Inc(mCurrent);
result := (mCurrent < mCount);
end;
function PoolIter.getCurrent (): Ptr; inline;
begin
if (mPool = nil) then raise Exception.Create('getCurrent() on nothing');
if (mCurrent < 0) or (mCurrent >= mCount) then raise Exception.Create('getCurrent() range error');
result := Ptr(mPool^.getPtr(mMark+mCurrent*Integer(sizeof(T))));
end;
function PoolIter.getEnumerator (): PoolIter; inline;
begin
result := self;
end;
function PoolIter.first (): Ptr; inline;
begin
if (mPool = nil) then raise Exception.Create('void moveNext()');
if (not mFinished) then raise Exception.Create('moveNext() on unfinished');
result := Ptr(mPool^.getPtr(mMark));
end;
// ////////////////////////////////////////////////////////////////////////// //
{$IFDEF USE_MEMPOOL}
uses
hashtable;
type
THashKeyPtr = class
public
class function hash (const k: Pointer): LongWord; inline;
class function equ (const a, b: Pointer): Boolean; inline;
class procedure freekey (k: Pointer); inline;
end;
THashPtrPtr = specialize THashBase; // key: TClass; value: PMemPool
var
pools: THashPtrPtr = nil;
// ////////////////////////////////////////////////////////////////////////// //
class function THashKeyPtr.hash (const k: Pointer): LongWord; inline; begin result := fnvHash(PByte(@k)^, sizeof(k)); end;
class function THashKeyPtr.equ (const a, b: Pointer): Boolean; inline; begin result := (a = b); end;
class procedure THashKeyPtr.freekey (k: Pointer); inline; begin end;
function getPoolFor (c: TClass): PMemPool;
begin
if (pools = nil) then pools := THashPtrPtr.Create();
if not pools.get(Pointer(c), result) then
begin
GetMem(result, sizeof(TMemPool));
result.Create(c.ClassName, c.InstanceSize);
pools.put(Pointer(c), result);
end;
end;
// ////////////////////////////////////////////////////////////////////////// //
constructor TMemPool.Create (const aname: AnsiString; aobjsize: Integer);
begin
if (aobjsize < 1) then aobjsize := 16; // arbitrary number
mName := aname;
mObjSize := aobjsize;
mFirstPage := nil;
mLastPage := nil;
mAllocTotal := 0;
mAllocCount := 0;
end;
procedure TMemPool.setCapacity (acount: Integer); // ensure capacity for at least `acount` objects
begin
end;
procedure TMemPool.release (); // release all pool memory
begin
end;
function TMemPool.alloc (len: Integer): Pointer; // throws on OOM
begin
if (len > 0) then mAllocTotal += len;
if (len < 1) then len := 1;
GetMem(result, len);
FillChar(PByte(result)^, len, 0);
Inc(mAllocCount);
end;
procedure TMemPool.free (ptr: Pointer); // currently it is noop
begin
FreeMem(ptr);
end;
// ////////////////////////////////////////////////////////////////////////// //
{$IF DEFINED(D2F_DEBUG) and NOT DEFINED(MEM_DISABLE_ACCOUNTING)}
class function TPoolObject.NewInstance (): TObject;
var
{$IF DEFINED(D2F_DEBUG) and NOT DEFINED(MEM_DISABLE_ACCOUNTING)}
pool: PMemPool;
{$ENDIF}
ptr: Pointer;
begin
{$IF DEFINED(D2F_DEBUG) and NOT DEFINED(MEM_DISABLE_ACCOUNTING)}
pool := getPoolFor(self.ClassType);
ptr := pool.alloc(self.InstanceSize);
{$ELSE}
GetMem(ptr, self.InstanceSize);
FillChar(PByte(ptr)^, self.InstanceSize, 0); // hello, Wyoming Knott!
{$ENDIF}
result := TObject(ptr);
self.InitInstance(ptr);
end;
procedure TPoolObject.FreeInstance ();
var
pool: PMemPool;
begin
pool := getPoolFor(self.ClassType);
pool.free(Pointer(self));
end;
{$ENDIF}
// ////////////////////////////////////////////////////////////////////////// //
{$IF DEFINED(D2F_DEBUG) and NOT DEFINED(MEM_DISABLE_ACCOUNTING)}
procedure dumpPools ();
var
fo: TextFile;
kv: THashPtrPtr.PEntry;
begin
AssignFile(fo, 'zmemlog.txt');
Rewrite(fo);
for kv in pools.byKeyValue do
begin
writeln(fo, kv.value.name, ': count=', kv.value.allocCount, '; total=', kv.value.allocTotal);
end;
CloseFile(fo);
end;
{$ENDIF}
initialization
//mpoolMap := TMemPool.Create('textmap', 64);
framePool := TPoolMarkRelease.Create(65536);
finalization
{$IF DEFINED(D2F_DEBUG) and NOT DEFINED(MEM_DISABLE_ACCOUNTING)}
dumpPools();
{$ENDIF}
{$ENDIF} // USE_MEMPOOL
end.