diff --git a/sources/Avarice/Avarice.uc b/sources/Avarice/Avarice.uc index cf50dc3..a7e0473 100644 --- a/sources/Avarice/Avarice.uc +++ b/sources/Avarice/Avarice.uc @@ -43,58 +43,67 @@ var public config array link; // connection too long. var public config float reconnectTime; -protected function AssociativeArray ToData() +protected function HashTable ToData() { - local int i; - local AssociativeArray data; - local AssociativeArray linkData; - local DynamicArray linksArray; - data = __().collections.EmptyAssociativeArray(); + local int i; + local Text nextValue; + local HashTable data; + local HashTable linkData; + local ArrayList linksArray; + data = __().collections.EmptyHashTable(); data.SetFloat(P("reconnectTime"), reconnectTime, true); - linksArray = __().collections.EmptyDynamicArray(); + linksArray = __().collections.EmptyArrayList(); data.SetItem(P("link"), linksArray); for (i = 0; i < link.length; i += 1) { - linkData = __().collections.EmptyAssociativeArray(); - linkData.SetItem(P("name"), __().text.FromString(link[i].name)); - linkData.SetItem(P("address"), __().text.FromString(link[i].address)); + linkData = __().collections.EmptyHashTable(); + nextValue = __().text.FromString(link[i].name); + linkData.SetItem(P("name"), nextValue); + nextValue.FreeSelf(); + nextValue = __().text.FromString(link[i].address); + linkData.SetItem(P("address"), nextValue); + nextValue.FreeSelf(); linksArray.AddItem(linkData); + linkData.FreeSelf(); } + linksArray.FreeSelf(); return data; } -protected function FromData(AssociativeArray source) +protected function FromData(HashTable source) { local int i; local Text nextText; - local DynamicArray linksArray; - local AssociativeArray nextLink; + local ArrayList linksArray; + local HashTable nextLink; local AvariceLinkRecord nextRecord; if (source == none) { return; } reconnectTime = source.GetFloat(P("reconnectTime")); link.length = 0; - linksArray = source.GetDynamicArray(P("link")); + linksArray = source.GetArrayList(P("link")); if (linksArray == none) { return; } for (i = 0; i < linksArray.GetLength(); i += 1) { - nextLink = linksArray.GetAssociativeArray(i); + nextLink = linksArray.GetHashTable(i); if (nextLink == none) { continue; } nextText = nextLink.GetText(P("name")); if (nextText != none) { - nextRecord.name = nextText.ToString(); + nextRecord.name = __().text.ToString(nextText); } nextText = nextLink.GetText(P("address")); if (nextText != none) { - nextRecord.address = nextText.ToString(); + nextRecord.address = __().text.ToString(nextText); } link[i] = nextRecord; + _.memory.Free(nextLink); } + _.memory.Free(linksArray); } protected function DefaultIt() diff --git a/sources/Commands/Commands.uc b/sources/Commands/Commands.uc index d6b37ff..a22b005 100644 --- a/sources/Commands/Commands.uc +++ b/sources/Commands/Commands.uc @@ -25,18 +25,20 @@ var public config bool useChatInput; var public config bool useMutateInput; var public config string chatCommandPrefix; -protected function AssociativeArray ToData() +protected function HashTable ToData() { - local AssociativeArray data; - data = __().collections.EmptyAssociativeArray(); + local Text chatPrefix; + local HashTable data; + data = __().collections.EmptyHashTable(); data.SetBool(P("useChatInput"), useChatInput, true); data.SetBool(P("useMutateInput"), useMutateInput, true); - data.SetItem( P("chatCommandPrefix"), - _.text.FromString(chatCommandPrefix), true); + chatPrefix = _.text.FromString(chatCommandPrefix); + data.SetItem(P("chatCommandPrefix"), chatPrefix); + _.memory.Free(chatPrefix); return data; } -protected function FromData(AssociativeArray source) +protected function FromData(HashTable source) { local Text newChatPrefix; if (source == none) { diff --git a/sources/Config/AcediaConfig.uc b/sources/Config/AcediaConfig.uc index b450497..aa05628 100644 --- a/sources/Config/AcediaConfig.uc +++ b/sources/Config/AcediaConfig.uc @@ -8,7 +8,7 @@ * is that "" must be considered *valid* by * `BaseText.IsValidName()` standards, otherwise method will return `none`. * - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -26,7 +26,7 @@ * along with Acedia. If not, see . */ class AcediaConfig extends AcediaObject - dependson(AssociativeArray) + dependson(HashTable) abstract; /** @@ -65,7 +65,7 @@ class AcediaConfig extends AcediaObject // In case it has a `none` value stored under some key - it means that value // was detected in config, but not yet loaded. // Only its default value is ever used. -var private AssociativeArray existingConfigs; +var private HashTable existingConfigs; // Stores name of the config where settings are to be stored. // Must correspond to value in `config(...)` modifier in class definition. @@ -79,10 +79,10 @@ var public const bool supportsDataConversion; /** * These methods must be overloaded to store and load all the config - * variables inside an `AssociativeArray` collection. How exactly to store + * variables inside an `HashTable` collection. How exactly to store * them is up to each config class to decide, as long as it allows conversion * into JSON (see `JSONAPI.IsCompatible()` for details). - * Note that `AssociativeArray` reference `FromData()` receives is + * Note that `HashTable` reference `FromData()` receives is * not necessarily the same one your `ToData()` method returns - any particular * value boxes can be replaced with value references and vice versa. * NOTE: DO NOT use `P()`, `C()`, `F()` or `T()` methods for keys or @@ -90,8 +90,8 @@ var public const bool supportsDataConversion; * deallocated when necessary, so these methods for creating `Text` values are * not suitable. */ -protected function AssociativeArray ToData() { return none; } -protected function FromData(AssociativeArray source) {} +protected function HashTable ToData() { return none; } +protected function FromData(HashTable source) {} /** * This method must be overloaded to setup default values for all config @@ -99,12 +99,6 @@ protected function FromData(AssociativeArray source) {} */ protected function DefaultIt() {} -protected static function StaticFinalizer() -{ - __().memory.Free(default.existingConfigs); - default.existingConfigs = none; -} - /** * This reads all of the `AcediaConfig`'s settings objects into internal * storage. Must be called before any other methods. Actual loading might be @@ -113,12 +107,12 @@ protected static function StaticFinalizer() public static function Initialize() { local int i; - local Text nextName; + local Text nextName, lowerName; local array names; if (default.existingConfigs != none) { return; } - default.existingConfigs = __().collections.EmptyAssociativeArray(); + default.existingConfigs = __().collections.EmptyHashTable(); names = GetPerObjectNames( default.configName, string(default.class.name), MaxInt); for (i = 0; i < names.length; i += 1) @@ -127,8 +121,11 @@ public static function Initialize() continue; } nextName = __().text.FromString(NameToActualVersion(names[i])); - if (nextName.IsValidName()) { - default.existingConfigs.SetItem(nextName.LowerCopy(), none); + if (nextName.IsValidName()) + { + lowerName = nextName.LowerCopy(); + default.existingConfigs.SetItem(lowerName, none); + lowerName.FreeSelf(); } nextName.FreeSelf(); } @@ -178,6 +175,7 @@ public final static function bool NewConfig(BaseText name) newConfig.DefaultIt(); newConfig.SaveConfig(); default.existingConfigs.SetItem(name, newConfig); + name.FreeSelf(); return true; } @@ -214,15 +212,16 @@ public final static function bool Exists(BaseText name) */ public final static function DeleteConfig(BaseText name) { - local AssociativeArray.Entry entry; - if (default.existingConfigs == none) { - return; - } - entry = default.existingConfigs.TakeEntry(name); - if (entry.value != none) { - entry.value.ClearConfig(); + local AcediaObject value; + if (name == none) return; + if (default.existingConfigs == none) return; + + name = name.LowerCopy(); + value = default.existingConfigs.TakeItem(name); + if (value != none) { + value.ClearConfig(); } - __().memory.Free(entry.key); + __().memory.Free(name); } /** @@ -235,7 +234,7 @@ public static function array AvailableConfigs() { local array emptyResult; if (default.existingConfigs != none) { - return default.existingConfigs.CopyTextKeys(); + return default.existingConfigs.GetTextKeys(); } return emptyResult; } @@ -250,7 +249,7 @@ public static function array AvailableConfigs() */ public final static function AcediaConfig GetConfigInstance(BaseText name) { - local AssociativeArray.Entry configEntry; + local HashTable.Entry configEntry; if (name == none) return none; if (!name.IsValidName()) return none; if (default.existingConfigs == none) return none; @@ -265,6 +264,8 @@ public final static function AcediaConfig GetConfigInstance(BaseText name) default.existingConfigs.SetItem(configEntry.key, configEntry.value); } __().memory.Free(name); + // We return value, so do not deallocate it + __().memory.Free(configEntry.key); return AcediaConfig(configEntry.value); } @@ -284,10 +285,10 @@ public final static function AcediaConfig GetConfigInstance(BaseText name) * For correctly implemented config objects should only return `none` if * their class was not yet initialized (see `self.Initialize()` method). */ -public final static function AssociativeArray LoadData(BaseText name) +public final static function HashTable LoadData(BaseText name) { - local AssociativeArray result; - local AcediaConfig requiredConfig; + local HashTable result; + local AcediaConfig requiredConfig; requiredConfig = GetConfigInstance(name); if (requiredConfig != none) { result = requiredConfig.ToData(); @@ -309,7 +310,7 @@ public final static function AssociativeArray LoadData(BaseText name) * allows for JSON deserialization (see `JSONAPI.IsCompatible()` for * details). */ -public final static function SaveData(BaseText name, AssociativeArray data) +public final static function SaveData(BaseText name, HashTable data) { local AcediaConfig requiredConfig; requiredConfig = GetConfigInstance(name); diff --git a/sources/Config/Tests/MockConfig.uc b/sources/Config/Tests/MockConfig.uc index bb2ecf2..378609e 100644 --- a/sources/Config/Tests/MockConfig.uc +++ b/sources/Config/Tests/MockConfig.uc @@ -1,6 +1,6 @@ /** * Mock object for testing config functionality of Acedia's `Feature`s. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -23,15 +23,15 @@ class MockConfig extends AcediaConfig var public config int value; -protected function AssociativeArray ToData() +protected function HashTable ToData() { - local AssociativeArray data; - data = __().collections.EmptyAssociativeArray(); + local HashTable data; + data = __().collections.EmptyHashTable(); data.SetInt(P("value").Copy(), value, true); return data; } -protected function FromData(AssociativeArray source) +protected function FromData(HashTable source) { if (source != none) { value = source.GetIntBy(P("/value")); diff --git a/sources/Config/Tests/TEST_AcediaConfig.uc b/sources/Config/Tests/TEST_AcediaConfig.uc index 924c993..dd4202a 100644 --- a/sources/Config/Tests/TEST_AcediaConfig.uc +++ b/sources/Config/Tests/TEST_AcediaConfig.uc @@ -1,6 +1,6 @@ /** * Set of tests for `AcediaConfig` class. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -68,19 +68,19 @@ protected static function TEST_AvailableConfigs() protected static function TEST_DataGetSet() { - local AssociativeArray data, newData; + local HashTable data, newData; data = class'MockConfig'.static.LoadData(P("other")); Issue("Wrong value is loaded from config."); TEST_ExpectTrue(data.GetIntBy(P("/value")) == 11); - newData = __().collections.EmptyAssociativeArray(); + newData = __().collections.EmptyHashTable(); newData.SetItem(P("value"), __().box.int(903)); class'MockConfig'.static.SaveData(P("other"), newData); data = class'MockConfig'.static.LoadData(P("other")); Issue("Wrong value is loaded from config after saving another value."); TEST_ExpectTrue(data.GetIntBy(P("/value")) == 903); - Issue("`AcediaConfig` returns `AssociativeArray` reference that was" + Issue("`AcediaConfig` returns `HashTable` reference that was" @ "passed in `SaveData()` call instead of a new collection."); TEST_ExpectTrue(data != newData); @@ -91,7 +91,7 @@ protected static function TEST_DataGetSet() protected static function TEST_DataNew() { - local AssociativeArray data; + local HashTable data; Issue("Creating new config with existing name succeeds."); TEST_ExpectFalse(class'MockConfig'.static.NewConfig(P("another.config"))); data = class'MockConfig'.static.LoadData(P("another.config")); diff --git a/sources/Data/Collections/ArrayList.uc b/sources/Data/Collections/ArrayList.uc index b074fdf..b98875e 100644 --- a/sources/Data/Collections/ArrayList.uc +++ b/sources/Data/Collections/ArrayList.uc @@ -870,6 +870,30 @@ public final function ArrayList GetArrayList(int index) return result; } +/** + * Returns `HashTable` item at `index`. If index is invalid or + * stores a non-`HashTable` value, returns `none`. + * + * Referred value must be stored as `Text` (or one of it's sub-classes, + * such as `MutableText`) for this method to work. + * + * @param index Index of a `HashTable` item that caller `ArrayList` + * has to return. + * @return `HashTable` value at `index` in the caller `ArrayList`. + * `none` if passed `index` is invalid or non-`HashTable` value + * is stored there. + */ +public final function HashTable GetHashTable(int index) +{ + local HashTable result; + + result = HashTable(BorrowItem(index)); + if (result != none) { + result.NewRef(); + } + return result; +} + defaultproperties { iteratorClass = class'ArrayListIterator' diff --git a/sources/Data/Collections/HashTable.uc b/sources/Data/Collections/HashTable.uc index a783b44..40d4b5c 100644 --- a/sources/Data/Collections/HashTable.uc +++ b/sources/Data/Collections/HashTable.uc @@ -388,6 +388,7 @@ public final function HashTable SetItem( storedElementCount += 1; } key.NewRef(); + _.memory.Free(oldEntry.key); newEntry.key = key; newEntry.value = value; if (value != none) {