Browse Source

Refactor `MemoryAPI` to only work with `AcediaObject`s

`MemoryAPI` is a part of base realm and should not work with `Actor`s.
core_refactor
Anton Tarasenko 2 years ago
parent
commit
e027c3cc53
  1. 6
      sources/Avarice/AvariceLink.uc
  2. 191
      sources/BaseRealm/API/Memory/AcediaObjectPool.uc
  3. 498
      sources/BaseRealm/API/Memory/MemoryAPI.uc
  4. 69
      sources/BaseRealm/API/Memory/Tests/TEST_Memory.uc
  5. 12
      sources/CoreRealm/CoreGlobal.uc
  6. 2
      sources/Data/Collections/ArrayList.uc
  7. 2
      sources/Data/Collections/HashTable.uc
  8. 2
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc
  9. 2
      sources/KFRealm/Server/Unreal/GameRulesAPI/KF1_GameRulesAPI.uc
  10. 2
      sources/KFRealm/Server/Unreal/InventoryAPI/InventoryService.uc
  11. 14
      sources/KFRealm/Server/Unreal/Tests/TEST_ServerUnrealAPI.uc
  12. 7
      sources/Service.uc
  13. 9
      sources/Singleton.uc
  14. 3
      sources/Text/Tests/TEST_JSON.uc
  15. 65
      sources/Types/AcediaActor.uc
  16. 12
      sources/Types/Tests/TEST_ActorService.uc

6
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

191
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<AcediaObject> storedClass;
// Actual storage, functions on LIFO principle.
var public array<AcediaObject> 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<AcediaObject> objectClass;
var int maxPoolSize;
};
var private config const array<PoolSizeSetting> poolSizeOverwrite;
// Capacity for object pool that we are using.
// Set during initialization and cannot be changed later.
var private int usedMaxPoolSize;
// Class of objects that this `AcediaObjectPool` stores.
// if `== none`, - object pool is considered uninitialized.
var private class<AcediaObject> storedClass;
/**
* 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<AcediaObject> initStoredClass,
optional int forcedPoolSize)
{
if (storedClass != none) return false;
if (initStoredClass == none) return false;
/// Capacity for object pool that we are using.
/// Obtained from [`poolSizeOverwrite`] during initialization and cannot be changed later.
var private int usedMaxPoolSize;
// 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;
}
// Actual storage, functions on LIFO principle.
var private array<AcediaObject> objectPool;
// Determines default object pool size for the initialization.
private final function int GetMaxPoolSizeForClass(
class<AcediaObject> classToCheck)
{
private final function int GetMaxPoolSizeForClass(class<AcediaObject> 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<AcediaObject> 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<AcediaObject> 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<AcediaObject> 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 {
}

498
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<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 function class<Object> 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<Object> LoadClass(BaseText classReference) {
if (classReference == none) {
return none;
}
return class<Object>(
DynamicLoadObject(classReference.ToString(),
class'Class',
true));
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 function class<Object> LoadClass_S(string classReference)
{
/// Creates a class instance from its `string` representation.
///
/// Does not generate log messages upon failure.
public 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 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;
/// 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<AcediaObject> classToAllocate,
optional bool forceNewInstance
) {
local AcediaObject 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();
}
// 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<Actor>(classToAllocate);
if (actorClassToAllocate != none)
{
core = class'ServerLevelCore'.static.GetInstance();
if (core == none) {
core = class'ClientLevelCore'.static.GetInstance();
}
allocatedObject = core.Spawn(actorClassToAllocate);
}
else {
// If pools did not work - simply create object manually
if (allocatedObject == none) {
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();
}
// 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(
/// 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)
{
return Allocate(LoadClass(refToClassToAllocate), forceNewInstance);
optional bool forceNewInstance
) {
local class<Object> classToAllocate;
classToAllocate = LoadClass(refToClassToAllocate);
return Allocate(class<AcediaObject>(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(
/// 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)
{
return Allocate(LoadClass_S(refToClassToAllocate), forceNewInstance);
optional bool forceNewInstance
) {
local class<Object> classToAllocate;
classToAllocate = LoadClass_S(refToClassToAllocate);
return Allocate(class<AcediaObject>(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`
/// 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;
local Actor objectAsActor;
local AcediaActor objectAsAcediaActor;
local AcediaObject objectAsAcediaObject;
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;
}
objectAsAcediaActor._finalizer();
}
// Try to store freed object in a pool
if (relevantPool != none && relevantPool.Store(objectAsAcediaObject)) {
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);
}
// 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<Object> 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<AcediaObject> objectsToRelease) {
local int i;
for (i = 0; i < objectsToRelease.length; i += 1) {
@ -349,26 +202,21 @@ public function FreeMany(array<Object> 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 {
}

69
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"

12
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;

2
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;

2
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;

2
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;
}

2
sources/KFRealm/Server/Unreal/GameRulesAPI/KF1_GameRulesAPI.uc

@ -173,7 +173,7 @@ public function GameRules Add(class<GameRules> newRulesClass)
if (AreAdded(newRulesClass)) {
return none;
}
newGameRules = GameRules(_.memory.Allocate(newRulesClass));
newGameRules = GameRules(__core().GetLevelCore().Allocate(newRulesClass));
_server.unreal.GetGameType().AddGameModifier(newGameRules);
return newGameRules;
}

2
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

14
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);

7
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);

9
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;
}

3
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()

65
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.
*

12
sources/Types/Tests/TEST_ActorService.uc

@ -47,7 +47,7 @@ protected static function Test_AddDestroy(class<Actor> 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<Actor> 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<Actor> 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<Actor> 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<Actor> 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<Actor> 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]);
}
}

Loading…
Cancel
Save