Fixed method of access game modules.

Added some documentation.
Changed data persistence structure.
General naming changes.
Updated tests appropriately.
This commit is contained in:
Harrison Deng 2021-03-30 15:22:28 -05:00
parent c7fadd8166
commit f402b8b7f2
14 changed files with 446 additions and 553 deletions

View File

@ -55,7 +55,6 @@ namespace GameServiceWarden.Core.Games
}
throw new FormatException($"\"{path}\" is corrupted. Could not find value for: {key}.");
}
public IEnumerable<string> GetAllServiceInfoPaths()

View File

@ -25,14 +25,18 @@ namespace GameServiceWarden.Core.Games
private volatile ServiceState state;
private readonly IGameService service;
private readonly string assemblyName;
private readonly string moduleName;
/// <summary>
/// Name of module this service uses.
/// </summary>
public string ModuleName { get; private set; }
private readonly IReadOnlyDictionary<string, IGameConfigurable> configurables;
private bool disposed;
public GameServiceInfo(IGameService service, string moduleName, string assemblyName)
{
this.service = service ?? throw new ArgumentNullException("service");
this.moduleName = moduleName ?? throw new ArgumentNullException("moduleName");
this.ModuleName = moduleName ?? throw new ArgumentNullException("moduleName");
this.assemblyName = assemblyName ?? throw new ArgumentNullException("assemblyName");
this.service.StateChangeEvent += OnServiceStateChange;
@ -121,12 +125,6 @@ namespace GameServiceWarden.Core.Games
}
}
/// <returns>The name of the module this service uses.</returns>
public string GetModuleName()
{
return moduleName;
}
/// <returns>The name of assembly this module is contained in.</returns>
public string GetAssemblyName()
{

View File

@ -8,22 +8,24 @@ namespace GameServiceWarden.Core.Games
{
public class GameServiceManager
{
private readonly IDictionary<string, GameServiceInfo> running;
private readonly IPersistentDictionary<GameServiceInfo> services;
private readonly IReadOnlyPersistentDictionary<IGameServiceModule> modules;
private readonly IReadOnlyPersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> modules;
public GameServiceManager(IPersistentDictionary<GameServiceInfo> services, IReadOnlyPersistentDictionary<IGameServiceModule> modules)
public GameServiceManager(IPersistentDictionary<GameServiceInfo> services, IReadOnlyPersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> modules)
{
this.services = services;
this.modules = modules;
this.running = new Dictionary<string, GameServiceInfo>();
}
public void CreateService(string serviceName, string assemblyName, string moduleName)
{
string moduleID = ToModuleID(assemblyName, moduleName);
if (!modules.ContainsKey(moduleID)) throw new KeyNotFoundException($"No module registered from \"{assemblyName}\" named \"{moduleName}\".");
if (!this.modules.ContainsKey(assemblyName)) throw new KeyNotFoundException($"No file \"{assemblyName}\" found.");
IReadOnlyDictionary<string, IGameServiceModule> modules = this.modules[assemblyName];
if (services.ContainsKey(serviceName)) throw new ArgumentException($"Service of Name \"{serviceName}\" already exists.");
services.Add(serviceName, new GameServiceInfo(modules[moduleID].InstantiateGameService(services.GetPathForKey(serviceName), true), moduleName, assemblyName));
services.Add(serviceName, new GameServiceInfo(modules[moduleName].InstantiateGameService(services.GetPathForKey(serviceName), true), moduleName, assemblyName));
}
public void DeleteService(string serviceName)
@ -75,30 +77,29 @@ namespace GameServiceWarden.Core.Games
public void StartService(string serviceName)
{
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
services[serviceName].Start();
if (running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is already running.");
running.Add(serviceName, services[serviceName]);
running[serviceName].Start();
}
public void StopService(string serviceName)
{
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
services[serviceName].Stop();
if (!running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is not running.");
running[serviceName].Stop();
services[serviceName] = running[serviceName];
running.Remove(serviceName);
}
public void ExecuteCommand(string serviceName, string command)
{
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
services[serviceName].ExecuteCommand(command);
if (!running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is not running.");
running[serviceName].ExecuteCommand(command);
}
public Stream GetServiceConsoleStream(string serviceName)
{
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
return services[serviceName].ServiceConsoleStream;
}
private string ToModuleID(string assemblyName, string moduleName)
{
return assemblyName + Path.DirectorySeparatorChar + moduleName;
if (!running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is not running.");
return running[serviceName].ServiceConsoleStream;
}
}
}

View File

@ -6,7 +6,7 @@ using GameServiceWarden.ModuleAPI;
namespace GameServiceWarden.Core.Games.Modules
{
public class GameModuleLoader //Gateway
public class GameServiceModuleLoader //Gateway
{
/// <summary>
/// Loads an extension module.

View File

@ -0,0 +1,128 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using GameServiceWarden.Core.Games.Modules;
using GameServiceWarden.ModuleAPI;
namespace GameServiceWarden.Core.Persistence
{
public class GameServiceModuleDictionary : IReadOnlyPersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>
{
private readonly string mapDirectory;
private readonly GameServiceModuleLoader loader = new GameServiceModuleLoader();
public GameServiceModuleDictionary(string mapDirectory)
{
this.mapDirectory = mapDirectory;
}
public IReadOnlyDictionary<string, IGameServiceModule> this[string key]
{
get
{
if (!ContainsKey(key)) throw new KeyNotFoundException($"Key \"{key}\" not found.");
Dictionary<string, IGameServiceModule> res = new Dictionary<string, IGameServiceModule>();
IEnumerable<IGameServiceModule> modules = loader.LoadModules(GetPathForKey(key));
foreach (IGameServiceModule module in modules)
{
res.Add(module.Name, module);
}
return new ReadOnlyDictionary<string, IGameServiceModule>(res);
}
}
public string MapDirectory
{
get
{
return mapDirectory;
}
}
public IEnumerable<string> Keys
{
get
{
IEnumerable<string> files = Directory.EnumerateFiles(mapDirectory);
foreach (string file in files)
{
if (Path.GetExtension(file).ToLower().Equals("dll"))
{
yield return Path.GetFileName(file);
}
}
}
}
public IEnumerable<IReadOnlyDictionary<string, IGameServiceModule>> Values
{
get
{
IEnumerable<string> keys = Keys;
foreach (string key in keys)
{
yield return this[key];
}
}
}
public int Count
{
get
{
int count = 0;
IEnumerable<string> files = Directory.EnumerateFiles(mapDirectory);
foreach (string file in files)
{
if (Path.GetExtension(file).ToLower().Equals("dll"))
{
count++;
}
}
return count;
}
}
public bool ContainsKey(string key)
{
string path = GetPathForKey(key);
return File.Exists(path) && Path.GetExtension(path).ToLower().Equals("dll");
}
public IEnumerator<KeyValuePair<string, IReadOnlyDictionary<string, IGameServiceModule>>> GetEnumerator()
{
IEnumerable<string> keys = Keys;
foreach (string key in keys)
{
yield return new KeyValuePair<string, IReadOnlyDictionary<string, IGameServiceModule>>(key, this[key]);
}
}
public string GetPathForKey(string key)
{
return mapDirectory + Path.DirectorySeparatorChar + key;
}
public bool TryGetValue(string key, [MaybeNullWhen(false)] out IReadOnlyDictionary<string, IGameServiceModule> value)
{
try
{
value = this[key];
return true;
}
catch (KeyNotFoundException)
{
value = null;
return false;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -1,159 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace GameServiceWarden.Core.Persistence
{
public class GeneralPersistentDictionary<V> : IPersistentDictionary<V>, ICollection<KeyValuePair<string, V>>
{
private readonly string mapDirectory;
private readonly IPersistentDictionaryWriter<V> writer;
private readonly IPersistentDictionaryReader<V> reader;
private readonly GeneralReadOnlyPersistentDictionary<V> readDictionary;
public GeneralPersistentDictionary(string mapDirectory, IPersistentDictionaryReader<V> reader, IPersistentDictionaryWriter<V> writer)
{
this.mapDirectory = mapDirectory ?? throw new ArgumentNullException("mapDirectory");
this.reader = reader ?? throw new ArgumentNullException("reader");
this.readDictionary = new GeneralReadOnlyPersistentDictionary<V>(this.mapDirectory, reader);
this.writer = writer ?? throw new ArgumentNullException("writer");
}
public V this[string key] { get { return readDictionary[key]; } set { writer.Write(GetPathForKey(key), value); } }
public ICollection<string> Keys
{
get
{
IEnumerable<string> keys = Directory.EnumerateFileSystemEntries(mapDirectory);
List<string> res = new List<string>();
foreach (string key in keys)
{
if (reader.IsValidValue(GetPathForKey(key))) res.Add(key);
}
return res;
}
}
public ICollection<V> Values
{
get
{
IEnumerable<string> keys = Keys;
List<V> res = new List<V>();
foreach (string key in keys)
{
res.Add(reader.Read(GetPathForKey(key)));
}
return res;
}
}
public int Count {get { return readDictionary.Count; } }
public bool IsReadOnly {get { return false; } }
public string MapDirectory { get { return mapDirectory; } }
public void Add(string key, V value)
{
string path = GetPathForKey(key);
if (key == null) throw new ArgumentNullException("key");
if (File.Exists(path) || Directory.Exists(path))
{
if (reader.IsValidValue(path)) throw new ArgumentException($"Key \"{key}\" already exists.");
throw new ArgumentException($"Corrupt item at \"{path}\" associated with key \"{key}\".");
}
writer.Write(GetPathForKey(key), value);
}
void ICollection<KeyValuePair<string,V>>.Add(KeyValuePair<string, V> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
IEnumerable<string> keys = Keys;
foreach (string key in keys)
{
string path = GetPathForKey(key);
if (File.Exists(path))
{
File.Delete(path);
}
else
{
Directory.Delete(path);
}
}
}
bool ICollection<KeyValuePair<string, V>>.Contains(KeyValuePair<string, V> item)
{
string path = GetPathForKey(item.Key);
return ContainsKey(item.Key) && readDictionary[item.Key].Equals(item.Value);
}
public bool ContainsKey(string key)
{
return readDictionary.ContainsKey(key);
}
public void CopyTo(KeyValuePair<string, V>[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException("array");
if (arrayIndex < 0) throw new ArgumentOutOfRangeException("arrayIndex");
if (array.Length - arrayIndex < Count) throw new ArgumentException($"The number of elements in this dictionary ({Count}) is greater than the space from arrayIndex to the end of the array ({array.Length - arrayIndex}).");
IEnumerator<KeyValuePair<string, V>> pairs = GetEnumerator();
for (int i = arrayIndex; i < array.Length; i++)
{
if (!pairs.MoveNext()) return;
array[i] = pairs.Current;
}
}
public IEnumerator<KeyValuePair<string, V>> GetEnumerator()
{
return ((IEnumerable<KeyValuePair<string, V>>) readDictionary).GetEnumerator();
}
public bool Remove(string key)
{
if (key == null) throw new ArgumentNullException("key");
if (!ContainsKey(key)) return false;
string path = GetPathForKey(key);
if (Directory.Exists(path))
{
Directory.Delete(path);
return true;
}
File.Delete(path);
return true;
}
public bool Remove(KeyValuePair<string, V> item)
{
return Remove(item.Key);
}
public bool TryGetValue(string key, [MaybeNullWhen(false)] out V value)
{
return readDictionary.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) readDictionary).GetEnumerator();
}
public string GetPathForKey(string key)
{
return mapDirectory + Path.DirectorySeparatorChar + key;
}
}
}

View File

@ -1,109 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace GameServiceWarden.Core.Persistence
{
public class GeneralReadOnlyPersistentDictionary<V> : IReadOnlyPersistentDictionary<V>, IReadOnlyCollection<KeyValuePair<string, V>>
{
private readonly string mapDirectory;
public string MapDirectory { get { return mapDirectory; } }
private IPersistentDictionaryReader<V> reader;
public GeneralReadOnlyPersistentDictionary(string mapDirectory, IPersistentDictionaryReader<V> reader)
{
this.mapDirectory = mapDirectory;
this.reader = reader;
}
public V this[string key]
{
get
{
return reader.Read(GetPathForKey(key));
}
}
public IEnumerable<string> Keys
{
get
{
IEnumerable<string> keys = Directory.EnumerateFileSystemEntries(mapDirectory);
foreach (string key in keys)
{
string path = GetPathForKey(key);
if ((File.Exists(path) || Directory.Exists(path)) && reader.IsValidValue(path)) yield return key;
}
}
}
public IEnumerable<V> Values
{
get
{
IEnumerable<string> keys = Directory.EnumerateFileSystemEntries(mapDirectory);
foreach (string key in keys)
{
yield return reader.Read(GetPathForKey(key));
}
}
}
public int Count
{
get
{
int count = 0;
IEnumerable<string> keys = Directory.EnumerateFileSystemEntries(mapDirectory);
foreach (string key in keys)
{
string path = GetPathForKey(key);
if ((File.Exists(path) || Directory.Exists(path)) && reader.IsValidValue(path)) count++;
}
return count;
}
}
public bool ContainsKey(string key)
{
if (key == null) throw new ArgumentNullException(key);
string path = GetPathForKey(key);
return (File.Exists(path) || Directory.Exists(path)) && reader.IsValidValue(path);
}
public IEnumerator<KeyValuePair<string, V>> GetEnumerator()
{
IEnumerable<string> keys = Keys;
List<KeyValuePair<string, V>> pairs = new List<KeyValuePair<string, V>>();
foreach (string key in keys)
{
pairs.Add(new KeyValuePair<string, V>(key, this[key]));
}
return pairs.GetEnumerator();
}
public bool TryGetValue(string key, [MaybeNullWhen(false)] out V value)
{
string path = GetPathForKey(key);
if (!File.Exists(path) || !Directory.Exists(path) || !reader.IsValidValue(path))
{
value = default(V);
return false;
}
value = reader.Read(path);
return true;
}
public string GetPathForKey(string key)
{
return mapDirectory + Path.DirectorySeparatorChar + key;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -4,7 +4,16 @@ namespace GameServiceWarden.Core.Persistence
{
public interface IPersistentDictionary<V> : IDictionary<string, V>
{
/// <summary>
/// The directory for this dictionary to use.
/// </summary>
string MapDirectory { get; }
/// <summary>
/// The path to the data representing a specific key.
/// </summary>
/// <param name="key">The key to get the path for.</param>
/// <returns>A <see cref="string"/> representing the key.</returns>
string GetPathForKey(string key);
}
}

View File

@ -1,8 +0,0 @@
namespace GameServiceWarden.Core.Persistence
{
public interface IPersistentDictionaryReader<V>
{
V Read(string path);
bool IsValidValue(string path);
}
}

View File

@ -1,7 +0,0 @@
namespace GameServiceWarden.Core.Persistence
{
public interface IPersistentDictionaryWriter<V>
{
void Write(string path, V value);
}
}

View File

@ -4,7 +4,16 @@ namespace GameServiceWarden.Core.Persistence
{
public interface IReadOnlyPersistentDictionary<V> : IReadOnlyDictionary<string, V>
{
/// <summary>
/// The directory for this dictionary to use.
/// </summary>
string MapDirectory { get; }
/// <summary>
/// The path to the data representing a specific key.
/// </summary>
/// <param name="key">The key to get the path for.</param>
/// <returns>A <see cref="string"/> representing the key.</returns>
string GetPathForKey(string key);
}
}

View File

@ -1,6 +1,6 @@
<mxfile host="65bd71144e" modified="2020-12-29T18:25:57.297Z" 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="YsJOF2puyTwjZNHuS3ue" version="13.10.0" type="embed" pages="2">
<mxfile host="65bd71144e" pages="2">
<diagram id="LHR7ubqCPd17_LyHkaH9" name="Structure">
<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">
<mxGraphModel dx="1113" dy="480" 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"/>
@ -18,7 +18,7 @@
<mxCell id="dmd0HlDYcxYugIlahWj0-7" 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-5" vertex="1">
<mxGeometry y="166" width="401" height="8" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-8" value="+ Start(): void&#10;+ Stop(): void&#10;+ GetConfigurableOptions(): ISet&lt;string&gt;&#10;+ SetConfigurableValue(configurationName: string, value: string): bool&#10;+ GetConfigurableValue(configurationName: string): string&#10;+ GetServiceState(): ServiceState&#10;+ getModuleName(): string&#10;+ GetassemblyName(): string&#10;+ SetServiceName(name: string): void // Implemented as property&#10;+ GetServiceName(): string // Implemented as property&#10;+ GetServiceConsoleStream(): Stream // Implemented as property&#10;- OnServiceStateChange(curr: ServiceState, prev: ServiceState): void&#10;# Dispose(disposing: bool): void&#10;+ Dispose(): voide" 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">
<mxCell id="dmd0HlDYcxYugIlahWj0-8" value="+ Start(): void&#10;+ Stop(): void&#10;+ GetConfigurableOptions(): ISet&lt;string&gt;&#10;+ SetConfigurableValue(configurationName: string, value: string): bool&#10;+ GetConfigurableValue(configurationName: string): string&#10;+ GetServiceState(): ServiceState&#10;+ getModuleName(): string //Implemented as property&#10;+ GetassemblyName(): string&#10;+ SetServiceName(name: string): void // Implemented as property&#10;+ GetServiceName(): string // Implemented as property&#10;+ GetServiceConsoleStream(): Stream // Implemented as property&#10;- OnServiceStateChange(curr: ServiceState, prev: ServiceState): void&#10;# Dispose(disposing: bool): void&#10;+ Dispose(): voide" 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">
<mxGeometry y="174" width="401" height="212" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-9" value="«interface»&lt;br&gt;&lt;span&gt;IDisposable&lt;/span&gt;" style="html=1;sketch=1;" parent="1" vertex="1">
@ -43,18 +43,18 @@
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<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="697" y="640" width="531" height="300" as="geometry">
<mxGeometry x="669.5" y="640" width="561" height="300" as="geometry">
<mxRectangle x="25" y="490" width="120" height="26" as="alternateBounds"/>
</mxGeometry>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-12" value="- serviceDir: string&#10;- services: IDictionary&lt;string, Service&gt;&#10;- modules: IReadOnlyDictionary&lt;string, Dictionary&lt;string, IGameServiceModule&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="dmd0HlDYcxYugIlahWj0-11" vertex="1">
<mxGeometry y="26" width="531" height="54" as="geometry"/>
<mxCell id="dmd0HlDYcxYugIlahWj0-12" value="- services: IPersistentDictionary&lt;GameServiceInfo&gt;&#10;- modules: IReadOnlyPersistentDictionary&lt;string, IReadOnlyDictionary&lt;string, IGameServiceModule&gt;&gt;&#10;- running: IDictionary&lt;string, GameServiceInfo&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-11" vertex="1">
<mxGeometry y="26" width="561" height="54" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-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="dmd0HlDYcxYugIlahWj0-11" vertex="1">
<mxGeometry y="80" width="531" height="8" as="geometry"/>
<mxGeometry y="80" width="561" height="8" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-14" value="+ CreateService(serviceName: string, assemblyName: string, moduleName: string): void&#10;+ DeleteService(serviceName: string): void&#10;+ GetModuleIDs(): IEnumerable&lt;string&gt;&#10;+ GetServiceNames(): IReadOnlyCollection&lt;string&gt;&#10;+ GetServiceOptions(serviceName: string): IEnumerable&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&#10;- ToModuleID(assemblyName: string, moduleName: string): 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-11" vertex="1">
<mxGeometry y="88" width="531" height="212" as="geometry"/>
<mxCell id="dmd0HlDYcxYugIlahWj0-14" value="+ CreateService(serviceName: string, assemblyName: string, moduleName: string): void&#10;+ DeleteService(serviceName: string): void&#10;+ GetModuleNames(): IEnumerable&lt;string&gt;&#10;+ GetServiceNames(): IReadOnlyCollection&lt;string&gt;&#10;+ GetServiceOptions(serviceName: string): IEnumerable&lt;string&gt;&#10;+ GetServiceOptionValue(serviceName: string, optionName: 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="88" width="561" height="212" 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="138" width="181" height="114" as="geometry"/>
@ -176,7 +176,12 @@
<mxGeometry y="26" width="160" height="8" as="geometry"/>
</mxCell>
<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"/>
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="495" y="660"/>
<mxPoint x="407" y="660"/>
</Array>
</mxGeometry>
</mxCell>
<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"/>
@ -209,348 +214,348 @@
</mxGraphModel>
</diagram>
<diagram id="gj0qHRc3eh050ABAey3g" name="Data-Flow">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGraphModel dx="1216" 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">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<root>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-0"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-1" parent="jVG6p58vlRYGO9X4wXeX-0"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-21" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="10" y="300" width="30" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-12" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-22" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-3" value="Console View" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="80" y="300" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-13" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-4" value="string command (request)" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="260" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-8" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-3" value="Use" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-5" value="MainController" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="420" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry y="840" width="480" height="20" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-7" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;ICommand" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="420" y="372.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-10" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="809.9999999999998" y="512.5000000000002" as="sourcePoint"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxGeometry>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-9" value="ServiceController" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="575" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-4" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<Array as="points">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="960" y="338"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="960" y="580"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</Array>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxGeometry>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<Array as="points">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="960" y="338"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="960" y="83"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</Array>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxGeometry>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-1" value="ServiceManager" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="307.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-9" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-3" style="edgeStyle=orthogonalEdgeStyle;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;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-5" value="ServicePresenter" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="575" y="122.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-8" style="edgeStyle=orthogonalEdgeStyle;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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-5" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-7" target="tM_Gde3HH8YiZ2frBV5J-0" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-7" value="String Output" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="260" y="122.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-1" value="ServiceAction &amp;lt;DS&amp;gt;" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="542.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-2" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;IServiceManipulator" style="whiteSpace=wrap;html=1;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="432.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-10" value="ServicesResult &amp;lt;DS&amp;gt;" style="whiteSpace=wrap;html=1;fillColor=none;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="67.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-11" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;IServicesMonitor" style="whiteSpace=wrap;html=1;fillColor=none;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="167.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="220" y="50" width="10" height="570" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="760" y="50" width="10" height="570" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry y="870" width="480" height="20" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-2" style="edgeStyle=orthogonalEdgeStyle;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;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="tM_Gde3HH8YiZ2frBV5J-0" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-4" value="Use" style="edgeStyle=orthogonalEdgeStyle;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;sketch=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="tM_Gde3HH8YiZ2frBV5J-0" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="420" y="122.5" width="110" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#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;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="420" y="20" width="110" height="50" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</root>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</mxGraphModel>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;&#xa;&#xa;
</diagram>
</mxfile>

View File

@ -7,7 +7,7 @@ using Xunit;
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,
// fakes are generic test objects,
// mocks are the objects being asserted upon,
// stubs are objects used as part of the test.
public class GameServiceInfoTest
@ -63,7 +63,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
//When
serviceInfo.SetConfigurableValue(stubConfigurable.OptionName, "success");
//Then
Assert.Equal<string>("success", serviceInfo.GetConfigurableValue(stubConfigurable.OptionName));
Assert.True("success".Equals(serviceInfo.GetConfigurableValue(stubConfigurable.OptionName)));
}
[Fact]
@ -98,7 +98,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
IGameService stubService = new FakeGameService();
GameServiceInfo serviceInfo = new GameServiceInfo(stubService, MODULE_NAME, "FakeAssembly");
//Then
Assert.Equal(MODULE_NAME, serviceInfo.GetModuleName());
Assert.Equal(MODULE_NAME, serviceInfo.ModuleName);
serviceInfo.Dispose();
}
@ -124,7 +124,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
//When
serviceInfo.ServiceName = SERVICE_NAME;
//Then
Assert.Equal(SERVICE_NAME, serviceInfo.ServiceName);
Assert.True(SERVICE_NAME.Equals(serviceInfo.ServiceName));
serviceInfo.Dispose();
}

View File

@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using GameServiceWarden.Core.Games;
using GameServiceWarden.ModuleAPI;
@ -14,12 +16,14 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
//Then
Assert.Contains<string>(FAKE_SERVICE_NAME, serviceManager.GetServiceNames());
@ -31,12 +35,14 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.DeleteService(FAKE_SERVICE_NAME);
//Then
@ -49,12 +55,14 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_PREFIX = "FakeService_";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
for (int i = 0; i < 100; i++)
{
serviceManager.CreateService(FAKE_SERVICE_PREFIX + i, ASSEMBLY_NAME, stubGameServiceModule.Name);
@ -71,42 +79,45 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule(
new FakeGameConfigurable("A"),
new FakeGameConfigurable("B"),
new FakeGameConfigurable("C")
);
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
//Then
Assert.Contains<string>("A", serviceManager.GetServiceOptions(SERVICE_NAME));
Assert.Contains<string>("B", serviceManager.GetServiceOptions(SERVICE_NAME));
Assert.Contains<string>("C", serviceManager.GetServiceOptions(SERVICE_NAME));
Assert.Contains<string>("A", serviceManager.GetServiceOptions(FAKE_SERVICE_NAME));
Assert.Contains<string>("B", serviceManager.GetServiceOptions(FAKE_SERVICE_NAME));
Assert.Contains<string>("C", serviceManager.GetServiceOptions(FAKE_SERVICE_NAME));
}
[Fact]
public void SetandGetServiceOptionValue_OneOption_OptionChanged()
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule(
new FakeGameConfigurable("A")
);
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.SetServiceOptionValue(SERVICE_NAME, "A", "Test");
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.SetServiceOptionValue(FAKE_SERVICE_NAME, "A", "Test");
//Then
Assert.Equal<string>("Test", serviceManager.GetServiceOptionValue(SERVICE_NAME, "A"));
Assert.True("Test".Equals(serviceManager.GetServiceOptionValue(FAKE_SERVICE_NAME, "A")));
}
[Fact]
@ -114,16 +125,18 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
//Then
Assert.Equal<ServiceState>(ServiceState.Stopped, serviceManager.GetServiceState(SERVICE_NAME));
Assert.Equal<ServiceState>(ServiceState.Stopped, serviceManager.GetServiceState(FAKE_SERVICE_NAME));
}
[Fact]
@ -131,17 +144,19 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.StartService(SERVICE_NAME);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.StartService(FAKE_SERVICE_NAME);
//Then
Assert.Equal<ServiceState>(ServiceState.Running, serviceManager.GetServiceState(SERVICE_NAME));
Assert.Equal<ServiceState>(ServiceState.Running, serviceManager.GetServiceState(FAKE_SERVICE_NAME));
}
[Fact]
@ -149,17 +164,19 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.StartService(SERVICE_NAME);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.StartService(FAKE_SERVICE_NAME);
//Then
Assert.Equal<ServiceState>(ServiceState.Running, serviceManager.GetServiceState(SERVICE_NAME));
Assert.Equal<ServiceState>(ServiceState.Running, serviceManager.GetServiceState(FAKE_SERVICE_NAME));
}
[Fact]
@ -167,18 +184,20 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.StartService(SERVICE_NAME);
serviceManager.StopService(SERVICE_NAME);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.StartService(FAKE_SERVICE_NAME);
serviceManager.StopService(FAKE_SERVICE_NAME);
//Then
Assert.Equal<ServiceState>(ServiceState.Stopped, serviceManager.GetServiceState(SERVICE_NAME));
Assert.Equal<ServiceState>(ServiceState.Stopped, serviceManager.GetServiceState(FAKE_SERVICE_NAME));
}
[Fact]
@ -186,22 +205,24 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.StartService(SERVICE_NAME);
serviceManager.ExecuteCommand(SERVICE_NAME, "Test");
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.StartService(FAKE_SERVICE_NAME);
serviceManager.ExecuteCommand(FAKE_SERVICE_NAME, "Test");
//Then
Stream stream = serviceManager.GetServiceConsoleStream(SERVICE_NAME);
Stream stream = serviceManager.GetServiceConsoleStream(FAKE_SERVICE_NAME);
stream.Position = 0;
using (StreamReader reader = new StreamReader(stream))
{
Assert.Equal<string>("Test", reader.ReadLine());
Assert.True("Test".Equals(reader.ReadLine()));
}
}
@ -210,16 +231,22 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string SERVICE_NAME = "FakeService";
FakePersistentDictionary<IGameServiceModule> stubPersistentModuleDictionary = new FakePersistentDictionary<IGameServiceModule>();
const string FAKE_SERVICE_NAME = "FakeService";
FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>> stubPersistentModuleDictionary = new FakePersistentDictionary<IReadOnlyDictionary<string, IGameServiceModule>>();
FakePersistentDictionary<GameServiceInfo> stubPersistentServiceDictionary = new FakePersistentDictionary<GameServiceInfo>();
GameServiceManager serviceManager = new GameServiceManager(stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IGameServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IGameServiceModule>();
IGameServiceModule stubGameServiceModule = new FakeGameServiceModule();
stubAssemblyModulesDictionary.Add(stubGameServiceModule.Name, stubGameServiceModule);
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
stubPersistentModuleDictionary.Add(ASSEMBLY_NAME + Path.DirectorySeparatorChar + stubGameServiceModule.Name, stubGameServiceModule);
serviceManager.CreateService(SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubGameServiceModule.Name);
//Then
Assert.Null(serviceManager.GetServiceConsoleStream(SERVICE_NAME));
Action action = delegate()
{
serviceManager.GetServiceConsoleStream(FAKE_SERVICE_NAME);
};
Assert.Throws<InvalidOperationException>(action);
}
}
}