Anton Tarasenko
4 years ago
9 changed files with 799 additions and 20 deletions
@ -0,0 +1,294 @@ |
|||||||
|
/** |
||||||
|
* Service for safe `Actor` storage, aimed to resolve two issues that arise |
||||||
|
* from storing references to `Actor`s inside non-`Actor` objects: |
||||||
|
* 1. This can potentially prevent the whole level from being |
||||||
|
* garbage-collected on level change, leading to excessive memory use |
||||||
|
* and crashes; |
||||||
|
* 2. This can lead to stored `Actor` temporarily being put into |
||||||
|
* a "faulty state", where any sort of access to that `Actor` |
||||||
|
* (except converting it into a `string` ¯\_(ツ)_/¯) will lead to |
||||||
|
* server/game crashing. |
||||||
|
* This `Service` resolves these issues by storing `Actor`s in such a way |
||||||
|
* that they can be relatively quickly obtained via a simple struct value |
||||||
|
* (`ActorReference`) that can be safely stored inside any `Object`. |
||||||
|
* It is recommended to use `ActorRef` / `ActorBox` instead of accessing |
||||||
|
* this `Service` directly. |
||||||
|
* Copyright 2021 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class ActorService extends Service; |
||||||
|
|
||||||
|
/** |
||||||
|
* This service works as follows: it contains an array filled with `Actor`s |
||||||
|
* that will dynamically grow in size whenever more space is needed for new |
||||||
|
* `Actor`s. To refer to the stored `Actor` we put it's index in the storage |
||||||
|
* array in `ActorReference` struct. |
||||||
|
* |
||||||
|
* The main problem with that approach is that once `Actor` is destroyed or |
||||||
|
* removed- it leaves a "hole" inside our storage and if we keep simply adding |
||||||
|
* new `Actor`s at the end of the storage array - we will eventually use |
||||||
|
* too much space, even if we only store a limited amount of `Actor`s at any |
||||||
|
* given point in time. |
||||||
|
* To avoid this problem we also maintain an array of "empty indices" that |
||||||
|
* remembers where the "holes" are located and can let us re-allocate them to |
||||||
|
* place new `Actor`s in, without needlessly increasing storage's size. |
||||||
|
* |
||||||
|
* Another problem is that now two different `Actor`s can occupy the same |
||||||
|
* spot at different points in time. To deal with that we will also record |
||||||
|
* an `Actor`'s "version" for each storage index and increment it each time |
||||||
|
* a new `Actor` is stored at that index. We will also store that "version" |
||||||
|
* inside `ActorReference` to help us invalidate references to old `Actor`s |
||||||
|
* that were stored there before. |
||||||
|
* |
||||||
|
* NOTE that this `Service` avoids doing any sort of manual cleaning, |
||||||
|
* instead relying on the proper interface (`ActorRef` / `ActorBox`) to do it. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Reference to the `Actor` storage inside this service. |
||||||
|
* Reference is only considered invalid after an `Actor` it was referring |
||||||
|
* to was removed from the storage (possibly by being replaced with |
||||||
|
* another `Actor`). |
||||||
|
*/ |
||||||
|
struct ActorReference |
||||||
|
{ |
||||||
|
var private int index; |
||||||
|
var private int version; |
||||||
|
}; |
||||||
|
|
||||||
|
/* Arrays `actorRecords`, `actorVersions` and `indexAllocationFlag` could |
||||||
|
* be represented as an array of a single struct with 3 element, but instead, |
||||||
|
* for performance reasons, were separated into three distinct arrays - one for |
||||||
|
* each variable. |
||||||
|
* This means that they must at all times have the same length and |
||||||
|
* elements with the same index should be considered to belong to |
||||||
|
* the same record. |
||||||
|
*/ |
||||||
|
// Storage for `Actor`s. It's size can grow, but it cannot shrink, instead |
||||||
|
// marking some of array's indices as vacant |
||||||
|
// (by putting them into `emptyIndices`). |
||||||
|
var private array<Actor> actorRecords; |
||||||
|
// Defines a "version" of a corresponding `Actor`. |
||||||
|
var private array<int> actorVersions; |
||||||
|
// Marks whether values at a particular index are currently used to |
||||||
|
// store some `Actor` (`1`) or not (`0`). |
||||||
|
// Without these flags we would have no way of knowing whether a spot in |
||||||
|
// the array was already freed and recorded in `emptyIndices`, since stored |
||||||
|
// `Actor`s can turn into `none` by themselves upon destruction. |
||||||
|
var private array<byte> indexAllocationFlag; |
||||||
|
|
||||||
|
// A set of empty indices - that once contained stored an `Actor`, but can |
||||||
|
// now be reused. |
||||||
|
// Used like a LIFO-queue to quickly find an empty spot in `actorRecords`. |
||||||
|
var private array<int> emptyIndices; |
||||||
|
|
||||||
|
// Finds an vacant index in our records (expands array if all indices are |
||||||
|
// already taken) and puts `candidate` there. |
||||||
|
// Does not do any checks for whether `candidate != none`. |
||||||
|
private final function int InsertAtEmptyIndex(Actor candidate) |
||||||
|
{ |
||||||
|
local int newIndex; |
||||||
|
if (emptyIndices.length > 0) |
||||||
|
{ |
||||||
|
newIndex = emptyIndices[emptyIndices.length - 1]; |
||||||
|
emptyIndices.length = emptyIndices.length - 1; |
||||||
|
actorVersions[newIndex] += 1; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
newIndex = actorRecords.length; |
||||||
|
actorVersions[newIndex] = 0; |
||||||
|
} |
||||||
|
actorRecords[newIndex] = candidate; |
||||||
|
indexAllocationFlag[newIndex] = 1; |
||||||
|
return newIndex; |
||||||
|
} |
||||||
|
|
||||||
|
// Forces `indexToFree` to become vacant, not storing any `Actor`. |
||||||
|
private final function FreeIndex(int indexToFree) |
||||||
|
{ |
||||||
|
if (indexAllocationFlag[indexToFree] > 0) |
||||||
|
{ |
||||||
|
actorRecords[indexToFree] = none; |
||||||
|
emptyIndices[emptyIndices.length] = indexToFree; |
||||||
|
} |
||||||
|
// No need to bump `actorVersions[indexToFree]` here, since any refences to |
||||||
|
// this index will automatically be invalidated by this: |
||||||
|
indexAllocationFlag[indexToFree] = 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a new `Actor` to the storage. |
||||||
|
* |
||||||
|
* Any reference created with `AddActor()` must be release with |
||||||
|
* `RemoveActor()`. |
||||||
|
* |
||||||
|
* This method does not attempt to check whether `newActor` is already stored |
||||||
|
* in `ActorService`. Therefore it is possible for the same actor to be stored |
||||||
|
* multiple times under different `ActorReference` records. |
||||||
|
* |
||||||
|
* Can only fail if provided `Actor` is either equal to `none`. |
||||||
|
* |
||||||
|
* Note that while this storage is supposed to be "safe", meaning that it |
||||||
|
* aims to reduce probability of `Actor`-related crashes, it assumes that |
||||||
|
* passed `Actor`s are not already "broken", as there is no way to check |
||||||
|
* for that. |
||||||
|
* `Actor`s that were not stored in a non-`Actor` object as a non-local |
||||||
|
* variable should not be broken. |
||||||
|
* |
||||||
|
* @param newActor `Actor` to store. If it's equal to `none`, then |
||||||
|
* this method will do nothing. |
||||||
|
* @return `ActorReference` struct that can be used to retrieve stored |
||||||
|
* `Actor` later. Returned reference will point at `none` once stored |
||||||
|
* `Actor` gets destroyed or removed via the `RemoveActor()` call. |
||||||
|
*/ |
||||||
|
public final function ActorReference AddActor(Actor newActor) |
||||||
|
{ |
||||||
|
local int newIndex; |
||||||
|
local ActorReference result; |
||||||
|
// This will make the very first check inside `ValidateIndexVersion()` fail |
||||||
|
result.index = -1; |
||||||
|
if (newActor == none) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
newIndex = InsertAtEmptyIndex(newActor); |
||||||
|
result.index = newIndex; |
||||||
|
result.version = actorVersions[newIndex]; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns an `Actor` that provided `reference` points at. |
||||||
|
* |
||||||
|
* @param reference Reference to the `Actor` this method should return. |
||||||
|
* @return `Actor`, stored in caller `ActorService` with `reference`. |
||||||
|
* If stored `Actor` was already destroyed or removed, this method will |
||||||
|
* return `none`. |
||||||
|
*/ |
||||||
|
public final function Actor GetActor(ActorReference reference) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
local Actor result; |
||||||
|
index = reference.index; |
||||||
|
if (!ValidateIndexVersion(index, reference.version)) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
// While this can be considered a side-effect in a getter, removing `none` |
||||||
|
// from the storage will not affect how `reference` behaves, since it can |
||||||
|
// only refer to `none` from now on. |
||||||
|
result = actorRecords[index]; |
||||||
|
if (result == none) |
||||||
|
{ |
||||||
|
FreeIndex(index); |
||||||
|
return none; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Replaces an `Actor`, that provided `reference` points at, with a `newActor`, |
||||||
|
* returning a new `ActorReference` and invalidating the old one (`reference`), |
||||||
|
* making it to refer to `none`. |
||||||
|
* |
||||||
|
* This method is guaranteed to be functionally identical (as far as public, |
||||||
|
* not encapsulated, interface is concerned) to calling two methods: |
||||||
|
* `RemoveActor(reference)` and `AddActor(newActor)`. But is expected to |
||||||
|
* provide be more efficient. |
||||||
|
* |
||||||
|
* @param reference Reference to `Actor` that should be replaced. |
||||||
|
* @param newActor New `Actor` to store. |
||||||
|
* @return Reference to the `newActor` inside the storage. |
||||||
|
*/ |
||||||
|
public final function ActorReference UpdateActor( |
||||||
|
ActorReference reference, |
||||||
|
Actor newActor) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = reference.index; |
||||||
|
// Nothing to remove |
||||||
|
if (!ValidateIndexVersion(index, reference.version)) { |
||||||
|
return AddActor(newActor); |
||||||
|
} |
||||||
|
// Nothing to add |
||||||
|
if (newActor == none) { |
||||||
|
RemoveActor(reference); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// If we need to both remove and add, we can just replace an `Actor` |
||||||
|
// and bump the stored version to invalidate copies of the passed |
||||||
|
// `reference`: |
||||||
|
actorRecords[index] = newActor; |
||||||
|
actorVersions[index] += 1; |
||||||
|
reference.version += 1; |
||||||
|
} |
||||||
|
return reference; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unconditionally removes an `Actor` recorded with `reference` from storage. |
||||||
|
* |
||||||
|
* Cannot fail. |
||||||
|
* |
||||||
|
* If referred `Actor` is recorded in storage under several different |
||||||
|
* references - only this reference will be affected. The rest are still going |
||||||
|
* to refer to that `Actor`. |
||||||
|
* |
||||||
|
* @param reference Reference to `Actor` to be removed. |
||||||
|
*/ |
||||||
|
public final function RemoveActor(ActorReference reference) |
||||||
|
{ |
||||||
|
if (ValidateIndexVersion(reference.index, reference.version)) { |
||||||
|
FreeIndex(reference.index); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns size of this storage. |
||||||
|
* |
||||||
|
* @param totalSize Default value (`false`) means that method should return |
||||||
|
* amount of currently stored `Actor`s, while `true` means how much space |
||||||
|
* this storage takes up (in the sense that the actual amount in bytes is |
||||||
|
* `O(GetSize(true))` in big-O notation). |
||||||
|
* @return Size of this storage. |
||||||
|
*/ |
||||||
|
public final function int GetSize(optional bool totalSize) |
||||||
|
{ |
||||||
|
if (totalSize) { |
||||||
|
return actorRecords.length; |
||||||
|
} |
||||||
|
return actorRecords.length - emptyIndices.length; |
||||||
|
} |
||||||
|
|
||||||
|
// Validates passed index and `Actor`'s version, taken from |
||||||
|
// the `ActorReference`: returns `true` iff `Actor` referred to by them |
||||||
|
// currently exists in this storage (even if it's now equal to `none`). |
||||||
|
private final function bool ValidateIndexVersion( |
||||||
|
int refIndex, |
||||||
|
int refVersion) |
||||||
|
{ |
||||||
|
if (refIndex < 0) return false; |
||||||
|
if (refIndex >= actorRecords.length) return false; |
||||||
|
if (actorVersions[refIndex] != refVersion) return false; |
||||||
|
|
||||||
|
return (indexAllocationFlag[refIndex] > 0); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
/** |
||||||
|
* This file either was manually edited with minimal changes from the template |
||||||
|
* for value boxes. |
||||||
|
* Copyright 2021 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class ActorBox extends ValueBox |
||||||
|
dependson(ActorService); |
||||||
|
|
||||||
|
var protected int boxHashCode; |
||||||
|
var protected bool hasValue; |
||||||
|
var protected ActorService.ActorReference valueRef; |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
if (hasValue) { |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
} |
||||||
|
if (service != none) { |
||||||
|
service.RemoveActor(valueRef); |
||||||
|
} |
||||||
|
hasValue = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns stored value. |
||||||
|
* |
||||||
|
* @return Value, stored in this reference. |
||||||
|
*/ |
||||||
|
public final function AcediaActor Get() |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
if (!hasValue) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service != none) { |
||||||
|
return AcediaActor(service.GetActor(valueRef)); |
||||||
|
} |
||||||
|
return none; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Initialized box value. Can only be called once. |
||||||
|
* |
||||||
|
* @param boxValue Value to store in this reference. |
||||||
|
* @return Reference to the caller `ActorBox` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ActorBox Initialize(AcediaActor boxValue) |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
if (IsInitialized()) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service == none) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
valueRef = service.AddActor(boxValue); |
||||||
|
hasValue = true; |
||||||
|
MarkInitialized(); |
||||||
|
if (boxValue != none) { |
||||||
|
boxHashCode = boxValue.GetHashCode(); |
||||||
|
} |
||||||
|
else { |
||||||
|
boxHashCode = super.GetHashCode(); |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public function bool IsEqual(Object other) |
||||||
|
{ |
||||||
|
local ActorBox otherBox; |
||||||
|
local ActorService service; |
||||||
|
otherBox = ActorBox(other); |
||||||
|
if (otherBox == none) return false; |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service == none) return false; |
||||||
|
|
||||||
|
return Get() == otherBox.Get(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function int CalculateHashCode() |
||||||
|
{ |
||||||
|
return boxHashCode; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,95 @@ |
|||||||
|
/** |
||||||
|
* This file either was manually edited with minimal changes from the template |
||||||
|
* for value references. |
||||||
|
* Copyright 2021 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class ActorRef extends ValueRef |
||||||
|
dependson(ActorService); |
||||||
|
|
||||||
|
var protected bool hasValue; |
||||||
|
var protected ActorService.ActorReference valueRef; |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
if (hasValue) { |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
} |
||||||
|
if (service != none) { |
||||||
|
service.RemoveActor(valueRef); |
||||||
|
} |
||||||
|
hasValue = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns stored value. |
||||||
|
* |
||||||
|
* @return Value, stored in this reference. |
||||||
|
*/ |
||||||
|
public final function AcediaActor Get() |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
if (!hasValue) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service != none) { |
||||||
|
return AcediaActor(service.GetActor(valueRef)); |
||||||
|
} |
||||||
|
return none; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Changes stored value. Cannot fail. |
||||||
|
* |
||||||
|
* @param newValue New value to store in this reference. |
||||||
|
* @return Reference to the caller `ActorRef` to allow for |
||||||
|
* method chaining. |
||||||
|
*/ |
||||||
|
public final function ActorRef Set(AcediaActor newValue) |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service == none) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
if (hasValue) { |
||||||
|
valueRef = service.UpdateActor(valueRef, newValue); |
||||||
|
} |
||||||
|
else { |
||||||
|
valueRef = service.AddActor(newValue); |
||||||
|
} |
||||||
|
hasValue = true; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public function bool IsEqual(Object other) |
||||||
|
{ |
||||||
|
local ActorRef otherBox; |
||||||
|
local ActorService service; |
||||||
|
otherBox = ActorRef(other); |
||||||
|
if (otherBox == none) return false; |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service == none) return false; |
||||||
|
|
||||||
|
return Get() == otherBox.Get(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
class MockAcediaActor extends AcediaActor; |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
bCollideActors = false |
||||||
|
bBlockActors = false |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
class MockNativeActor extends Actor; |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
bCollideActors = false |
||||||
|
bBlockActors = false |
||||||
|
} |
@ -0,0 +1,160 @@ |
|||||||
|
/** |
||||||
|
* Set of tests for `ActorService`'s functionality. |
||||||
|
* Copyright 2021 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class TEST_ActorService extends TestCase |
||||||
|
dependson(ActorService) |
||||||
|
abstract; |
||||||
|
|
||||||
|
protected static function TESTS() |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
Context("Testing adding and retrieving native `Actor`s from" |
||||||
|
@ "`ActorService`."); |
||||||
|
Issue("Cannot get instance of `ActorService`."); |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
TEST_ExpectNotNone(service); |
||||||
|
Test_AddDestroy(class'MockNativeActor'); |
||||||
|
Test_AddDestroy(class'MockAcediaActor'); |
||||||
|
Test_AddRemove(class'MockNativeActor'); |
||||||
|
Test_AddRemove(class'MockAcediaActor'); |
||||||
|
Test_Update(class'MockNativeActor'); |
||||||
|
Test_Update(class'MockAcediaActor'); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_AddDestroy(class<Actor> classToTest) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local ActorService service; |
||||||
|
local array<ActorService.ActorReference> actorRefs; |
||||||
|
local array<Actor> nativeActors; |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
Issue("Cannot retrieve recorded `Actor`s with `GetActor()`."); |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); |
||||||
|
actorRefs[i] = service.AddActor(nativeActors[i]); |
||||||
|
} |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
TEST_ExpectNotNone(service.GetActor(actorRefs[i])); |
||||||
|
TEST_ExpectTrue(service.GetActor(actorRefs[i]) == nativeActors[i]); |
||||||
|
} |
||||||
|
Issue("Just destroyed `Actor`s are not returned as `none` from" |
||||||
|
@ "`GetActor()`."); |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
if (i % 2 == 1) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
nativeActors[i].Destroy(); |
||||||
|
TEST_ExpectNone(service.GetActor(actorRefs[i])); |
||||||
|
} |
||||||
|
|
||||||
|
Issue("`Actor`s are not properly added to service after destruction of the" |
||||||
|
@ "previously stored ones."); |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
if (i % 2 == 0) |
||||||
|
{ |
||||||
|
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); |
||||||
|
actorRefs[i] = service.AddActor(nativeActors[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
TEST_ExpectNotNone(service.GetActor(actorRefs[i])); |
||||||
|
TEST_ExpectTrue(service.GetActor(actorRefs[i]) == nativeActors[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_AddRemove(class<Actor> classToTest) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local ActorService service; |
||||||
|
local array<ActorService.ActorReference> actorRefs; |
||||||
|
local array<Actor> nativeActors; |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); |
||||||
|
actorRefs[i] = service.AddActor(nativeActors[i]); |
||||||
|
} |
||||||
|
|
||||||
|
Issue("Just `Remove()`-ed `Actor`s are not returned as `none` from" |
||||||
|
@ "`GetActor()`."); |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
if (i % 2 == 1) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
service.RemoveActor(actorRefs[i]); |
||||||
|
TEST_ExpectNone(service.GetActor(actorRefs[i])); |
||||||
|
} |
||||||
|
|
||||||
|
Issue("`Actor`s are not properly added to service after removal of the" |
||||||
|
@ "previously stored ones."); |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
if (i % 2 == 0) |
||||||
|
{ |
||||||
|
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); |
||||||
|
actorRefs[i] = service.AddActor(nativeActors[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
TEST_ExpectNotNone(service.GetActor(actorRefs[i])); |
||||||
|
TEST_ExpectTrue(service.GetActor(actorRefs[i]) == nativeActors[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_Update(class<Actor> classToTest) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local ActorService service; |
||||||
|
local array<ActorService.ActorReference> actorRefs; |
||||||
|
local array<Actor> nativeActors; |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); |
||||||
|
actorRefs[i] = service.AddActor(nativeActors[i]); |
||||||
|
} |
||||||
|
|
||||||
|
Issue("`Actor`s are not properly updated."); |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
if (i % 2 == 0) |
||||||
|
{ |
||||||
|
nativeActors[i] = Actor(__().memory.Allocate(classToTest)); |
||||||
|
actorRefs[i] = service.UpdateActor(actorRefs[i], nativeActors[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < 1000; i += 1) |
||||||
|
{ |
||||||
|
TEST_ExpectNotNone(service.GetActor(actorRefs[i])); |
||||||
|
TEST_ExpectTrue(service.GetActor(actorRefs[i]) == nativeActors[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
caseGroup = "Types" |
||||||
|
caseName = "ActorService" |
||||||
|
} |
@ -0,0 +1,95 @@ |
|||||||
|
/** |
||||||
|
* This file either was manually edited with minimal changes from the template |
||||||
|
* for value references. |
||||||
|
* Copyright 2021 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class NativeActorRef extends ValueRef |
||||||
|
dependson(ActorService); |
||||||
|
|
||||||
|
var protected bool hasValue; |
||||||
|
var protected ActorService.ActorReference valueRef; |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
if (hasValue) { |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
} |
||||||
|
if (service != none) { |
||||||
|
service.RemoveActor(valueRef); |
||||||
|
} |
||||||
|
hasValue = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns stored value. |
||||||
|
* |
||||||
|
* @return Value, stored in this reference. |
||||||
|
*/ |
||||||
|
public final function Actor Get() |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
if (!hasValue) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service != none) { |
||||||
|
return service.GetActor(valueRef); |
||||||
|
} |
||||||
|
return none; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Changes stored value. Cannot fail. |
||||||
|
* |
||||||
|
* @param newValue New value to store in this reference. |
||||||
|
* @return Reference to the caller `NativeActorRef` to allow for |
||||||
|
* method chaining. |
||||||
|
*/ |
||||||
|
public final function NativeActorRef Set(Actor newValue) |
||||||
|
{ |
||||||
|
local ActorService service; |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service == none) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
if (hasValue) { |
||||||
|
valueRef = service.UpdateActor(valueRef, newValue); |
||||||
|
} |
||||||
|
else { |
||||||
|
valueRef = service.AddActor(newValue); |
||||||
|
} |
||||||
|
hasValue = true; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public function bool IsEqual(Object other) |
||||||
|
{ |
||||||
|
local NativeActorRef otherBox; |
||||||
|
local ActorService service; |
||||||
|
otherBox = NativeActorRef(other); |
||||||
|
if (otherBox == none) return false; |
||||||
|
service = ActorService(class'ActorService'.static.Require()); |
||||||
|
if (service == none) return false; |
||||||
|
|
||||||
|
return Get() == otherBox.Get(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
Loading…
Reference in new issue