Anton Tarasenko
3 years ago
6 changed files with 263 additions and 115 deletions
Binary file not shown.
@ -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": "<service_name>", "t": "<command_type>", "p": <any_json_value> } |
||||
* where |
||||
* * <service_name> describes a particular source of messages |
||||
* (it can be a name of the database or an alias for |
||||
* a connected application); |
||||
* * <command_type> simply states the name of a command, for a database it |
||||
* can be "get", "set", "delete", etc.. |
||||
* * <any_json_value> 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 <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Avarice_Feature extends Feature |
||||
dependson(Avarice); |
||||
|
||||
var private /*config*/ array<Avarice.AvariceLinkRecord> 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<AvariceLink> 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<AvariceLink> 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\"") |
||||
} |
Loading…
Reference in new issue