Anton Tarasenko
4 years ago
1 changed files with 290 additions and 0 deletions
@ -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