Browse Source

Add signal-slot functionality to Acedia

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
858091d668
  1. 347
      sources/Events/Signal.uc
  2. 151
      sources/Events/Slot.uc
  3. 43
      sources/Events/Tests/MockSignal.uc
  4. 49
      sources/Events/Tests/MockSignalSlotClient.uc
  5. 41
      sources/Events/Tests/MockSlot.uc
  6. 214
      sources/Events/Tests/TEST_SignalsSlots.uc
  7. 35
      sources/Manifest.uc

347
sources/Events/Signal.uc

@ -0,0 +1,347 @@
/**
* One of the two classes that make up a core of event system in Acedia.
* `Signal`s, along with `Slot`s, are used for communication between
* objects. Signals can be connected to slots of appropriate class and emitted.
* When a signal is emitted, all connected slots are notified and their handler
* is called.
* This `Signal`-`Slot` system is essentially a wrapper for delegates
* (`Slot` wraps over a single delegate, allowing us to store them in array),
* but, unlike them, makes it possible to add several handlers for any event in
* a convenient to use way, e.g..:
* `_.unreal.OnTick(self).connect = myTickHandler`
* To create your own `Signal` you need to:
* 1. Make a non-abstract child class of `Signal`;
* 2. Use one of the templates presented in this file below;
* 3. Create a paired `Slot` class and set it's class to `relatedSlotClass`
* in `defaultproperties`.
* 4. (Recommended) Provide a standard interface by defining an event
* method (similar to `_.unreal.OnTick()`) in an object that will own
* this signal, example of definition is also listed below.
* More detailed information can be found in documentation.
* 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 Signal extends AcediaObject
abstract;
// Class of the slot that can catch your `Signal` class
var public const class<Slot> relatedSlotClass;
// We want to always return a non-`none` slot to avoid "Access 'none'"
// errors.
// So if provided signal receiver is invalid - we still create a `Slot`
// for it, but remember it in this array of slots we aren't going to use
// in order to dispose of them later.
var private array<Slot> failedSlots;
// Set to `true` when we are in the process of deleting our `Slot`s.
// Once `Slot` is deallocated, it notifies it's `Signal` (us) that it
// should be removed.
// But if it's deallocated because we are removing it we want to ignore
// their notification and this flag helps us do that.
var private bool doingSelfCleaning;
// Used to provide iterating interface (`StartIterating()` / `GetNextSlot()`).
// Points at the next slot to return.
var private int nextSlotIndex;
// These arrays could be defined as one array of `struct`s with four
// elements.
// We use four different arrays instead for performance reasons.
// They must have the same length at all times and elements with the
// same index correspond to the same "record".
// Reference to registered `Slot`
var private array<Slot> registeredSlots;
// Life version of the registered `Slot`, to track unexpected deallocations
var private array<int> slotLifeVersions;
// Receiver, associated with the `Slot`: when it's deallocated,
// corresponding `Slot` should be removed
var private array<AcediaObject> slotReceivers;
// Life version of the registered receiver, to track it's deallocation
var private array<int> slotReceiversLifeVersions;
/* TEMPLATE for handlers without returned values:
public final function Emit(<PARAMETERS>)
{
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
<SLOT_CLASS>(nextSlot).connect(<PARAMETERS>);
nextSlot = GetNextSlot();
}
CleanEmptySlots();
}
*/
/* TEMPLATE for handlers with returned values:
public final function int Emit(<PARAMETERS>)
{
local <RETURN_TYPE> newValue;
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
newValue = <SLOT_CLASS>(nextSlot).connect(<PARAMETERS>);
// This check if necessary before using returned value
if (!nextSlot.IsEmpty())
{
// Now handle `newValue` however you see fit
}
nextSlot = GetNextSlot();
}
CleanEmptySlots();
return value;
}
*/
/* TEMPLATE for the interface method:
var private <SIGNAL_CLASS> mySignal;
public final function <SLOT_CLASS> OnMyEvent(AcediaObject receiver)
{
return <SLOT_CLASS>(mySignal.NewSlot(receiver));
}
*/
protected function Finalizer()
{
doingSelfCleaning = true;
_.memory.FreeMany(registeredSlots);
doingSelfCleaning = false;
registeredSlots.length = 0;
slotLifeVersions.length = 0;
slotReceivers.length = 0;
slotReceiversLifeVersions.length = 0;
}
/**
* Creates a new slot for `receiver` to catch emitted signals.
* Supposed to be used inside a special interface method only.
*
* @param receiver Receiver to which new `Slot` would be connected.
* Method connected to a `Slot` generated by this method must belong to
* the `receiver`, otherwise behavior of `Signal`-`Slot` system is
* undefined.
* Must be a properly allocated `AcediaObject`.
* @return New `Slot` object that will be connected to the caller `Signal` if
* provided `receiver` is correct. Guaranteed to have class
* `relatedSlotClass`.
*/
public final function Slot NewSlot(AcediaObject receiver)
{
local Slot newSlot;
newSlot = Slot(_.memory.Allocate(relatedSlotClass));
newSlot.Initialize(self);
AddSlot(newSlot, receiver);
return newSlot;
}
/**
* Disconnects all of the `receiver`'s `Slot`s from the caller `Signal`.
*
* @param receiver Object to disconnect from the caller `Signal`.
*/
public final function Disconnect(AcediaObject receiver)
{
local int i;
doingSelfCleaning = true;
while (i < slotReceivers.length)
{
if (slotReceivers[i] == none || slotReceivers[i] == receiver)
{
_.memory.Free(registeredSlots[i]);
registeredSlots.Remove(i, 1);
slotLifeVersions.Remove(i, 1);
slotReceivers.Remove(i, 1);
slotReceiversLifeVersions.Remove(i, 1);
}
else {
i += 1;
}
}
doingSelfCleaning = false;
}
/**
* Adds new `Slot` `newSlot` with receiver `receiver` to the caller `Signal`.
*
* Does nothing if `newSlot` is already added to the caller `Signal`.
*
* @param newSlot Slot to add. Must be initialize for the caller `Signal`.
* @param receiver Receiver to which new `Slot` would be connected.
* Method connected to a `Slot` generated by this method must belong to
* the `receiver`, otherwise behavior of `Signal`-`Slot` system is
* undefined.
* Must be a properly allocated `AcediaObject`.
*/
protected final function AddSlot(Slot newSlot, AcediaObject receiver)
{
local int i;
local int newSlotIndex;
if (newSlot == none) return;
if (newSlot.class != relatedSlotClass) return;
if (!newSlot.IsOwnerSignal(self)) return;
if (receiver == none || !receiver.IsAllocated())
{
failedSlots[failedSlots.length] = newSlot;
return;
}
for (i = 0; i < registeredSlots.length; i += 1)
{
if (registeredSlots[i] != newSlot) {
continue;
}
if (slotLifeVersions[i] != newSlot.GetLifeVersion())
{
slotLifeVersions[i] = newSlot.GetLifeVersion();
slotReceivers[i] = receiver;
if (receiver != none) {
slotReceiversLifeVersions[i] = receiver.GetLifeVersion();
}
}
return;
}
newSlotIndex = registeredSlots.length;
registeredSlots[newSlotIndex] = newSlot;
slotLifeVersions[newSlotIndex] = newSlot.GetLifeVersion();
slotReceivers[newSlotIndex] = receiver;
slotReceiversLifeVersions[newSlotIndex] = receiver.GetLifeVersion();
}
/**
* Removes given `slotToRemove` if it was connected to the caller `Signal`.
*
* Does not deallocate `slotToRemove`.
*
* Cannot fail.
*
* @param slotToRemove Slot to be removed.
*/
public final function RemoveSlot(Slot slotToRemove)
{
local int i;
if (slotToRemove == none) return;
if (doingSelfCleaning) return;
for (i = 0; i < registeredSlots.length; i += 1)
{
if (registeredSlots[i] == slotToRemove)
{
registeredSlots.Remove(i, 1);
slotLifeVersions.Remove(i, 1);
slotReceivers.Remove(i, 1);
slotReceiversLifeVersions.Remove(i, 1);
return;
}
}
}
/**
* One of two methods that provide an iterator access to the private array of
* `Slot`s and perform all the necessary safety checks.
*
* Must be called before each new iterating cycle.
*
* There cannot be any `Slot` additions or removal during one iteration cycle.
*/
protected final function StartIterating()
{
nextSlotIndex = 0;
}
/**
* One of two methods that provide an iterator access to the private array of
* `Slot`s and perform all the necessary safety checks.
*
* `StartIterating()` must be called to initialize iteration cycle, then this
* method can be called until it returns `none`.
*
* There cannot be any `Slot` additions or removal during one iteration cycle.
*
* @return Next `Slot` that must receive emitted signal. `none` means that
* there are no more `Slot`s to iterate over.
*/
protected final function Slot GetNextSlot()
{
local bool isNextSlotValid;
local int nextSlotLifeVersion, nextReceiverLifeVersion;
local Slot nextSlot;
local AcediaObject nextReceiver;
doingSelfCleaning = true;
while (nextSlotIndex < registeredSlots.length)
{
nextSlot = registeredSlots[nextSlotIndex];
nextSlotLifeVersion = slotLifeVersions[nextSlotIndex];
nextReceiver = slotReceivers[nextSlotIndex];
nextReceiverLifeVersion = slotReceiversLifeVersions[nextSlotIndex];
isNextSlotValid = (nextSlot.GetLifeVersion() == nextSlotLifeVersion)
&& (nextReceiver.GetLifeVersion() == nextReceiverLifeVersion);
if (isNextSlotValid)
{
nextSlotIndex += 1;
return nextSlot;
}
else
{
registeredSlots.Remove(nextSlotIndex, 1);
slotLifeVersions.Remove(nextSlotIndex, 1);
slotReceivers.Remove(nextSlotIndex, 1);
slotReceiversLifeVersions.Remove(nextSlotIndex, 1);
_.memory.Free(nextSlot);
}
}
doingSelfCleaning = false;
return none;
}
/**
* In case it's detected that some of the slots do not actually have any
* handler setup - this method will clean them up.
*/
protected final function CleanEmptySlots()
{
local int index;
_.memory.FreeMany(failedSlots);
failedSlots.length = 0;
doingSelfCleaning = true;
while (index < registeredSlots.length)
{
if (registeredSlots[index].IsEmpty())
{
registeredSlots[index].FreeSelf(slotLifeVersions[index]);
_.memory.Free(registeredSlots[index]);
registeredSlots.Remove(index, 1);
slotLifeVersions.Remove(index, 1);
slotReceivers.Remove(index, 1);
slotReceiversLifeVersions.Remove(index, 1);
}
else {
index += 1;
}
}
doingSelfCleaning = false;
}
defaultproperties
{
relatedSlotClass = class'Slot'
}

151
sources/Events/Slot.uc

@ -0,0 +1,151 @@
/**
* One of the two classes that make up a core of event system in Acedia.
* `Signal`s, along with `Slot`s, are used for communication between
* objects. Signals can be connected to slots of appropriate class and emitted.
* When a signal is emitted, all connected slots are notified and their handler
* is called.
* This `Signal`-`Slot` system is essentially a wrapper for delegates
* (`Slot` wraps over a single delegate, allowing us to store them in array),
* but, unlike them, makes it possible to add several handlers for any event in
* a convenient to use way, e.g.:
* `_.unreal.OnTick(self).connect = myTickHandler`
* To create your own `Slot` you need to:
* 1. Make a non-abstract child class of `Signal`;
* 2. Use one of the templates presented in this file below.
* More detailed information can be found in documentation.
* 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 Slot extends AcediaObject
abstract;
var private bool dummyMethodCalled;
var private Signal ownerSignal;
/* TEMPLATE for handlers without returned values:
delegate connect(<PARAMETERS>)
{
DummyCall();
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
*/
/* TEMPLATE for handlers with returned values:
delegate <RETURN_TYPE> connect(<PARAMETERS>)
{
DummyCall();
// Return anything you want:
// this value will be filtered inside corresponding `Signal`
return <???>;
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
*/
protected function Finalizer()
{
dummyMethodCalled = false;
if (ownerSignal != none) {
ownerSignal.RemoveSlot(self);
}
ownerSignal = none;
}
/**
* Calling this method marks caller `Slot` as "empty", i.e. having an empty
* delegate. `Slot`s like that are deleted from `Signal`s upon detection.
*
* Must be called inside your `connect()` implementation.
*/
protected final function DummyCall()
{
dummyMethodCalled = true;
// We do not want to call `ownerSignal.RemoveSlot(self)` here, since
// `ownerSignal` is likely in process of iterating through it's `Slot`s
// and removing (or adding) `Slot`s from it can mess up that process.
}
/**
* Initialized caller `Slot` to receive signals emitted by `newOwnerSignal`.
*
* Can only be done once for every `Slot`.
*
* @param newOwnerSignal `Signal` we want to receive emitted signals from.
* @return `true` if initialization was successful and `false` otherwise
* (if `newOwnerSignal` is invalid or caller `Slot` was
* already initialized).
*/
public final function bool Initialize(Signal newOwnerSignal)
{
if (ownerSignal != none) {
return false;
}
if (newOwnerSignal == none || !newOwnerSignal.IsAllocated())
{
FreeSelf();
return false;
}
ownerSignal = newOwnerSignal;
return true;
}
/**
* Checks if caller `Slot` was initialized to receive `testSignal`'s signals.
*
* @param testSignal `Signal` to test.
* @return `true` if caller `Slot` was initialized to receiver `testSignal`'s
* signals and `false` otherwise.
*/
public final function bool IsOwnerSignal(Signal testSignal)
{
return (ownerSignal == testSignal);
}
/**
* Checks if caller `Slot` was detected to be "empty", i.e. having an empty
* delegate. `Slot`s like that are deleted from `Signal`s upon detection.
*
* @return `true` if caller `Slot` is empty (and should be removed from the
* appropriate `Signal`) and `false` otherwise.
*/
public final function bool IsEmpty()
{
return dummyMethodCalled;
}
defaultproperties
{
}

43
sources/Events/Tests/MockSignal.uc

@ -0,0 +1,43 @@
/**
* `Signal` class intended for testing signal/slot functionality of Acedia.
* 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 MockSignal extends Signal;
public final function int Emit(int value, optional bool mod)
{
local int newValue;
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
newValue = MockSlot(nextSlot).connect(value, mod);
if (!nextSlot.IsEmpty()) {
value = newValue;
}
nextSlot = GetNextSlot();
}
CleanEmptySlots();
return value;
}
defaultproperties
{
relatedSlotClass = class'MockSlot'
}

49
sources/Events/Tests/MockSignalSlotClient.uc

@ -0,0 +1,49 @@
/**
* Object that provides a signal handler for testing signal/slot functionality
* of Acedia.
* 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 MockSignalSlotClient extends AcediaObject;
var private int value;
public final function SetValue(int newValue)
{
value = newValue;
}
// Return `SMockSlot` for testing purposes
public final function MockSlot AddToSignal(MockSignal signal)
{
local MockSlot slot;
slot = MockSlot(signal.NewSlot(self));
slot.connect = Handler;
return slot;
}
private final function int Handler(int inputValue, optional bool doSubtract)
{
if (doSubtract) {
return inputValue - value;
}
return inputValue + value;
}
defaultproperties
{
}

41
sources/Events/Tests/MockSlot.uc

@ -0,0 +1,41 @@
/**
* `Slot` class intended for testing signal/slot functionality of Acedia.
* 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 MockSlot extends Slot;
delegate int connect(int value, optional bool mod)
{
DummyCall();
return 13;
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

214
sources/Events/Tests/TEST_SignalsSlots.uc

@ -0,0 +1,214 @@
/**
* Set of tests for signal/slot functionality of Acedia.
* 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_SignalsSlots extends TestCase
abstract;
protected static function TESTS()
{
Context("Testing regularly connecting and disconnecting slots to"
@ "a signal.");
Test_Connecting();
Test_Disconnecting();
Context("Testing how signals and slots system handles deallocations and"
@ "unexpected changes to managed objects.");
Test_DeallocSlots();
Test_EmptySlots();
Test_DeallocReceivers();
}
protected static function Test_Connecting()
{
local int i;
local MockSignal signal;
local MockSignalSlotClient nextObject;
local array<MockSignalSlotClient> objects;
Issue("Slots are not connected correctly.");
signal = MockSignal(__().memory.Allocate(class'MockSignal'));
for (i = 0; i < 100; i += 1)
{
nextObject = MockSignalSlotClient(
__().memory.Allocate(class'MockSignalSlotClient'));
objects[objects.length] = nextObject;
nextObject.AddToSignal(signal);
nextObject.SetValue(i + 1);
}
// 1 + ... + 100 = 100 * 101 / 2 = 5050
// 5050 + 100 = 5150
TEST_ExpectTrue(signal.Emit(100) == 5150);
// -5050 + 20 = -5030
TEST_ExpectTrue(signal.Emit(20, true) == -5030);
__().memory.Free(signal);
Issue("Several slots from the same object are not connected correctly.");
signal = MockSignal(__().memory.Allocate(class'MockSignal'));
// Object with `SetValue(100)`
nextObject.AddToSignal(signal);
nextObject.AddToSignal(signal);
nextObject.AddToSignal(signal);
TEST_ExpectTrue(signal.Emit(400, true) == 100);
__().memory.FreeMany(objects);
}
protected static function Test_Disconnecting()
{
local int i;
local MockSignal signal;
local MockSignalSlotClient nextObject;
local array<MockSignalSlotClient> objects;
Issue("Slots are not disconnected correctly.");
signal = MockSignal(__().memory.Allocate(class'MockSignal'));
for (i = 0; i < 100; i += 1)
{
nextObject = MockSignalSlotClient(
__().memory.Allocate(class'MockSignalSlotClient'));
objects[objects.length] = nextObject;
nextObject.AddToSignal(signal);
nextObject.SetValue(i + 1);
}
// Now disconnect the ones with odd values
for (i = 0; i < 100; i += 2) {
signal.Disconnect(objects[i]); // value is `i + 1`, so 1, 3, 5,...
}
// 2 + 4 + 6 + ... + 100 = 2 * (1 + ... + 50) = 50 * 51 = 2550
// 2550 + 50 = 2600
TEST_ExpectTrue(signal.Emit(50) == 2600);
// -2550 + 550 = -2000
TEST_ExpectTrue(signal.Emit(550, true) == -2000);
__().memory.Free(signal);
__().memory.FreeMany(objects);
}
protected static function Test_DeallocSlots()
{
local int i;
local MockSignal signal;
local MockSignalSlotClient nextObject;
local array<MockSlot> slots;
local array<MockSignalSlotClient> objects;
Issue("Deallocated slots are still being called.");
signal = MockSignal(__().memory.Allocate(class'MockSignal'));
for (i = 0; i < 100; i += 1)
{
nextObject = MockSignalSlotClient(
__().memory.Allocate(class'MockSignalSlotClient'));
objects[objects.length] = nextObject;
slots[slots.length] = nextObject.AddToSignal(signal);
nextObject.SetValue(i + 1);
}
// Now disconnect the ones with odd values
for (i = 0; i < 100; i += 2) {
slots[i].FreeSelf(); // value is `i + 1`, so 1, 3, 5,...
}
// 2 + 4 + 6 + ... + 100 = 2 * (1 + ... + 50) = 50 * 51 = 2550
// 2550 + 50 = 2600
TEST_ExpectTrue(signal.Emit(50) == 2600);
// -2550 + 550 = -2000
TEST_ExpectTrue(signal.Emit(550, true) == -2000);
__().memory.Free(signal);
__().memory.FreeMany(objects);
}
protected static function Test_EmptySlots()
{
local int i;
local bool slotsAreNotDeallocated;
local MockSignal signal;
local MockSignalSlotClient nextObject;
local array<MockSlot> slots;
local array<MockSignalSlotClient> objects;
Issue("Slots with emptied delegates are still being called.");
signal = MockSignal(__().memory.Allocate(class'MockSignal'));
for (i = 0; i < 100; i += 1)
{
nextObject = MockSignalSlotClient(
__().memory.Allocate(class'MockSignalSlotClient'));
objects[objects.length] = nextObject;
slots[slots.length] = nextObject.AddToSignal(signal);
nextObject.SetValue(i + 1);
}
// Now disconnect the ones with odd values
for (i = 0; i < 100; i += 2) {
slots[i].connect = none; // value is `i + 1`, so 1, 3, 5,...
}
// 2 + 4 + 6 + ... + 100 = 2 * (1 + ... + 50) = 50 * 51 = 2550
// 2550 + 50 = 2600
TEST_ExpectTrue(signal.Emit(50) == 2600);
// -2550 + 550 = -2000
TEST_ExpectTrue(signal.Emit(550, true) == -2000);
for (i = 0; i < 100; i += 2)
{
if (slots[i].IsAllocated())
{
slotsAreNotDeallocated = true;
break;
}
}
Issue("Slots with emptied delegates are not deallocated.");
TEST_ExpectFalse(slotsAreNotDeallocated);
__().memory.Free(signal);
__().memory.FreeMany(objects);
}
protected static function Test_DeallocReceivers()
{
local int i;
local bool slotsAreNotDeallocated;
local MockSignal signal;
local MockSignalSlotClient nextObject;
local array<MockSlot> slots;
local array<MockSignalSlotClient> objects;
Issue("Deallocated receivers still receive messages.");
signal = MockSignal(__().memory.Allocate(class'MockSignal'));
for (i = 0; i < 100; i += 1)
{
nextObject = MockSignalSlotClient(
__().memory.Allocate(class'MockSignalSlotClient'));
objects[objects.length] = nextObject;
slots[slots.length] = nextObject.AddToSignal(signal);
nextObject.SetValue(i + 1);
}
// Now disconnect the ones with odd values
for (i = 0; i < 100; i += 2) {
objects[i].FreeSelf(); // value is `i + 1`, so 1, 3, 5,...
}
// 2 + 4 + 6 + ... + 100 = 2 * (1 + ... + 50) = 50 * 51 = 2550
// 2550 + 50 = 2600
TEST_ExpectTrue(signal.Emit(50) == 2600);
// -2550 + 550 = -2000
TEST_ExpectTrue(signal.Emit(550, true) == -2000);
for (i = 0; i < 100; i += 2)
{
if (slots[i].IsAllocated())
{
slotsAreNotDeallocated = true;
break;
}
}
Issue("Slots with deallocated receivers are not deallocated.");
TEST_ExpectFalse(true);
__().memory.Free(signal);
__().memory.FreeMany(objects);
}
defaultproperties
{
caseGroup = "Events"
caseName = "Signals and slots"
}

35
sources/Manifest.uc

@ -34,21 +34,22 @@ defaultproperties
testCases(0) = class'TEST_Base' testCases(0) = class'TEST_Base'
testCases(1) = class'TEST_Boxes' testCases(1) = class'TEST_Boxes'
testCases(2) = class'TEST_Refs' testCases(2) = class'TEST_Refs'
testCases(3) = class'TEST_UnrealAPI' testCases(3) = class'TEST_SignalsSlots'
testCases(4) = class'TEST_Aliases' testCases(4) = class'TEST_UnrealAPI'
testCases(5) = class'TEST_ColorAPI' testCases(5) = class'TEST_Aliases'
testCases(6) = class'TEST_Text' testCases(6) = class'TEST_ColorAPI'
testCases(7) = class'TEST_TextAPI' testCases(7) = class'TEST_Text'
testCases(8) = class'TEST_Parser' testCases(8) = class'TEST_TextAPI'
testCases(9) = class'TEST_JSON' testCases(9) = class'TEST_Parser'
testCases(10) = class'TEST_TextCache' testCases(10) = class'TEST_JSON'
testCases(11) = class'TEST_User' testCases(11) = class'TEST_TextCache'
testCases(12) = class'TEST_Memory' testCases(12) = class'TEST_User'
testCases(13) = class'TEST_DynamicArray' testCases(13) = class'TEST_Memory'
testCases(14) = class'TEST_AssociativeArray' testCases(14) = class'TEST_DynamicArray'
testCases(15) = class'TEST_CollectionsMixed' testCases(15) = class'TEST_AssociativeArray'
testCases(16) = class'TEST_Iterator' testCases(16) = class'TEST_CollectionsMixed'
testCases(17) = class'TEST_Command' testCases(17) = class'TEST_Iterator'
testCases(18) = class'TEST_CommandDataBuilder' testCases(18) = class'TEST_Command'
testCases(19) = class'TEST_LogMessage' testCases(19) = class'TEST_CommandDataBuilder'
testCases(20) = class'TEST_LogMessage'
} }
Loading…
Cancel
Save