Browse Source

Add reference counting to AcediaCore types

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
94d4a8dbdc
  1. 8
      sources/Memory/MemoryAPI.uc
  2. 111
      sources/Memory/Tests/TEST_Memory.uc
  3. 56
      sources/Types/AcediaActor.uc
  4. 58
      sources/Types/AcediaObject.uc

8
sources/Memory/MemoryAPI.uc

@ -209,6 +209,10 @@ public final function Free(Object objectToDeallocate)
if (!objectAsAcediaObject.IsAllocated()) {
return;
}
objectAsAcediaObject._deref();
if (objectAsAcediaObject._getRefCount() > 0) {
return;
}
relevantPool = objectAsAcediaObject._getPool();
objectAsAcediaObject._finalizer();
}
@ -217,6 +221,10 @@ public final function Free(Object objectToDeallocate)
if (!objectAsAcediaActor.IsAllocated()) {
return;
}
objectAsAcediaActor._deref();
if (objectAsAcediaActor._getRefCount() > 0) {
return;
}
objectAsAcediaActor._finalizer();
}
// Try to store freed object in a pool

111
sources/Memory/Tests/TEST_Memory.uc

@ -27,6 +27,7 @@ protected static function TESTS()
Test_ActorConstructorsFinalizers();
Test_ObjectPoolUsage();
Test_LifeVersionIsUnique();
Test_RefCounting();
}
protected static function Test_LifeVersionIsUnique()
@ -164,6 +165,116 @@ protected static function Test_ObjectPoolUsage()
TEST_ExpectTrue(obj1 != obj2);
}
protected static function Test_RefCounting()
{
Context("Testing usage of reference counting.");
SubTest_RefCountingObjectFreeSelf();
SubTest_RefCountingObjectFree();
SubTest_RefCountingActorFreeSelf();
SubTest_RefCountingActorFree();
}
protected static function SubTest_RefCountingObjectFreeSelf()
{
local MockObject temp;
Issue("Reference counting for `AcediaObject`s does not work correctly"
@ "with `FreeSelf()`");
temp = MockObject(__().memory.Allocate(class'MockObject'));
temp.NewRef().NewRef().NewRef();
TEST_ExpectTrue(temp._getRefCount() == 4);
TEST_ExpectTrue(temp.IsAllocated());
temp.FreeSelf();
TEST_ExpectTrue(temp._getRefCount() == 3);
TEST_ExpectTrue(temp.IsAllocated());
temp.FreeSelf();
TEST_ExpectTrue(temp._getRefCount() == 2);
TEST_ExpectTrue(temp.IsAllocated());
temp.FreeSelf();
TEST_ExpectTrue(temp._getRefCount() == 1);
TEST_ExpectTrue(temp.IsAllocated());
temp.FreeSelf();
TEST_ExpectTrue(temp._getRefCount() == 0);
TEST_ExpectFalse(temp.IsAllocated());
}
protected static function SubTest_RefCountingObjectFree()
{
local MockObject temp;
Issue("Reference counting for `AcediaObject`s does not work correctly"
@ "with `__().memory.Free()`");
temp = MockObject(__().memory.Allocate(class'MockObject'));
temp.NewRef().NewRef().NewRef();
TEST_ExpectTrue(temp._getRefCount() == 4);
TEST_ExpectTrue(temp.IsAllocated());
__().memory.Free(temp);
TEST_ExpectTrue(temp._getRefCount() == 3);
TEST_ExpectTrue(temp.IsAllocated());
__().memory.Free(temp);
TEST_ExpectTrue(temp._getRefCount() == 2);
TEST_ExpectTrue(temp.IsAllocated());
__().memory.Free(temp);
TEST_ExpectTrue(temp._getRefCount() == 1);
TEST_ExpectTrue(temp.IsAllocated());
__().memory.Free(temp);
TEST_ExpectTrue(temp._getRefCount() == 0);
TEST_ExpectFalse(temp.IsAllocated());
}
protected static function SubTest_RefCountingActorFreeSelf()
{
local MockActor temp;
class'MockActor'.default.actorCount = 0;
Issue("Reference counting for `AcediaActor`s does not work correctly"
@ "with `FreeSelf()`");
temp = MockActor(__().memory.Allocate(class'MockActor'));
temp.NewRef().NewRef().NewRef();
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
TEST_ExpectTrue(temp._getRefCount() == 4);
TEST_ExpectTrue(temp.IsAllocated());
temp.FreeSelf();
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
TEST_ExpectTrue(temp._getRefCount() == 3);
TEST_ExpectTrue(temp.IsAllocated());
temp.FreeSelf();
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
TEST_ExpectTrue(temp._getRefCount() == 2);
TEST_ExpectTrue(temp.IsAllocated());
temp.FreeSelf();
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
TEST_ExpectTrue(temp._getRefCount() == 1);
TEST_ExpectTrue(temp.IsAllocated());
temp.FreeSelf();
TEST_ExpectTrue(class'MockActor'.default.actorCount == 0);
}
protected static function SubTest_RefCountingActorFree()
{
local MockActor temp;
class'MockActor'.default.actorCount = 0;
Issue("Reference counting for `AcediaActor`s does not work correctly"
@ "with `Free()`");
temp = MockActor(__().memory.Allocate(class'MockActor'));
temp.NewRef().NewRef().NewRef();
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
TEST_ExpectTrue(temp._getRefCount() == 4);
TEST_ExpectTrue(temp.IsAllocated());
__().memory.Free(temp);
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
TEST_ExpectTrue(temp._getRefCount() == 3);
TEST_ExpectTrue(temp.IsAllocated());
__().memory.Free(temp);
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
TEST_ExpectTrue(temp._getRefCount() == 2);
TEST_ExpectTrue(temp.IsAllocated());
__().memory.Free(temp);
TEST_ExpectTrue(class'MockActor'.default.actorCount == 1);
TEST_ExpectTrue(temp._getRefCount() == 1);
TEST_ExpectTrue(temp.IsAllocated());
__().memory.Free(temp);
TEST_ExpectTrue(class'MockActor'.default.actorCount == 0);
}
defaultproperties
{
caseGroup = "Memory"

56
sources/Types/AcediaActor.uc

@ -26,6 +26,12 @@ class AcediaActor extends Actor
// Reference to Acedia's APIs for simple access.
var protected Global _;
// To make logic simpler and increase efficiency, we allow storing a reference
// to any actors in many different places. To know when we can actually
// deallocate an actor, we keep this reference counter and only move actor
// to the actor pool once nothing refers to it anymore.
var private int _refCounter;
// Store allocation status to prevent possible issues
// with freeing the same object several times without reallocating it
// (such as preventing finalizers or constructors being called several times)
@ -66,6 +72,7 @@ public function _constructor()
{
if (_isAllocated) return;
_isAllocated = true;
_refCounter = 1;
_ = class'Global'.static.GetInstance();
if (!default._staticConstructorWasCalled)
{
@ -89,6 +96,7 @@ public function _finalizer()
{
if (!_isAllocated) return;
_isAllocated = false;
_refCounter = 0;
Finalizer();
_ = none;
}
@ -179,7 +187,53 @@ private final static function CreateTextCache(optional bool forceCreation)
}
/**
* Acedia actors cannot be deallocated into an object pool, but they still
* 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 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 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 final function AcediaActor NewRef()
{
if (!_isAllocated) {
return none;
}
_refCounter = Max(0, _refCounter + 1);
return self;
}
/**
* Acedia actors cannot be deallocated into an actor pool, but they still
* support constructors and destructors and, therefore, track their own
* allocation status (`AcediaActor` is considered allocated between constructor
* and finalizer calls).

58
sources/Types/AcediaObject.uc

@ -35,6 +35,12 @@ var public const bool usesObjectPool;
// This value can be changes through Acedia's system settings.
var public const int defaultMaxPoolSize;
// To make logic simpler and increase efficiency, we allow storing a reference
// to any objects in many different places. To know when we can actually
// deallocate an object, we keep this reference counter and only move object
// to the object pool once nothing refers to it anymore.
var private int _refCounter;
// Same object can be reallocated for different purposes and, as far as
// users are concerned, - it should be considered a different object after each
// reallocation.
@ -106,9 +112,12 @@ public final static function AcediaObjectPool _getPool()
*/
public final function _constructor()
{
if (_isAllocated) return;
if (_isAllocated) {
return;
}
_isAllocated = true;
_lifeVersion += 1;
_refCounter = 1;
_ = class'Global'.static.GetInstance();
if (!default._staticConstructorWasCalled)
{
@ -131,6 +140,7 @@ public final function _finalizer()
{
if (!_isAllocated) return;
_isAllocated = false;
_refCounter = 0;
Finalizer();
_ = none;
}
@ -220,6 +230,52 @@ private final static function CreateTextCache(optional bool forceCreation)
}
}
/**
* 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 final function _deref()
{
if (!_isAllocated) {
return;
}
_refCounter = Max(0, _refCounter - 1);
}
/**
* This function returns current reference counter for the caller object.
* 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 final function int _getRefCount()
{
if (!_isAllocated) {
return 0;
}
return _refCounter;
}
/**
* Method that creates new reference to the given object.
* Call this if you do not have ownership over the object, but want to store
* somewhere - this way it should not get deallocated until you free your
* own reference.
*
* @return Caller object, to allow for easier use.
*/
public final function AcediaObject NewRef()
{
if (!_isAllocated) {
return none;
}
_refCounter = Max(0, _refCounter + 1);
return self;
}
/**
* Acedia objects can be deallocated into an object pool to be reused later and
* such instances should not be used while in the pool.

Loading…
Cancel
Save