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. 195
      sources/BaseRealm/API/Memory/AcediaObjectPool.uc
  3. 508
      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() public final function StartUp()
{ {
local AvariceTcpStream newStream; local AvariceTcpStream newStream;
local LevelCore core;
if (tcpStream == none) return; if (tcpStream == none) return;
if (tcpStream.Get() != 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) if (newStream == none)
{ {
// `linkName` has to be defined if `tcpStream` is defined // `linkName` has to be defined if `tcpStream` is defined

195
sources/BaseRealm/API/Memory/AcediaObjectPool.uc

@ -1,9 +1,8 @@
/** /**
* Acedia's implementation for object pool that can only store objects of * Author: dkanus
* one specific class to allow for both faster allocation and * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* faster deallocation. * License: GPL
* Allows to set a maximum capacity. * Copyright 2020-2023 Anton Tarasenko
* Copyright 2020-2021 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -23,67 +22,41 @@
class AcediaObjectPool extends Object class AcediaObjectPool extends Object
config(AcediaSystem); config(AcediaSystem);
// Class of objects that this `AcediaObjectPool` stores. //! Acedia's implementation for object pool.
// if `== none`, - object pool is considered uninitialized. //!
var private class<AcediaObject> storedClass; //! Unlike generic built in [`Engine::ObjectPool`], that can only store objects of one specific
// Actual storage, functions on LIFO principle. //! class, it specializes in a single class to allow for both faster allocation and
var public array<AcediaObject> objectPool; //! 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 /// Represents config entry about pool capacity.
// server admins to rewrite the pool capacity for each class. ///
struct PoolSizeSetting /// 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 class<AcediaObject> objectClass;
var int maxPoolSize; var int maxPoolSize;
}; };
var private config const array<PoolSizeSetting> poolSizeOverwrite; var private config const array<PoolSizeSetting> poolSizeOverwrite;
// Capacity for object pool that we are using. // Class of objects that this `AcediaObjectPool` stores.
// Set during initialization and cannot be changed later. // if `== none`, - object pool is considered uninitialized.
var private class<AcediaObject> storedClass;
/// Capacity for object pool that we are using.
/// Obtained from [`poolSizeOverwrite`] during initialization and cannot be changed later.
var private int usedMaxPoolSize; var private int usedMaxPoolSize;
/** // Actual storage, functions on LIFO principle.
* Initialize caller object pool to store objects of `initStoredClass` class. var private array<AcediaObject> objectPool;
*
* 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;
// If does not matter that we've set those variables until // Determines default object pool size for the initialization.
// we set `storedClass`. private final function int GetMaxPoolSizeForClass(class<AcediaObject> classToCheck) {
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<AcediaObject> classToCheck)
{
local int i; local int i;
local int result; local int result;
if (classToCheck != none) { if (classToCheck != none) {
result = classToCheck.default.defaultMaxPoolSize; result = classToCheck.default.defaultMaxPoolSize;
} }
@ -91,10 +64,8 @@ private final function int GetMaxPoolSizeForClass(
result = -1; result = -1;
} }
// Try to replace it with server's settings // Try to replace it with server's settings
for (i = 0; i < poolSizeOverwrite.length; i += 1) for (i = 0; i < poolSizeOverwrite.length; i += 1) {
{ if (poolSizeOverwrite[i].objectClass == classToCheck) {
if (poolSizeOverwrite[i].objectClass == classToCheck)
{
result = poolSizeOverwrite[i].maxPoolSize; result = poolSizeOverwrite[i].maxPoolSize;
break; break;
} }
@ -102,45 +73,63 @@ private final function int GetMaxPoolSizeForClass(
return result; return result;
} }
/** /// Initializes caller object pool to store objects of the given class.
* Returns class of objects inside the caller `AcediaObjectPool`. ///
* /// Returns `true` if initialization completed, `false` otherwise (including if it was already
* @return class of objects inside caller the caller object pool; /// completed with passed [`classToStore`]).
* `none` means object pool was not initialized. ///
*/ /// If successful, this action is irreversible: same pool cannot be re-initialized.
public final function class<AcediaObject> GetClassOfStoredObjects() ///
{ /// [`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; return storedClass;
} }
/** /// Clear the storage of all its contents.
* Clear the storage of all it's contents. public final function Clear() {
*
* Can be used before UnrealEngine's garbage collection to free pooled objects.
*/
public final function Clear()
{
objectPool.length = 0; objectPool.length = 0;
} }
/** /// Adds object to the caller storage (that needs to be initialized to store [`newObject.class`]
* Adds object to the caller storage /// classes).
* (that needs to be initialized to store `newObject.class` classes). ///
* /// Returns `true` on success and `false` on failure (can happen if passed [`newObject`] reference
* For performance purposes does not do duplicates checks, /// was invalid, caller storage is not initialized yet or reached it's capacity).
* this should be verified from outside `AcediaObjectPool`. ///
* /// For performance purposes does not do duplicates checks, this should be verified from outside
* Does type checks and only allows objects of the class that caller /// [`AcediaObjectPool`].
* `AcediaObjectPool` was initialized for. ///
* /// Performs type checks and only allows objects of the class that caller [`AcediaObjectPool`] was
* @param newObject Object to put inside caller pool. Must be not `none` and /// initialized for.
* have precisely the class this object pool was initialized to store. public final function bool Store(AcediaObject newObject) {
* @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)
{
if (newObject == none) return false; if (newObject == none) return false;
if (newObject.class != storedClass) return false; if (newObject.class != storedClass) return false;
@ -151,17 +140,12 @@ public final function bool Store(AcediaObject newObject)
return true; return true;
} }
/** /// Returns last stored object from the pool, removing it from that pool in the process.
* Extracts last stored object from the pool. Returned object will no longer ///
* be stored in the pool. /// Only returns `none` if caller `AcediaObjectPool` is either empty or not initialized.
* public final function AcediaObject Fetch() {
* @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()
{
local AcediaObject result; local AcediaObject result;
if (storedClass == none) return none; if (storedClass == none) return none;
if (objectPool.length <= 0) return none; if (objectPool.length <= 0) return none;
@ -170,6 +154,5 @@ public final function AcediaObject Fetch()
return result; return result;
} }
defaultproperties defaultproperties {
{
} }

508
sources/BaseRealm/API/Memory/MemoryAPI.uc

@ -1,10 +1,8 @@
/** /**
* API that provides functions for managing object of classes, derived from * Author: dkanus
* `AcediaObject`. It takes care of managing their object pools, as well as * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* ensuring that constructors and finalizers are called properly. * License: GPL
* Almost all `AcediaObject`s should use this API's methods for their own * Copyright 2020-2023 Anton Tarasenko
* creation and destruction.
* Copyright 2020-2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -23,325 +21,180 @@
*/ */
class MemoryAPI extends AcediaObject; class MemoryAPI extends AcediaObject;
/** //! API that provides functions for managing object of classes, derived from `AcediaObject`.
* # Memory API //!
* //! This is most-basic API that must be created before anything else in Acedia, since it is
* This is most-basic API that must be created before anything else in Acedia, //! responsible for the proper creation of `AcediaObject`s.
* 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
* It takes care of managing their object pools, as well as ensuring that //! finalizers are called properly.
* constructors and finalizers are called properly. //!
* Almost all `AcediaObject`s should use this API's methods for their own //! Almost all `AcediaObject`s should use this API's methods for their own creation and destruction.
* creation and destruction. //!
* //! ## Usage
* ## Usage //!
* //! First of all, this API is only meant for non-actor `Object` creation.
* 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
* `Actor` creation is generally avoided in Acedia and, when unavoidable, //! are dealing with that.
* different APIs are dealing with that. `MemoryAPI` is designed to work in //! `MemoryAPI` is designed to work in the absence of any level (and, therefore, `Actor`s) at all.
* the absence of any level (and, therefore, `Actor`s) at all. //!
* Simply use `MemoryAPI.Allocate()` to create a new object and //! Simply use `MemoryAPI.Allocate()` to create a new object and `MemoryAPI.Free()` to get rid of
* `MemoryAPI.Free()` to get rid on unneeded reference. Do note that //! unneeded reference.
* `AcediaObject`s use reference counting and object will be deallocated and //! Do note that `AcediaObject`s use reference counting and object will be deallocated and pooled
* pooled only after every trackable reference was released by //! only after every trackable reference was released by `MemoryAPI.Free()`.
* `MemoryAPI.Free()`. //!
* Best practice is to only care about what object reference you're //! Best practice is to only care about what object reference you're keeping, properly release them
* keeping, properly release them with `MemoryAPI.Free()` and to NEVER EVER USE //! with `MemoryAPI.Free()` and to NEVER EVER USE THEM after you've release them.
* THEM after you've release them. Regardless of whether they were actually //! Regardless of whether they were actually deallocated.
* deallocated. //!
* //! There's also a set of auxiliary methods for either loading `class`es from their
* There's also a set of auxiliary methods for either loading `class`es from //! `BaseText`/`string`-given names or even directly creating objects of said classes.
* their `BaseText`/`string`-given names or even directly creating objects of //!
* said classes. //! ## Customizing object pools for your classes
* //!
* ## Motivation //! Object pool usage can be disabled completely for your class by setting `usesObjectPool = false`
* //! in `defaultproperties` block.
* UnrealScript lacks any practical way to destroy non-actor objects on //! Without object pools `MemoryAPI.Allocate()` will create a new instance of your class every
* demand: the best one can do is remove any references to the object and wait //! single time.
* for garbage collection. But garbage collection itself is too slow and causes //!
* noticeable lag spikes for players, making it suitable only for cleaning //! You can also set a limit to how many objects will be stored in an object pool with
* objects when switching levels. To alleviate this problem, there exists //! `defaultMaxPoolSize` variable.
* a standard class `ObjectPool` that stores unused objects (mostly resources //! Negative number (default for `AcediaObject`) means that object pool can grow without a limit.
* such as textures) inside dynamic array until they are needed. //! `0` effectively disables object pool, similar to setting `usesObjectPool = false`.
* Unfortunately, using a single ObjectPool for a large volume of objects //! However, this can be overwritten by server's settings
* is impractical from performance perspective, since it stores objects of all //! (see `AcediaSystem.ini`: `AcediaObjectPool`).
* classes together and each object allocation from the pool can potentially
* require going through the whole array (see `Engine/ObjectPool.uc`).
* Acedia uses a separate object pool (implemented by `AcediaObjectPool`)
* for every single class, making object allocation as trivial as grabbing
* the last stored object from `AcediaObjectPool`'s internal dynamic array.
* New pool is prepared for every class you create, as long as it is
* derived from `AcediaObject`. `AcediaActors` do not use object pools and are
* meant to be simply `Destroy()`ed.
*
* ## Customizing object pools for your classes
*
* Object pool usage can be disabled completely for your class by setting
* `usesObjectPool = false` in `defaultproperties` block. Without object pools
* `MemoryAPI.Allocate()` will create a new instance of your class every single
* time.
* You can also set a limit to how many objects will be stored in an object
* pool with defaultMaxPoolSize variable. Negative number (default for
* `AcediaObject`) means that object pool can grow without a limit.
* `0` effectively disables object pool, similar to setting
* `usesObjectPool = false`. However, this can be overwritten by server's
* settings (see `AcediaSystem.ini`: `AcediaObjectPool`).
*/
// Store all created pools, so that we can quickly forget stored objects upon // Store all created pools, so that we can quickly forget stored objects upon garbage collection
// garbage collection
var private array<AcediaObjectPool> registeredPools; var private array<AcediaObjectPool> registeredPools;
/** /// Forgets about all stored (deallocated) object references in registered object pools.
* Creates a class instance from its `Text` representation. protected function DropPools() {
* local int i;
* Does not generate log messages upon failure.
* registeredPools = default.registeredPools;
* @param classReference Text representation of the class to return. for (i = 0; i < registeredPools.length; i += 1) {
* @return Loaded class, corresponding to its name from `classReference`. if (registeredPools[i] == none) {
*/ continue;
public function class<Object> LoadClass(BaseText classReference) }
{ 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) { if (classReference == none) {
return none; return none;
} }
return class<Object>( return class<Object>(DynamicLoadObject(classReference.ToString(), class'Class', true));
DynamicLoadObject(classReference.ToString(),
class'Class',
true));
} }
/** /// Creates a class instance from its `string` representation.
* Creates a class instance from its `string` representation. ///
* /// Does not generate log messages upon failure.
* Does not generate log messages upon failure. public function class<Object> LoadClass_S(string classReference) {
*
* @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)
{
return class<Object>(DynamicLoadObject(classReference, class'Class', true)); return class<Object>(DynamicLoadObject(classReference, class'Class', true));
} }
/** /// Creates a new `AcediaObject` of a given subclass using pool (if permitted by settings) and
* Creates a new `Object` of a given class. /// calling its constructor.
* ///
* For `AcediaObject`s calls constructors and tries (uses them only if they /// If Acedia's object does make use of object pools, this method guarantees to return last pooled
* aren't forbidden for a given class) to make use of their classes' object /// object (in a LIFO queue), unless `forceNewInstance` is set to `true`.
* pools. ///
* /// Return value will only be `none` if `classToAllocate` is `none` or abstract.
* If Acedia's object does make use of object pools, - public function AcediaObject Allocate(
* guarantees to return last pooled object (in a LIFO queue), class<AcediaObject> classToAllocate,
* unless `forceNewInstance` is set to `true`. optional bool forceNewInstance
* ) {
* @see `AllocateByReference()`, `AllocateByReference_S()` local AcediaObject allocatedObject;
* local AcediaObjectPool relevantPool;
* @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;
local AcediaObjectPool relevantPool;
local class<AcediaObject> acediaObjectClassToAllocate;
local class<AcediaActor> acediaActorClassToAllocate;
local class<Actor> actorClassToAllocate;
if (classToAllocate == none) { if (classToAllocate == none) {
return none; return none;
} }
// Try using pool first (only if new instance is not required) // Try using pool first (but only if new instance is not required)
acediaObjectClassToAllocate = class<AcediaObject>(classToAllocate); if (!forceNewInstance) {
acediaActorClassToAllocate = class<AcediaActor>(classToAllocate); relevantPool = classToAllocate.static._getPool();
if (!forceNewInstance)
{
if (acediaObjectClassToAllocate != none) {
relevantPool = acediaObjectClassToAllocate.static._getPool();
}
// `relevantPool == none` is expected if object / actor of is setup to // `relevantPool == none` is expected if object / actor of is setup to
// not use object pools. // not use object pools.
if (relevantPool != none) { if (relevantPool != none) {
allocatedObject = relevantPool.Fetch(); allocatedObject = relevantPool.Fetch();
} }
} }
// If pools did not work - spawn / create object through regular methods // If pools did not work - simply create object manually
if (allocatedObject == none) if (allocatedObject == none) {
{ allocatedObject = (new classToAllocate);
actorClassToAllocate = class<Actor>(classToAllocate);
if (actorClassToAllocate != none)
{
core = class'ServerLevelCore'.static.GetInstance();
if (core == none) {
core = class'ClientLevelCore'.static.GetInstance();
}
allocatedObject = core.Spawn(actorClassToAllocate);
}
else {
allocatedObject = (new classToAllocate);
}
}
// Call constructors
if (acediaObjectClassToAllocate != none) {
AcediaObject(allocatedObject)._constructor();
}
if (acediaActorClassToAllocate != none)
{
// Call it here, just in case, to make sure constructor is called
// as soon as possible
AcediaActor(allocatedObject)._constructor();
} }
// Allocation through `new` cannot fail, so its safe to call constructor
allocatedObject._constructor();
return allocatedObject; return allocatedObject;
} }
/** /// Creates a new `AcediaObject` of a given subclass using pool (if permitted by settings) and
* Creates a new `Object` of a given class using its `BaseText` /// calling its constructor.
* representation. ///
* /// If Acedia's object does make use of object pools, this method guarantees to return last pooled
* For `AcediaObject`s calls constructors and tries (uses them only if they /// object (in a LIFO queue), unless `forceNewInstance` is set to `true`.
* aren't forbidden for a given class) to make use of their classes' object ///
* pools. /// Return value will only be `none` if `refToClassToAllocate` is `none`, doesn't refer to
* /// an existing class or refers to an abstract class.
* If Acedia's object does make use of object pools, - public function AcediaObject AllocateByReference(
* guarantees to return last pooled object (in a LIFO queue), BaseText refToClassToAllocate,
* unless `forceNewInstance` is set to `true`. optional bool forceNewInstance
* @see `Allocate()`, `AllocateByReference_S()` ) {
* local class<Object> classToAllocate;
* @param refToClassToAllocate `BaseText` representation of the class' name
* of the `Object` that this method will create. Must not be subclass of classToAllocate = LoadClass(refToClassToAllocate);
* `Actor`. return Allocate(class<AcediaObject>(classToAllocate), forceNewInstance);
* @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
* Creates a new `Object` of a given class using its `string` /// calling its constructor.
* representation. ///
* /// If Acedia's object does make use of object pools, this method guarantees to return last pooled
* For `AcediaObject`s calls constructors and tries (uses them only if they /// object (in a LIFO queue), unless `forceNewInstance` is set to `true`.
* aren't forbidden for a given class) to make use of their classes' object ///
* pools. /// Return value will only be `none` if `refToClassToAllocate` is `none`, doesn't refer to
* /// an existing class or refers to an abstract class.
* If Acedia's object does make use of object pools, - public function AcediaObject AllocateByReference_S(
* guarantees to return last pooled object (in a LIFO queue), string refToClassToAllocate,
* unless `forceNewInstance` is set to `true`. optional bool forceNewInstance
* ) {
* @see `Allocate()`, `AllocateByReference()` local class<Object> classToAllocate;
*
* @param refToClassToAllocate `string` representation of the class' name classToAllocate = LoadClass_S(refToClassToAllocate);
* of the `Object` that this method will create. Must not be subclass of return Allocate(class<AcediaObject>(classToAllocate), forceNewInstance);
* `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);
} }
/** /// Releases one reference to a given `AcediaObject`, calling its finalizers in case all references
* Releases one reference to a given `AcediaObject`, calling its finalizers in /// were released.
* case all references were released. ///
* /// Method will attempt to store `objectToRelease` in its object pool once deallocated, unless it is
* Method will attempt to store `objectToRelease` in its object pool once /// forbidden by its class' settings.
* deallocated, unless it is forbidden by its class' settings. public function Free(AcediaObject objectToRelease) {
* local AcediaObjectPool relevantPool;
* @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;
if (objectToRelease == none) { if (objectToRelease == none) return;
return; if (!objectToRelease.IsAllocated()) return;
}
// Call finalizers for Acedia's objects and actors objectToRelease._deref();
objectAsAcediaObject = AcediaObject(objectToRelease); // Finalize object if all of its references are gone
objectAsAcediaActor = AcediaActor(objectToRelease); if (objectToRelease._getRefCount() <= 0) {
if (objectAsAcediaObject != none) relevantPool = objectToRelease._getPool();
{ objectToRelease._finalizer();
if (!objectAsAcediaObject.IsAllocated()) { if (relevantPool != none) {
return; relevantPool.Store(objectToRelease);
}
objectAsAcediaObject._deref();
if (objectAsAcediaObject._getRefCount() > 0) {
return;
}
relevantPool = objectAsAcediaObject._getPool();
objectAsAcediaObject._finalizer();
}
if (objectAsAcediaActor != none)
{
if (!objectAsAcediaActor.IsAllocated()) {
return;
}
objectAsAcediaActor._deref();
if (objectAsAcediaActor._getRefCount() > 0) {
return;
} }
objectAsAcediaActor._finalizer();
}
// Try to store freed object in a pool
if (relevantPool != none && relevantPool.Store(objectAsAcediaObject)) {
return;
}
// Otherwise destroy actors and forget about objects
objectAsActor = Actor(objectToRelease);
if (objectAsActor != none) {
objectAsActor.Destroy();
} }
} }
/** /// Releases one reference for each `AcediaObject` inside the given array `objectsToRelease`,
* Releases one reference to each `AcediaObject` inside the given array /// calling finalizers for the ones that got all of their references released.
* `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.
* Method will attempt to store objects inside `objectsToRelease` in their public function FreeMany(array<AcediaObject> objectsToRelease) {
* 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`
local int i; local int i;
for (i = 0; i < objectsToRelease.length; i += 1) { for (i = 0; i < objectsToRelease.length; i += 1) {
@ -349,26 +202,21 @@ public function FreeMany(array<Object> objectsToRelease)
} }
} }
/** /// Forces engine to perform garbage collection.
* Forces Unreal Engine to perform garbage collection. ///
* By default also cleans up all of the Acedia's objects pools. /// Process of manual garbage collection causes significant lag spike during the game and should be
* /// used sparingly and at right moments.
* Process of 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.
* If not `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.
* @param keepAcediaPools Set this to `true` to NOT garbage collect /// Pools won't be dropped regardless of this parameter if no `LevelCore` is found.
* objects inside pools. Otherwise keep it `false`. ///
* Pools won't be dropped regardless of this parameter if no `LevelCore` is /// Returns `true` if garbage collection successfully happened and `false` if it failed.
* found. /// Garbage collection can only fail if no `LevelCore` was yet setup.
* @return `true` if garbage collection successfully happened and `false` if it public function bool /*unreal*/ CollectGarbage(optional bool keepAcediaPools) {
* failed. Garbage collection can only fail if no `LevelCore` was yet
* setup.
*/
public function bool CollectGarbage(optional bool keepAcediaPools)
{
local LevelCore core; local LevelCore core;
// Try to find level core // Try to find level core
@ -388,24 +236,18 @@ public function bool CollectGarbage(optional bool keepAcediaPools)
return true; return true;
} }
/** /// Registers new object pool to auto-clean before Acedia's garbage collection.
* 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
* @param newPool New object pool that can get cleaned if `CollectGarbage()` /// registered.
* is called with appropriate parameters. public function bool RegisterNewPool(AcediaObjectPool newPool) {
* @return `true` if `newPool` was registered,
* `false` if `newPool == none` or was already registered.
*/
public function bool RegisterNewPool(AcediaObjectPool newPool)
{
local int i; local int i;
if (newPool == none) { if (newPool == none) {
return false; return false;
} }
registeredPools = default.registeredPools; registeredPools = default.registeredPools;
for (i = 0; i < registeredPools.length; i += 1) for (i = 0; i < registeredPools.length; i += 1) {
{
if (registeredPools[i] == newPool) { if (registeredPools[i] == newPool) {
return false; return false;
} }
@ -415,23 +257,5 @@ public function bool RegisterNewPool(AcediaObjectPool newPool)
return true; return true;
} }
/** defaultproperties {
* 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
{
} }

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" Context("Testing that Acedia actor's constructors and finalizers are"
@ "called properly."); @ "called properly.");
Issue("Actor's constructor is not called."); 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); 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); TEST_ExpectTrue(class'MockActor'.default.actorCount == 2);
Issue("Actor's finalizer is not called."); Issue("Actor's finalizer is not called.");
__().memory.Free(act1); act1.Destroy();
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1); TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
Issue("`IsAllocated()` returns `false` for allocated actors."); Issue("`IsAllocated()` returns `false` for allocated actors.");
TEST_ExpectTrue(act2.IsAllocated()); 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."); Issue("Actor's finalizer is not called.");
__().memory.Free(act2); act2.Destroy();
TEST_ExpectTrue(class'MockActor'.default.actorCount == 0); TEST_ExpectTrue(class'MockActor'.default.actorCount == 0);
} }
@ -170,8 +166,6 @@ protected static function Test_RefCounting()
Context("Testing usage of reference counting."); Context("Testing usage of reference counting.");
SubTest_RefCountingObjectFreeSelf(); SubTest_RefCountingObjectFreeSelf();
SubTest_RefCountingObjectFree(); SubTest_RefCountingObjectFree();
SubTest_RefCountingActorFreeSelf();
SubTest_RefCountingActorFree();
} }
protected static function SubTest_RefCountingObjectFreeSelf() protected static function SubTest_RefCountingObjectFreeSelf()
@ -220,61 +214,6 @@ protected static function SubTest_RefCountingObjectFree()
TEST_ExpectFalse(temp.IsAllocated()); 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 defaultproperties
{ {
caseGroup = "Memory" caseGroup = "Memory"

12
sources/CoreRealm/CoreGlobal.uc

@ -38,6 +38,18 @@ public function UnrealAPI unreal_api()
return none; 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() public final static function CoreGlobal GetGenericInstance()
{ {
local ServerGlobal serverAPI; local ServerGlobal serverAPI;

2
sources/Data/Collections/ArrayList.uc

@ -364,7 +364,7 @@ public final function ArrayList CreateItem(
if (index < 0) return self; if (index < 0) return self;
if (valueClass == none) return self; if (valueClass == none) return self;
newObject = AcediaObject(_.memory.Allocate(valueClass)); newObject = _.memory.Allocate(valueClass);
SetItem(index, newObject); SetItem(index, newObject);
_.memory.Free(newObject); _.memory.Free(newObject);
return self; return self;

2
sources/Data/Collections/HashTable.uc

@ -449,7 +449,7 @@ public final function HashTable CreateItem(
if (key == none) return self; if (key == none) return self;
if (valueClass == none) return self; if (valueClass == none) return self;
newItem = AcediaObject(_.memory.Allocate(valueClass)); newItem = _.memory.Allocate(valueClass);
SetItem(key, newItem); SetItem(key, newItem);
newItem.FreeSelf(); newItem.FreeSelf();
return self; return self;

2
sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc

@ -331,7 +331,7 @@ public function EItem AddTemplate(
if (newWeaponClass == none) if (newWeaponClass == none)
{ {
newItem = class'EKFUnknownItem'.static newItem = class'EKFUnknownItem'.static
.Wrap(Inventory(_.memory.Allocate(newInventoryClass))); .Wrap(pawn.Spawn(newInventoryClass));
if (newItem != none && TryAddUnknownItem(newItem) != none) { if (newItem != none && TryAddUnknownItem(newItem) != none) {
return newItem; 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)) { if (AreAdded(newRulesClass)) {
return none; return none;
} }
newGameRules = GameRules(_.memory.Allocate(newRulesClass)); newGameRules = GameRules(__core().GetLevelCore().Allocate(newRulesClass));
_server.unreal.GetGameType().AddGameModifier(newGameRules); _server.unreal.GetGameType().AddGameModifier(newGameRules);
return newGameRules; return newGameRules;
} }

2
sources/KFRealm/Server/Unreal/InventoryAPI/InventoryService.uc

@ -33,7 +33,7 @@ public function Weapon AddWeaponWithAmmo(
local Weapon newWeapon; local Weapon newWeapon;
local KFWeapon newKFWeapon; local KFWeapon newKFWeapon;
if (pawn == none) return none; if (pawn == none) return none;
newWeapon = Weapon(_.memory.Allocate(weaponClassToAdd)); newWeapon = Spawn(weaponClassToAdd);
if (newWeapon == none) return none; if (newWeapon == none) return none;
// It is possible that `newWeapon` can get destroyed somewhere here, // It is possible that `newWeapon` can get destroyed somewhere here,
// so add two more checks // 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` // where A = `MockInventoryA`
// a = `MockInventoryAChild` // a = `MockInventoryAChild`
// B = `MockInventoryB` // B = `MockInventoryB`
chainStart = Inventory(__().memory.Allocate(class'MockInventoryAChild')); chainStart = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryAChild'));
chainEnd = chainStart; chainEnd = chainStart;
chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryB')); chainEnd.inventory = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryB'));
chainEnd = chainEnd.inventory; chainEnd = chainEnd.inventory;
chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryA')); chainEnd.inventory = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryA'));
chainEnd = chainEnd.inventory; chainEnd = chainEnd.inventory;
chainEnd.inventory = chainEnd.inventory =
Inventory(__().memory.Allocate(class'MockInventoryAChild')); Inventory(__core().GetLevelCore().Allocate(class'MockInventoryAChild'));
chainEnd = chainEnd.inventory; chainEnd = chainEnd.inventory;
chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryB')); chainEnd.inventory = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryB'));
chainEnd = chainEnd.inventory; chainEnd = chainEnd.inventory;
chainEnd.inventory = chainEnd.inventory =
Inventory(__().memory.Allocate(class'MockInventoryAChild')); Inventory(__core().GetLevelCore().Allocate(class'MockInventoryAChild'));
chainEnd = chainEnd.inventory; chainEnd = chainEnd.inventory;
chainEnd.inventory = Inventory(__().memory.Allocate(class'MockInventoryA')); chainEnd.inventory = Inventory(__core().GetLevelCore().Allocate(class'MockInventoryA'));
chainEnd = chainEnd.inventory; chainEnd = chainEnd.inventory;
Context("Testing auxiliary methods for working with inventory chains."); Context("Testing auxiliary methods for working with inventory chains.");
SubTest_InventoryChainFetchingSingle(chainStart); SubTest_InventoryChainFetchingSingle(chainStart);

7
sources/Service.uc

@ -31,12 +31,17 @@ var private LoggerAPI.Definition errNoService;
public simulated static final function Service Require() public simulated static final function Service Require()
{ {
local Service newInstance; local Service newInstance;
local LevelCore core;
if (IsRunning()) { if (IsRunning()) {
return Service(GetInstance()); return Service(GetInstance());
} }
core = __core().GetLevelCore();
if (core == none) {
return none;
}
default.blockSpawning = false; default.blockSpawning = false;
newInstance = Service(__().memory.Allocate(default.class)); newInstance = Service(core.Allocate(default.class));
default.blockSpawning = true; default.blockSpawning = true;
if (newInstance == none) { if (newInstance == none) {
__().logger.Auto(default.errNoService).ArgClass(default.class); __().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) optional bool spawnIfMissing)
{ {
local bool instanceExists; local bool instanceExists;
local LevelCore core;
instanceExists = default.activeInstance != none instanceExists = default.activeInstance != none
&& !default.activeInstance.bPendingDelete; && !default.activeInstance.bPendingDelete;
if (instanceExists) { if (instanceExists) {
return default.activeInstance; return default.activeInstance;
} }
if (spawnIfMissing) { if (spawnIfMissing)
return Singleton(__().memory.Allocate(default.class)); {
core = __core().GetLevelCore();
if (core != none) {
return Singleton(core.Allocate(default.class));
}
} }
return none; return none;
} }

3
sources/Text/Tests/TEST_JSON.uc

@ -339,8 +339,7 @@ protected static function SubTest_SimplePrint()
== "\"\\\"comp\\/lex\\\"\\n\\\\str\""); == "\"\\\"comp\\/lex\\\"\\n\\\\str\"");
Issue("Printing unrelated objects does not produce `none`s."); Issue("Printing unrelated objects does not produce `none`s.");
TEST_ExpectNone( TEST_ExpectNone(__().json.Print(__().memory.Allocate(class'Parser')));
__().json.Print(AcediaObject(__().memory.Allocate(class'Parser'))));
} }
protected static function SubTest_ArrayPrint() protected static function SubTest_ArrayPrint()

65
sources/Types/AcediaActor.uc

@ -28,12 +28,6 @@ var protected Global _;
var protected ServerGlobal _server; var protected ServerGlobal _server;
var protected ClientGlobal _client; 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 // Store allocation status to prevent possible issues
// with freeing the same object several times without reallocating it // with freeing the same object several times without reallocating it
// (such as preventing finalizers or constructors being called several times) // (such as preventing finalizers or constructors being called several times)
@ -74,7 +68,6 @@ public simulated function _constructor()
{ {
if (_isAllocated) return; if (_isAllocated) return;
_isAllocated = true; _isAllocated = true;
_refCounter = 1;
_ = class'Global'.static.GetInstance(); _ = class'Global'.static.GetInstance();
_server = class'ServerGlobal'.static.GetInstance(); _server = class'ServerGlobal'.static.GetInstance();
_client = class'ClientGlobal'.static.GetInstance(); _client = class'ClientGlobal'.static.GetInstance();
@ -100,7 +93,6 @@ public simulated function _finalizer()
{ {
if (!_isAllocated) return; if (!_isAllocated) return;
_isAllocated = false; _isAllocated = false;
_refCounter = 0;
Finalizer(); Finalizer();
_ = none; _ = none;
_server = 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 * Acedia actors cannot be deallocated into an actor pool, but they still
* support constructors and destructors and, therefore, track their own * support constructors and destructors and, therefore, track their own
@ -251,17 +197,6 @@ public simulated final function bool IsAllocated()
return _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. * 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()`."); Issue("Cannot retrieve recorded `Actor`s with `GetActor()`.");
for (i = 0; i < 1000; i += 1) 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]); actorRefs[i] = service.AddActor(nativeActors[i]);
} }
for (i = 0; i < 1000; i += 1) for (i = 0; i < 1000; i += 1)
@ -72,7 +72,7 @@ protected static function Test_AddDestroy(class<Actor> classToTest)
{ {
if (i % 2 == 0) if (i % 2 == 0)
{ {
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); nativeActors[i] = __core().GetLevelCore().Allocate(classToTest);
actorRefs[i] = service.AddActor(nativeActors[i]); actorRefs[i] = service.AddActor(nativeActors[i]);
} }
} }
@ -92,7 +92,7 @@ protected static function Test_AddRemove(class<Actor> classToTest)
service = ActorService(class'ActorService'.static.Require()); service = ActorService(class'ActorService'.static.Require());
for (i = 0; i < 1000; i += 1) 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]); actorRefs[i] = service.AddActor(nativeActors[i]);
} }
@ -113,7 +113,7 @@ protected static function Test_AddRemove(class<Actor> classToTest)
{ {
if (i % 2 == 0) if (i % 2 == 0)
{ {
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); nativeActors[i] = __core().GetLevelCore().Allocate(classToTest);
actorRefs[i] = service.AddActor(nativeActors[i]); actorRefs[i] = service.AddActor(nativeActors[i]);
} }
} }
@ -133,7 +133,7 @@ protected static function Test_Update(class<Actor> classToTest)
service = ActorService(class'ActorService'.static.Require()); service = ActorService(class'ActorService'.static.Require());
for (i = 0; i < 1000; i += 1) 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]); actorRefs[i] = service.AddActor(nativeActors[i]);
} }
@ -142,7 +142,7 @@ protected static function Test_Update(class<Actor> classToTest)
{ {
if (i % 2 == 0) if (i % 2 == 0)
{ {
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); nativeActors[i] = __core().GetLevelCore().Allocate(classToTest);
actorRefs[i] = service.UpdateActor(actorRefs[i], nativeActors[i]); actorRefs[i] = service.UpdateActor(actorRefs[i], nativeActors[i]);
} }
} }

Loading…
Cancel
Save