From 1fc7006dc2c0df760fb3666c89cdaefa3683c366 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Mon, 11 Jul 2022 04:29:24 +0700 Subject: [PATCH] Refactor `MemoryAPI` Part of the refactoring to redo Acedia's loading and remove `Service`s. --- .../API/Memory}/AcediaObjectPool.uc | 0 sources/CoreRealm/API/Memory/MemoryAPI.uc | 434 ++++++++++++++++++ .../API}/Memory/Tests/MockActor.uc | 0 .../API}/Memory/Tests/MockObject.uc | 0 .../API}/Memory/Tests/MockObjectNoPool.uc | 0 .../API}/Memory/Tests/TEST_Memory.uc | 2 +- sources/Memory/MemoryAPI.uc | 289 ------------ sources/Memory/MemoryService.uc | 67 --- sources/Types/AcediaObject.uc | 9 +- 9 files changed, 438 insertions(+), 363 deletions(-) rename sources/{ => CoreRealm/API/Memory}/AcediaObjectPool.uc (100%) create mode 100644 sources/CoreRealm/API/Memory/MemoryAPI.uc rename sources/{ => CoreRealm/API}/Memory/Tests/MockActor.uc (100%) rename sources/{ => CoreRealm/API}/Memory/Tests/MockObject.uc (100%) rename sources/{ => CoreRealm/API}/Memory/Tests/MockObjectNoPool.uc (100%) rename sources/{ => CoreRealm/API}/Memory/Tests/TEST_Memory.uc (99%) delete mode 100644 sources/Memory/MemoryAPI.uc delete mode 100644 sources/Memory/MemoryService.uc diff --git a/sources/AcediaObjectPool.uc b/sources/CoreRealm/API/Memory/AcediaObjectPool.uc similarity index 100% rename from sources/AcediaObjectPool.uc rename to sources/CoreRealm/API/Memory/AcediaObjectPool.uc diff --git a/sources/CoreRealm/API/Memory/MemoryAPI.uc b/sources/CoreRealm/API/Memory/MemoryAPI.uc new file mode 100644 index 0000000..c63b7ba --- /dev/null +++ b/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 . + */ +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 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 LoadClass( + BaseText classReference) +{ + if (classReference == none) { + return none; + } + return class( + 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 LoadClass_S(string classReference) +{ + return class(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 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 acediaObjectClassToAllocate; + local class acediaActorClassToAllocate; + local class actorClassToAllocate; + + if (classToAllocate == none) { + return none; + } + // Try using pool first (only if new instance is not required) + acediaObjectClassToAllocate = class(classToAllocate); + acediaActorClassToAllocate = class(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(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 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 +{ +} \ No newline at end of file diff --git a/sources/Memory/Tests/MockActor.uc b/sources/CoreRealm/API/Memory/Tests/MockActor.uc similarity index 100% rename from sources/Memory/Tests/MockActor.uc rename to sources/CoreRealm/API/Memory/Tests/MockActor.uc diff --git a/sources/Memory/Tests/MockObject.uc b/sources/CoreRealm/API/Memory/Tests/MockObject.uc similarity index 100% rename from sources/Memory/Tests/MockObject.uc rename to sources/CoreRealm/API/Memory/Tests/MockObject.uc diff --git a/sources/Memory/Tests/MockObjectNoPool.uc b/sources/CoreRealm/API/Memory/Tests/MockObjectNoPool.uc similarity index 100% rename from sources/Memory/Tests/MockObjectNoPool.uc rename to sources/CoreRealm/API/Memory/Tests/MockObjectNoPool.uc diff --git a/sources/Memory/Tests/TEST_Memory.uc b/sources/CoreRealm/API/Memory/Tests/TEST_Memory.uc similarity index 99% rename from sources/Memory/Tests/TEST_Memory.uc rename to sources/CoreRealm/API/Memory/Tests/TEST_Memory.uc index e32b77e..3110edb 100644 --- a/sources/Memory/Tests/TEST_Memory.uc +++ b/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. * diff --git a/sources/Memory/MemoryAPI.uc b/sources/Memory/MemoryAPI.uc deleted file mode 100644 index 96c04df..0000000 --- a/sources/Memory/MemoryAPI.uc +++ /dev/null @@ -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 . - */ -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 LoadClass(BaseText classReference) -{ - if (classReference == none) { - return none; - } - return class( 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 LoadClassS(string classReference) -{ - return class(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 classToAllocate, - optional bool forceNewInstance) -{ - local LevelCore core; - local Object allocatedObject; - local AcediaObjectPool relevantPool; - local class acediaObjectClassToAllocate; - local class acediaActorClassToAllocate; - local class actorClassToAllocate; - if (classToAllocate == none) { - return none; - } - // Try using pool first (only if new instance is not required) - acediaObjectClassToAllocate = class(classToAllocate); - acediaActorClassToAllocate = class(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(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 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 -{ -} \ No newline at end of file diff --git a/sources/Memory/MemoryService.uc b/sources/Memory/MemoryService.uc deleted file mode 100644 index fbe30df..0000000 --- a/sources/Memory/MemoryService.uc +++ /dev/null @@ -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 . - */ -class MemoryService extends Service; - -var private array 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 -{ -} \ No newline at end of file diff --git a/sources/Types/AcediaObject.uc b/sources/Types/AcediaObject.uc index 6435836..340fb62 100644 --- a/sources/Types/AcediaObject.uc +++ b/sources/Types/AcediaObject.uc @@ -87,17 +87,14 @@ var protected const array 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; }