diff --git a/sources/Avarice/Avarice.uc b/sources/Avarice/Avarice.uc index f162618..6460192 100644 --- a/sources/Avarice/Avarice.uc +++ b/sources/Avarice/Avarice.uc @@ -1,24 +1,5 @@ /** - * This feature makes it possible to use TCP connection to exchange - * messages (represented by JSON objects) with external applications. - * There are some peculiarities to UnrealEngine's `TCPLink`, so to simplify - * communication process for external applications, they are expected to - * connect to the server through the "Avarice" utility that can accept a stream - * of utf8-encoded JSON messageand feed them to our `TCPLink` (we use child - * class `AvariceTcpStream`) in a way it can receive them. - * Every message sent to us must have the following structure: - * { "s": "", "t": "", "p": } - * where - * * describes a particular source of messages - * (it can be a name of the database or an alias for - * a connected application); - * * simply states the name of a command, for a database it - * can be "get", "set", "delete", etc.. - * * can be an arbitrary json value and can be used to - * pass any additional information along with the message. - * Acedia provides a special treatment for any messages that have their - * service set to "echo" - it always returns them back as-is, except for the - * message type that gets set to "end". + * Config object for `Avarice_Feature`. * Copyright 2021 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. @@ -36,13 +17,10 @@ * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ -class Avarice extends Feature +class Avarice extends FeatureConfig + perobjectconfig config(AcediaAvarice); -// The feature itself is dead simple - it simply creates list of -// `AvariceLink` objects, according to its settings, and stores them -var private array createdLinks; - struct AvariceLinkRecord { var string name; @@ -65,108 +43,71 @@ var private config array link; // connection too long. var private config float reconnectTime; -var private const int TECHO, TEND, TCOLON; - -var private LoggerAPI.Definition errorBadAddress; - -protected function OnEnabled() +protected function AssociativeArray ToData() { - local int i; - local Text name; - local MutableText host; - local int port; - local AvariceLink nextLink; + local int i; + local AssociativeArray data; + local AssociativeArray linkData; + local DynamicArray linksArray; + data = __().collections.EmptyAssociativeArray(); + data.SetFloat(P("reconnectTime"), reconnectTime, true); + linksArray = __().collections.EmptyDynamicArray(); + data.SetItem(P("link"), linksArray); for (i = 0; i < link.length; i += 1) { - name = _.text.FromString(link[i].name); - if (ParseAddress(link[i].address, host, port)) - { - nextLink = AvariceLink(_.memory.Allocate(class'AvariceLink')); - nextLink.Initialize(name, host, port); - nextLink.StartUp(); - nextLink.OnMessage(self, T(TECHO)).connect = EchoHandler; - createdLinks[createdLinks.length] = nextLink; - } - else { - _.logger.Auto(errorBadAddress).Arg(_.text.FromString(link[i].name)); - } - _.memory.Free(name); - _.memory.Free(host); + linkData = __().collections.EmptyAssociativeArray(); + linkData.SetItem(P("name"), __().text.FromString(link[i].name)); + linkData.SetItem(P("address"), __().text.FromString(link[i].address)); + linksArray.AddItem(linkData); } + return data; } -protected function OnDisabled() -{ - _.memory.FreeMany(createdLinks); -} - -// Reply back any messages from "echo" service -private function EchoHandler(AvariceLink link, AvariceMessage message) -{ - link.SendMessage(T(TECHO), T(TEND), message.parameters); -} - -private final function bool ParseAddress( - string address, - out MutableText host, - out int port) +protected function FromData(AssociativeArray source) { - local bool success; - local Parser parser; - parser = _.text.ParseString(address); - parser.Skip() - .MUntil(host, T(TCOLON).GetCharacter(0)) - .Match(T(TCOLON)) - .MUnsignedInteger(port) - .Skip(); - success = parser.Ok() && parser.GetRemainingLength() == 0; - parser.FreeSelf(); - return success; -} - -/** - * Method that returns all the `AvariceLink` created by this feature. - * - * @return Array of links created by this feature. - * Guaranteed to not contain `none` values. - */ -public final function array GetAllLinks() -{ - local int i; - while (i < createdLinks.length) + local int i; + local Text nextText; + local DynamicArray linksArray; + local AssociativeArray nextLink; + local AvariceLinkRecord nextRecord; + if (source == none) { + return; + } + reconnectTime = source.GetFloat(P("reconnectTime")); + link.length = 0; + linksArray = source.GetDynamicArray(P("link")); + if (linksArray == none) { + return; + } + for (i = 0; i < linksArray.GetLength(); i += 1) { - if (createdLinks[i] == none) { - createdLinks.Remove(i, 1); + nextLink = linksArray.GetAssociativeArray(i); + if (nextLink == none) { + continue; + } + nextText = nextLink.GetText(P("name")); + if (nextText != none) { + nextRecord.name = nextText.ToPlainString(); } - else { - i += 1; + nextText = nextLink.GetText(P("address")); + if (nextText != none) { + nextRecord.address = nextText.ToPlainString(); } + link[i] = nextRecord; } - return createdLinks; } -/** - * Returns its current `reconnectTime` setting that describes amount of time - * between connection attempts. - * - * @return Value of `reconnectTime` config variable. - */ -public final static function float GetReconnectTime() +protected function DefaultIt() { - return default.reconnectTime; + local AvariceLinkRecord defaultRecord; + reconnectTime = 10.0; + link.length = 0; + defaultRecord.name = "avarice"; + defaultRecord.address = "127.0.0.1:1234"; + link[0] = defaultRecord; } defaultproperties { - // Settings - reconnectTime = 10.0 - // `Text` constants - TECHO = 0 - stringConstants(0) = "echo" - TEND = 1 - stringConstants(1) = "end" - TCOLON = 2 - stringConstants(2) = ":" - // Log messages - errorBadAddress = (l=LOG_Error,m="Cannot parse address \"%1\"") + configName = "AcediaAvarice" } \ No newline at end of file diff --git a/sources/Avarice/AvariceAPI.uc b/sources/Avarice/AvariceAPI.uc index ddcdc1d..3a9557c 100644 --- a/sources/Avarice/AvariceAPI.uc +++ b/sources/Avarice/AvariceAPI.uc @@ -28,9 +28,10 @@ class AvariceAPI extends AcediaObject; */ public final function array GetAllLinks() { - local Avarice avariceFeature; + local Avarice_Feature avariceFeature; local array emptyResult; - avariceFeature = Avarice(class'Avarice'.static.GetInstance()); + avariceFeature = + Avarice_Feature(class'Avarice_Feature'.static.GetInstance()); if (avariceFeature != none) { return avariceFeature.GetAllLinks(); } diff --git a/sources/Avarice/AvariceLink.uc b/sources/Avarice/AvariceLink.uc index 8012cb4..4001be3 100644 --- a/sources/Avarice/AvariceLink.uc +++ b/sources/Avarice/AvariceLink.uc @@ -246,7 +246,7 @@ public final function StartUp() return; } tcpStream.Set(newStream); - newStream.StartUp(self, class'Avarice'.static.GetReconnectTime()); + newStream.StartUp(self, class'Avarice_Feature'.static.GetReconnectTime()); } /** diff --git a/sources/Avarice/AvariceTcpStream.uc b/sources/Avarice/AvariceTcpStream.uc index afc2dc7..e0c3a1d 100644 Binary files a/sources/Avarice/AvariceTcpStream.uc and b/sources/Avarice/AvariceTcpStream.uc differ diff --git a/sources/Avarice/Avarice_Feature.uc b/sources/Avarice/Avarice_Feature.uc new file mode 100644 index 0000000..cd0d4cc --- /dev/null +++ b/sources/Avarice/Avarice_Feature.uc @@ -0,0 +1,206 @@ +/** + * This feature makes it possible to use TCP connection to exchange + * messages (represented by JSON objects) with external applications. + * There are some peculiarities to UnrealEngine's `TCPLink`, so to simplify + * communication process for external applications, they are expected to + * connect to the server through the "Avarice" utility that can accept a stream + * of utf8-encoded JSON messageand feed them to our `TCPLink` (we use child + * class `AvariceTcpStream`) in a way it can receive them. + * Every message sent to us must have the following structure: + * { "s": "", "t": "", "p": } + * where + * * describes a particular source of messages + * (it can be a name of the database or an alias for + * a connected application); + * * simply states the name of a command, for a database it + * can be "get", "set", "delete", etc.. + * * can be an arbitrary json value and can be used to + * pass any additional information along with the message. + * Acedia provides a special treatment for any messages that have their + * service set to "echo" - it always returns them back as-is, except for the + * message type that gets set to "end". + * 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 . + */ +class Avarice_Feature extends Feature + dependson(Avarice); + +var private /*config*/ array link; +var private /*config*/ float reconnectTime; + +// The feature itself is dead simple - it simply creates list of +// `AvariceLink` objects, according to its settings, and stores them +var private array createdLinks; + +var private const int TECHO, TEND, TCOLON; + +var private LoggerAPI.Definition errorBadAddress; + +public static function StaticConstructor() +{ + if (StaticConstructorGuard()) return; + super.StaticConstructor(); +} + +protected function OnEnabled() +{ + local int i; + local Text name; + local MutableText host; + local int port; + local AvariceLink nextLink; + for (i = 0; i < link.length; i += 1) + { + name = _.text.FromString(link[i].name); + if (ParseAddress(link[i].address, host, port)) + { + nextLink = AvariceLink(_.memory.Allocate(class'AvariceLink')); + nextLink.Initialize(name, host, port); + nextLink.StartUp(); + nextLink.OnMessage(self, T(TECHO)).connect = EchoHandler; + createdLinks[createdLinks.length] = nextLink; + } + else + { + _.logger.Auto(errorBadAddress) + .Arg(_.text.FromString(link[i].address)) + .Arg(_.text.FromString(link[i].name)); + } + _.memory.Free(name); + _.memory.Free(host); + } +} + +protected function OnDisabled() +{ + _.memory.FreeMany(createdLinks); +} + +protected function SwapConfig( + AssociativeArray previousConfigData, + AssociativeArray newConfigData) +{ + local int i; + local Text nextText; + local DynamicArray linksArray; + local AssociativeArray nextLink; + local Avarice.AvariceLinkRecord nextRecord; + // Simply restart all the links + for (i = 0; i < createdLinks.length; i += 1) + { + if (createdLinks[i] != none) { + createdLinks[i].FreeSelf(); + } + } + createdLinks.length = 0; + link.length = 0; + if (newConfigData == none) { + return; + } + reconnectTime = newConfigData.GetFloat(P("reconnectTime")); + default.reconnectTime = reconnectTime; + linksArray = newConfigData.GetDynamicArray(P("link")); + if (linksArray == none) { + return; + } + for (i = 0; i < linksArray.GetLength(); i += 1) + { + nextLink = linksArray.GetAssociativeArray(i); + if (nextLink == none) { + continue; + } + nextText = nextLink.GetText(P("name")); + if (nextText != none) { + nextRecord.name = nextText.ToPlainString(); + } + nextText = nextLink.GetText(P("address")); + if (nextText != none) { + nextRecord.address = nextText.ToPlainString(); + } + link[i] = nextRecord; + } +} + +// Reply back any messages from "echo" service +private function EchoHandler(AvariceLink link, AvariceMessage message) +{ + link.SendMessage(T(TECHO), T(TEND), message.parameters); +} + +private final function bool ParseAddress( + string address, + out MutableText host, + out int port) +{ + local bool success; + local Parser parser; + parser = _.text.ParseString(address); + parser.Skip() + .MUntil(host, T(TCOLON).GetCharacter(0)) + .Match(T(TCOLON)) + .MUnsignedInteger(port) + .Skip(); + success = parser.Ok() && parser.GetRemainingLength() == 0; + parser.FreeSelf(); + return success; +} + +/** + * Method that returns all the `AvariceLink` created by this feature. + * + * @return Array of links created by this feature. + * Guaranteed to not contain `none` values. + */ +public final function array GetAllLinks() +{ + local int i; + while (i < createdLinks.length) + { + if (createdLinks[i] == none) { + createdLinks.Remove(i, 1); + } + else { + i += 1; + } + } + return createdLinks; +} + +/** + * Returns its current `reconnectTime` setting that describes amount of time + * between connection attempts. + * + * @return Value of `reconnectTime` config variable. + */ +public final static function float GetReconnectTime() +{ + return default.reconnectTime; +} + +defaultproperties +{ + configClass = class'Avarice' + // `Text` constants + TECHO = 0 + stringConstants(0) = "echo" + TEND = 1 + stringConstants(1) = "end" + TCOLON = 2 + stringConstants(2) = ":" + // Log messages + errorBadAddress = (l=LOG_Error,m="Cannot parse address \"%1\" for \"%2\"") +} \ No newline at end of file diff --git a/sources/Manifest.uc b/sources/Manifest.uc index 1ca4c1c..8ae99d9 100644 --- a/sources/Manifest.uc +++ b/sources/Manifest.uc @@ -23,7 +23,7 @@ defaultproperties { features(0) = class'Commands_Feature' - features(1) = class'Avarice' + features(1) = class'Avarice_Feature' commands(0) = class'ACommandHelp' commands(1) = class'ACommandDosh' commands(2) = class'ACommandNick'