/** * 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 extends Feature 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; var string address; }; // List all the names (and addresses) of all Avarice instances Acedia must // connect to. // `name` parameter is a useful (case-insensitive) identifier that // can be used in other configs to point at each link. // `address` must have form "host:port", where "host" is either ip or // domain name and "port" is a numerical port value. var private config array link; // In case Avarice utility is launched after link started trying open // the connection - that connection attempt will fail. To fix that link must // try connecting again. // This variable sets the time (in seconds), after which link will // re-attempt opening connection. Setting value too low can prevent any // connection from opening, setting it too high might make you wait for // connection too long. var private config float reconnectTime; var private const int TECHO, TEND, TCOLON; var private LoggerAPI.Definition errorBadAddress; 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].name)); } _.memory.Free(name); _.memory.Free(host); } } 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) { 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 { // 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\"") }