Add MemoryAPI
This commit is contained in:
parent
2c1a43f9d4
commit
f6364229ed
290
sources/Core/Memory/MemoryAPI.uc
Normal file
290
sources/Core/Memory/MemoryAPI.uc
Normal file
@ -0,0 +1,290 @@
|
||||
/**
|
||||
* API that provides functions for managing objects and actors by providing
|
||||
* easy and general means to create and destroy them, that allow to make use of
|
||||
* temporary `Object`s in a more efficient way.
|
||||
* This is a low-level API that most users of Acedia, most likely,
|
||||
* would not have to use, since creation of most objects would use their own
|
||||
* wrapper functions around this API.
|
||||
* Copyright 2020 Anton Tarasenko
|
||||
*------------------------------------------------------------------------------
|
||||
* This file is part of Acedia.
|
||||
*
|
||||
* Acedia 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, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Acedia 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 Acedia. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class MemoryAPI extends Singleton;
|
||||
|
||||
// This variable counts ticks and should be different each new tick.
|
||||
var private int currentTick;
|
||||
|
||||
// Stores instance of an `Object` that can be borrowed from the pool.
|
||||
struct BorrowableRecord
|
||||
{
|
||||
// Borrowable instance
|
||||
var Object instance;
|
||||
// Was this object borrowed?
|
||||
// This flag will persist unless object was explicitly freed,
|
||||
// even if borrowed reference timed out.
|
||||
var bool borrowed;
|
||||
// When was this object borrowed?
|
||||
// Used to automatically free borrowed objects after the tick has passed.
|
||||
var int borrowTick;
|
||||
};
|
||||
|
||||
// Available object pools
|
||||
var private array<BorrowableRecord> borrowPool;
|
||||
|
||||
// Checks if instance in the given `record` is borrowed.
|
||||
private final function bool IsBorrowed(BorrowableRecord record)
|
||||
{
|
||||
// `record.borrowed` means instance was borrowed,
|
||||
// but not explicitly freed;
|
||||
// `record.borrowTick >= currentTick` means that rights to the borrowed
|
||||
// instance hasn't yet ran out.
|
||||
return (record.borrowed && record.borrowTick >= currentTick);
|
||||
}
|
||||
|
||||
// Loads a reference to class instance from it's string representation.
|
||||
private final function class<Object> LoadClass(string classReference)
|
||||
{
|
||||
return class<Object>(DynamicLoadObject(classReference, class'Class', true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `Object` / `Actor` of a given class.
|
||||
*
|
||||
* If uses a proper spawning mechanism for both objects (`new`)
|
||||
* and actors (`Spawn`).
|
||||
*
|
||||
* @param classToAllocate Class of the `Object` / `Actor` that this method
|
||||
* must create.
|
||||
* @return Newly created object, might be `none` if creation has failed.
|
||||
*/
|
||||
public final function Object Allocate(class<Object> classToAllocate)
|
||||
{
|
||||
local class<Actor> actorClassToSpawn;
|
||||
if (classToAllocate == none) return none;
|
||||
|
||||
actorClassToSpawn = class<Actor>(classToAllocate);
|
||||
if (actorClassToSpawn != none)
|
||||
{
|
||||
return Spawn(actorClassToSpawn);
|
||||
}
|
||||
return (new classToAllocate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `Object` / `Actor` of a given class.
|
||||
*
|
||||
* If uses a proper spawning mechanism for both objects (`new`)
|
||||
* and actors (`Spawn`).
|
||||
*
|
||||
* @param classToAllocate Text representation (name) of the class of the
|
||||
* `Object` / `Actor` that this method must create.
|
||||
* Should contain full package-path.
|
||||
* @return Newly created object, might be `none` if creation has failed.
|
||||
*/
|
||||
public final function Object AllocateByReference(string refToClassToAllocate)
|
||||
{
|
||||
return Allocate(LoadClass(refToClassToAllocate));
|
||||
}
|
||||
|
||||
/**
|
||||
* Borrows an instance of an `Object` / `Actor` of the given class
|
||||
* from the pool.
|
||||
* Borrowed instance will be auto-freed during next tick.
|
||||
*
|
||||
* @param classToBorrow Class of an `Object` / `Actor` we want to borrow.
|
||||
* @return Borrowed object, might be `none` if borrow pool is empty and
|
||||
* creation of a new `Object` / `Actor` has failed.
|
||||
*/
|
||||
public final function Object Borrow(class<Object> classToBorrow)
|
||||
{
|
||||
local int i;
|
||||
local BorrowableRecord newRecord;
|
||||
for (i = 0; i < borrowPool.length; i += 1)
|
||||
{
|
||||
if (IsBorrowed(borrowPool[i])) continue;
|
||||
if (borrowPool[i].instance == none) continue;
|
||||
if (borrowPool[i].instance.class != classToBorrow) continue;
|
||||
|
||||
borrowPool[i].borrowed = true;
|
||||
borrowPool[i].borrowTick = currentTick;
|
||||
return borrowPool[i].instance;
|
||||
}
|
||||
// Create a new instance to borrow, if there isn't any available for
|
||||
// the given class.
|
||||
newRecord.borrowed = false;
|
||||
newRecord.instance = Allocate(classToBorrow);
|
||||
if (newRecord.instance != none)
|
||||
{
|
||||
borrowPool[borrowPool.length] = newRecord;
|
||||
}
|
||||
return newRecord.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Borrows an instance of an `Object` / `Actor` of the given class
|
||||
* from the pool.
|
||||
* Borrowed instance will be auto-freed during next tick.
|
||||
*
|
||||
* @param classToBorrow Text representation (name) of the class of
|
||||
* an `Object` / `Actor` we want to borrow.
|
||||
* @return Borrowed object, might be `none` if borrow pool is empty and
|
||||
* creation of a new `Object` / `Actor` has failed.
|
||||
*/
|
||||
public final function Object BorrowByReference(string refToClassToBorrow)
|
||||
{
|
||||
return Borrow(LoadClass(refToClassToBorrow));
|
||||
}
|
||||
|
||||
/**
|
||||
* Claims an instance of an `Object` / `Actor` of the given class
|
||||
* from the pool.
|
||||
* Claimed instances are removed from the borrow pool and
|
||||
* will not be automatically freed.
|
||||
*
|
||||
* @param classToClaim Class of an `Object` / `Actor` we wish to borrow.
|
||||
* @return Borrowed object, might be `none` if borrow pool is empty and
|
||||
* creation of a new `Object` / `Actor` has failed.
|
||||
*/
|
||||
public final function Object Claim(class<Object> classToClaim)
|
||||
{
|
||||
local int i;
|
||||
local Object instance;
|
||||
for (i = 0; i < borrowPool.length; i += 1)
|
||||
{
|
||||
if (IsBorrowed(borrowPool[i])) continue;
|
||||
if (borrowPool[i].instance == none) continue;
|
||||
if (borrowPool[i].instance.class != classToClaim) continue;
|
||||
|
||||
instance = borrowPool[i].instance;
|
||||
borrowPool.Remove(i, 1);
|
||||
return instance;
|
||||
}
|
||||
// Create a new instance to borrow, if there isn't any available for
|
||||
// the given class.
|
||||
return Allocate(classToClaim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claims an instance of an `Object` / `Actor` of the given class
|
||||
* from the pool.
|
||||
* Claimed instances are removed from the borrow pool and
|
||||
* will not be automatically freed.
|
||||
*
|
||||
* @param classToClaim Text representation (name) of the class of
|
||||
* an `Object` / `Actor` we wish to claim.
|
||||
* @return Borrowed object, might be `none` if borrow pool is empty and
|
||||
* creation of a new `Object` / `Actor` has failed.
|
||||
*/
|
||||
public final function Object ClaimByReference(string refToClassToClaim)
|
||||
{
|
||||
return Claim(LoadClass(refToClassToClaim));
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees given `Object` / `Actor` resource.
|
||||
*
|
||||
* By default `Actor`s are destroyed.
|
||||
* Due to limitations of the engine objects cannot be outright destroyed.
|
||||
* Instead, they are put into a "borrow pool", from where they can later be
|
||||
* taken for a reuse.
|
||||
*
|
||||
* @param objectToDelete `Object` / `Actor` that must be freed.
|
||||
* @param forceMakeBorrowable Only has an effect if `objectToDelete`
|
||||
* is an `Actor`, in which case it forces it to be added
|
||||
* to the borrow pool, instead of being destroyed.
|
||||
*/
|
||||
public final function Free
|
||||
(
|
||||
Object objectToDelete,
|
||||
optional bool forceMakeBorrowable
|
||||
)
|
||||
{
|
||||
local int i;
|
||||
local Actor actorToDelete;
|
||||
local BorrowableRecord newRecord;
|
||||
if (objectToDelete == none) return;
|
||||
|
||||
actorToDelete = Actor(objectToDelete);
|
||||
if (actorToDelete != none && !forceMakeBorrowable)
|
||||
{
|
||||
actorToDelete.Destroy();
|
||||
return;
|
||||
}
|
||||
// Check if `objectToDelete` is already in our records.
|
||||
for (i = 0; i < borrowPool.length; i += 1)
|
||||
{
|
||||
if (borrowPool[i].instance == objectToDelete)
|
||||
{
|
||||
borrowPool[i].borrowed = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If not - add it
|
||||
newRecord.instance = objectToDelete;
|
||||
newRecord.borrowed = false;
|
||||
borrowPool[borrowPool.length] = newRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces Unreal Engine to do garbage collection.
|
||||
* By default also cleans up all the objects in the borrow object pool.
|
||||
*
|
||||
* Process of garbage collection causes significant lag spike during the game
|
||||
* and should be used carefully.
|
||||
*
|
||||
* NOTE: method does not guarantee that borrow pool will be empty after
|
||||
* this call (even with `keepBorrowedObjectPool = true`),
|
||||
* since some of the borrowable objects might be currently in use and,
|
||||
* therefore, cannot be garbage collected.
|
||||
*
|
||||
* @param keepBorrowedObjectPool Set this to `true` to NOT garbage collect
|
||||
* objects in a borrow pool. Otherwise keep it `false`.
|
||||
*/
|
||||
public final function CollectGarbage(optional bool keepBorrowedObjectPool)
|
||||
{
|
||||
local int i;
|
||||
if (!keepBorrowedObjectPool)
|
||||
{
|
||||
// Dereference all non-borrowed objects from borrow pool,
|
||||
// so that they can be garbage collected.
|
||||
i = 0;
|
||||
while (i < borrowPool.length)
|
||||
{
|
||||
if ( borrowPool[i].instance == none
|
||||
|| !IsBorrowed(borrowPool[i]) )
|
||||
{
|
||||
borrowPool.Remove(i, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This makes Unreal Engine do garbage collection
|
||||
ConsoleCommand("obj garbage");
|
||||
}
|
||||
|
||||
event Tick(float delta)
|
||||
{
|
||||
currentTick += 1;
|
||||
}
|
||||
|
||||
// TODO: add cleaning on cooldown
|
||||
defaultproperties
|
||||
{
|
||||
currentTick = 0
|
||||
}
|
Reference in New Issue
Block a user