Anton Tarasenko
4 years ago
9 changed files with 1135 additions and 103 deletions
@ -0,0 +1,218 @@
|
||||
/** |
||||
* A class, implementing a hash-table-based dictionary for quick access to |
||||
* aliases' values. |
||||
* It does not support dynamic hash table capacity change and |
||||
* requires to set the size upfront. |
||||
* Copyright 2020 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 AliasHash extends AcediaObject |
||||
dependson(AliasSource) |
||||
config(AcediaSystem); |
||||
|
||||
// Reasonable lower and upper limits on hash table capacity, |
||||
// that will be enforced if user requires something outside those bounds |
||||
var private config const int MINIMUM_CAPACITY; |
||||
var private config const int MAXIMUM_CAPACITY; |
||||
|
||||
// Bucket of alias-value pairs, with the same alias hash. |
||||
struct PairBucket |
||||
{ |
||||
var array<AliasSource.AliasValuePair> pairs; |
||||
}; |
||||
var private array<PairBucket> hashTable; |
||||
|
||||
/** |
||||
* Initializes caller `AliasHash`. |
||||
* |
||||
* Calling this function again will clear all existing data and will create |
||||
* a brand new hash table. |
||||
* |
||||
* @param desiredCapacity Desired capacity of the underlying hash table. |
||||
* Will be clamped between `MINIMUM_CAPACITY` and `MAXIMUM_CAPACITY`. |
||||
* Not specifying anything as this parameter creates a hash table of |
||||
* size `MINIMUM_CAPACITY`. |
||||
* @return A reference to a caller object to allow for function chaining. |
||||
*/ |
||||
public final function AliasHash Initialize(optional int desiredCapacity) |
||||
{ |
||||
desiredCapacity = Clamp(desiredCapacity, MINIMUM_CAPACITY, |
||||
MAXIMUM_CAPACITY); |
||||
hashTable.length = 0; |
||||
hashTable.length = desiredCapacity; |
||||
return self; |
||||
} |
||||
|
||||
// Helper method that is needed as a replacement for `%`, since it is |
||||
// an operation on `float`s in UnrealScript and does not have enough precision |
||||
// to work with hashes. |
||||
// Assumes positive input. |
||||
private function int Remainder(int number, int divisor) |
||||
{ |
||||
local int quotient; |
||||
quotient = number / divisor; |
||||
return (number - quotient * divisor); |
||||
} |
||||
|
||||
// Finds indices for: |
||||
// 1. Bucked that contains specified alias (`bucketIndex`); |
||||
// 2. Pair for specified alias in the bucket's collection (`pairIndex`). |
||||
// `bucketIndex` is always found, |
||||
// `pairIndex` is valid iff method returns `true`. |
||||
private final function bool FindPairIndices( |
||||
string alias, |
||||
out int bucketIndex, |
||||
out int pairIndex) |
||||
{ |
||||
local int i; |
||||
local array<AliasSource.AliasValuePair> bucketPairs; |
||||
// `Locs()` is used because aliases are case-insensitive. |
||||
bucketIndex = _().text.GetHash(Locs(alias)); |
||||
if (bucketIndex < 0) { |
||||
bucketIndex *= -1; |
||||
} |
||||
bucketIndex = Remainder(bucketIndex, hashTable.length); |
||||
// Check if bucket actually has given alias. |
||||
bucketPairs = hashTable[bucketIndex].pairs; |
||||
for (i = 0; i < bucketPairs.length; i += 1) |
||||
{ |
||||
if (bucketPairs[i].alias ~= alias) |
||||
{ |
||||
pairIndex = i; |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Finds a value for a given alias. |
||||
* |
||||
* @param alias Alias for which we need to find a value. |
||||
* Aliases are case-insensitive. |
||||
* @param value If given alias is present in caller `AliasHash`, - |
||||
* it's value will be written in this variable. |
||||
* Otherwise value is undefined. |
||||
* @return `true` if we found value, `false` otherwise. |
||||
*/ |
||||
public final function bool Find(string alias, out string value) |
||||
{ |
||||
local int bucketIndex; |
||||
local int pairIndex; |
||||
if (FindPairIndices(alias, bucketIndex, pairIndex)) |
||||
{ |
||||
value = hashTable[bucketIndex].pairs[pairIndex].value; |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Checks if caller `AliasHash` contains given alias. |
||||
* |
||||
* @param alias Alias to check for belonging to caller `AliasHash`. |
||||
* Aliases are case-insensitive. |
||||
* @return `true` if caller `AliasHash` contains the value for a given alias |
||||
* and `false` otherwise. |
||||
*/ |
||||
public final function bool Contains(string alias) |
||||
{ |
||||
local int bucketIndex; |
||||
local int pairIndex; |
||||
return FindPairIndices(alias, bucketIndex, pairIndex); |
||||
} |
||||
|
||||
/** |
||||
* Inserts new record for alias `alias` for value of `value`. |
||||
* |
||||
* If there is already a value for a given `alias` - it will be overwritten. |
||||
* |
||||
* @param alias Alias to insert. Aliases are case-insensitive. |
||||
* @param value Value for a given alias to store. |
||||
* @return A reference to a caller object to allow for function chaining. |
||||
*/ |
||||
public final function AliasHash Insert(string alias, string value) |
||||
{ |
||||
local int bucketIndex; |
||||
local int pairIndex; |
||||
local AliasSource.AliasValuePair newRecord; |
||||
newRecord.value = value; |
||||
newRecord.alias = alias; |
||||
if (!FindPairIndices(alias, bucketIndex, pairIndex)) { |
||||
pairIndex = hashTable[bucketIndex].pairs.length; |
||||
} |
||||
hashTable[bucketIndex].pairs[pairIndex] = newRecord; |
||||
return self; |
||||
} |
||||
|
||||
/** |
||||
* Inserts new record for alias `alias` for value of `value`. |
||||
* |
||||
* If there is already a value for a given `alias`, - new value will be |
||||
* discarded and `AliasHash` will not be changed. |
||||
* |
||||
* @param alias Alias to insert. Aliases are case-insensitive. |
||||
* @param value Value for a given alias to store. |
||||
* @param existingValue Value that will correspond to a given alias after |
||||
* this method's execution. If insertion was successful - given `value`, |
||||
* otherwise (if there already was a record for an `alias`) |
||||
* it will return value that already existed in caller `AliasHash`. |
||||
* @return `true` if given alias-value pair was inserted and `false` otherwise. |
||||
*/ |
||||
public final function bool InsertIfMissing( |
||||
string alias, |
||||
string value, |
||||
out string existingValue) |
||||
{ |
||||
local int bucketIndex; |
||||
local int pairIndex; |
||||
local AliasSource.AliasValuePair newRecord; |
||||
newRecord.value = value; |
||||
newRecord.alias = alias; |
||||
existingValue = value; |
||||
if (FindPairIndices(alias, bucketIndex, pairIndex)) { |
||||
existingValue = hashTable[bucketIndex].pairs[pairIndex].value; |
||||
return false; |
||||
} |
||||
pairIndex = hashTable[bucketIndex].pairs.length; |
||||
hashTable[bucketIndex].pairs[pairIndex] = newRecord; |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Removes record, corresponding to a given alias `alias`. |
||||
* |
||||
* @param alias Alias for which all records must be removed. |
||||
* @return `true` if record was removed, `false` if id did not |
||||
* (can only happen when `AliasHash` did not have any records for `alias`). |
||||
*/ |
||||
public final function bool Remove(string alias) |
||||
{ |
||||
local int bucketIndex; |
||||
local int pairIndex; |
||||
if (FindPairIndices(alias, bucketIndex, pairIndex)) { |
||||
hashTable[bucketIndex].pairs.Remove(pairIndex, 1); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
MINIMUM_CAPACITY = 10 |
||||
MAXIMUM_CAPACITY = 100000 |
||||
} |
@ -0,0 +1,135 @@
|
||||
/** |
||||
* Service that handles pending saving of aliases data into configs. |
||||
* Adding aliases into `AliasSource`s causes corresponding configs to update. |
||||
* This service allows to delay and spread config rewrites over time, |
||||
* which should help in case someone dynamically adds a lot of |
||||
* different aliases. |
||||
* Copyright 2020 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 AliasService extends Service |
||||
config(AcediaSystem); |
||||
|
||||
// Objects for which we are yet to write configs |
||||
var private array<AliasSource> sourcesPendingToSave; |
||||
var private array<Aliases> aliasesPendingToSave; |
||||
// How often should we do it. |
||||
// Negative or zero values would be reset to `0.05`. |
||||
var public config const float saveInterval; |
||||
|
||||
// To avoid creating yet another object for aliases system we will |
||||
// keep config variable pointing to weapon, color, etc. `AliasSource` |
||||
// subclasses here. It's not the best regarding separation of responsibility, |
||||
// but should make config files less fragmented. |
||||
// Changing these allows you to change in what sources `AliasesAPI` |
||||
// looks for weapon and color aliases. |
||||
var public config const class<AliasSource> weaponAliasesSource; |
||||
var public config const class<AliasSource> colorAliasesSource; |
||||
|
||||
protected function OnLaunch() |
||||
{ |
||||
local float actualInterval; |
||||
actualInterval = saveInterval; |
||||
if (actualInterval <= 0) |
||||
{ |
||||
actualInterval = 0.05; |
||||
} |
||||
SetTimer(actualInterval, true); |
||||
} |
||||
|
||||
protected function OnShutdown() |
||||
{ |
||||
SaveAllPendingObjects(); |
||||
} |
||||
|
||||
public final function PendingSaveSource(AliasSource sourceToSave) |
||||
{ |
||||
local int i; |
||||
if (sourceToSave == none) return; |
||||
// Starting searching from the end of an array will make situations when |
||||
// we add several aliases to a single source in a row more efficient. |
||||
for (i = sourcesPendingToSave.length - 1;i >= 0; i -= 1) { |
||||
if (sourcesPendingToSave[i] == sourceToSave) return; |
||||
} |
||||
sourcesPendingToSave[sourcesPendingToSave.length] = sourceToSave; |
||||
} |
||||
|
||||
public final function PendingSaveObject(Aliases objectToSave) |
||||
{ |
||||
local int i; |
||||
if (objectToSave == none) return; |
||||
// Starting searching from the end of an array will make situations when |
||||
// we add several aliases to a single `Aliases` object in a row |
||||
// more efficient. |
||||
for (i = aliasesPendingToSave.length - 1;i >= 0; i -= 1) { |
||||
if (aliasesPendingToSave[i] == objectToSave) return; |
||||
} |
||||
aliasesPendingToSave[aliasesPendingToSave.length] = objectToSave; |
||||
} |
||||
|
||||
/** |
||||
* Forces saving of the next object (either `AliasSource` or `Aliases`) |
||||
* in queue to the config file. |
||||
* |
||||
* Does not reset the timer until next saving. |
||||
*/ |
||||
private final function DoSaveNextPendingObject() |
||||
{ |
||||
if (sourcesPendingToSave.length > 0) |
||||
{ |
||||
if (sourcesPendingToSave[0] != none) { |
||||
sourcesPendingToSave[0].SaveConfig(); |
||||
} |
||||
sourcesPendingToSave.Remove(0, 1); |
||||
return; |
||||
} |
||||
if (aliasesPendingToSave.length > 0) |
||||
{ |
||||
aliasesPendingToSave[0].SaveOrClear(); |
||||
aliasesPendingToSave.Remove(0, 1); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Forces saving of all objects (both `AliasSource`s or `Aliases`s) in queue |
||||
* to their config files. |
||||
*/ |
||||
private final function SaveAllPendingObjects() |
||||
{ |
||||
local int i; |
||||
for (i = 0; i < sourcesPendingToSave.length; i += 1) { |
||||
if (sourcesPendingToSave[i] == none) continue; |
||||
sourcesPendingToSave[i].SaveConfig(); |
||||
} |
||||
for (i = 0; i < aliasesPendingToSave.length; i += 1) { |
||||
aliasesPendingToSave[i].SaveOrClear(); |
||||
} |
||||
sourcesPendingToSave.length = 0; |
||||
aliasesPendingToSave.length = 0; |
||||
} |
||||
|
||||
event Timer() |
||||
{ |
||||
DoSaveNextPendingObject(); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
saveInterval = 0.05 |
||||
weaponAliasesSource = class'WeaponAliasSource' |
||||
colorAliasesSource = class'ColorAliasSource' |
||||
} |
@ -0,0 +1,379 @@
|
||||
/** |
||||
* Aliases allow users to define human-readable and easier to use |
||||
* "synonyms" to some symbol sequences (mainly names of UnrealScript classes). |
||||
* This class implements an alias database that stores aliases inside |
||||
* standard config ini-files. |
||||
* Several `AliasSource`s are supposed to exist separately, each storing |
||||
* aliases of particular kind: for weapon, zeds, colors, etc.. |
||||
* Copyright 2020 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 AliasSource extends Singleton |
||||
config(AcediaAliases); |
||||
|
||||
// Name of the configurational file (without extension) where |
||||
// this `AliasSource`'s data will be stored. |
||||
var private const string configName; |
||||
|
||||
// (Sub-)class of `Aliases` objects that this `AliasSource` uses to store |
||||
// aliases in per-object-config manner. |
||||
// Leaving this variable `none` will produce an `AliasSource` that can |
||||
// only store aliases in form of `record=(alias="...",value="...")`. |
||||
var public const class<Aliases> aliasesClass; |
||||
// Storage for all objects of `aliasesClass` class in the config. |
||||
// Exists after `OnCreated()` event and is maintained up-to-date at all times. |
||||
var private array<Aliases> loadedAliasObjects; |
||||
|
||||
// Links alias to a value. |
||||
// An array of these structures (without duplicate `alias` records) defines |
||||
// a function from the space of aliases to the space of values. |
||||
struct AliasValuePair |
||||
{ |
||||
var string alias; |
||||
var string value; |
||||
}; |
||||
// Aliases data for saving and loading on a disk (ini-file). |
||||
// Name is chosen to make configurational files more readable. |
||||
var private config array<AliasValuePair> record; |
||||
// Hash table for a faster access to value by alias' name. |
||||
// It contains same records as `record` array + aliases from |
||||
// `loadedAliasObjects` objects when there are no duplicate aliases. |
||||
// Otherwise only stores first loaded alias. |
||||
var private AliasHash hash; |
||||
|
||||
|
||||
// How many times bigger capacity of `hash` should be, compared to amount of |
||||
// initially loaded data from a config. |
||||
var private const float HASH_TABLE_SCALE; |
||||
|
||||
// Load and hash all the data `AliasSource` creation. |
||||
protected function OnCreated() |
||||
{ |
||||
local int entriesAmount; |
||||
if (!AssertAliasesClassIsOwnedByMe()) { |
||||
return; |
||||
} |
||||
// Load and hash |
||||
entriesAmount = LoadData(); |
||||
hash = AliasHash(_.memory.Allocate(class'AliasHash')); |
||||
hash.Initialize(int(entriesAmount * HASH_TABLE_SCALE)); |
||||
HashValidAliases(); |
||||
} |
||||
|
||||
// Ensures invariant of our `Aliases` class only belonging to us by |
||||
// itself ourselves otherwise. |
||||
private final function bool AssertAliasesClassIsOwnedByMe() |
||||
{ |
||||
if (aliasesClass == none) return true; |
||||
if (aliasesClass.default.sourceClass == class) return true; |
||||
_.logger.Failure("`AliasSource`-`Aliases` class pair is incorrectly" |
||||
@ "setup for source `" $ string(class) $ "`. Omitting it."); |
||||
Destroy(); |
||||
return false; |
||||
} |
||||
|
||||
// This method loads all the defined aliases from the config file and |
||||
// returns how many entries are there are total. |
||||
// Does not change data, including fixing duplicates. |
||||
private final function int LoadData() |
||||
{ |
||||
local int i; |
||||
local int entriesAmount; |
||||
local array<string> objectNames; |
||||
entriesAmount = record.length; |
||||
if (aliasesClass == none) { |
||||
return entriesAmount; |
||||
} |
||||
objectNames = |
||||
GetPerObjectNames(configName, string(aliasesClass.name), MaxInt); |
||||
loadedAliasObjects.length = objectNames.length; |
||||
for (i = 0; i < objectNames.length; i += 1) |
||||
{ |
||||
loadedAliasObjects[i] = new(none, objectNames[i]) aliasesClass; |
||||
entriesAmount += loadedAliasObjects[i].GetAliases().length; |
||||
} |
||||
return entriesAmount; |
||||
} |
||||
|
||||
/** |
||||
* Simply checks if given alias is present in caller `AliasSource`. |
||||
* |
||||
* @param alias Alias to check, case-insensitive. |
||||
* @return `true` if present, `false` otherwise. |
||||
*/ |
||||
public function bool ContainsAlias(string alias) |
||||
{ |
||||
return hash.Contains(alias); |
||||
} |
||||
|
||||
/** |
||||
* Tries to look up a value, stored for given alias in caller `AliasSource` and |
||||
* reports error upon failure. |
||||
* |
||||
* Also see `Try()` method. |
||||
* |
||||
* @param alias Alias, for which method will attempt to look up a value. |
||||
* Case-insensitive. |
||||
* @param value If passed `alias` was recorded in caller `AliasSource`, |
||||
* it's corresponding value will be written in this variable. |
||||
* Otherwise value is undefined. |
||||
* @return `true` if lookup was successful (alias present in 'AliasSource`) |
||||
* and correct value was written into `value`, `false` otherwise. |
||||
*/ |
||||
public function bool Resolve(string alias, out string value) |
||||
{ |
||||
return hash.Find(alias, value); |
||||
} |
||||
|
||||
/** |
||||
* Tries to look up a value, stored for given alias in caller `AliasSource` and |
||||
* silently returns given `alias` value upon failure. |
||||
* |
||||
* Also see `Resolve()` method. |
||||
* |
||||
* @param alias Alias, for which method will attempt to look up a value. |
||||
* Case-insensitive. |
||||
* @return Value corresponding to a given alias, if it was present in |
||||
* caller `AliasSource` and value of `alias` parameter instead. |
||||
*/ |
||||
public function string Try(string alias) |
||||
{ |
||||
local string result; |
||||
if (hash.Find(alias, result)) { |
||||
return result; |
||||
} |
||||
return alias; |
||||
} |
||||
|
||||
/** |
||||
* Adds another alias to the caller `AliasSource`. |
||||
* If alias with the same name as `aliasToAdd` already exists, - |
||||
* method overwrites it. |
||||
* |
||||
* Can fail iff `aliasToAdd` is an invalid alias. |
||||
* |
||||
* When adding alias to an object (`saveInObject == true`) alias `aliasToAdd` |
||||
* will be altered by changing any ':' inside it into a '.'. |
||||
* This is a necessary measure to allow storing class names in |
||||
* config files via per-object-config. |
||||
* |
||||
* NOTE: This call will cause update of an ini-file. That update can be |
||||
* slightly delayed, so do not make assumptions about it's immediacy. |
||||
* |
||||
* NOTE #2: Removing alias would require this method to go through the |
||||
* whole `AliasSource` to remove possible duplicates. |
||||
* This means that unless you can guarantee that there is no duplicates, - |
||||
* performing a lot of alias additions during run-time can be costly. |
||||
* |
||||
* @param aliasToAdd Alias that you want to add to caller source. |
||||
* Alias names are case-insensitive. |
||||
* @param aliasValue Intended value of this alias. |
||||
* @param saveInObject Setting this to `true` will make `AliasSource` save |
||||
* given alias in per-object-config storage, while keeping it at default |
||||
* `false` will just add alias to the `record=` storage. |
||||
* If caller `AliasSource` does not support per-object-config storage, - |
||||
* this flag will be ignores. |
||||
* @return `true` if alias was added and `false` otherwise (alias was invalid). |
||||
*/ |
||||
public final function bool AddAlias( |
||||
string aliasToAdd, |
||||
string aliasValue, |
||||
optional bool saveInObject) |
||||
{ |
||||
local AliasValuePair newPair; |
||||
if (_.alias.IsAliasValid(aliasToAdd)) { |
||||
return false; |
||||
} |
||||
if (hash.Contains(aliasToAdd)) { |
||||
RemoveAlias(aliasToAdd); |
||||
} |
||||
// We might not be able to use per-object-config storage |
||||
if (saveInObject && aliasesClass == none) { |
||||
saveInObject = false; |
||||
_.logger.Warning("Cannot save alias in object for source `" |
||||
$ string(class) |
||||
$ "`, because it does not have appropriate `Aliases` class setup."); |
||||
} |
||||
// Save |
||||
if (saveInObject) { |
||||
GetAliasesObjectWithValue(aliasValue).AddAlias(aliasToAdd); |
||||
} |
||||
else |
||||
{ |
||||
newPair.alias = aliasToAdd; |
||||
newPair.value = aliasValue; |
||||
record[record.length] = newPair; |
||||
} |
||||
hash.Insert(aliasToAdd, aliasValue); |
||||
AliasService(class'AliasService'.static.Require()).PendingSaveSource(self); |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Removes alias (all records with it, in case of duplicates) from |
||||
* the caller `AliasSource`. |
||||
* |
||||
* Cannot fail. |
||||
* |
||||
* NOTE: This call will cause update of an ini-file. That update can be |
||||
* slightly delayed, so do not make assumptions about it's immediacy. |
||||
* |
||||
* NOTE #2: removing alias requires this method to go through the |
||||
* whole `AliasSource` to remove possible duplicates, which can make |
||||
* performing a lot of alias removal during run-time costly. |
||||
* |
||||
* @param aliasToRemove Alias that you want to remove from caller source. |
||||
*/ |
||||
public final function RemoveAlias(string aliasToRemove) |
||||
{ |
||||
local int i; |
||||
local bool removedAliasFromRecord; |
||||
hash.Remove(aliasToRemove); |
||||
while (i < record.length) |
||||
{ |
||||
if (record[i].alias ~= aliasToRemove) |
||||
{ |
||||
record.Remove(i, 1); |
||||
removedAliasFromRecord = true; |
||||
} |
||||
else { |
||||
i += 1; |
||||
} |
||||
} |
||||
for (i = 0; i < loadedAliasObjects.length; i += 1) { |
||||
loadedAliasObjects[i].RemoveAlias(aliasToRemove); |
||||
} |
||||
if (removedAliasFromRecord) |
||||
{ |
||||
AliasService(class'AliasService'.static.Require()) |
||||
.PendingSaveSource(self); |
||||
} |
||||
} |
||||
|
||||
// Performs initial hashing of every record with valid alias. |
||||
// In case of duplicate or invalid aliases - method will skip them |
||||
// and log warnings. |
||||
private final function HashValidAliases() |
||||
{ |
||||
if (hash == none) { |
||||
_.logger.Warning("Alias source `" $ string(class) $ "` called" |
||||
$ "`HashValidAliases()` function without creating an `AliasHasher`" |
||||
$ "instance first. This should not have happened."); |
||||
return; |
||||
} |
||||
HashValidAliasesFromRecord(); |
||||
HashValidAliasesFromPerObjectConfig(); |
||||
} |
||||
|
||||
private final function LogDuplicateAliasWarning( |
||||
string alias, |
||||
string existingValue) |
||||
{ |
||||
_.logger.Warning("Alias source `" $ string(class) |
||||
$ "` has duplicate record for alias \"" $ alias |
||||
$ "\". This is likely due to an erroneous config. \"" $ existingValue |
||||
$ "\" value will be used."); |
||||
} |
||||
|
||||
private final function LogInvalidAliasWarning(string invalidAlias) |
||||
{ |
||||
_.logger.Warning("Alias source `" $ string(class) |
||||
$ "` contains invalid alias name \"" $ invalidAlias |
||||
$ "\". This alias will not be loaded."); |
||||
} |
||||
|
||||
private final function HashValidAliasesFromRecord() |
||||
{ |
||||
local int i; |
||||
local bool isDuplicate; |
||||
local string existingValue; |
||||
for (i = 0; i < record.length; i += 1) |
||||
{ |
||||
if (!_.alias.IsAliasValid(record[i].alias)) |
||||
{ |
||||
LogInvalidAliasWarning(record[i].alias); |
||||
continue; |
||||
} |
||||
isDuplicate = !hash.InsertIfMissing(record[i].alias, record[i].value, |
||||
existingValue); |
||||
if (isDuplicate) { |
||||
LogDuplicateAliasWarning(record[i].alias, existingValue); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private final function HashValidAliasesFromPerObjectConfig() |
||||
{ |
||||
local int i, j; |
||||
local bool isDuplicate; |
||||
local string existingValue; |
||||
local string objectValue; |
||||
local array<string> objectAliases; |
||||
for (i = 0; i < loadedAliasObjects.length; i += 1) |
||||
{ |
||||
objectValue = loadedAliasObjects[i].GetValue(); |
||||
objectAliases = loadedAliasObjects[i].GetAliases(); |
||||
for (j = 0; j < objectAliases.length; j += 1) |
||||
{ |
||||
if (!_.alias.IsAliasValid(objectAliases[j])) |
||||
{ |
||||
LogInvalidAliasWarning(objectAliases[j]); |
||||
continue; |
||||
} |
||||
isDuplicate = !hash.InsertIfMissing(objectAliases[j], objectValue, |
||||
existingValue); |
||||
if (isDuplicate) { |
||||
LogDuplicateAliasWarning(objectAliases[j], existingValue); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tries to find a loaded `Aliases` config object that stores aliases for |
||||
// the given value. If such object does not exists - creates a new one. |
||||
private final function Aliases GetAliasesObjectWithValue(string value) |
||||
{ |
||||
local int i; |
||||
local Aliases newAliasesObject; |
||||
// This method only makes sense if this `AliasSource` supports |
||||
// per-object-config storage. |
||||
if (aliasesClass == none) |
||||
{ |
||||
_.logger.Warning("`GetAliasesObjectForValue()` function was called for " |
||||
$ "alias source with `aliasesClass == none`." |
||||
$ "This should not happen."); |
||||
return none; |
||||
} |
||||
for (i = 0; i < loadedAliasObjects.length; i += 1) |
||||
{ |
||||
if (loadedAliasObjects[i].GetValue() ~= value) { |
||||
return loadedAliasObjects[i]; |
||||
} |
||||
} |
||||
newAliasesObject = new(none, value) aliasesClass; |
||||
loadedAliasObjects[loadedAliasObjects.length] = newAliasesObject; |
||||
return newAliasesObject; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
// Source main parameters |
||||
configName = "AcediaAliases" |
||||
aliasesClass = class'Aliases' |
||||
// HashTable twice the size of data entries should do it |
||||
HASH_TABLE_SCALE = 2.0 |
||||
} |
@ -0,0 +1,27 @@
|
||||
/** |
||||
* Source intended for color aliases. |
||||
* Copyright 2020 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 ColorAliasSource extends AliasSource |
||||
config(AcediaAliases_Colors); |
||||
|
||||
defaultproperties |
||||
{ |
||||
configName = "AcediaAliases_Colors" |
||||
aliasesClass = class'ColorAliases' |
||||
} |
@ -0,0 +1,27 @@
|
||||
/** |
||||
* Per-object-configuration intended for color aliases. |
||||
* Copyright 2020 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 ColorAliases extends Aliases |
||||
perObjectConfig |
||||
config(AcediaAliases_Colors); |
||||
|
||||
defaultproperties |
||||
{ |
||||
sourceClass = class'ColorAliasSource' |
||||
} |
@ -0,0 +1,27 @@
|
||||
/** |
||||
* Source intended for weapon aliases. |
||||
* Copyright 2020 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 WeaponAliasSource extends AliasSource |
||||
config(AcediaAliases_Weapons); |
||||
|
||||
defaultproperties |
||||
{ |
||||
configName = "AcediaAliases_Weapons" |
||||
aliasesClass = class'WeaponAliases' |
||||
} |
@ -0,0 +1,27 @@
|
||||
/** |
||||
* Per-object-configuration intended for weapon aliases. |
||||
* Copyright 2020 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 WeaponAliases extends Aliases |
||||
perObjectConfig |
||||
config(AcediaAliases_Weapons); |
||||
|
||||
defaultproperties |
||||
{ |
||||
sourceClass = class'WeaponAliasSource' |
||||
} |
Reference in new issue