Refactored naming and directory layout of project.

This commit is contained in:
Harrison Deng 2020-12-28 00:43:02 -06:00
parent 334fd37dc6
commit 0cf2335aa7
28 changed files with 324 additions and 183 deletions

4
.vscode/launch.json vendored
View File

@ -10,9 +10,9 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/GameServiceWarden.Host/bin/Debug/netcoreapp3.1/GameServiceWarden.Host.dll",
"program": "${workspaceFolder}/src/GameServiceWarden.Core/bin/Debug/netcoreapp3.1/GameServiceWarden.Core.dll",
"args": [],
"cwd": "${workspaceFolder}/src/GameServiceWarden.Host",
"cwd": "${workspaceFolder}/src/GameServiceWarden.Core",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false

6
.vscode/tasks.json vendored
View File

@ -7,7 +7,7 @@
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/GameServiceWarden.Host/GameServiceWarden.Host.csproj",
"${workspaceFolder}/src/GameServiceWarden.Core/GameServiceWarden.Core.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -19,7 +19,7 @@
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/GameServiceWarden.Host/GameServiceWarden.Host.csproj",
"${workspaceFolder}/src/GameServiceWarden.Core/GameServiceWarden.Core.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -32,7 +32,7 @@
"args": [
"watch",
"run",
"${workspaceFolder}/src/GameServiceWarden.Host/GameServiceWarden.Host.csproj",
"${workspaceFolder}/src/GameServiceWarden.Core/GameServiceWarden.Core.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],

View File

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using GameServiceWarden.Host.Modules.Exceptions;
namespace GameServiceWarden.Host.Modules
namespace GameServiceWarden.Core.Games
{
public class ServiceGateway
public class GameServiceGateway
{
private readonly string dataDirectory;
private const string SERVICE_NAME = "Service Name";
@ -12,7 +12,7 @@ namespace GameServiceWarden.Host.Modules
private const string MODULE_NAME = "Module Name";
private const string EXTENSION = ".sin"; //Service info
public ServiceGateway(string dataDirectory)
public GameServiceGateway(string dataDirectory)
{
if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory);
this.dataDirectory = dataDirectory;
@ -53,7 +53,8 @@ namespace GameServiceWarden.Host.Modules
return line.Substring(key.Length + 2);
}
}
throw new CorruptedServiceInfoException($"\"{path}\" is corrupted. Could not find value for: {key}.");
throw new FormatException($"\"{path}\" is corrupted. Could not find value for: {key}.");
}

View File

@ -4,12 +4,12 @@ using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Threading;
using GameServiceWarden.Host.Preferences;
using GameServiceWarden.Core.Preferences;
using GameServiceWarden.ModuleAPI;
namespace GameServiceWarden.Host.Modules
namespace GameServiceWarden.Core.Games
{
public class ServiceInfo : IDisposable //entity
public class GameServiceInfo : IDisposable //entity
{
/// <summary>
@ -27,22 +27,22 @@ namespace GameServiceWarden.Host.Modules
private readonly IGameService service;
private readonly string assemblyName;
private readonly string moduleName;
private readonly IReadOnlyDictionary<string, IConfigurable> configurables;
private readonly IReadOnlyDictionary<string, IGameConfigurable> configurables;
private bool disposed;
public ServiceInfo(IGameService service, string moduleName, string assemblyName)
public GameServiceInfo(IGameService service, string moduleName, string assemblyName)
{
this.service = service ?? throw new ArgumentNullException("service");
this.moduleName = moduleName ?? throw new ArgumentNullException("moduleName");
this.assemblyName = assemblyName ?? throw new ArgumentNullException("assemblyName");
this.service.StateChangeEvent += OnServiceStateChange;
Dictionary<string, IConfigurable> configurables = new Dictionary<string, IConfigurable>();
foreach (IConfigurable configurable in service.Configurables)
Dictionary<string, IGameConfigurable> configurables = new Dictionary<string, IGameConfigurable>();
foreach (IGameConfigurable configurable in service.Configurables)
{
configurables.Add(configurable.OptionName, configurable);
}
this.configurables = new ReadOnlyDictionary<string, IConfigurable>(configurables);
this.configurables = new ReadOnlyDictionary<string, IGameConfigurable>(configurables);
}
/// <summary>
@ -93,7 +93,7 @@ namespace GameServiceWarden.Host.Modules
}
/// <summary>
/// Gets the possible <see cref="IConfigurable"/>'s names for this service.
/// Gets the possible <see cref="IGameConfigurable"/>'s names for this service.
/// </summary>
/// <returns>A <see cref="ISet{string}"/> returned where the string is the option's name.</returns>
public ISet<string> GetConfigurableOptions()

View File

@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.IO;
using GameServiceWarden.ModuleAPI;
namespace GameServiceWarden.Host.Modules
namespace GameServiceWarden.Core.Games
{
public class ServiceManager
public class GameServiceManager
{
private readonly Dictionary<string, ServiceInfo> services = new Dictionary<string, ServiceInfo>();
private readonly Dictionary<string, GameServiceInfo> services = new Dictionary<string, GameServiceInfo>();
private readonly Dictionary<string, Dictionary<string, IGameServiceModule>> modules = new Dictionary<string, Dictionary<string, IGameServiceModule>>();
public void AddModule(string assemblyName, IGameServiceModule module)
@ -44,7 +44,7 @@ namespace GameServiceWarden.Host.Modules
if (!modules.ContainsKey(assemblyName) || !modules[assemblyName].ContainsKey(moduleName)) throw new KeyNotFoundException($"No module registered from \"{assemblyName}\" named \"{moduleName}\".");
if (services.ContainsKey(serviceName)) throw new ArgumentException($"Service of Name \"{serviceName}\" already exists.");
services.Add(serviceName, new ServiceInfo(modules[assemblyName][moduleName].CreateGameService(), moduleName, assemblyName));
services.Add(serviceName, new GameServiceInfo(modules[assemblyName][moduleName].CreateGameService(), moduleName, assemblyName));
}
public void DeleteService(string serviceName)
@ -64,7 +64,7 @@ namespace GameServiceWarden.Host.Modules
public IEnumerable<string> GetServiceOptions(string serviceName)
{
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
ServiceInfo serviceInfo = services[serviceName];
GameServiceInfo serviceInfo = services[serviceName];
return serviceInfo.GetConfigurableOptions();
}

View File

@ -0,0 +1,16 @@
using System;
using System.Runtime.Serialization;
namespace GameServiceWarden.Core.Games.Modules.Exceptions
{
[System.Serializable]
public class ModuleLoadException : Exception
{
public ModuleLoadException() { }
public ModuleLoadException(string message) : base(message) { }
public ModuleLoadException(string message, Exception inner) : base(message, inner) { }
protected ModuleLoadException(
SerializationInfo info,
StreamingContext context) : base(info, context) { }
}
}

View File

@ -2,13 +2,13 @@ using System;
using System.Reflection;
using System.Runtime.Loader;
namespace GameServiceWarden.Host.Modules
namespace GameServiceWarden.Core.Games.Modules
{
class ModuleLoadContext : AssemblyLoadContext
class GameModuleLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver dependencyResolver;
public ModuleLoadContext(string path) {
public GameModuleLoadContext(string path) {
dependencyResolver = new AssemblyDependencyResolver(path);
}

View File

@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using GameServiceWarden.Host.Modules.Exceptions;
using GameServiceWarden.Core.Games.Modules.Exceptions;
using GameServiceWarden.ModuleAPI;
namespace GameServiceWarden.Host.Modules
namespace GameServiceWarden.Core.Games.Modules
{
public class ModuleLoader //Gateway
public class GameModuleLoader //Gateway
{
/// <summary>
/// Loads an extension module.
@ -36,7 +36,7 @@ namespace GameServiceWarden.Host.Modules
private Assembly loadAssembly(string path)
{
ModuleLoadContext moduleLoadContext = new ModuleLoadContext(path);
GameModuleLoadContext moduleLoadContext = new GameModuleLoadContext(path);
return moduleLoadContext.LoadFromAssemblyPath(path);
}
@ -65,7 +65,7 @@ namespace GameServiceWarden.Host.Modules
}
string types = String.Join(',', typeNames);
throw new NoServiceableFoundException(
throw new ModuleLoadException(
$"No public classes in {assembly} from {assembly.Location} implemented {typeof(IGameService).FullName}." +
$"Detected types: {types}");
}

View File

@ -1,6 +1,6 @@
using System;
namespace GameServiceWarden.Host.Logging
namespace GameServiceWarden.Core.Logging
{
public class FileLogReceiver : ILogReceiver
{

View File

@ -1,6 +1,6 @@
using System;
namespace GameServiceWarden.Host.Logging
namespace GameServiceWarden.Core.Logging
{
public interface ILogReceiver
{

View File

@ -1,4 +1,4 @@
namespace GameServiceWarden.Host.Logging
namespace GameServiceWarden.Core.Logging
{
public enum LogLevel
{

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace GameServiceWarden.Host.Logging
namespace GameServiceWarden.Core.Logging
{
public class Logger {
private readonly HashSet<ILogReceiver> listeners = new HashSet<ILogReceiver>();

View File

@ -4,7 +4,7 @@ using System.Net;
using System.Reflection;
using System.Xml.Serialization;
namespace GameServiceWarden.Host.Preferences
namespace GameServiceWarden.Core.Preferences
{
[Serializable]
public class GeneralPreferences : IPersistable

View File

@ -1,4 +1,4 @@
namespace GameServiceWarden.Host.Preferences
namespace GameServiceWarden.Core.Preferences
{
public interface IPersistable
{

View File

@ -1,6 +1,6 @@
using System;
namespace GameServiceWarden.Host
namespace GameServiceWarden.Core
{
class Program
{

View File

@ -1,13 +1,13 @@
<mxfile host="65bd71144e" modified="2020-12-28T01:14:12.847Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.1 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36" etag="ucZUuT6Z_sggXklr14bH" version="13.10.0" type="embed" pages="2">
<mxfile host="65bd71144e" modified="2020-12-28T04:29:17.687Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.1 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36" etag="3-G5HDQr3muydbMwkt5P" version="13.10.0" type="embed" pages="2">
<diagram id="LHR7ubqCPd17_LyHkaH9" name="Structure">
<mxGraphModel dx="1009" dy="418" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<mxGraphModel dx="1298" dy="740" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="dmd0HlDYcxYugIlahWj0-10" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;sketch=1;" parent="1" source="dmd0HlDYcxYugIlahWj0-5" target="dmd0HlDYcxYugIlahWj0-9" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-5" value="ServiceInfo" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxCell id="dmd0HlDYcxYugIlahWj0-5" value="GameServiceInfo" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="762" y="1030" width="401" height="400" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-6" value="- serviceName: string&#10;- controlLock: object&#10;- state: ServiceState&#10;- service: IGameService&#10;- serviceConsoleStream: Stream&#10;- moduleName: string&#10;- assemblyName: string&#10;- Dictionary&lt;string, IConfigurable&gt;&#10;- disposed: bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="dmd0HlDYcxYugIlahWj0-5" vertex="1">
@ -40,7 +40,7 @@
<mxCell id="SI3d9EEbteElKQB4Ic5T-14" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="1" source="dmd0HlDYcxYugIlahWj0-11" target="SI3d9EEbteElKQB4Ic5T-10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-11" value="ServiceManager" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxCell id="dmd0HlDYcxYugIlahWj0-11" value="GameServiceManager" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="722" y="670" width="481" height="280" as="geometry">
<mxRectangle x="25" y="490" width="120" height="26" as="alternateBounds"/>
</mxGeometry>
@ -54,7 +54,7 @@
<mxCell id="dmd0HlDYcxYugIlahWj0-14" value="+ AddModule(assemblyName: string, module: IGameServiceModule): void&#10;+ RemoveModule(assemblyName: string, moduleName string): void&#10;+ CreateService(serviceName: string, assemblyName: string, moduleName: string): void&#10;+ DeleteService(serviceName: string): void&#10;+ GetServiceNames(): IReadOnlyCollection&lt;string&gt;&#10;+ GetServiceOptionsValue(serviceName: string): IEnumerable&lt;string&gt;&#10;+ SetServiceOptionValue(serviceName: string, optionName: string, string: value): bool&#10;+ GetServiceState(serviceName: string): ServiceState&#10;+ StartService(serviceName: string): void&#10;+ StopService(serviceName: string): void&#10;+ ExecuteCommand(serviceName: string, command: string): void&#10;+ GetServiceConsoleStream(): Stream&#10;+ ExecuteServiceAction(serviceAction: serviceAction): void" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="dmd0HlDYcxYugIlahWj0-11" vertex="1">
<mxGeometry y="78" width="481" height="202" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-16" value="ModuleLoader" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxCell id="dmd0HlDYcxYugIlahWj0-16" value="GameModuleLoader" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1700" y="490" width="460" height="140" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-17" value="- dataDirectory: string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="dmd0HlDYcxYugIlahWj0-16" vertex="1">
@ -66,8 +66,8 @@
<mxCell id="dmd0HlDYcxYugIlahWj0-19" value="- InstantiateServiceables(assembly: Assembly): void&#10;- LoadAssembly(path: string): void&#10;+ LoadModules(path: string): IEnumerable&lt;IGameServiceModule&gt;&#10;+ LoadAllModules(path: string[]): IEnumerable&lt;IGameServiceModule&gt;&#10;+ LoadAllModules(path: IEnumerable&lt;string&gt;): IEnumerable&lt;IGameServiceModule&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="dmd0HlDYcxYugIlahWj0-16" vertex="1">
<mxGeometry y="60" width="460" height="80" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-20" value="ServiceGateway" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="2590" y="490" width="440" height="130" as="geometry"/>
<mxCell id="dmd0HlDYcxYugIlahWj0-20" value="GameServiceGateway" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="2190" y="490" width="440" height="130" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-21" value="- dataDirectory: string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="dmd0HlDYcxYugIlahWj0-20" vertex="1">
<mxGeometry y="26" width="440" height="26" as="geometry"/>
@ -79,7 +79,7 @@
<mxGeometry y="60" width="440" height="70" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-38" value="&lt;&lt;Interface&gt;&gt;&#10;ITextCommand" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1285" y="28" width="181" height="114" as="geometry"/>
<mxGeometry x="1285" y="138" width="181" height="114" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-40" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;sketch=1;" parent="dmd0HlDYcxYugIlahWj0-38" vertex="1">
<mxGeometry y="40" width="181" height="8" as="geometry"/>
@ -88,7 +88,7 @@
<mxGeometry y="48" width="181" height="66" as="geometry"/>
</mxCell>
<mxCell id="2br9O0FZKGLhbr8u3XJU-1" value="ConsoleView" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="870" width="200" height="104" as="geometry"/>
<mxGeometry x="850" y="30" width="200" height="104" as="geometry"/>
</mxCell>
<mxCell id="2br9O0FZKGLhbr8u3XJU-2" value="- mainController: ITextCommand&#10;- mainPresenter: ITextOutput" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="2br9O0FZKGLhbr8u3XJU-1" vertex="1">
<mxGeometry y="26" width="200" height="44" as="geometry"/>
@ -104,11 +104,11 @@
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-10" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="1" source="qpeZJq-dxPH0P0VpmRa_-7" target="dmd0HlDYcxYugIlahWj0-38" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="950" y="82" as="targetPoint"/>
<mxPoint x="950" y="162" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="qpeZJq-dxPH0P0VpmRa_-7" value="MainController" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1505" y="110" width="270" height="60" as="geometry"/>
<mxGeometry x="1505" y="220" width="270" height="60" as="geometry"/>
</mxCell>
<mxCell id="qpeZJq-dxPH0P0VpmRa_-8" value="+ commands: Dictionary&lt;string, ITextCommand&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="qpeZJq-dxPH0P0VpmRa_-7" vertex="1">
<mxGeometry y="26" width="270" height="26" as="geometry"/>
@ -125,8 +125,8 @@
<mxCell id="V3nv0dmUtDNsDw_gxP-z-3" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="1" source="K1k0_LUP-qlT_3mlrptx-2" target="dmd0HlDYcxYugIlahWj0-38" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="K1k0_LUP-qlT_3mlrptx-2" value="ServiceController" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1255" y="240" width="241" height="60" as="geometry"/>
<mxCell id="K1k0_LUP-qlT_3mlrptx-2" value="GameServiceController" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1255" y="320" width="241" height="60" as="geometry"/>
</mxCell>
<mxCell id="K1k0_LUP-qlT_3mlrptx-3" value="- serviceManipulator: IServiceManipulator" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="K1k0_LUP-qlT_3mlrptx-2" vertex="1">
<mxGeometry y="26" width="241" height="26" as="geometry"/>
@ -137,8 +137,8 @@
<mxCell id="wwlaSBDwwZOn0hO83bWU-16" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="1" source="wwlaSBDwwZOn0hO83bWU-2" target="wwlaSBDwwZOn0hO83bWU-12" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-2" value="&lt;&lt;Interface&gt;&gt;&#10;IServiceActionExecuter" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1275" y="350" width="201" height="74" as="geometry"/>
<mxCell id="wwlaSBDwwZOn0hO83bWU-2" value="&lt;&lt;Interface&gt;&gt;&#10;IGameServiceActionExecuter" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1275" y="480" width="201" height="74" as="geometry"/>
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-4" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;sketch=1;" parent="wwlaSBDwwZOn0hO83bWU-2" vertex="1">
<mxGeometry y="40" width="201" height="8" as="geometry"/>
@ -149,20 +149,17 @@
<mxCell id="SI3d9EEbteElKQB4Ic5T-15" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="1" source="wwlaSBDwwZOn0hO83bWU-12" target="SI3d9EEbteElKQB4Ic5T-10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-12" value="&lt;&lt;DS&gt;&gt;&#10;ServiceAction" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1400" y="464" width="161" height="144" as="geometry"/>
<mxCell id="wwlaSBDwwZOn0hO83bWU-12" value="&lt;&lt;DS&gt;&gt;&#10;GameServiceAction" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="1466" y="600" width="161" height="128" as="geometry"/>
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-13" value="+ assemblyName: string&#10;+ moduleName: string&#10;+ serviceName: string&#10;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="wwlaSBDwwZOn0hO83bWU-12" vertex="1">
<mxGeometry y="40" width="161" height="70" as="geometry"/>
<mxCell id="wwlaSBDwwZOn0hO83bWU-13" value="+ AssemblyName: string&#10;+ ModuleName: string&#10;+ ServiceName: string&#10;+ Module: IModule&#10;+ Action: ServiceActions" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="wwlaSBDwwZOn0hO83bWU-12" vertex="1">
<mxGeometry y="40" width="161" height="80" as="geometry"/>
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-14" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;sketch=1;" parent="wwlaSBDwwZOn0hO83bWU-12" vertex="1">
<mxGeometry y="110" width="161" height="8" as="geometry"/>
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-15" value="+ method(type): type" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="wwlaSBDwwZOn0hO83bWU-12" vertex="1">
<mxGeometry y="118" width="161" height="26" as="geometry"/>
<mxGeometry y="120" width="161" height="8" as="geometry"/>
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-19" value="&lt;&lt;Interface&gt;&gt;&#10;ITextOutput" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="340" y="33" width="310" height="110" as="geometry"/>
<mxGeometry x="340" y="137" width="310" height="110" as="geometry"/>
</mxCell>
<mxCell id="wwlaSBDwwZOn0hO83bWU-21" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;sketch=1;" parent="wwlaSBDwwZOn0hO83bWU-19" vertex="1">
<mxGeometry y="40" width="310" height="8" as="geometry"/>
@ -180,7 +177,7 @@
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="HUSvFZX5SimreebZp30a-5" value="MainPresenter" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="30" y="110" width="270" height="60" as="geometry"/>
<mxGeometry x="30" y="214" width="270" height="60" as="geometry"/>
</mxCell>
<mxCell id="HUSvFZX5SimreebZp30a-6" value="- textPresenters: Dictionary&lt;string, ITextOutput&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="HUSvFZX5SimreebZp30a-5" vertex="1">
<mxGeometry y="26" width="270" height="26" as="geometry"/>
@ -195,7 +192,7 @@
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="HUSvFZX5SimreebZp30a-11" value="ServicePresenter" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="415" y="240" width="160" height="34" as="geometry"/>
<mxGeometry x="415" y="344" width="160" height="34" as="geometry"/>
</mxCell>
<mxCell id="HUSvFZX5SimreebZp30a-13" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;sketch=1;" parent="HUSvFZX5SimreebZp30a-11" vertex="1">
<mxGeometry y="26" width="160" height="8" as="geometry"/>
@ -203,8 +200,8 @@
<mxCell id="SI3d9EEbteElKQB4Ic5T-5" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="1" source="zFFzFwxISwJASp9ezwbr-1" target="fdKXkHfjRXYybK0fejAG-9" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="zFFzFwxISwJASp9ezwbr-1" value="&lt;&lt;Interface&gt;&gt;&#10;IServiceOutputMonitor" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="355" y="360" width="280" height="74" as="geometry"/>
<mxCell id="zFFzFwxISwJASp9ezwbr-1" value="&lt;&lt;Interface&gt;&gt;&#10;IGameServiceOutputMonitor" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="355" y="464" width="280" height="74" as="geometry"/>
</mxCell>
<mxCell id="zFFzFwxISwJASp9ezwbr-3" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;sketch=1;" parent="zFFzFwxISwJASp9ezwbr-1" vertex="1">
<mxGeometry y="40" width="280" height="8" as="geometry"/>
@ -212,17 +209,17 @@
<mxCell id="zFFzFwxISwJASp9ezwbr-4" value="+ ServicesChanged(ServicesResult results): void" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="zFFzFwxISwJASp9ezwbr-1" vertex="1">
<mxGeometry y="48" width="280" height="26" as="geometry"/>
</mxCell>
<mxCell id="fdKXkHfjRXYybK0fejAG-9" value="&lt;&lt;DS&gt;&gt;&#10;ServicesResults" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="320" y="860" width="350" height="138" as="geometry"/>
<mxCell id="fdKXkHfjRXYybK0fejAG-9" value="&lt;&lt;DS&gt;&gt;&#10;GameServicesActionResults" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="225" y="830" width="365" height="138" as="geometry"/>
</mxCell>
<mxCell id="fdKXkHfjRXYybK0fejAG-10" value="+ Services: ICollection&lt;string&gt;&#10;+ Running: ICollection&lt;string&gt;&#10;+ Errors: ICollection&lt;string&gt;&#10;+ Consoles: IDictionary&lt;string, Stream&gt;&#10;+ ServiceOptions: IDictionary&lt;string, IDictionary&lt;string, string&gt;&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="fdKXkHfjRXYybK0fejAG-9" vertex="1">
<mxGeometry y="40" width="350" height="90" as="geometry"/>
<mxGeometry y="40" width="365" height="90" as="geometry"/>
</mxCell>
<mxCell id="fdKXkHfjRXYybK0fejAG-11" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;sketch=1;" parent="fdKXkHfjRXYybK0fejAG-9" vertex="1">
<mxGeometry y="130" width="350" height="8" as="geometry"/>
<mxGeometry y="130" width="365" height="8" as="geometry"/>
</mxCell>
<mxCell id="SI3d9EEbteElKQB4Ic5T-10" value="&lt;&lt;Enum&gt;&gt;&#10;ServiceActions" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="882.5" y="442" width="160" height="188" as="geometry"/>
<mxCell id="SI3d9EEbteElKQB4Ic5T-10" value="&lt;&lt;Enum&gt;&gt;&#10;GameServiceActions" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;sketch=1;" parent="1" vertex="1">
<mxGeometry x="882.5" y="420" width="160" height="188" as="geometry"/>
</mxCell>
<mxCell id="SI3d9EEbteElKQB4Ic5T-11" value="+ Start&#10;+ Stop&#10;+ AddModule&#10;+ RemoveModule&#10;+ CreateService&#10;+ DeleteService&#10;+ Execute&#10;+ SetServiceOption&#10;+ View" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;sketch=1;" parent="SI3d9EEbteElKQB4Ic5T-10" vertex="1">
<mxGeometry y="40" width="160" height="140" as="geometry"/>
@ -234,176 +231,348 @@
</mxGraphModel>
</diagram>
<diagram id="gj0qHRc3eh050ABAey3g" name="Data-Flow">
&#xa; &#xa;&#xa;
<mxGraphModel dx="1009" dy="418" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
&#xa; &#xa;&#xa;
<root>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-0"/>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-1" parent="jVG6p58vlRYGO9X4wXeX-0"/>
&#xa; &#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-2" target="jVG6p58vlRYGO9X4wXeX-3" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-2" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="10" y="300" width="30" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fillColor=#1ba1e2;strokeColor=#006EAF;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-4" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.25;exitDx=0;exitDy=0;entryX=0.75;entryY=0.1;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-2" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-0" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-4" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-6" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="28FAlPysTx9DMYvLwa-2-7" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-3" value="Console View" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="80" y="300" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-4" target="jVG6p58vlRYGO9X4wXeX-5" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-2" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-4" target="jVG6p58vlRYGO9X4wXeX-5" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-4" value="string command (request)" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="260" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-3" value="Use" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;dashed=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-18" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-9" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-5" value="MainController" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="420" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-6" value="http://www.plainionist.net/Implementing-Clean-Architecture-Controller-Presenter/" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry y="840" width="480" height="20" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-7" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;ICommand" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="420" y="372.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-8" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.25;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa;&#xa;
<mxPoint x="809.9999999999998" y="512.5000000000002" as="sourcePoint"/>
&#xa; &#xa;&#xa;
</mxGeometry>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-3" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-4" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-9" value="ServiceController" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="575" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=none;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-5" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;strokeColor=#f0f0f0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-7" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1.008;entryY=0.625;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa;&#xa;
<Array as="points">
&#xa; &#xa;&#xa;
<mxPoint x="960" y="338"/>
&#xa; &#xa;&#xa;
<mxPoint x="960" y="580"/>
&#xa; &#xa;&#xa;
</Array>
&#xa; &#xa;&#xa;
</mxGeometry>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-16" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;dashed=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-11" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-7" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa;&#xa;
<Array as="points">
&#xa; &#xa;&#xa;
<mxPoint x="960" y="338"/>
&#xa; &#xa;&#xa;
<mxPoint x="960" y="83"/>
&#xa; &#xa;&#xa;
</Array>
&#xa; &#xa;&#xa;
</mxGeometry>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-1" value="ServiceManager" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="800" y="307.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="tM_Gde3HH8YiZ2frBV5J-0" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-12" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=block;endFill=0;strokeColor=#f0f0f0;dashed=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="UY-EM7-1ECCvWtENr50b-11" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-13" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-3" style="edgeStyle=orthogonalEdgeStyle;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-5" value="ServicePresenter" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="575" y="122.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-7" target="jVG6p58vlRYGO9X4wXeX-3" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-5" value="Use" style="edgeStyle=orthogonalEdgeStyle;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-7" target="tM_Gde3HH8YiZ2frBV5J-0" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-7" value="String Output" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="260" y="122.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-1" value="ServiceAction &amp;lt;DS&amp;gt;" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="800" y="542.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-6" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-2" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-20" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-1" target="28FAlPysTx9DMYvLwa-2-1" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-2" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;IServiceManipulator" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="800" y="432.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-22" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-10" target="28FAlPysTx9DMYvLwa-2-5" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-10" value="ServicesResult &amp;lt;DS&amp;gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="800" y="67.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-14" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-11" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-21" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-11" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;IServicesMonitor" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="800" y="167.5" width="120" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-70" value="" style="line;strokeWidth=2;direction=south;html=1;fillColor=none;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="220" y="50" width="10" height="570" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-71" value="" style="line;strokeWidth=2;direction=south;html=1;fillColor=none;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="760" y="50" width="10" height="570" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-73" value="Page 191 (Chapter 22) of Clean Architecture" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry y="870" width="480" height="20" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-2" style="edgeStyle=orthogonalEdgeStyle;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="tM_Gde3HH8YiZ2frBV5J-0" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-4" value="Use" style="edgeStyle=orthogonalEdgeStyle;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="tM_Gde3HH8YiZ2frBV5J-0" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-0" value="MainPresenter" style="html=1;dashed=0;whitespace=wrap;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="420" y="122.5" width="110" height="60" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-1" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;IConsoleOutput" style="html=1;dashed=0;whitespace=wrap;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa;&#xa;
<mxGeometry x="420" y="20" width="110" height="50" as="geometry"/>
&#xa; &#xa;&#xa;
</mxCell>
&#xa; &#xa;&#xa;
</root>
&#xa; &#xa;&#xa;
</mxGraphModel>
&#xa; &#xa;&#xa;
</diagram>
</mxfile>

View File

@ -1,13 +0,0 @@
namespace GameServiceWarden.Host.Modules.Exceptions
{
[System.Serializable]
public class CorruptedServiceInfoException : System.Exception
{
public CorruptedServiceInfoException() { }
public CorruptedServiceInfoException(string message) : base(message) { }
public CorruptedServiceInfoException(string message, System.Exception inner) : base(message, inner) { }
protected CorruptedServiceInfoException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
}

View File

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
namespace GameServiceWarden.Host.Modules.Exceptions
{
[Serializable]
public class NoServiceableFoundException : Exception
{
public NoServiceableFoundException(string message) : base(message) { }
public NoServiceableFoundException(string message, Exception inner) : base(message, inner) { }
protected NoServiceableFoundException(
SerializationInfo info,
StreamingContext context) : base(info, context) { }
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace GameServiceWarden.Host.Modules.Exceptions
{
[System.Serializable]
public class NotServiceableTypeException : Exception
{
public NotServiceableTypeException() { }
public NotServiceableTypeException(string message) : base(message) { }
public NotServiceableTypeException(string message, System.Exception inner) : base(message, inner) { }
protected NotServiceableTypeException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
}

View File

@ -1,6 +1,6 @@
namespace GameServiceWarden.ModuleAPI
{
public interface IConfigurable
public interface IGameConfigurable
{
string OptionName { get; }
bool SetValue(string value);

View File

@ -7,7 +7,7 @@ namespace GameServiceWarden.ModuleAPI
public interface IGameService
{
event EventHandler<ServiceState> StateChangeEvent;
IReadOnlyCollection<IConfigurable> Configurables{ get; }
IReadOnlyCollection<IGameConfigurable> Configurables{ get; }
void InitializeService(TextWriter stream);
void ElegantShutdown();
void ExecuteCommand(string command);

View File

@ -14,7 +14,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\GameServiceWarden.Host\GameServiceWarden.Host.csproj" />
<ProjectReference Include="..\..\src\GameServiceWarden.Core\GameServiceWarden.Core.csproj" />
<ProjectReference Include="..\..\src\GameServiceWarden.ModuleAPI\GameServiceWarden.ModuleAPI.csproj" />
</ItemGroup>

View File

@ -1,13 +1,13 @@
using GameServiceWarden.ModuleAPI;
namespace GameServiceWarden.Host.Tests.Modules
namespace GameServiceWarden.Core.Tests.Modules.Games
{
public class FakeConfigurable : IConfigurable
public class FakeGameConfigurable : IGameConfigurable
{
private string value;
public string OptionName { get; private set; }
public FakeConfigurable(string optionName)
public FakeGameConfigurable(string optionName)
{
this.OptionName = optionName;
}

View File

@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.IO;
using GameServiceWarden.ModuleAPI;
namespace GameServiceWarden.Host.Tests.Modules
namespace GameServiceWarden.Core.Tests.Modules.Games
{
public class FakeService : IGameService
public class FakeGameService : IGameService
{
public IReadOnlyCollection<IConfigurable> Configurables { get; set; }
public IReadOnlyCollection<IGameConfigurable> Configurables { get; set; }
public event EventHandler<ServiceState> StateChangeEvent;
@ -15,10 +15,10 @@ namespace GameServiceWarden.Host.Tests.Modules
private TextWriter consoleStream;
public FakeService(params IConfigurable[] configurables)
public FakeGameService(params IGameConfigurable[] configurables)
{
HashSet<IConfigurable> modifiable = new HashSet<IConfigurable>();
foreach (IConfigurable configurable in configurables)
HashSet<IGameConfigurable> modifiable = new HashSet<IGameConfigurable>();
foreach (IGameConfigurable configurable in configurables)
{
modifiable.Add(configurable);
}

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using GameServiceWarden.ModuleAPI;
namespace GameServiceWarden.Host.Tests.Modules
namespace GameServiceWarden.Core.Tests.Modules.Games
{
public class FakeGameServiceModule : IGameServiceModule
{
@ -9,8 +9,8 @@ namespace GameServiceWarden.Host.Tests.Modules
public string Description => "A fake module for testing.";
private IConfigurable[] configurables;
public FakeGameServiceModule(params IConfigurable[] configurables)
private IGameConfigurable[] configurables;
public FakeGameServiceModule(params IGameConfigurable[] configurables)
{
this.configurables = configurables;
}
@ -19,7 +19,7 @@ namespace GameServiceWarden.Host.Tests.Modules
public IGameService CreateGameService()
{
return new FakeService(configurables);
return new FakeGameService(configurables);
}
}
}

View File

@ -1,24 +1,24 @@
using System.Collections.Generic;
using System.IO;
using GameServiceWarden.Host.Modules;
using GameServiceWarden.Core.Games;
using GameServiceWarden.ModuleAPI;
using Xunit;
namespace GameServiceWarden.Host.Tests.Modules
namespace GameServiceWarden.Core.Tests.Modules.Games
{
// Testing convention from: https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices
// Fakes are generic test objects,
// mocks are the objects being asserted upon,
// stubs are objects used as part of the test.
public class ServiceInfoTest
public class GameServiceInfoTest
{
//MethodTested_ScenarioTested_ExpectedBehavior
[Fact]
public void Start_FromStopped_StateIsRunning()
{
//Arrange, Act, Assert
IGameService stubGameService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubGameService, "FakeModule", "FakeAssembly");
IGameService stubGameService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubGameService, "FakeModule", "FakeAssembly");
serviceInfo.Start();
Assert.Equal(ServiceState.Running, serviceInfo.GetServiceState());
serviceInfo.Dispose();
@ -27,8 +27,8 @@ namespace GameServiceWarden.Host.Tests.Modules
[Fact]
public void Stop_FromStart_Stopped()
{
IGameService stubService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly");
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", "FakeAssembly");
serviceInfo.Start();
serviceInfo.Stop();
Assert.Equal(ServiceState.Stopped, serviceInfo.GetServiceState());
@ -39,12 +39,12 @@ namespace GameServiceWarden.Host.Tests.Modules
public void GetConfigurableOptions_ServiceStopped_ReturnsConfigurables()
{
//Given
FakeService stubService = new FakeService();
FakeConfigurable stubConfigurable = new FakeConfigurable("Option");
HashSet<IConfigurable> configurables = new HashSet<IConfigurable>();
FakeGameService stubService = new FakeGameService();
FakeGameConfigurable stubConfigurable = new FakeGameConfigurable("Option");
HashSet<IGameConfigurable> configurables = new HashSet<IGameConfigurable>();
configurables.Add(stubConfigurable);
stubService.Configurables = configurables;
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly");
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", "FakeAssembly");
//Then
Assert.Contains<string>(stubConfigurable.OptionName, serviceInfo.GetConfigurableOptions());
serviceInfo.Dispose();
@ -54,12 +54,12 @@ namespace GameServiceWarden.Host.Tests.Modules
public void SetAndGetConfigurationValue_ServiceStopped_AppropriateValueReturned()
{
//Given
FakeService stubService = new FakeService();
FakeConfigurable stubConfigurable = new FakeConfigurable("Option");
HashSet<IConfigurable> configurables = new HashSet<IConfigurable>();
FakeGameService stubService = new FakeGameService();
FakeGameConfigurable stubConfigurable = new FakeGameConfigurable("Option");
HashSet<IGameConfigurable> configurables = new HashSet<IGameConfigurable>();
configurables.Add(stubConfigurable);
stubService.Configurables = configurables;
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly");
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", "FakeAssembly");
//When
serviceInfo.SetConfigurableValue(stubConfigurable.OptionName, "success");
//Then
@ -70,8 +70,8 @@ namespace GameServiceWarden.Host.Tests.Modules
public void GetServiceState_ServiceNotStarted_ReturnsStoppedState()
{
//Given
IGameService stubService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly");
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", "FakeAssembly");
//Then
Assert.Equal(ServiceState.Stopped, serviceInfo.GetServiceState());
serviceInfo.Dispose();
@ -81,8 +81,8 @@ namespace GameServiceWarden.Host.Tests.Modules
public void GetServiceState_ServiceStarted_ReturnsRunningState()
{
//Given
IGameService stubService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly");
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", "FakeAssembly");
//When
serviceInfo.Start();
//Then
@ -95,8 +95,8 @@ namespace GameServiceWarden.Host.Tests.Modules
{
//Given
const string MODULE_NAME = "FakeModule";
IGameService stubService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubService, MODULE_NAME, "FakeAssembly");
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, MODULE_NAME, "FakeAssembly");
//Then
Assert.Equal(MODULE_NAME, serviceInfo.GetModuleName());
serviceInfo.Dispose();
@ -107,8 +107,8 @@ namespace GameServiceWarden.Host.Tests.Modules
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
IGameService stubService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", ASSEMBLY_NAME);
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", ASSEMBLY_NAME);
//Then
Assert.Equal(ASSEMBLY_NAME, serviceInfo.GetAssemblyName());
serviceInfo.Dispose();
@ -119,8 +119,8 @@ namespace GameServiceWarden.Host.Tests.Modules
{
//Given
const string SERVICE_NAME = "Service";
IGameService stubService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssemblyName");
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", "FakeAssemblyName");
//When
serviceInfo.ServiceName = SERVICE_NAME;
//Then
@ -132,8 +132,8 @@ namespace GameServiceWarden.Host.Tests.Modules
public void ServiceConsoleStream_ServiceNotStarted_NullReturned()
{
//Given
IGameService stubService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly");
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", "FakeAssembly");
//Then
Assert.Null(serviceInfo.ServiceConsoleStream);
serviceInfo.Dispose();
@ -143,8 +143,8 @@ namespace GameServiceWarden.Host.Tests.Modules
public void ServiceConsoleStream_ServiceStarted_StreamReturned()
{
//Given
IGameService stubService = new FakeService();
ServiceInfo serviceInfo = new ServiceInfo(stubService, "FakeModule", "FakeAssembly");
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, "FakeModule", "FakeAssembly");
//When
serviceInfo.Start();
//Then

View File

@ -1,18 +1,18 @@
using System.IO;
using GameServiceWarden.Host.Modules;
using GameServiceWarden.Core.Games;
using GameServiceWarden.ModuleAPI;
using Xunit;
namespace GameServiceWarden.Host.Tests.Modules
namespace GameServiceWarden.Core.Tests.Modules.Games
{
public class ServiceManagerTest
public class GameServiceManagerTest
{
[Fact]
public void AddModule_NewManager_SuccessfulAddition()
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -26,7 +26,7 @@ namespace GameServiceWarden.Host.Tests.Modules
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -41,7 +41,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
//When
@ -56,7 +56,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
@ -72,7 +72,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_PREFIX = "FakeService_";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -93,11 +93,11 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule(
new FakeConfigurable("A"),
new FakeConfigurable("B"),
new FakeConfigurable("C")
new FakeGameConfigurable("A"),
new FakeGameConfigurable("B"),
new FakeGameConfigurable("C")
);
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -114,9 +114,9 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule(
new FakeConfigurable("A")
new FakeGameConfigurable("A")
);
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -132,7 +132,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -147,7 +147,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -163,7 +163,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -179,7 +179,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -196,7 +196,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);
@ -218,7 +218,7 @@ namespace GameServiceWarden.Host.Tests.Modules
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
ServiceManager serviceManager = new ServiceManager();
GameServiceManager serviceManager = new GameServiceManager();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
//When
serviceManager.AddModule(ASSEMBLY_NAME, stubGameServiceModule);