Browse Source

Refactor `MemoryAPI`

Part of the refactoring to redo Acedia's loading and remove `Service`s.
pull/8/head
Anton Tarasenko 2 years ago
parent
commit
1fc7006dc2
  1. 0
      sources/CoreRealm/API/Memory/AcediaObjectPool.uc
  2. 434
      sources/CoreRealm/API/Memory/MemoryAPI.uc
  3. 0
      sources/CoreRealm/API/Memory/Tests/MockActor.uc
  4. 0
      sources/CoreRealm/API/Memory/Tests/MockObject.uc
  5. 0
      sources/CoreRealm/API/Memory/Tests/MockObjectNoPool.uc
  6. 2
      sources/CoreRealm/API/Memory/Tests/TEST_Memory.uc
  7. 289
      sources/Memory/MemoryAPI.uc
  8. 67
      sources/Memory/MemoryService.uc
  9. 9
      sources/Types/AcediaObject.uc

0
sources/AcediaObjectPool.uc → sources/CoreRealm/API/Memory/AcediaObjectPool.uc

434
sources/CoreRealm/API/Memory/MemoryAPI.uc

@ -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
{
}

0
sources/Memory/Tests/MockActor.uc → sources/CoreRealm/API/Memory/Tests/MockActor.uc

0
sources/Memory/Tests/MockObject.uc → sources/CoreRealm/API/Memory/Tests/MockObject.uc

0
sources/Memory/Tests/MockObjectNoPool.uc → sources/CoreRealm/API/Memory/Tests/MockObjectNoPool.uc

2
sources/Memory/Tests/TEST_Memory.uc → sources/CoreRealm/API/Memory/Tests/TEST_Memory.uc

@ -1,7 +1,7 @@
/**
* Set of tests related to `MemoryAPI` class and the chain of events related to
* creating/destroying Acedia's objects / actors.
* Copyright 2020 - 2021 Anton Tarasenko
* Copyright 2020-2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*

289
sources/Memory/MemoryAPI.uc

@ -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
{
}

67
sources/Memory/MemoryService.uc

@ -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
{
}

9
sources/Types/AcediaObject.uc

@ -87,17 +87,14 @@ var protected const array<string> stringConstants;
*/
public final static function AcediaObjectPool _getPool()
{
local MemoryService service;
if (!default.usesObjectPool) {
return none;
}
if (default._objectPool == none) {
if (default._objectPool == none)
{
default._objectPool = new class'AcediaObjectPool';
default._objectPool.Initialize(default.class);
service = MemoryService(class'MemoryService'.static.Require());
if (service != none) {
service.RegisterNewPool(default._objectPool);
}
__().memory.RegisterNewPool(default._objectPool);
}
return default._objectPool;
}

Loading…
Cancel
Save