/** * 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\"") }