Browse Source

Add `ActorBox` / `ActorRef` / `NativeActorRef`

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
c6a0565314
  1. 42
      sources/Manifest.uc
  2. 294
      sources/Types/ActorService.uc
  3. 105
      sources/Types/Boxes/Native/ActorBox.uc
  4. 95
      sources/Types/Refs/Native/ActorRef.uc
  5. 7
      sources/Types/Tests/MockAcediaActor.uc
  6. 7
      sources/Types/Tests/MockNativeActor.uc
  7. 160
      sources/Types/Tests/TEST_ActorService.uc
  8. 95
      sources/Unreal/NativeActorRef.uc
  9. 14
      sources/Unreal/UnrealAPI.uc

42
sources/Manifest.uc

@ -26,30 +26,32 @@ defaultproperties
commands(0) = class'ACommandHelp'
commands(1) = class'ACommandDosh'
commands(2) = class'ACommandNick'
commands(3) = class'ACommandTest'
services(0) = class'ConnectionService'
services(1) = class'PlayerService'
aliasSources(0) = class'AliasSource'
aliasSources(1) = class'WeaponAliasSource'
aliasSources(2) = class'ColorAliasSource'
testCases(0) = class'TEST_Base'
testCases(1) = class'TEST_Boxes'
testCases(2) = class'TEST_Refs'
testCases(3) = class'TEST_SignalsSlots'
testCases(4) = class'TEST_UnrealAPI'
testCases(5) = class'TEST_Aliases'
testCases(6) = class'TEST_ColorAPI'
testCases(7) = class'TEST_Text'
testCases(8) = class'TEST_TextAPI'
testCases(9) = class'TEST_Parser'
testCases(10) = class'TEST_JSON'
testCases(11) = class'TEST_TextCache'
testCases(12) = class'TEST_User'
testCases(13) = class'TEST_Memory'
testCases(14) = class'TEST_DynamicArray'
testCases(15) = class'TEST_AssociativeArray'
testCases(16) = class'TEST_CollectionsMixed'
testCases(17) = class'TEST_Iterator'
testCases(18) = class'TEST_Command'
testCases(19) = class'TEST_CommandDataBuilder'
testCases(20) = class'TEST_LogMessage'
testCases(1) = class'TEST_ActorService'
testCases(2) = class'TEST_Boxes'
testCases(3) = class'TEST_Refs'
testCases(4) = class'TEST_SignalsSlots'
testCases(5) = class'TEST_UnrealAPI'
testCases(6) = class'TEST_Aliases'
testCases(7) = class'TEST_ColorAPI'
testCases(8) = class'TEST_Text'
testCases(9) = class'TEST_TextAPI'
testCases(10) = class'TEST_Parser'
testCases(11) = class'TEST_JSON'
testCases(12) = class'TEST_TextCache'
testCases(13) = class'TEST_User'
testCases(14) = class'TEST_Memory'
testCases(15) = class'TEST_DynamicArray'
testCases(16) = class'TEST_AssociativeArray'
testCases(17) = class'TEST_CollectionsMixed'
testCases(18) = class'TEST_Iterator'
testCases(19) = class'TEST_Command'
testCases(20) = class'TEST_CommandDataBuilder'
testCases(21) = class'TEST_LogMessage'
}

294
sources/Types/ActorService.uc

@ -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
{
}

105
sources/Types/Boxes/Native/ActorBox.uc

@ -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
{
}

95
sources/Types/Refs/Native/ActorRef.uc

@ -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
{
}

7
sources/Types/Tests/MockAcediaActor.uc

@ -0,0 +1,7 @@
class MockAcediaActor extends AcediaActor;
defaultproperties
{
bCollideActors = false
bBlockActors = false
}

7
sources/Types/Tests/MockNativeActor.uc

@ -0,0 +1,7 @@
class MockNativeActor extends Actor;
defaultproperties
{
bCollideActors = false
bBlockActors = false
}

160
sources/Types/Tests/TEST_ActorService.uc

@ -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"
}

95
sources/Unreal/NativeActorRef.uc

@ -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
{
}

14
sources/Unreal/UnrealAPI.uc

@ -241,6 +241,20 @@ public final function array<Inventory> GetAllInventoryFrom(
return result;
}
/**
* Creates reference object to store a `Actor` value.
*
* @param value Initial value to store in reference.
* @return `NativeActorRef`, containing `value`.
*/
public final function NativeActorRef ActorRef(optional Actor value)
{
local NativeActorRef ref;
ref = NativeActorRef(_.memory.Allocate(class'NativeActorRef'));
ref.Set(value);
return ref;
}
defaultproperties
{
}
Loading…
Cancel
Save