From e027c3cc53afbe48c2cc1981c17fa177865f41e9 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Tue, 7 Mar 2023 03:30:47 +0700 Subject: [PATCH] Refactor `MemoryAPI` to only work with `AcediaObject`s `MemoryAPI` is a part of base realm and should not work with `Actor`s. --- sources/Avarice/AvariceLink.uc | 6 +- .../BaseRealm/API/Memory/AcediaObjectPool.uc | 195 +++---- sources/BaseRealm/API/Memory/MemoryAPI.uc | 508 ++++++------------ .../BaseRealm/API/Memory/Tests/TEST_Memory.uc | 69 +-- sources/CoreRealm/CoreGlobal.uc | 12 + sources/Data/Collections/ArrayList.uc | 2 +- sources/Data/Collections/HashTable.uc | 2 +- .../BaseImplementation/EKFInventory.uc | 2 +- .../Unreal/GameRulesAPI/KF1_GameRulesAPI.uc | 2 +- .../Unreal/InventoryAPI/InventoryService.uc | 2 +- .../Unreal/Tests/TEST_ServerUnrealAPI.uc | 14 +- sources/Service.uc | 7 +- sources/Singleton.uc | 9 +- sources/Text/Tests/TEST_JSON.uc | 3 +- sources/Types/AcediaActor.uc | 65 --- sources/Types/Tests/TEST_ActorService.uc | 12 +- 16 files changed, 308 insertions(+), 602 deletions(-) diff --git a/sources/Avarice/AvariceLink.uc b/sources/Avarice/AvariceLink.uc index 4098a6e..21ceb17 100644 --- a/sources/Avarice/AvariceLink.uc +++ b/sources/Avarice/AvariceLink.uc @@ -237,10 +237,14 @@ private final function Avarice_OnMessage_Signal GetServiceSignal( public final function StartUp() { local AvariceTcpStream newStream; + local LevelCore core; + if (tcpStream == none) return; if (tcpStream.Get() != none) return; + core = __core().GetLevelCore(); + if (core == none) return; - newStream = AvariceTcpStream(_.memory.Allocate(class'AvariceTcpStream')); + newStream = AvariceTcpStream(core.Allocate(class'AvariceTcpStream')); if (newStream == none) { // `linkName` has to be defined if `tcpStream` is defined diff --git a/sources/BaseRealm/API/Memory/AcediaObjectPool.uc b/sources/BaseRealm/API/Memory/AcediaObjectPool.uc index a4f7d4c..1f41f8b 100644 --- a/sources/BaseRealm/API/Memory/AcediaObjectPool.uc +++ b/sources/BaseRealm/API/Memory/AcediaObjectPool.uc @@ -1,9 +1,8 @@ /** - * Acedia's implementation for object pool that can only store objects of - * one specific class to allow for both faster allocation and - * faster deallocation. - * Allows to set a maximum capacity. - * Copyright 2020-2021 Anton Tarasenko + * Author: dkanus + * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore + * License: GPL + * Copyright 2020-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -23,67 +22,41 @@ class AcediaObjectPool extends Object config(AcediaSystem); -// Class of objects that this `AcediaObjectPool` stores. -// if `== none`, - object pool is considered uninitialized. -var private class storedClass; -// Actual storage, functions on LIFO principle. -var public array objectPool; +//! Acedia's implementation for object pool. +//! +//! Unlike generic built in [`Engine::ObjectPool`], that can only store objects of one specific +//! class, it specializes in a single class to allow for both faster allocation and +//! faster deallocation (we don't need to look for an object of particular class to return +//! an unused instance). +//! +//! Allows to set a maximum capacity in a config. -// This struct and it's associated array `poolSizeOverwrite` allows -// server admins to rewrite the pool capacity for each class. -struct PoolSizeSetting -{ +/// Represents config entry about pool capacity. +/// +/// This struct and it's associated array [`poolSizeOverwrite`] allows server admins to rewrite +/// the pool capacity for each class. +struct PoolSizeSetting { var class objectClass; - var int maxPoolSize; + var int maxPoolSize; }; + var private config const array poolSizeOverwrite; -// Capacity for object pool that we are using. -// Set during initialization and cannot be changed later. +// Class of objects that this `AcediaObjectPool` stores. +// if `== none`, - object pool is considered uninitialized. +var private class storedClass; + +/// Capacity for object pool that we are using. +/// Obtained from [`poolSizeOverwrite`] during initialization and cannot be changed later. var private int usedMaxPoolSize; -/** - * Initialize caller object pool to store objects of `initStoredClass` class. - * - * If successful, this action is irreversible: same pool cannot be - * re-initialized. - * - * @param initStoredClass Class of objects that caller object pool will store. - * @param forcedPoolSize Max pool size for the caller `AcediaObjectPool`. - * Leaving it at default `0` value will cause method to auto-determine - * the size: gives priority to the `poolSizeOverwrite` config array; - * if not specified, uses `AcediaObject`'s `defaultMaxPoolSize` - * (ignoring `usesObjectPool` setting). - * @return `true` if initialization completed, `false` otherwise - * (including if it was already completed with passed `initStoredClass`). - */ -public final function bool Initialize( - class initStoredClass, - optional int forcedPoolSize) -{ - if (storedClass != none) return false; - if (initStoredClass == none) return false; +// Actual storage, functions on LIFO principle. +var private array objectPool; - // If does not matter that we've set those variables until - // we set `storedClass`. - if (forcedPoolSize == 0) { - usedMaxPoolSize = GetMaxPoolSizeForClass(initStoredClass); - } - else { - usedMaxPoolSize = forcedPoolSize; - } - if (usedMaxPoolSize == 0) { - return false; - } - storedClass = initStoredClass; - return true; -} - -// Determines default object pool size for the initialization. -private final function int GetMaxPoolSizeForClass( - class classToCheck) -{ +// Determines default object pool size for the initialization. +private final function int GetMaxPoolSizeForClass(class classToCheck) { local int i; local int result; + if (classToCheck != none) { result = classToCheck.default.defaultMaxPoolSize; } @@ -91,10 +64,8 @@ private final function int GetMaxPoolSizeForClass( result = -1; } // Try to replace it with server's settings - for (i = 0; i < poolSizeOverwrite.length; i += 1) - { - if (poolSizeOverwrite[i].objectClass == classToCheck) - { + for (i = 0; i < poolSizeOverwrite.length; i += 1) { + if (poolSizeOverwrite[i].objectClass == classToCheck) { result = poolSizeOverwrite[i].maxPoolSize; break; } @@ -102,45 +73,63 @@ private final function int GetMaxPoolSizeForClass( return result; } -/** - * Returns class of objects inside the caller `AcediaObjectPool`. - * - * @return class of objects inside caller the caller object pool; - * `none` means object pool was not initialized. - */ -public final function class GetClassOfStoredObjects() -{ +/// Initializes caller object pool to store objects of the given class. +/// +/// Returns `true` if initialization completed, `false` otherwise (including if it was already +/// completed with passed [`classToStore`]). +/// +/// If successful, this action is irreversible: same pool cannot be re-initialized. +/// +/// [`forcedPoolSize`] defines max pool size for the caller [`AcediaObjectPool`]. +/// Leaving it at default `0` value will cause method to auto-determine the size: gives priority to +/// the [`poolSizeOverwrite`] config array; if not specified, uses [`AcediaObject`]'s +/// [`AcediaObject::defaultMaxPoolSize`] (ignoring [`AcediaObject::usesObjectPool`] setting). +public final function bool Initialize( + class classToStore, + optional int forcedPoolSize +) { + if (storedClass != none) return false; + if (classToStore == none) return false; + + // If does not matter that we've set those variables until + // we set `storedClass`. + if (forcedPoolSize == 0) { + usedMaxPoolSize = GetMaxPoolSizeForClass(classToStore); + } + else { + usedMaxPoolSize = forcedPoolSize; + } + if (usedMaxPoolSize == 0) { + return false; + } + storedClass = classToStore; + return true; +} + +/// Returns class of objects stored inside the caller [`AcediaObjectPool`]. +/// +/// `none` means object pool was not initialized. +public final function class GetClassOfStoredObjects() { return storedClass; } -/** - * Clear the storage of all it's contents. - * - * Can be used before UnrealEngine's garbage collection to free pooled objects. - */ -public final function Clear() -{ +/// Clear the storage of all its contents. +public final function Clear() { objectPool.length = 0; } -/** - * Adds object to the caller storage - * (that needs to be initialized to store `newObject.class` classes). - * - * For performance purposes does not do duplicates checks, - * this should be verified from outside `AcediaObjectPool`. - * - * Does type checks and only allows objects of the class that caller - * `AcediaObjectPool` was initialized for. - * - * @param newObject Object to put inside caller pool. Must be not `none` and - * have precisely the class this object pool was initialized to store. - * @return `true` on success and `false` on failure - * (can happen if passed `newObject` reference was invalid, caller storage - * is not initialized yet or reached it's capacity). - */ -public final function bool Store(AcediaObject newObject) -{ +/// Adds object to the caller storage (that needs to be initialized to store [`newObject.class`] +/// classes). +/// +/// Returns `true` on success and `false` on failure (can happen if passed [`newObject`] reference +/// was invalid, caller storage is not initialized yet or reached it's capacity). +/// +/// For performance purposes does not do duplicates checks, this should be verified from outside +/// [`AcediaObjectPool`]. +/// +/// Performs type checks and only allows objects of the class that caller [`AcediaObjectPool`] was +/// initialized for. +public final function bool Store(AcediaObject newObject) { if (newObject == none) return false; if (newObject.class != storedClass) return false; @@ -151,17 +140,12 @@ public final function bool Store(AcediaObject newObject) return true; } -/** - * Extracts last stored object from the pool. Returned object will no longer - * be stored in the pool. - * - * @return Reference to the last (not destroyed) stored object. - * Only returns `none` if caller `AcediaObjectPool` is either empty or - * not initialized. - */ -public final function AcediaObject Fetch() -{ +/// Returns last stored object from the pool, removing it from that pool in the process. +/// +/// Only returns `none` if caller `AcediaObjectPool` is either empty or not initialized. +public final function AcediaObject Fetch() { local AcediaObject result; + if (storedClass == none) return none; if (objectPool.length <= 0) return none; @@ -170,6 +154,5 @@ public final function AcediaObject Fetch() return result; } -defaultproperties -{ +defaultproperties { } \ No newline at end of file diff --git a/sources/BaseRealm/API/Memory/MemoryAPI.uc b/sources/BaseRealm/API/Memory/MemoryAPI.uc index 8082f98..da570ad 100644 --- a/sources/BaseRealm/API/Memory/MemoryAPI.uc +++ b/sources/BaseRealm/API/Memory/MemoryAPI.uc @@ -1,10 +1,8 @@ /** - * 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 + * Author: dkanus + * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore + * License: GPL + * Copyright 2020-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -23,325 +21,180 @@ */ 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 auxiliary 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`). - */ +//! API that provides functions for managing object of classes, derived from `AcediaObject`. +//! +//! 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 of +//! 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 auxiliary methods for either loading `class`es from their +//! `BaseText`/`string`-given names or even directly creating objects of said classes. +//! +//! ## 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 +// 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 function class LoadClass(BaseText classReference) -{ +/// Forgets about all stored (deallocated) object references in registered object pools. +protected function DropPools() { + local int i; + + registeredPools = default.registeredPools; + for (i = 0; i < registeredPools.length; i += 1) { + if (registeredPools[i] == none) { + continue; + } + registeredPools[i].Clear(); + } +} + +/// Creates a class instance from its `BaseText` representation. +/// +/// Does not generate log messages upon failure. +public function class LoadClass(BaseText classReference) { if (classReference == none) { return none; } - return class( - DynamicLoadObject(classReference.ToString(), - class'Class', - true)); + 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 function class LoadClass_S(string classReference) -{ +/// Creates a class instance from its `string` representation. +/// +/// Does not generate log messages upon failure. +public 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 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; +/// Creates a new `AcediaObject` of a given subclass using pool (if permitted by settings) and +/// calling its constructor. +/// +/// If Acedia's object does make use of object pools, this method guarantees to return last pooled +/// object (in a LIFO queue), unless `forceNewInstance` is set to `true`. +/// +/// Return value will only be `none` if `classToAllocate` is `none` or abstract. +public function AcediaObject Allocate( + class classToAllocate, + optional bool forceNewInstance +) { + local AcediaObject allocatedObject; + local AcediaObjectPool relevantPool; 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(); - } + // Try using pool first (but only if new instance is not required) + if (!forceNewInstance) { + relevantPool = classToAllocate.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(); + // If pools did not work - simply create object manually + if (allocatedObject == none) { + allocatedObject = (new classToAllocate); } + // Allocation through `new` cannot fail, so its safe to call constructor + allocatedObject._constructor(); return allocatedObject; } -/** - * Creates a new `Object` of a given class using its `BaseText` - * representation. - * - * 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 function Object AllocateByReference( - BaseText refToClassToAllocate, - optional bool forceNewInstance) -{ - return Allocate(LoadClass(refToClassToAllocate), forceNewInstance); +/// Creates a new `AcediaObject` of a given subclass using pool (if permitted by settings) and +/// calling its constructor. +/// +/// If Acedia's object does make use of object pools, this method guarantees to return last pooled +/// object (in a LIFO queue), unless `forceNewInstance` is set to `true`. +/// +/// Return value will only be `none` if `refToClassToAllocate` is `none`, doesn't refer to +/// an existing class or refers to an abstract class. +public function AcediaObject AllocateByReference( + BaseText refToClassToAllocate, + optional bool forceNewInstance +) { + local class classToAllocate; + + classToAllocate = LoadClass(refToClassToAllocate); + return Allocate(class(classToAllocate), forceNewInstance); } -/** - * Creates a new `Object` of a given class using its `string` - * representation. - * - * 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 function Object AllocateByReference_S( - string refToClassToAllocate, - optional bool forceNewInstance) -{ - return Allocate(LoadClass_S(refToClassToAllocate), forceNewInstance); +/// Creates a new `AcediaObject` of a given subclass using pool (if permitted by settings) and +/// calling its constructor. +/// +/// If Acedia's object does make use of object pools, this method guarantees to return last pooled +/// object (in a LIFO queue), unless `forceNewInstance` is set to `true`. +/// +/// Return value will only be `none` if `refToClassToAllocate` is `none`, doesn't refer to +/// an existing class or refers to an abstract class. +public function AcediaObject AllocateByReference_S( + string refToClassToAllocate, + optional bool forceNewInstance +) { + local class classToAllocate; + + classToAllocate = LoadClass_S(refToClassToAllocate); + return Allocate(class(classToAllocate), forceNewInstance); } -/** - * Releases one reference to a given `AcediaObject`, calling its finalizers in - * case all references were released. - * - * Method will attempt to store `objectToRelease` in its object pool once - * deallocated, unless it is forbidden by its class' settings. - * - * @see `FreeMany()` - * - * @param objectToRelease Object, which reference method needs to release. - */ -public function Free(Object objectToRelease) -{ - // 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; +/// Releases one reference to a given `AcediaObject`, calling its finalizers in case all references +/// were released. +/// +/// Method will attempt to store `objectToRelease` in its object pool once deallocated, unless it is +/// forbidden by its class' settings. +public function Free(AcediaObject objectToRelease) { + local AcediaObjectPool relevantPool; - if (objectToRelease == none) { - return; - } - // Call finalizers for Acedia's objects and actors - objectAsAcediaObject = AcediaObject(objectToRelease); - objectAsAcediaActor = AcediaActor(objectToRelease); - 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; + if (objectToRelease == none) return; + if (!objectToRelease.IsAllocated()) return; + + objectToRelease._deref(); + // Finalize object if all of its references are gone + if (objectToRelease._getRefCount() <= 0) { + relevantPool = objectToRelease._getPool(); + objectToRelease._finalizer(); + if (relevantPool != none) { + relevantPool.Store(objectToRelease); } - 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(objectToRelease); - if (objectAsActor != none) { - objectAsActor.Destroy(); } } -/** - * Releases one reference to each `AcediaObject` inside the given array - * `objectsToRelease`, calling finalizers for the ones that got all of their - * references released. - * - * Method will attempt to store objects inside `objectsToRelease` in their - * object pools, unless it is forbidden by their class' settings. - * - * @see `Free()` - * - * @param objectToRelease Array of objects, which reference method needs - * to release. - */ -public function FreeMany(array objectsToRelease) -{ - // TODO: this is an old code require while we still didn't get rid of - // services - replace it later, changing argument to `AcediaObject` +/// Releases one reference for each `AcediaObject` inside the given array `objectsToRelease`, +/// calling finalizers for the ones that got all of their references released. +/// +/// Method will attempt to store objects inside `objectsToRelease` in their object pools, unless it +/// is forbidden by their class' settings. +public function FreeMany(array objectsToRelease) { local int i; for (i = 0; i < objectsToRelease.length; i += 1) { @@ -349,26 +202,21 @@ public function FreeMany(array objectsToRelease) } } -/** - * 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 collection can only fail if no `LevelCore` was yet - * setup. - */ -public function bool CollectGarbage(optional bool keepAcediaPools) -{ +/// Forces engine to perform garbage collection. +/// +/// Process of manual garbage collection causes significant lag spike during the game and should be +/// used sparingly and at right moments. +/// +/// If no `LevelCore` was setup, Acedia doesn't have access to the level and cannot perform garbage +/// collection, meaning that this method can fail. +/// +/// By default also cleans up all of the Acedia's objects pools. +/// Set [`keepAcediaPools`] to `true` to NOT garbage collect objects inside pools. +/// Pools won't be dropped regardless of this parameter if no `LevelCore` is found. +/// +/// Returns `true` if garbage collection successfully happened and `false` if it failed. +/// Garbage collection can only fail if no `LevelCore` was yet setup. +public function bool /*unreal*/ CollectGarbage(optional bool keepAcediaPools) { local LevelCore core; // Try to find level core @@ -388,24 +236,18 @@ public function bool CollectGarbage(optional bool keepAcediaPools) 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 function bool RegisterNewPool(AcediaObjectPool newPool) -{ +/// Registers new object pool to auto-clean before Acedia's garbage collection. +/// +/// Returns `true` if `newPool` was registered and `false` if `newPool == none` or was already +/// registered. +public function bool RegisterNewPool(AcediaObjectPool newPool) { local int i; if (newPool == none) { return false; } registeredPools = default.registeredPools; - for (i = 0; i < registeredPools.length; i += 1) - { + for (i = 0; i < registeredPools.length; i += 1) { if (registeredPools[i] == newPool) { return false; } @@ -415,23 +257,5 @@ public function bool RegisterNewPool(AcediaObjectPool newPool) return true; } -/** - * Forgets about all stored (deallocated) object references in registered - * object pools. - */ -protected 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 -{ +defaultproperties { } \ No newline at end of file diff --git a/sources/BaseRealm/API/Memory/Tests/TEST_Memory.uc b/sources/BaseRealm/API/Memory/Tests/TEST_Memory.uc index 3110edb..8a71b1d 100644 --- a/sources/BaseRealm/API/Memory/Tests/TEST_Memory.uc +++ b/sources/BaseRealm/API/Memory/Tests/TEST_Memory.uc @@ -98,24 +98,20 @@ protected static function Test_ActorConstructorsFinalizers() Context("Testing that Acedia actor's constructors and finalizers are" @ "called properly."); Issue("Actor's constructor is not called."); - act1 = MockActor(__().memory.Allocate(class'MockActor')); + act1 = MockActor(__core().GetLevelCore().Allocate(class'MockActor')); TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - act2 = MockActor(__().memory.Allocate(class'MockActor')); + act2 = MockActor(__core().GetLevelCore().Allocate(class'MockActor')); TEST_ExpectTrue(class'MockActor'.default.actorCount == 2); Issue("Actor's finalizer is not called."); - __().memory.Free(act1); + act1.Destroy(); TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); Issue("`IsAllocated()` returns `false` for allocated actors."); TEST_ExpectTrue(act2.IsAllocated()); - Issue("Actor's finalizer is called for already freed object."); - __().memory.Free(act1); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - Issue("Actor's finalizer is not called."); - __().memory.Free(act2); + act2.Destroy(); TEST_ExpectTrue(class'MockActor'.default.actorCount == 0); } @@ -170,8 +166,6 @@ protected static function Test_RefCounting() Context("Testing usage of reference counting."); SubTest_RefCountingObjectFreeSelf(); SubTest_RefCountingObjectFree(); - SubTest_RefCountingActorFreeSelf(); - SubTest_RefCountingActorFree(); } protected static function SubTest_RefCountingObjectFreeSelf() @@ -220,61 +214,6 @@ protected static function SubTest_RefCountingObjectFree() TEST_ExpectFalse(temp.IsAllocated()); } -protected static function SubTest_RefCountingActorFreeSelf() -{ - local MockActor temp; - class'MockActor'.default.actorCount = 0; - Issue("Reference counting for `AcediaActor`s does not work correctly" - @ "with `FreeSelf()`"); - temp = MockActor(__().memory.Allocate(class'MockActor')); - temp.NewRef().NewRef().NewRef(); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - TEST_ExpectTrue(temp._getRefCount() == 4); - TEST_ExpectTrue(temp.IsAllocated()); - temp.FreeSelf(); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - TEST_ExpectTrue(temp._getRefCount() == 3); - TEST_ExpectTrue(temp.IsAllocated()); - temp.FreeSelf(); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - TEST_ExpectTrue(temp._getRefCount() == 2); - TEST_ExpectTrue(temp.IsAllocated()); - temp.FreeSelf(); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - TEST_ExpectTrue(temp._getRefCount() == 1); - TEST_ExpectTrue(temp.IsAllocated()); - temp.FreeSelf(); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 0); -} - -protected static function SubTest_RefCountingActorFree() -{ - local MockActor temp; - class'MockActor'.default.actorCount = 0; - Issue("Reference counting for `AcediaActor`s does not work correctly" - @ "with `Free()`"); - temp = MockActor(__().memory.Allocate(class'MockActor')); - temp.NewRef().NewRef().NewRef(); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - TEST_ExpectTrue(temp._getRefCount() == 4); - TEST_ExpectTrue(temp.IsAllocated()); - __().memory.Free(temp); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - TEST_ExpectTrue(temp._getRefCount() == 3); - TEST_ExpectTrue(temp.IsAllocated()); - __().memory.Free(temp); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - TEST_ExpectTrue(temp._getRefCount() == 2); - TEST_ExpectTrue(temp.IsAllocated()); - __().memory.Free(temp); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); - TEST_ExpectTrue(temp._getRefCount() == 1); - TEST_ExpectTrue(temp.IsAllocated()); - __().memory.Free(temp); - TEST_ExpectTrue(class'MockActor'.default.actorCount == 0); -} - - defaultproperties { caseGroup = "Memory" diff --git a/sources/CoreRealm/CoreGlobal.uc b/sources/CoreRealm/CoreGlobal.uc index a4aa24f..4b84aa9 100644 --- a/sources/CoreRealm/CoreGlobal.uc +++ b/sources/CoreRealm/CoreGlobal.uc @@ -38,6 +38,18 @@ public function UnrealAPI unreal_api() return none; } +public final static function LevelCore GetLevelCore() +{ + local LevelCore availableCore; + + availableCore = class'ServerLevelCore'.static.GetInstance(); + if (availableCore != none) { + return availableCore; + } + availableCore = class'ClientLevelCore'.static.GetInstance(); + return availableCore; +} + public final static function CoreGlobal GetGenericInstance() { local ServerGlobal serverAPI; diff --git a/sources/Data/Collections/ArrayList.uc b/sources/Data/Collections/ArrayList.uc index abc0a56..dc05f55 100644 --- a/sources/Data/Collections/ArrayList.uc +++ b/sources/Data/Collections/ArrayList.uc @@ -364,7 +364,7 @@ public final function ArrayList CreateItem( if (index < 0) return self; if (valueClass == none) return self; - newObject = AcediaObject(_.memory.Allocate(valueClass)); + newObject = _.memory.Allocate(valueClass); SetItem(index, newObject); _.memory.Free(newObject); return self; diff --git a/sources/Data/Collections/HashTable.uc b/sources/Data/Collections/HashTable.uc index 844f343..a19012e 100644 --- a/sources/Data/Collections/HashTable.uc +++ b/sources/Data/Collections/HashTable.uc @@ -449,7 +449,7 @@ public final function HashTable CreateItem( if (key == none) return self; if (valueClass == none) return self; - newItem = AcediaObject(_.memory.Allocate(valueClass)); + newItem = _.memory.Allocate(valueClass); SetItem(key, newItem); newItem.FreeSelf(); return self; diff --git a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc index cff004b..800005a 100644 --- a/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc +++ b/sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc @@ -331,7 +331,7 @@ public function EItem AddTemplate( if (newWeaponClass == none) { newItem = class'EKFUnknownItem'.static - .Wrap(Inventory(_.memory.Allocate(newInventoryClass))); + .Wrap(pawn.Spawn(newInventoryClass)); if (newItem != none && TryAddUnknownItem(newItem) != none) { return newItem; } diff --git a/sources/KFRealm/Server/Unreal/GameRulesAPI/KF1_GameRulesAPI.uc b/sources/KFRealm/Server/Unreal/GameRulesAPI/KF1_GameRulesAPI.uc index 89cbf67..9018262 100644 --- a/sources/KFRealm/Server/Unreal/GameRulesAPI/KF1_GameRulesAPI.uc +++ b/sources/KFRealm/Server/Unreal/GameRulesAPI/KF1_GameRulesAPI.uc @@ -173,7 +173,7 @@ public function GameRules Add(class newRulesClass) if (AreAdded(newRulesClass)) { return none; } - newGameRules = GameRules(_.memory.Allocate(newRulesClass)); + newGameRules = GameRules(__core().GetLevelCore().Allocate(newRulesClass)); _server.unreal.GetGameType().AddGameModifier(newGameRules); return newGameRules; } diff --git a/sources/KFRealm/Server/Unreal/InventoryAPI/InventoryService.uc b/sources/KFRealm/Server/Unreal/InventoryAPI/InventoryService.uc index 90898c5..85bcc94 100644 --- a/sources/KFRealm/Server/Unreal/InventoryAPI/InventoryService.uc +++ b/sources/KFRealm/Server/Unreal/InventoryAPI/InventoryService.uc @@ -33,7 +33,7 @@ public function Weapon AddWeaponWithAmmo( local Weapon newWeapon; local KFWeapon newKFWeapon; if (pawn == none) return none; - newWeapon = Weapon(_.memory.Allocate(weaponClassToAdd)); + newWeapon = Spawn(weaponClassToAdd); if (newWeapon == none) return none; // It is possible that `newWeapon` can get destroyed somewhere here, // so add two more checks diff --git a/sources/KFRealm/Server/Unreal/Tests/TEST_ServerUnrealAPI.uc b/sources/KFRealm/Server/Unreal/Tests/TEST_ServerUnrealAPI.uc index 922cd45..2eafefc 100644 --- a/sources/KFRealm/Server/Unreal/Tests/TEST_ServerUnrealAPI.uc +++ b/sources/KFRealm/Server/Unreal/Tests/TEST_ServerUnrealAPI.uc @@ -139,21 +139,21 @@ protected static function Test_InventoryChainFetching() // where A = `MockInventoryA` // a = `MockInventoryAChild` // B = `MockInventoryB` - chainStart = Inventory(__().memory.Allocate(class'MockInventoryAChild')); + chainStart = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryAChild')); chainEnd = chainStart; - chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryB')); + chainEnd.inventory = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryB')); chainEnd = chainEnd.inventory; - chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryA')); + chainEnd.inventory = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryA')); chainEnd = chainEnd.inventory; chainEnd.inventory = - Inventory(__().memory.Allocate(class'MockInventoryAChild')); + Inventory(__core().GetLevelCore().Allocate(class'MockInventoryAChild')); chainEnd = chainEnd.inventory; - chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryB')); + chainEnd.inventory = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryB')); chainEnd = chainEnd.inventory; chainEnd.inventory = - Inventory(__().memory.Allocate(class'MockInventoryAChild')); + Inventory(__core().GetLevelCore().Allocate(class'MockInventoryAChild')); chainEnd = chainEnd.inventory; - chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryA')); + chainEnd.inventory = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryA')); chainEnd = chainEnd.inventory; Context("Testing auxiliary methods for working with inventory chains."); SubTest_InventoryChainFetchingSingle(chainStart); diff --git a/sources/Service.uc b/sources/Service.uc index 776d472..125ebe0 100644 --- a/sources/Service.uc +++ b/sources/Service.uc @@ -31,12 +31,17 @@ var private LoggerAPI.Definition errNoService; public simulated static final function Service Require() { local Service newInstance; + local LevelCore core; if (IsRunning()) { return Service(GetInstance()); } + core = __core().GetLevelCore(); + if (core == none) { + return none; + } default.blockSpawning = false; - newInstance = Service(__().memory.Allocate(default.class)); + newInstance = Service(core.Allocate(default.class)); default.blockSpawning = true; if (newInstance == none) { __().logger.Auto(default.errNoService).ArgClass(default.class); diff --git a/sources/Singleton.uc b/sources/Singleton.uc index 560e7e1..9e7171d 100644 --- a/sources/Singleton.uc +++ b/sources/Singleton.uc @@ -41,14 +41,19 @@ public simulated final static function Singleton GetInstance( optional bool spawnIfMissing) { local bool instanceExists; + local LevelCore core; instanceExists = default.activeInstance != none && !default.activeInstance.bPendingDelete; if (instanceExists) { return default.activeInstance; } - if (spawnIfMissing) { - return Singleton(__().memory.Allocate(default.class)); + if (spawnIfMissing) + { + core = __core().GetLevelCore(); + if (core != none) { + return Singleton(core.Allocate(default.class)); + } } return none; } diff --git a/sources/Text/Tests/TEST_JSON.uc b/sources/Text/Tests/TEST_JSON.uc index 79534a8..8e45da9 100644 --- a/sources/Text/Tests/TEST_JSON.uc +++ b/sources/Text/Tests/TEST_JSON.uc @@ -339,8 +339,7 @@ protected static function SubTest_SimplePrint() == "\"\\\"comp\\/lex\\\"\\n\\\\str\""); Issue("Printing unrelated objects does not produce `none`s."); - TEST_ExpectNone( - __().json.Print(AcediaObject(__().memory.Allocate(class'Parser')))); + TEST_ExpectNone(__().json.Print(__().memory.Allocate(class'Parser'))); } protected static function SubTest_ArrayPrint() diff --git a/sources/Types/AcediaActor.uc b/sources/Types/AcediaActor.uc index c7d2cb1..768d3d5 100644 --- a/sources/Types/AcediaActor.uc +++ b/sources/Types/AcediaActor.uc @@ -28,12 +28,6 @@ var protected Global _; var protected ServerGlobal _server; var protected ClientGlobal _client; -// To make logic simpler and increase efficiency, we allow storing a reference -// to any actors in many different places. To know when we can actually -// deallocate an actor, we keep this reference counter and only move actor -// to the actor pool once nothing refers to it anymore. -var private int _refCounter; - // Store allocation status to prevent possible issues // with freeing the same object several times without reallocating it // (such as preventing finalizers or constructors being called several times) @@ -74,7 +68,6 @@ public simulated function _constructor() { if (_isAllocated) return; _isAllocated = true; - _refCounter = 1; _ = class'Global'.static.GetInstance(); _server = class'ServerGlobal'.static.GetInstance(); _client = class'ClientGlobal'.static.GetInstance(); @@ -100,7 +93,6 @@ public simulated function _finalizer() { if (!_isAllocated) return; _isAllocated = false; - _refCounter = 0; Finalizer(); _ = none; _server = none; @@ -191,52 +183,6 @@ private simulated final static function CreateTextCache( } } -/** - * This function is called each time this object is freed, to decrease it - * internal reference counter and know when it can be actually deallocated. - * - * AVOID MANUALLY CALLING IT. - */ -public simulated final function _deref() -{ - if (!_isAllocated) { - return; - } - _refCounter = Max(0, _refCounter - 1); -} - -/** - * This function returns current reference counter for the caller actor. - * It is an amount of times it can be freed before being deallocated. - * This should correspond to the amount of places that reference it. - * - * AVOID MANUALLY CALLING IT. - */ -public simulated final function int _getRefCount() -{ - if (!_isAllocated) { - return 0; - } - return _refCounter; -} - -/** - * Method that creates new reference to the given actor. - * Call this if you do not have ownership over the actor, but want to store - * somewhere - this way it should not get deallocated until you free your - * own reference. - * - * @return Caller actor, to allow for easier use. - */ -public simulated final function AcediaActor NewRef() -{ - if (!_isAllocated) { - return none; - } - _refCounter = Max(0, _refCounter + 1); - return self; -} - /** * Acedia actors cannot be deallocated into an actor pool, but they still * support constructors and destructors and, therefore, track their own @@ -251,17 +197,6 @@ public simulated final function bool IsAllocated() return _isAllocated; } -/** - * Deallocates caller `AcediaActor`, calling its finalizer and then - * destroying it. - */ -public simulated final function FreeSelf() -{ - if (IsAllocated()) { - _.memory.Free(self); - } -} - /** * Determines whether passed `Object` is equal to the caller. * diff --git a/sources/Types/Tests/TEST_ActorService.uc b/sources/Types/Tests/TEST_ActorService.uc index d2d94ab..32793e9 100644 --- a/sources/Types/Tests/TEST_ActorService.uc +++ b/sources/Types/Tests/TEST_ActorService.uc @@ -47,7 +47,7 @@ protected static function Test_AddDestroy(class classToTest) Issue("Cannot retrieve recorded `Actor`s with `GetActor()`."); for (i = 0; i < 1000; i += 1) { - nativeActors[i] = Actor(__().memory.Allocate(classToTest)); + nativeActors[i] = __core().GetLevelCore().Allocate(classToTest); actorRefs[i] = service.AddActor(nativeActors[i]); } for (i = 0; i < 1000; i += 1) @@ -72,7 +72,7 @@ protected static function Test_AddDestroy(class classToTest) { if (i % 2 == 0) { - nativeActors[i] = Actor(__().memory.Allocate(classToTest)); + nativeActors[i] = __core().GetLevelCore().Allocate(classToTest); actorRefs[i] = service.AddActor(nativeActors[i]); } } @@ -92,7 +92,7 @@ protected static function Test_AddRemove(class classToTest) service = ActorService(class'ActorService'.static.Require()); for (i = 0; i < 1000; i += 1) { - nativeActors[i] = Actor(__().memory.Allocate(classToTest)); + nativeActors[i] = __core().GetLevelCore().Allocate(classToTest); actorRefs[i] = service.AddActor(nativeActors[i]); } @@ -113,7 +113,7 @@ protected static function Test_AddRemove(class classToTest) { if (i % 2 == 0) { - nativeActors[i] = Actor(__().memory.Allocate(classToTest)); + nativeActors[i] = __core().GetLevelCore().Allocate(classToTest); actorRefs[i] = service.AddActor(nativeActors[i]); } } @@ -133,7 +133,7 @@ protected static function Test_Update(class classToTest) service = ActorService(class'ActorService'.static.Require()); for (i = 0; i < 1000; i += 1) { - nativeActors[i] = Actor(__().memory.Allocate(classToTest)); + nativeActors[i] = __core().GetLevelCore().Allocate(classToTest); actorRefs[i] = service.AddActor(nativeActors[i]); } @@ -142,7 +142,7 @@ protected static function Test_Update(class classToTest) { if (i % 2 == 0) { - nativeActors[i] = Actor(__().memory.Allocate(classToTest)); + nativeActors[i] = __core().GetLevelCore().Allocate(classToTest); actorRefs[i] = service.UpdateActor(actorRefs[i], nativeActors[i]); } }