From 47c08b3d86b952dcab68bad12210e8916e492565 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Fri, 5 Nov 2021 04:09:46 +0700 Subject: [PATCH] Improve key deallocation support for `Collection`s `RemoveItem()` method for `AssociativeArray()` is now able to deallocate keys. `Empty()` method is now defined in a `Collectino` class and allows users to recursively free nested collections, while deallocating their keys. --- sources/Data/Collections/AssociativeArray.uc | 33 +++++++++++-------- sources/Data/Collections/Collection.uc | 16 +++++++++ sources/Data/Collections/DynamicArray.uc | 21 +++++++----- .../Tests/TEST_CollectionsMixed.uc | 29 ++++++++++++++++ 4 files changed, 78 insertions(+), 21 deletions(-) diff --git a/sources/Data/Collections/AssociativeArray.uc b/sources/Data/Collections/AssociativeArray.uc index be9ecbd..72a82cf 100644 --- a/sources/Data/Collections/AssociativeArray.uc +++ b/sources/Data/Collections/AssociativeArray.uc @@ -474,10 +474,13 @@ public final function AssociativeArray CreateItem( * Removed values are deallocated if they are managed. If you wish to avoid * that, use `TakeItem()` or `TakeEntry()` methods. * - * @param key Key for which to remove value. + * @param key Key for which to remove value. + * @param deallocateKey Should key be also deallocated? * @return Caller `AssociativeArray` to allow for method chaining. */ -public final function AssociativeArray RemoveItem(AcediaObject key) +public final function AssociativeArray RemoveItem( + AcediaObject key, + optional bool deallocateKey) { local Entry entryToRemove; local int bucketIndex, entryIndex; @@ -493,23 +496,16 @@ public final function AssociativeArray RemoveItem(AcediaObject key) if (entryToRemove.managed && entryToRemove.value != none) { entryToRemove.value.FreeSelf(entryToRemove.valueLifeVersion); } + if (deallocateKey && entryToRemove.key != none) { + entryToRemove.key.FreeSelf(entryToRemove.keyLifeVersion); + } return self; } -/** - * Completely clears caller `AssociativeArray` of all stored entries, - * deallocating any stored managed values. - * - * @param deallocateKeys Setting this to `true` will force this method to - * also deallocate all keys from the caller `AssociativeArray`. - * Since we do not record whether `AssociativeArray` manages keys like it - * does values - all keys will be deallocated, so use this parameter with - * caution. - * @return Caller `AssociativeArray` to allow for method chaining. - */ public function Empty(optional bool deallocateKeys) { local int i, j; + local Collection subCollection; local array nextEntries; for (i = 0; i < hashTable.length; i += 1) { @@ -518,6 +514,17 @@ public function Empty(optional bool deallocateKeys) { if (!nextEntries[j].managed) continue; if (nextEntries[j].value == none) continue; + if ( nextEntries[j].value.GetLifeVersion() + != nextEntries[j].valueLifeVersion) { + continue; + } + if (deallocateKeys) + { + subCollection = Collection(nextEntries[j].value); + if (subCollection != none) { + subCollection.Empty(true); + } + } nextEntries[j].value.FreeSelf(nextEntries[j].valueLifeVersion); } if (deallocateKeys) diff --git a/sources/Data/Collections/Collection.uc b/sources/Data/Collections/Collection.uc index 16d03a4..f882f9b 100644 --- a/sources/Data/Collections/Collection.uc +++ b/sources/Data/Collections/Collection.uc @@ -60,6 +60,22 @@ public final function Iter Iterate() return newIterator; } +/** + * Completely clears caller `Collections` of all stored entries, + * deallocating any stored managed values. + * + * @param deallocateKeys Setting this to `true` will force this method to + * also deallocate all keys from the caller `Collection`, if it uses them. + * If this parameter is set to `true`, then `Empty()` call will also be + * made recursively for all stored `Collection`, also causing them to + * deallocate their keys. + * For Acedia keys are only used by `AssociativeArray`. + * Since we do not record whether `Collection` manages keys like it + * does values - all keys will be deallocated, so use this parameter with + * caution. + */ +public function Empty(optional bool deallocateKeys) {} + /** * Returns stored `AcediaObject` from the caller storage * (or from it's sub-storages) via given `JSONPointer` path. diff --git a/sources/Data/Collections/DynamicArray.uc b/sources/Data/Collections/DynamicArray.uc index 34cb7d5..c4e985f 100644 --- a/sources/Data/Collections/DynamicArray.uc +++ b/sources/Data/Collections/DynamicArray.uc @@ -114,16 +114,21 @@ protected final function DynamicArray FreeManagedItem(int index) return self; } -/** - * Empties caller `DynamicArray`, erasing it's contents. - * All managed objects will be deallocated. - * - * @return Reference to the caller `DynamicArray` to allow for method chaining. - */ -public final function DynamicArray Empty() +public function Empty(optional bool deallocateKeys) { + local int i; + local Collection subCollection; + if (deallocateKeys) + { + for (i = 0; i < storedObjects.length; i += 1) + { + subCollection = Collection(GetItem(i)); + if (subCollection != none) { + subCollection.Empty(true); + } + } + } SetLength(0); - return self; } /** diff --git a/sources/Data/Collections/Tests/TEST_CollectionsMixed.uc b/sources/Data/Collections/Tests/TEST_CollectionsMixed.uc index c8bd615..ec58a9b 100644 --- a/sources/Data/Collections/Tests/TEST_CollectionsMixed.uc +++ b/sources/Data/Collections/Tests/TEST_CollectionsMixed.uc @@ -27,6 +27,8 @@ protected static function TESTS() Context("Testing accessing collections by JSON pointers."); Test_GetBy(); Test_GetTypeBy(); + Context("Testing erasing collections with keys via `Empty()` method."); + Test_EmptyWithKeys(); } protected static function Test_GetBy() @@ -96,6 +98,33 @@ protected static function Test_GetTypeBy() TEST_ExpectNone(obj.GetTextBy(P(""))); } +protected static function Test_EmptyWithKeys() +{ + local Text outerKey, innerKey1, innerKey2, innerKey3; + local DynamicArray middleArray; + local AssociativeArray outerObject, innerObject1, innerObject2; + outerKey = __().text.FromString("first"); + innerKey1 = __().text.FromString("third?"); + innerKey2 = __().text.FromString("Or not?"); + innerKey3 = __().text.FromString("Like hell if I know!!!1111"); + outerObject = __().collections.EmptyAssociativeArray(); + innerObject1 = __().collections.EmptyAssociativeArray(); + innerObject2 = __().collections.EmptyAssociativeArray(); + middleArray = __().collections.EmptyDynamicArray(); + innerObject1.SetItem(innerKey1, __().box.int(4)); + innerObject1.SetItem(innerKey2, __().box.float(-4.6)); + innerObject2.SetItem(innerKey2, __().ref.bool(true)); + innerObject2.SetItem(innerKey3, none); + middleArray.AddItem(innerObject1).AddItem(innerObject2); + outerObject.SetItem(outerKey, middleArray); + outerObject.Empty(true); + Issue("Collection keys are not deallocated by `Empty(true)` method"); + TEST_ExpectFalse(outerKey.IsAllocated()); + TEST_ExpectFalse(innerKey1.IsAllocated()); + TEST_ExpectFalse(innerKey2.IsAllocated()); + TEST_ExpectFalse(innerKey3.IsAllocated()); +} + defaultproperties { caseGroup = "Collections"