Anton Tarasenko
2 years ago
9 changed files with 438 additions and 363 deletions
@ -0,0 +1,434 @@ |
|||||||
|
/** |
||||||
|
* API that provides functions for managing object of classes, derived from |
||||||
|
* `AcediaObject`. It takes care of managing their object pools, as well as |
||||||
|
* ensuring that constructors and finalizers are called properly. |
||||||
|
* Almost all `AcediaObject`s should use this API's methods for their own |
||||||
|
* creation and destruction. |
||||||
|
* Copyright 2020-2022 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 AcediaObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* # Memory API |
||||||
|
* |
||||||
|
* This is most-basic API that must be created before anything else in Acedia, |
||||||
|
* since it is responsible for the proper creation of `AcediaObject`s. |
||||||
|
* It takes care of managing their object pools, as well as ensuring that |
||||||
|
* constructors and finalizers are called properly. |
||||||
|
* Almost all `AcediaObject`s should use this API's methods for their own |
||||||
|
* creation and destruction. |
||||||
|
* |
||||||
|
* ## Usage |
||||||
|
* |
||||||
|
* First of all, this API is only meant for non-actor `Object` creation. |
||||||
|
* `Actor` creation is generally avoided in Acedia and, when unavoidable, |
||||||
|
* different APIs are dealing with that. `MemoryAPI` is designed to work in |
||||||
|
* the absence of any level (and, therefore, `Actor`s) at all. |
||||||
|
* Simply use `MemoryAPI.Allocate()` to create a new object and |
||||||
|
* `MemoryAPI.Free()` to get rid on unneeded reference. Do note that |
||||||
|
* `AcediaObject`s use reference counting and object will be deallocated and |
||||||
|
* pooled only after every trackable reference was released by |
||||||
|
* `MemoryAPI.Free()`. |
||||||
|
* Best practice is to only care about what object reference you're |
||||||
|
* keeping, properly release them with `MemoryAPI.Free()` and to NEVER EVER USE |
||||||
|
* THEM after you've release them. Regardless of whether they were actually |
||||||
|
* deallocated. |
||||||
|
* |
||||||
|
* There's also a set of auxiliry methods for either loading `class`es from |
||||||
|
* their `BaseText`/`string`-given names or even directly creating objects of |
||||||
|
* said classes. |
||||||
|
* |
||||||
|
* ## Motivation |
||||||
|
* |
||||||
|
* UnrealScript lacks any practical way to destroy non-actor objects on |
||||||
|
* demand: the best one can do is remove any references to the object and wait |
||||||
|
* for garbage collection. But garbage collection itself is too slow and causes |
||||||
|
* noticeable lag spikes for players, making it suitable only for cleaning |
||||||
|
* objects when switching levels. To alleviate this problem, there exists |
||||||
|
* a standard class `ObjectPool` that stores unused objects (mostly resources |
||||||
|
* such as textures) inside dynamic array until they are needed. |
||||||
|
* Unfortunately, using a single ObjectPool for a large volume of objects |
||||||
|
* is impractical from performance perspective, since it stores objects of all |
||||||
|
* classes together and each object allocation from the pool can potentially |
||||||
|
* require going through the whole array (see `Engine/ObjectPool.uc`). |
||||||
|
* Acedia uses a separate object pool (implemented by `AcediaObjectPool`) |
||||||
|
* for every single class, making object allocation as trivial as grabbing |
||||||
|
* the last stored object from `AcediaObjectPool`'s internal dynamic array. |
||||||
|
* New pool is prepared for every class you create, as long as it is |
||||||
|
* derived from `AcediaObject`. `AcediaActors` do not use object pools and are |
||||||
|
* meant to be simply `Destroy()`ed. |
||||||
|
* |
||||||
|
* ## Customizing object pools for your classes |
||||||
|
* |
||||||
|
* Object pool usage can be disabled completely for your class by setting |
||||||
|
* `usesObjectPool = false` in `defaultproperties` block. Without object pools |
||||||
|
* `MemoryAPI.Allocate()` will create a new instance of your class every single |
||||||
|
* time. |
||||||
|
* You can also set a limit to how many objects will be stored in an object |
||||||
|
* pool with defaultMaxPoolSize variable. Negative number (default for |
||||||
|
* `AcediaObject`) means that object pool can grow without a limit. |
||||||
|
* `0` effectively disables object pool, similar to setting |
||||||
|
* `usesObjectPool = false`. However, this can be overwritten by server's |
||||||
|
* settings (see `AcediaSystem.ini`: `AcediaObjectPool`). |
||||||
|
*/ |
||||||
|
|
||||||
|
// Store all created pools, so that we can quickly forget stored objects upon |
||||||
|
// garbage collection |
||||||
|
var private array<AcediaObjectPool> registeredPools; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a class instance from its `Text` representation. |
||||||
|
* |
||||||
|
* Does not generate log messages upon failure. |
||||||
|
* |
||||||
|
* @param classReference Text representation of the class to return. |
||||||
|
* @return Loaded class, corresponding to its name from `classReference`. |
||||||
|
*/ |
||||||
|
public final function class<Object> LoadClass( |
||||||
|
BaseText classReference) |
||||||
|
{ |
||||||
|
if (classReference == none) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
return class<Object>( |
||||||
|
DynamicLoadObject(classReference.ToString(), |
||||||
|
class'Class', |
||||||
|
true)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a class instance from its `string` representation. |
||||||
|
* |
||||||
|
* Does not generate log messages upon failure. |
||||||
|
* |
||||||
|
* @param classReference `string` representation of the class to return. |
||||||
|
* @return Loaded class, corresponding to its name from `classReference`. |
||||||
|
*/ |
||||||
|
public final function class<Object> LoadClass_S(string classReference) |
||||||
|
{ |
||||||
|
return class<Object>(DynamicLoadObject(classReference, class'Class', true)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new `Object` of a given class. |
||||||
|
* |
||||||
|
* For `AcediaObject`s calls constructors and tries (uses them only if they |
||||||
|
* aren't forbidden for a given class) to make use of their classes' object |
||||||
|
* pools. |
||||||
|
* |
||||||
|
* If Acedia's object does make use of object pools, - |
||||||
|
* guarantees to return last pooled object (in a LIFO queue), |
||||||
|
* unless `forceNewInstance` is set to `true`. |
||||||
|
* |
||||||
|
* @see `AllocateByReference()`, `AllocateByReference_S()` |
||||||
|
* |
||||||
|
* @param classToAllocate Class of the `Object` that this method will |
||||||
|
* create. Must not be subclass of `Actor`. |
||||||
|
* @param forceNewInstance Set this to `true` if you require this method to |
||||||
|
* create a new instance, bypassing any object pools. |
||||||
|
* @return Newly created object. Will only be `none` if: |
||||||
|
* 1. `classToAllocate` is `none`; |
||||||
|
* 2. `classToAllocate` is abstract; |
||||||
|
* 3. `classToAllocate` is derived from `Actor`. |
||||||
|
*/ |
||||||
|
public final function Object Allocate( |
||||||
|
class<Object> classToAllocate, |
||||||
|
optional bool forceNewInstance) |
||||||
|
{ |
||||||
|
// TODO: this is an old code require while we still didn't get rid of |
||||||
|
// services - replace it later |
||||||
|
local LevelCore core; |
||||||
|
local Object allocatedObject; |
||||||
|
local AcediaObjectPool relevantPool; |
||||||
|
local class<AcediaObject> acediaObjectClassToAllocate; |
||||||
|
local class<AcediaActor> acediaActorClassToAllocate; |
||||||
|
local class<Actor> actorClassToAllocate; |
||||||
|
|
||||||
|
if (classToAllocate == none) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
// Try using pool first (only if new instance is not required) |
||||||
|
acediaObjectClassToAllocate = class<AcediaObject>(classToAllocate); |
||||||
|
acediaActorClassToAllocate = class<AcediaActor>(classToAllocate); |
||||||
|
if (!forceNewInstance) |
||||||
|
{ |
||||||
|
if (acediaObjectClassToAllocate != none) { |
||||||
|
relevantPool = acediaObjectClassToAllocate.static._getPool(); |
||||||
|
} |
||||||
|
// `relevantPool == none` is expected if object / actor of is setup to |
||||||
|
// not use object pools. |
||||||
|
if (relevantPool != none) { |
||||||
|
allocatedObject = relevantPool.Fetch(); |
||||||
|
} |
||||||
|
} |
||||||
|
// If pools did not work - spawn / create object through regular methods |
||||||
|
if (allocatedObject == none) |
||||||
|
{ |
||||||
|
actorClassToAllocate = class<Actor>(classToAllocate); |
||||||
|
if (actorClassToAllocate != none) |
||||||
|
{ |
||||||
|
core = class'ServerLevelCore'.static.GetInstance(); |
||||||
|
if (core == none) { |
||||||
|
core = class'ClientLevelCore'.static.GetInstance(); |
||||||
|
} |
||||||
|
allocatedObject = core.Spawn(actorClassToAllocate); |
||||||
|
} |
||||||
|
else { |
||||||
|
allocatedObject = (new classToAllocate); |
||||||
|
} |
||||||
|
} |
||||||
|
// Call constructors |
||||||
|
if (acediaObjectClassToAllocate != none) { |
||||||
|
AcediaObject(allocatedObject)._constructor(); |
||||||
|
} |
||||||
|
if (acediaActorClassToAllocate != none) |
||||||
|
{ |
||||||
|
// Call it here, just in case, to make sure constructor is called |
||||||
|
// as soon as possible |
||||||
|
AcediaActor(allocatedObject)._constructor(); |
||||||
|
} |
||||||
|
return allocatedObject; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new `Object` of a given class using its `BaseText` |
||||||
|
* respresentation. |
||||||
|
* |
||||||
|
* For `AcediaObject`s calls constructors and tries (uses them only if they |
||||||
|
* aren't forbidden for a given class) to make use of their classes' object |
||||||
|
* pools. |
||||||
|
* |
||||||
|
* If Acedia's object does make use of object pools, - |
||||||
|
* guarantees to return last pooled object (in a LIFO queue), |
||||||
|
* unless `forceNewInstance` is set to `true`. |
||||||
|
* @see `Allocate()`, `AllocateByReference_S()` |
||||||
|
* |
||||||
|
* @param refToClassToAllocate `BaseText` representation of the class' name |
||||||
|
* of the `Object` that this method will create. Must not be subclass of |
||||||
|
* `Actor`. |
||||||
|
* @param forceNewInstance Set this to `true` if you require this method to |
||||||
|
* create a new instance, bypassing any object pools. |
||||||
|
* @return Newly created object. Will only be `none` if: |
||||||
|
* 1. `classToAllocate` is `none`; |
||||||
|
* 2. `classToAllocate` is abstract; |
||||||
|
* 3. `classToAllocate` is derived from `Actor`. |
||||||
|
*/ |
||||||
|
public final function Object AllocateByReference( |
||||||
|
BaseText refToClassToAllocate, |
||||||
|
optional bool forceNewInstance) |
||||||
|
{ |
||||||
|
return Allocate(LoadClass(refToClassToAllocate), forceNewInstance); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new `Object` of a given class using its `string` |
||||||
|
* respresentation. |
||||||
|
* |
||||||
|
* For `AcediaObject`s calls constructors and tries (uses them only if they |
||||||
|
* aren't forbidden for a given class) to make use of their classes' object |
||||||
|
* pools. |
||||||
|
* |
||||||
|
* If Acedia's object does make use of object pools, - |
||||||
|
* guarantees to return last pooled object (in a LIFO queue), |
||||||
|
* unless `forceNewInstance` is set to `true`. |
||||||
|
* |
||||||
|
* @see `Allocate()`, `AllocateByReference()` |
||||||
|
* |
||||||
|
* @param refToClassToAllocate `string` representation of the class' name |
||||||
|
* of the `Object` that this method will create. Must not be subclass of |
||||||
|
* `Actor`. |
||||||
|
* @param forceNewInstance Set this to `true` if you require this method to |
||||||
|
* create a new instance, bypassing any object pools. |
||||||
|
* @return Newly created object. Will only be `none` if: |
||||||
|
* 1. `classToAllocate` is `none`; |
||||||
|
* 2. `classToAllocate` is abstract; |
||||||
|
* 3. `classToAllocate` is derived from `Actor`. |
||||||
|
*/ |
||||||
|
public final function Object AllocateByReference_S( |
||||||
|
string refToClassToAllocate, |
||||||
|
optional bool forceNewInstance) |
||||||
|
{ |
||||||
|
return Allocate(LoadClassS(refToClassToAllocate), forceNewInstance); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Releases one reference to a given `AcediaObject`, calling its finalizers in |
||||||
|
* case all references were released. |
||||||
|
* |
||||||
|
* Method will attempt to store `objectToDeallocate` in its object pool once |
||||||
|
* deallocated, unless it is forbidden by its class' settings. |
||||||
|
* |
||||||
|
* @see `FreeMany()` |
||||||
|
* |
||||||
|
* @param objectToDeallocate Object that to deallocate. |
||||||
|
*/ |
||||||
|
public final function Free(Object objectToDeallocate) |
||||||
|
{ |
||||||
|
// TODO: this is an old code require while we still didn't get rid of |
||||||
|
// services - replace it later, changing argument to `AcediaObject` |
||||||
|
local AcediaObjectPool relevantPool; |
||||||
|
local Actor objectAsActor; |
||||||
|
local AcediaActor objectAsAcediaActor; |
||||||
|
local AcediaObject objectAsAcediaObject; |
||||||
|
|
||||||
|
if (objectToDeallocate == none) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// Call finalizers for Acedia's objects and actors |
||||||
|
objectAsAcediaObject = AcediaObject(objectToDeallocate); |
||||||
|
objectAsAcediaActor = AcediaActor(objectToDeallocate); |
||||||
|
if (objectAsAcediaObject != none) |
||||||
|
{ |
||||||
|
if (!objectAsAcediaObject.IsAllocated()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
objectAsAcediaObject._deref(); |
||||||
|
if (objectAsAcediaObject._getRefCount() > 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
relevantPool = objectAsAcediaObject._getPool(); |
||||||
|
objectAsAcediaObject._finalizer(); |
||||||
|
} |
||||||
|
if (objectAsAcediaActor != none) |
||||||
|
{ |
||||||
|
if (!objectAsAcediaActor.IsAllocated()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
objectAsAcediaActor._deref(); |
||||||
|
if (objectAsAcediaActor._getRefCount() > 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
objectAsAcediaActor._finalizer(); |
||||||
|
} |
||||||
|
// Try to store freed object in a pool |
||||||
|
if (relevantPool != none && relevantPool.Store(objectAsAcediaObject)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// Otherwise destroy actors and forget about objects |
||||||
|
objectAsActor = Actor(objectToDeallocate); |
||||||
|
if (objectAsActor != none) { |
||||||
|
objectAsActor.Destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Releases one reference to each `AcediaObject` inside the given array |
||||||
|
* `objectsToDelete`, calling finalizers for the ones that got all of their |
||||||
|
* references released. |
||||||
|
* |
||||||
|
* Method will attempt to store objects inside `objectsToDelete` in their |
||||||
|
* object pools, unless it is forbidden by their class' settings. |
||||||
|
* |
||||||
|
* @see `Free()` |
||||||
|
* |
||||||
|
* @param objectToDeallocate Object that to deallocate. |
||||||
|
*/ |
||||||
|
public final function FreeMany(array<Object> objectsToDelete) |
||||||
|
{ |
||||||
|
// TODO: this is an old code require while we still didn't get rid of |
||||||
|
// services - replace it later, changing argument to `AcediaObject` |
||||||
|
local int i; |
||||||
|
|
||||||
|
for (i = 0; i < objectsToDelete.length; i += 1) { |
||||||
|
Free(objectsToDelete[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Forces Unreal Engine to perform garbage collection. |
||||||
|
* By default also cleans up all of the Acedia's objects pools. |
||||||
|
* |
||||||
|
* Process of garbage collection causes significant lag spike during the game |
||||||
|
* and should be used sparingly and at right moments. |
||||||
|
* |
||||||
|
* If not `LevelCore` was setup, Acedia doesn't have access to the level and |
||||||
|
* cannot perform garbage collection, meaning that this method can fail. |
||||||
|
* |
||||||
|
* @param keepAcediaPools Set this to `true` to NOT garbage collect |
||||||
|
* objects inside pools. Otherwise keep it `false`. |
||||||
|
* Pools won't be dropped regardless of this parameter if no `LevelCore` is |
||||||
|
* found. |
||||||
|
* @return `true` if garbage collection successfully happened and `false` if it |
||||||
|
* failed. Garbage colelction can only fail if no `LevelCore` was yet |
||||||
|
* setup. |
||||||
|
*/ |
||||||
|
public final function bool CollectGarbage(optional bool keepAcediaPools) |
||||||
|
{ |
||||||
|
local LevelCore core; |
||||||
|
|
||||||
|
// Try to find level core |
||||||
|
core = class'ServerLevelCore'.static.GetInstance(); |
||||||
|
if (core == none) { |
||||||
|
core = class'ClientLevelCore'.static.GetInstance(); |
||||||
|
} |
||||||
|
if (core == none) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// Drop content of all `AcediaObjectPools` first |
||||||
|
if (!keepAcediaPools) { |
||||||
|
DropPools(); |
||||||
|
} |
||||||
|
// This makes Unreal Engine do garbage collection |
||||||
|
core.ConsoleCommand("obj garbage"); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Registers new object pool to auto-clean before Acedia's garbage collection. |
||||||
|
* |
||||||
|
* @param newPool New object pool that can get cleaned if `CollectGarbage()` |
||||||
|
* is called with appropriate parameters. |
||||||
|
* @return `true` if `newPool` was registered, |
||||||
|
* `false` if `newPool == none` or was already registered. |
||||||
|
*/ |
||||||
|
public final function bool RegisterNewPool(AcediaObjectPool newPool) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
|
||||||
|
if (newPool == none) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
registeredPools = default.registeredPools; |
||||||
|
for (i = 0; i < registeredPools.length; i += 1) |
||||||
|
{ |
||||||
|
if (registeredPools[i] == newPool) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
registeredPools[registeredPools.length] = newPool; |
||||||
|
default.registeredPools = registeredPools; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// Forgets about all stored object references in registered object pools |
||||||
|
private final function DropPools() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
registeredPools = default.registeredPools; |
||||||
|
for (i = 0; i < registeredPools.length; i += 1) |
||||||
|
{ |
||||||
|
if (registeredPools[i] == none) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
registeredPools[i].Clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -1,7 +1,7 @@ |
|||||||
/** |
/** |
||||||
* Set of tests related to `MemoryAPI` class and the chain of events related to |
* Set of tests related to `MemoryAPI` class and the chain of events related to |
||||||
* creating/destroying Acedia's objects / actors. |
* creating/destroying Acedia's objects / actors. |
||||||
* Copyright 2020 - 2021 Anton Tarasenko |
* Copyright 2020-2022 Anton Tarasenko |
||||||
*------------------------------------------------------------------------------ |
*------------------------------------------------------------------------------ |
||||||
* This file is part of Acedia. |
* This file is part of Acedia. |
||||||
* |
* |
@ -1,289 +0,0 @@ |
|||||||
/** |
|
||||||
* API that provides functions for managing objects and actors that make |
|
||||||
* use of Acedia's object pools and perform Acedia-specific initialization and |
|
||||||
* cleanup procedures. |
|
||||||
* This API's functions should be used for all Acedia's objects and actors. |
|
||||||
* Copyright 2020 - 2021 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 AcediaObject; |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a class instance from its `Text` representation. |
|
||||||
* |
|
||||||
* Does not generate log messages upon failure. |
|
||||||
* |
|
||||||
* @param classReference Text representation of the class to return. |
|
||||||
* @return Loaded class, corresponding to its name from `classReference`. |
|
||||||
*/ |
|
||||||
public final function class<Object> LoadClass(BaseText classReference) |
|
||||||
{ |
|
||||||
if (classReference == none) { |
|
||||||
return none; |
|
||||||
} |
|
||||||
return class<Object>( DynamicLoadObject(classReference.ToString(), |
|
||||||
class'Class', true)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a class instance from its `string` representation. |
|
||||||
* |
|
||||||
* Does not generate log messages upon failure. |
|
||||||
* |
|
||||||
* @param classReference `string` representation of the class to return. |
|
||||||
* @return Loaded class, corresponding to its name from `classReference`. |
|
||||||
*/ |
|
||||||
public final function class<Object> LoadClassS(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`). |
|
||||||
* |
|
||||||
* For Acedia's objects / actors calls constructors. |
|
||||||
* For Acedia's objects tries to make use of their object pools. |
|
||||||
* |
|
||||||
* If Acedia's object does make use of object pools, - |
|
||||||
* guarantees to return last pooled object (in a LIFO queue), |
|
||||||
* unless `forceNewInstance == true`. |
|
||||||
* |
|
||||||
* @param classToAllocate Class of the `Object` / `Actor` that this method |
|
||||||
* must create. |
|
||||||
* @param forceNewInstance Set this to `true` if you require this method to |
|
||||||
* create a new instance, bypassing any object pools. |
|
||||||
* @return Newly created object, |
|
||||||
* `none` if creation has failed (only possible for actors). |
|
||||||
*/ |
|
||||||
public final function Object Allocate( |
|
||||||
class<Object> classToAllocate, |
|
||||||
optional bool forceNewInstance) |
|
||||||
{ |
|
||||||
local LevelCore core; |
|
||||||
local Object allocatedObject; |
|
||||||
local AcediaObjectPool relevantPool; |
|
||||||
local class<AcediaObject> acediaObjectClassToAllocate; |
|
||||||
local class<AcediaActor> acediaActorClassToAllocate; |
|
||||||
local class<Actor> actorClassToAllocate; |
|
||||||
if (classToAllocate == none) { |
|
||||||
return none; |
|
||||||
} |
|
||||||
// Try using pool first (only if new instance is not required) |
|
||||||
acediaObjectClassToAllocate = class<AcediaObject>(classToAllocate); |
|
||||||
acediaActorClassToAllocate = class<AcediaActor>(classToAllocate); |
|
||||||
if (!forceNewInstance) |
|
||||||
{ |
|
||||||
if (acediaObjectClassToAllocate != none) { |
|
||||||
relevantPool = acediaObjectClassToAllocate.static._getPool(); |
|
||||||
} |
|
||||||
// `relevantPool == none` is expected if object / actor of is setup to |
|
||||||
// not use object pools. |
|
||||||
if (relevantPool != none) { |
|
||||||
allocatedObject = relevantPool.Fetch(); |
|
||||||
} |
|
||||||
} |
|
||||||
// If pools did not work - spawn / create object through regular methods |
|
||||||
if (allocatedObject == none) |
|
||||||
{ |
|
||||||
actorClassToAllocate = class<Actor>(classToAllocate); |
|
||||||
if (actorClassToAllocate != none) |
|
||||||
{ |
|
||||||
core = class'ServerLevelCore'.static.GetInstance(); |
|
||||||
if (core == none) { |
|
||||||
core = class'ClientLevelCore'.static.GetInstance(); |
|
||||||
} |
|
||||||
allocatedObject = core.Spawn(actorClassToAllocate); |
|
||||||
} |
|
||||||
else { |
|
||||||
allocatedObject = (new classToAllocate); |
|
||||||
} |
|
||||||
} |
|
||||||
// Call constructors |
|
||||||
if (acediaObjectClassToAllocate != none) { |
|
||||||
AcediaObject(allocatedObject)._constructor(); |
|
||||||
} |
|
||||||
if (acediaActorClassToAllocate != none) |
|
||||||
{ |
|
||||||
// Call it here, just in case, to make sure constructor is called |
|
||||||
// as soon as possible |
|
||||||
AcediaActor(allocatedObject)._constructor(); |
|
||||||
} |
|
||||||
return allocatedObject; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new `Object` / `Actor` of a class, given by its |
|
||||||
* string representation. |
|
||||||
* |
|
||||||
* If uses a proper spawning mechanism for both objects (`new`) |
|
||||||
* and actors (`Spawn`). |
|
||||||
* |
|
||||||
* For Acedia's objects / actors calls constructors. |
|
||||||
* For Acedia's objects tries to make use of their object pools. |
|
||||||
* |
|
||||||
* If Acedia's object does make use of object pools, - |
|
||||||
* guarantees to return last pooled object (in a LIFO queue), |
|
||||||
* unless `forceNewInstance == true`. |
|
||||||
* |
|
||||||
* @param refToClassToAllocate `BaseText` representation of the class of |
|
||||||
* the `Object` / `Actor` that this method must create. |
|
||||||
* @param forceNewInstance Set this to `true` if you require this method to |
|
||||||
* create a new instance, bypassing any object pools. |
|
||||||
* @return Newly created object, |
|
||||||
* `none` if creation has failed (only possible for actors). |
|
||||||
*/ |
|
||||||
public final function Object AllocateByReference( |
|
||||||
BaseText refToClassToAllocate, |
|
||||||
optional bool forceNewInstance) |
|
||||||
{ |
|
||||||
return Allocate(LoadClass(refToClassToAllocate), forceNewInstance); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new `Object` / `Actor` of a class, given by its |
|
||||||
* string representation. |
|
||||||
* |
|
||||||
* If uses a proper spawning mechanism for both objects (`new`) |
|
||||||
* and actors (`Spawn`). |
|
||||||
* |
|
||||||
* For Acedia's objects / actors calls constructors. |
|
||||||
* For Acedia's objects tries to make use of their object pools. |
|
||||||
* |
|
||||||
* If Acedia's object does make use of object pools, - |
|
||||||
* guarantees to return last pooled object (in a LIFO queue), |
|
||||||
* unless `forceNewInstance == true`. |
|
||||||
* |
|
||||||
* @param classToAllocate `string` representation of the class of |
|
||||||
* the `Object` / `Actor` that this method must create. |
|
||||||
* @param forceNewInstance Set this to `true` if you require this method to |
|
||||||
* create a new instance, bypassing any object pools. |
|
||||||
* @return Newly created object, |
|
||||||
* `none` if creation has failed (only possible for actors). |
|
||||||
*/ |
|
||||||
public final function Object AllocateByReferenceS( |
|
||||||
string refToClassToAllocate, |
|
||||||
optional bool forceNewInstance) |
|
||||||
{ |
|
||||||
return Allocate(LoadClassS(refToClassToAllocate), forceNewInstance); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Deallocates given `Object` / `Actor` resource, calling finalizers for |
|
||||||
* Acedia's objects and actors. |
|
||||||
* |
|
||||||
* If Acedia's object is passed, method will try to store it |
|
||||||
* in an object pool. |
|
||||||
* |
|
||||||
* Actors will be destroyed. |
|
||||||
* |
|
||||||
* @param objectToDeallocate `Object` / `Actor` to deallocate. |
|
||||||
*/ |
|
||||||
public final function Free(Object objectToDeallocate) |
|
||||||
{ |
|
||||||
local AcediaObjectPool relevantPool; |
|
||||||
local Actor objectAsActor; |
|
||||||
local AcediaActor objectAsAcediaActor; |
|
||||||
local AcediaObject objectAsAcediaObject; |
|
||||||
if (objectToDeallocate == none) { |
|
||||||
return; |
|
||||||
} |
|
||||||
// Call finalizers for Acedia's objects and actors |
|
||||||
objectAsAcediaObject = AcediaObject(objectToDeallocate); |
|
||||||
objectAsAcediaActor = AcediaActor(objectToDeallocate); |
|
||||||
if (objectAsAcediaObject != none) |
|
||||||
{ |
|
||||||
if (!objectAsAcediaObject.IsAllocated()) { |
|
||||||
return; |
|
||||||
} |
|
||||||
objectAsAcediaObject._deref(); |
|
||||||
if (objectAsAcediaObject._getRefCount() > 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
relevantPool = objectAsAcediaObject._getPool(); |
|
||||||
objectAsAcediaObject._finalizer(); |
|
||||||
} |
|
||||||
if (objectAsAcediaActor != none) |
|
||||||
{ |
|
||||||
if (!objectAsAcediaActor.IsAllocated()) { |
|
||||||
return; |
|
||||||
} |
|
||||||
objectAsAcediaActor._deref(); |
|
||||||
if (objectAsAcediaActor._getRefCount() > 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
objectAsAcediaActor._finalizer(); |
|
||||||
} |
|
||||||
// Try to store freed object in a pool |
|
||||||
if (relevantPool != none && relevantPool.Store(objectAsAcediaObject)) { |
|
||||||
return; |
|
||||||
} |
|
||||||
// Otherwise destroy actors and forget about objects |
|
||||||
objectAsActor = Actor(objectToDeallocate); |
|
||||||
if (objectAsActor != none) { |
|
||||||
objectAsActor.Destroy(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Frees given array of `Object` / `Actor` resources. |
|
||||||
* |
|
||||||
* If Acedia's object or actor is contained in the passed array, |
|
||||||
* method will try to store it in an object pool. |
|
||||||
* |
|
||||||
* @param objectsToDelete `Object` / `Actor` that must be freed. |
|
||||||
*/ |
|
||||||
public final function FreeMany(array<Object> objectsToDelete) |
|
||||||
{ |
|
||||||
local int i; |
|
||||||
for (i = 0; i < objectsToDelete.length; i += 1) { |
|
||||||
Free(objectsToDelete[i]); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Forces Unreal Engine to perform garbage collection. |
|
||||||
* By default also cleans up all the objects pools registered in |
|
||||||
* `MemoryService`, which includes all of the pools for |
|
||||||
* Acedia's built-in classes. |
|
||||||
* |
|
||||||
* Process of garbage collection causes significant lag spike during the game |
|
||||||
* and should be used sparingly and at right moments.. |
|
||||||
* |
|
||||||
* @param keepAcediaPools Set this to `true` to NOT garbage collect |
|
||||||
* objects in a borrow pool. Otherwise keep it `false`. |
|
||||||
*/ |
|
||||||
public final function CollectGarbage(optional bool keepAcediaPools) |
|
||||||
{ |
|
||||||
local MemoryService service; |
|
||||||
// Drop content of all `AcediaObjectPools` first |
|
||||||
if (!keepAcediaPools) |
|
||||||
{ |
|
||||||
service = MemoryService(class'MemoryService'.static.Require()); |
|
||||||
if (service != none) { |
|
||||||
service.ClearAll(); |
|
||||||
} |
|
||||||
} |
|
||||||
// This makes Unreal Engine do garbage collection |
|
||||||
class'ServerLevelCore'.static.GetInstance().ConsoleCommand("obj garbage"); |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties |
|
||||||
{ |
|
||||||
} |
|
@ -1,67 +0,0 @@ |
|||||||
/** |
|
||||||
* This service is meant to perform auxiliary functions for `MemoryAPI`. |
|
||||||
* It's main task is to keep track of all `AcediaObjectPool`s to force them to |
|
||||||
* get rid of object references before garbage collection. |
|
||||||
* Copyright 2020 - 2021 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 MemoryService extends Service; |
|
||||||
|
|
||||||
var private array<AcediaObjectPool> registeredPools; |
|
||||||
|
|
||||||
/** |
|
||||||
* Registers new object pool to auto-clean before Acedia's garbage collection. |
|
||||||
* |
|
||||||
* Registered `AcediaObjectPool`s will persist even if `MemoryService` is |
|
||||||
* destroyed and re-created. |
|
||||||
* |
|
||||||
* @param newPool Pool that service must clean during a `ClearAll()` call. |
|
||||||
* @return `true` if `newPool` was registered, |
|
||||||
* `false` if `newPool == none` or was already registered. |
|
||||||
*/ |
|
||||||
public final function bool RegisterNewPool(AcediaObjectPool newPool) |
|
||||||
{ |
|
||||||
local int i; |
|
||||||
if (newPool == none) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
registeredPools = default.registeredPools; |
|
||||||
for (i = 0; i < registeredPools.length; i += 1) { |
|
||||||
if (registeredPools[i] == newPool) return false; |
|
||||||
} |
|
||||||
registeredPools[registeredPools.length] = newPool; |
|
||||||
default.registeredPools = registeredPools; |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Clears all registered (via `RegisterNewPool()`) pools. |
|
||||||
*/ |
|
||||||
public final function ClearAll() |
|
||||||
{ |
|
||||||
local int i; |
|
||||||
registeredPools = default.registeredPools; |
|
||||||
for (i = 0; i < registeredPools.length; i += 1) |
|
||||||
{ |
|
||||||
if (registeredPools[i] == none) continue; |
|
||||||
registeredPools[i].Clear(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties |
|
||||||
{ |
|
||||||
} |
|
Loading…
Reference in new issue