From f093520ce8573ade98d568e39aa295c48130ffa7 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 6 Jan 2018 21:04:29 +0100 Subject: [PATCH] Added some module architecture --- src/Artemis.Core/Artemis.Core.csproj | 149 ++++++-- src/Artemis.Core/Constants.cs | 10 + src/Artemis.Core/Events/ModuleEventArgs.cs | 9 + .../Exceptions/ArtemisCoreException.cs | 19 + .../Exceptions/ArtemisModuleException.cs | 25 ++ src/Artemis.Core/Models/ModuleInfo.cs | 17 + .../Modules/Interfaces/IModule.cs | 6 + src/Artemis.Core/Scripting.Extensions.cs | 295 +++++++++++++++ src/Artemis.Core/Scripting.evaluator.cs | 345 +++++++++++++++++ src/Artemis.Core/Scripting.native.cs | 354 ++++++++++++++++++ src/Artemis.Core/Services/CoreService.cs | 24 +- .../Services/Interfaces/ICoreService.cs | 7 +- .../Services/Interfaces/IModuleService.cs | 14 +- src/Artemis.Core/Services/ModuleService.cs | 114 +++++- src/Artemis.Core/app.config | 52 +++ src/Artemis.Core/packages.config | 82 +++- src/Artemis.Modules/Artemis.Modules.csproj | 56 +++ .../Properties/AssemblyInfo.cs | 36 ++ src/Artemis.Modules/TestModule.cs | 8 + src/Artemis.Modules/app.config | 63 ++++ src/Artemis.UI/App.config | 56 +++ src/Artemis.UI/Artemis.UI.csproj | 4 + src/Artemis.UI/Bootstrapper.cs | 22 +- src/Artemis.UI/Stylet/NinjectBootstrapper.cs | 18 +- src/Artemis.sln | 6 + 25 files changed, 1732 insertions(+), 59 deletions(-) create mode 100644 src/Artemis.Core/Constants.cs create mode 100644 src/Artemis.Core/Events/ModuleEventArgs.cs create mode 100644 src/Artemis.Core/Exceptions/ArtemisCoreException.cs create mode 100644 src/Artemis.Core/Exceptions/ArtemisModuleException.cs create mode 100644 src/Artemis.Core/Models/ModuleInfo.cs create mode 100644 src/Artemis.Core/Modules/Interfaces/IModule.cs create mode 100644 src/Artemis.Core/Scripting.Extensions.cs create mode 100644 src/Artemis.Core/Scripting.evaluator.cs create mode 100644 src/Artemis.Core/Scripting.native.cs create mode 100644 src/Artemis.Modules/Artemis.Modules.csproj create mode 100644 src/Artemis.Modules/Properties/AssemblyInfo.cs create mode 100644 src/Artemis.Modules/TestModule.cs create mode 100644 src/Artemis.Modules/app.config diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 957a4b1ef..8760b66b8 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -33,11 +33,32 @@ - ..\packages\Castle.Core.4.2.0\lib\net45\Castle.Core.dll + ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll + + + ..\packages\CS-Script.bin.3.27.2.0\lib\net46\CSScriptLibrary.dll ..\packages\HidSharp.1.5\lib\net35\HidSharp.dll + + ..\packages\Microsoft.CodeAnalysis.Common.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.Scripting.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Scripting.dll + + + ..\packages\Microsoft.CodeAnalysis.Scripting.Common.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.Scripting.dll + + + ..\packages\CS-Script.bin.3.27.2.0\lib\net46\Mono.CSharp.dll + + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\Ninject.3.3.4\lib\net45\Ninject.dll @@ -48,44 +69,95 @@ ..\packages\Ninject.Extensions.Factory.3.3.2\lib\net45\Ninject.Extensions.Factory.dll - ..\packages\RGB.NET.Brushes.0.0.1.18\lib\net45\RGB.NET.Brushes.dll + ..\packages\RGB.NET.Brushes.0.0.1.20\lib\net45\RGB.NET.Brushes.dll - ..\packages\RGB.NET.Core.0.0.1.18\lib\net45\RGB.NET.Core.dll + ..\packages\RGB.NET.Core.0.0.1.20\lib\net45\RGB.NET.Core.dll - ..\packages\RGB.NET.Decorators.0.0.1.18\lib\net45\RGB.NET.Decorators.dll + ..\packages\RGB.NET.Decorators.0.0.1.20\lib\net45\RGB.NET.Decorators.dll - ..\packages\RGB.NET.Devices.Asus.0.0.1.18\lib\net45\RGB.NET.Devices.Asus.dll + ..\packages\RGB.NET.Devices.Asus.0.0.1.20\lib\net45\RGB.NET.Devices.Asus.dll - ..\packages\RGB.NET.Devices.CoolerMaster.0.0.1.18\lib\net45\RGB.NET.Devices.CoolerMaster.dll + ..\packages\RGB.NET.Devices.CoolerMaster.0.0.1.20\lib\net45\RGB.NET.Devices.CoolerMaster.dll - ..\packages\RGB.NET.Devices.Corsair.0.0.1.18\lib\net45\RGB.NET.Devices.Corsair.dll + ..\packages\RGB.NET.Devices.Corsair.0.0.1.20\lib\net45\RGB.NET.Devices.Corsair.dll - ..\packages\RGB.NET.Devices.Logitech.0.0.1.18\lib\net45\RGB.NET.Devices.Logitech.dll + ..\packages\RGB.NET.Devices.Logitech.0.0.1.20\lib\net45\RGB.NET.Devices.Logitech.dll - ..\packages\RGB.NET.Devices.Msi.0.0.1.18\lib\net45\RGB.NET.Devices.Msi.dll + ..\packages\RGB.NET.Devices.Msi.0.0.1.20\lib\net45\RGB.NET.Devices.Msi.dll - ..\packages\RGB.NET.Devices.Novation.0.0.1.18\lib\net45\RGB.NET.Devices.Novation.dll + ..\packages\RGB.NET.Devices.Novation.0.0.1.20\lib\net45\RGB.NET.Devices.Novation.dll - ..\packages\RGB.NET.Devices.Razer.0.0.1.18\lib\net45\RGB.NET.Devices.Razer.dll + ..\packages\RGB.NET.Devices.Razer.0.0.1.20\lib\net45\RGB.NET.Devices.Razer.dll - ..\packages\RGB.NET.Groups.0.0.1.18\lib\net45\RGB.NET.Groups.dll + ..\packages\RGB.NET.Groups.0.0.1.20\lib\net45\RGB.NET.Groups.dll - - ..\packages\Sanford.Multimedia.Midi.6.4.1\lib\net20\Sanford.Multimedia.Midi.dll + + ..\packages\Sanford.Multimedia.Midi.6.4.2\lib\net20\Sanford.Multimedia.Midi.dll + + ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + True + + + ..\packages\System.Collections.Immutable.1.4.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + + ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + + ..\packages\System.Reflection.Metadata.1.5.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net46\System.Security.Cryptography.Algorithms.dll + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net46\System.Security.Cryptography.X509Certificates.dll + True + + + ..\packages\System.Text.Encoding.CodePages.4.4.0\lib\net46\System.Text.Encoding.CodePages.dll + + + ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll @@ -95,36 +167,61 @@ + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + + + ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + + + ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + + + + + + + + + + + + + + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + + - - - - - + + + + + \ No newline at end of file diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs new file mode 100644 index 000000000..5c3942950 --- /dev/null +++ b/src/Artemis.Core/Constants.cs @@ -0,0 +1,10 @@ +using System; + +namespace Artemis.Core +{ + public static class Constants + { + public static readonly string DataFolder = + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\Artemis\\"; + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Events/ModuleEventArgs.cs b/src/Artemis.Core/Events/ModuleEventArgs.cs new file mode 100644 index 000000000..8c8c3459a --- /dev/null +++ b/src/Artemis.Core/Events/ModuleEventArgs.cs @@ -0,0 +1,9 @@ +using Artemis.Core.Modules.Interfaces; + +namespace Artemis.Core.Events +{ + public class ModuleEventArgs : System.EventArgs + { + public IModule Module { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Exceptions/ArtemisCoreException.cs b/src/Artemis.Core/Exceptions/ArtemisCoreException.cs new file mode 100644 index 000000000..163368e3d --- /dev/null +++ b/src/Artemis.Core/Exceptions/ArtemisCoreException.cs @@ -0,0 +1,19 @@ +using System; + +namespace Artemis.Core.Exceptions +{ + public class ArtemisCoreException : Exception + { + public ArtemisCoreException() + { + } + + public ArtemisCoreException(string message) : base(message) + { + } + + public ArtemisCoreException(string message, Exception inner) : base(message, inner) + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Exceptions/ArtemisModuleException.cs b/src/Artemis.Core/Exceptions/ArtemisModuleException.cs new file mode 100644 index 000000000..a629d7126 --- /dev/null +++ b/src/Artemis.Core/Exceptions/ArtemisModuleException.cs @@ -0,0 +1,25 @@ +using System; +using Artemis.Core.Models; + +namespace Artemis.Core.Exceptions +{ + public class ArtemisModuleException : Exception + { + public ArtemisModuleException(ModuleInfo moduleInfo) + { + ModuleInfo = moduleInfo; + } + + public ArtemisModuleException(ModuleInfo moduleInfo, string message) : base(message) + { + ModuleInfo = moduleInfo; + } + + public ArtemisModuleException(ModuleInfo moduleInfo, string message, Exception inner) : base(message, inner) + { + ModuleInfo = moduleInfo; + } + + public ModuleInfo ModuleInfo { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/ModuleInfo.cs b/src/Artemis.Core/Models/ModuleInfo.cs new file mode 100644 index 000000000..e531ef967 --- /dev/null +++ b/src/Artemis.Core/Models/ModuleInfo.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Artemis.Core.Modules.Interfaces; +using Newtonsoft.Json; + +namespace Artemis.Core.Models +{ + public class ModuleInfo + { + public string Name { get; set; } + public string Version { get; set; } + public string MainFile { get; set; } + public IReadOnlyList SubFiles { get; set; } + + [JsonIgnore] + public IModule Module { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Modules/Interfaces/IModule.cs b/src/Artemis.Core/Modules/Interfaces/IModule.cs new file mode 100644 index 000000000..d74e24855 --- /dev/null +++ b/src/Artemis.Core/Modules/Interfaces/IModule.cs @@ -0,0 +1,6 @@ +namespace Artemis.Core.Modules.Interfaces +{ + public interface IModule + { + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Scripting.Extensions.cs b/src/Artemis.Core/Scripting.Extensions.cs new file mode 100644 index 000000000..070536442 --- /dev/null +++ b/src/Artemis.Core/Scripting.Extensions.cs @@ -0,0 +1,295 @@ +using CSScriptLibrary; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.Remoting.Lifetime; +using System.Threading; +using System.Threading.Tasks; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios requiring asynchronous script execution as well as unloading the +// scripts being executed. +// AsyncSamples +// Samples demonstrate the use of Async and Await mechanism available in C# 5 and higher. Note that the async method extensions +// cover the complete set of CSScript.Evaluator methods. +// +// UnloadingSamples +// Samples demonstrate the use of temporary AppDoamain for loading and executing dynamic C# code (script). It is the +// only mechanism available for unloading dynamically loaded assemblies. This is a well known CLR design limitation that leads to +// memory leaks if the assembly/script loaded in the caller AppDomain. The problem affects all C# script engines (e.g. Roslyn, CodeDom) +// and it cannot be solved by the engine itself thus CS-Script provides a work around in form of the MethodExtensions for the +// CSScript.Evaluator methods that are compatible with the unloading mechanism. +// +// Nevertheless you should try to avoid using remote AppDoamain unless you have to. It is very heavy and also imposes the serialization +// constrains. +// +// All samples rely on the compiler agnostic CSScript.Evaluator API. +namespace CSScriptEvaluatorExtensions +{ + public class HostApp + { + public static void Test() + { + Console.WriteLine("---------------------------------------------"); + Console.WriteLine("Testing asynchronous API"); + Console.WriteLine("---------------------------------------------"); + new AsyncSamples().RunAll(); + Thread.Sleep(2000); + Console.WriteLine("\nPress 'Enter' to run uloading samples..."); + Console.ReadLine(); + Console.WriteLine("---------------------------------------------"); + Console.WriteLine("Testing unloading API"); + Console.WriteLine("---------------------------------------------"); + new UnloadingSamples().RunAll(); + } + + class AsyncSamples + { + public void RunAll() + { + Action run = (action, name) => { action(); Console.WriteLine(name); }; + + run(LoadDelegateAsync, "Start of " + nameof(LoadDelegateAsync)); + run(LoadMethodAsync, "Start of " + nameof(LoadMethodAsync)); + run(LoadCodeAsync, "Start of " + nameof(LoadCodeAsync)); + run(CreateDelegateAsync, "Start of " + nameof(CreateDelegateAsync)); + run(CompileCodeAsync, "Start of " + nameof(CompileCodeAsync)); + run(RemoteAsynch, "Start of " + nameof(RemoteAsynch)); + } + + async void LoadDelegateAsync() + { + var product = await CSScript.Evaluator + .LoadDelegateAsync>( + @"int Product(int a, int b) + { + return a * b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadDelegateAsync), product(4, 2)); + } + + public async void LoadMethodAsync() + { + dynamic script = await CSScript.Evaluator + .LoadMethodAsync(@"public int Sum(int a, int b) + { + return a + b; + } + public int Div(int a, int b) + { + return a/b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadMethodAsync), script.Div(15, 3)); + } + + public async void LoadCodeAsync() + { + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc calc = await CSScript.Evaluator + .LoadCodeAsync( + @"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadCodeAsync), calc.Sum(1, 2)); + } + + public async void CreateDelegateAsync() + { + var product = await CSScript.Evaluator + .CreateDelegateAsync( + @"int Product(int a, int b) + { + return a * b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(CreateDelegateAsync), product(15, 3)); + } + + public async void CompileCodeAsync() + { + Assembly script = await CSScript.Evaluator + .CompileCodeAsync(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + dynamic calc = script.CreateObject("*"); + + Console.WriteLine(" End of {0}: {1}", nameof(CompileCodeAsync), calc.Sum(15, 3)); + } + + public async void RemoteAsynch() + { + var sum = await Task.Run(() => + CSScript.Evaluator + .CreateDelegateRemotely( + @"int Sum(int a, int b) + { + return a+b; + }") + ); + Console.WriteLine(" End of {0}: {1}", nameof(RemoteAsynch), sum(1, 2)); + + sum.UnloadOwnerDomain(); + } + } + + class UnloadingSamples + { + public void RunAll() + { + CreateDelegateRemotely(); + LoadMethodRemotely(); + LoadCodeRemotely(); + LoadCodeRemotelyWithInterface(); + } + + public void CreateDelegateRemotely() + { + var sum = CSScript.Evaluator + .CreateDelegateRemotely(@"int Sum(int a, int b) + { + return a+b; + }"); + + Console.WriteLine("{0}: {1}", nameof(CreateDelegateRemotely), sum(15, 3)); + + sum.UnloadOwnerDomain(); + } + + public void LoadCodeRemotely() + { + // Class Calc doesn't implement ICals interface. Thus the compiled object cannot be typecasted into + // the interface and Evaluator will emit duck-typed assembly instead. + // But Mono and Roslyn build file-less assemblies, meaning that they cannot be used to build + // duck-typed proxies and CodeDomEvaluator needs to be used explicitly. + // Note class Calc also inherits from MarshalByRefObject. This is required for all object that + // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var script = CSScript.CodeDomEvaluator + .LoadCodeRemotely( + @"using System; + public class Calc : MarshalByRefObject + { + object t; + public int Sum(int a, int b) + { + t = new Test(); + return a+b; + } + } + + class Test + { + ~Test() + { + Console.WriteLine(""Domain is unloaded: ~Test()""); + } + } + "); + + Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotely), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + public void LoadCodeRemotelyWithInterface() + { + // Note class Calc also inherits from MarshalByRefObject. This is required for all object that + // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. + var script = CSScript.Evaluator + .LoadCodeRemotely( + @"using System; + public class Calc : MarshalByRefObject, CSScriptEvaluatorExtensions.ICalc + { + public int Sum(int a, int b) + { + return a+b; + } + } + "); + + Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotelyWithInterface), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + public void LoadMethodRemotely() + { + // LoadMethodRemotely is essentially the same as LoadCodeRemotely. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. The auto-generated class declaration also indicates + // that the class implements ICalc interface. Meaning that it will trigger compile error + // if the set of methods in the script code doesn't implement all interface members. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var script = CSScript.Evaluator + .LoadMethodRemotely( + @"public int Sum(int a, int b) + { + return a+b; + } + public int Sub(int a, int b) + { + return a-b; + }"); + + Console.WriteLine("{0}: {1}", nameof(LoadMethodRemotely), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + MethodDelegate sum; + ClientSponsor sumSponsor; + + public void KeepRemoteObjectAlive() + { + sum = CSScript.Evaluator + .CreateDelegateRemotely(@"int Sum(int a, int b) + { + return a+b; + }"); + + //Normally remote objects are disposed if they are not accessed withing a default timeout period. + //It is not even enough to keep transparent proxies or their wrappers (e.g. 'sum') referenced. + //To prevent GC collection in the remote domain use .NET ClientSponsor mechanism as below. + sumSponsor = sum.ExtendLifeFromMinutes(30); + } + } + } + + public interface ICalc + { + int Sum(int a, int b); + } + + public interface IFullCalc + { + int Sum(int a, int b); + + int Sub(int a, int b); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Scripting.evaluator.cs b/src/Artemis.Core/Scripting.evaluator.cs new file mode 100644 index 000000000..0e195776f --- /dev/null +++ b/src/Artemis.Core/Scripting.evaluator.cs @@ -0,0 +1,345 @@ +using CSScriptLibrary; +using System; +using System.Diagnostics; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios relying on CS-Script Evaluator interface (API). +// This API is a unified generic interface allowing dynamic switch of the underlying compiling services (Mono, Roslyn, CodeDom) +// without the need for changing the hosting code. +// +// Apart from Evaluator (compiler agnostic) API CS-Script offers alternative hosting model: CS-Script Native, +// which relies solely on CodeDom compiler. CS-Script Native offers some features that are not available with CS-Script Evaluator +// (e.g. script unloading). +// +// The choice of the underlying compiling engine (e.g. Mono vs CodeDom) when using CS-Script Evaluator is always dictated by the +// specifics of the hosting scenario. Thanks to in-process compiler hosting, Mono and Roslyn demonstrate much better compiling +// performance comparing to CodeDom engine. However they don't allow script debugging and caching easily supported with CodeDom. +// Mono and particularly Roslyn also leas create more memory pressure due to the higher volume of the temp assemblies loaded into +// the hosting AppDomain. Roslyn (at least CSharp.Scripting-v1.2.0.0) also has very high initial loading overhead up to 4 seconds. +// +// One of the possible approaches would be to use EvaluatorEngine.CodeDom during the active development and later on switch to Mono/Roslyn. + +namespace CSScriptEvaluatorApi +{ + public class HostApp + { + public static void Test() + { + // Just in case clear AlternativeCompiler so it is not set to Roslyn or anything else by + // the CS-Script installed (if any) on the host OS + CSScript.GlobalSettings.UseAlternativeCompiler = null; + + var samples = new EvaluatorSamples(); + + Console.WriteLine("Testing compiling services"); + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + samples.RunAll(); + + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + samples.RunAll(); + + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + + samples.RunAll(); + + //samples.DebugTest(); //uncomment if want to fire an assertion during the script execution + + //Profile(); //uncomment if want to test performance of the engines + } + + class EvaluatorSamples + { + public void RunAll() + { + Action run = (action, name) => { action(); Console.WriteLine(name + " - OK"); }; + + run(CompileMethod_Instance, nameof(CompileMethod_Instance)); + run(CompileMethod_Static, nameof(CompileMethod_Static)); + run(CreateDelegate, nameof(CreateDelegate)); + run(LoadDelegate, nameof(LoadDelegate)); + run(LoadCode, nameof(LoadCode)); + run(LoadMethod, nameof(LoadMethod)); + run(LoadMethodWithInterface, nameof(LoadMethodWithInterface)); + run(LoadCode_WithInterface, nameof(LoadCode_WithInterface)); + run(LoadCode_WithDuckTypedInterface, nameof(LoadCode_WithDuckTypedInterface)); + } + + public void CompileMethod_Instance() + { + // 1- CompileMethod wraps method into a class definition and returns compiled assembly + // 2 - CreateObject creates instance of a first class in the assembly + + dynamic script = CSScript.Evaluator + .CompileMethod(@"int Sqr(int data) + { + return data * data; + }") + .CreateObject("*"); + + var result = script.Sqr(7); + } + + public void CompileMethod_Static() + { + // 1 - CompileMethod wraps method into a class definition and returns compiled assembly + // 2 - GetStaticMethod returns duck-typed delegate that accepts 'params object[]' arguments + // Note: GetStaticMethodWithArgs can be replaced with a more convenient/shorter version + // that takes the object instead of the Type and then queries objects type internally: + // "GetStaticMethod("*.Test", data)" + + var test = CSScript.Evaluator + .CompileMethod(@"using CSScriptEvaluatorApi; + static void Test(InputData data) + { + data.Index = GetIndex(); + } + static int GetIndex() + { + return Environment.TickCount; + }") + .GetStaticMethodWithArgs("*.Test", typeof(InputData)); + + var data = new InputData(); + test(data); + } + + public void CreateDelegate() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // without any specific return type. + + var sqr = CSScript.Evaluator + .CreateDelegate(@"int Sqr(int a) + { + return a * a; + }"); + + var r = sqr(3); + } + + public void LoadDelegate() + { + // Wraps method into a class definition, loads the compiled assembly + // and returns the method delegate for the method, which matches the delegate specified + // as the type parameter of LoadDelegate + + var product = CSScript.Evaluator + .LoadDelegate>( + @"int Product(int a, int b) + { + return a * b; + }"); + + int result = product(3, 2); + } + + public void LoadCode() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + dynamic script = CSScript.Evaluator + .LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void LoadMethod() + { + // LoadMethod compiles code and returns instance of a first class + // in the compiled assembly. + // LoadMethod is essentially the same as LoadCode. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. + // 'public' is optional as it will be injected if the code doesn't start with it. + dynamic script = CSScript.Evaluator + .LoadMethod(@"using System; + public int Sum(int a, int b) + { + return a+b; + }"); + + int result = script.Sum(1, 2); + } + + public void LoadMethodWithInterface() + { + // LoadMethod compiles code and returns instance of a first class + // in the compiled assembly. + // LoadMethod is essentially the same as LoadCode. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. The auto-generated class declaration also indicates + // that the class implements ICalc interface. Meaning that it will trigger compile error + // if the set of methods in the script code doesn't implement all interface members. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc script = CSScript.Evaluator + .LoadMethod( + @"int Sum(int a, int b) + { + return a+b; + }"); + + int result = script.Sum(1, 2); + } + + public void LoadCode_WithInterface() + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2 - The script class implements host app interface so the returned object can be type casted into it + + var script = (ICalc)CSScript.Evaluator + .LoadCode(@"using System; + public class Script : CSScriptEvaluatorApi.ICalc + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void LoadCode_WithDuckTypedInterface() + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2- The script class doesn't implement host app interface but it can still be aligned to + // one as long at it implements the interface members + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc script = CSScript.MonoEvaluator + .LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void PerformanceTest(int count = -1) + { + var code = @"int Sqr(int a) + { + return a * a; + }"; + + if (count != -1) + code += "//" + count; //this unique extra code comment ensures the code to be compiled cannot be cached + + dynamic script = CSScript.Evaluator + .CompileMethod(code) + .CreateObject("*"); + + var r = script.Sqr(3); + } + + public void DebugTest() + { + //pops up an assertion dialog + + CSScript.EvaluatorConfig.DebugBuild = true; + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + + dynamic script = CSScript.Evaluator + .LoadCode(@"using System; + using System.Diagnostics; + public class Script + { + public int Sum(int a, int b) + { + Debug.Assert(false,""Testing CS-Script debugging...""); + return a+b; + } + }"); + + var r = script.Sum(3, 4); + } + } + + public static void Profile() + { + var sw = new Stopwatch(); + var samples = new EvaluatorSamples(); + var count = 20; + var inxed = 0; + bool preventCaching = false; + + Action run = () => + { + sw.Restart(); + for (int i = 0; i < count; i++) + if (preventCaching) + samples.PerformanceTest(inxed++); + else + samples.PerformanceTest(); + + Console.WriteLine(CSScript.Evaluator.GetType().Name + ": " + sw.ElapsedMilliseconds); + }; + + Action runAll = () => + { + Console.WriteLine("\n---------------------------------------------"); + Console.WriteLine($"Caching enabled: {!preventCaching}\n"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; + run(); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + run(); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; + run(); + }; + + RoslynEvaluator.LoadCompilers(); //Roslyn is extremely heavy so exclude startup time from profiling + + Console.WriteLine("Testing performance"); + + preventCaching = true; + runAll(); + + preventCaching = false; + runAll(); + } + } + + public interface ICalc + { + int Sum(int a, int b); + } + + public class InputData + { + public int Index = 0; + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Scripting.native.cs b/src/Artemis.Core/Scripting.native.cs new file mode 100644 index 000000000..37c7ef2f7 --- /dev/null +++ b/src/Artemis.Core/Scripting.native.cs @@ -0,0 +1,354 @@ +using CSScriptLibrary; +using System; +using System.Linq; +using System.IO; +using csscript; +using System.CodeDom.Compiler; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios relying on CS-Script Native interface (API). +// This API is a compiler specific interface, which relies solely on CodeDom compiler. In most of the cases +// CS-Script Native model is the most flexible and natural choice +// +// Apart from Native API CS-Script offers alternative hosting model: CS-Script Evaluator, which provides +// a unified generic interface allowing dynamic switch the underlying compiling services (Mono, Roslyn, CodeDom) +// without the need for changing the hosting code. +// +// The Native interface is the original API that was designed to take maximum advantage of the dynamic C# code +// execution with CodeDom. The original implementation of this API was developed even before any compiler-as-service +// solution became available. Being based solely on CodeDOM the API doesn't utilize neither Mono nor Roslyn +// scripting solutions. Despite that CS-Script Native is the most mature, powerful and flexible API available with CS-Script. +// +// Native interface allows some unique features that are not available with CS-Script Evaluator: +// - Debugging scripts +// - Script caching +// - Script unloading + +namespace CSScriptNativeApi +{ + public class HostApp + { + public static void Test() + { + var host = new HostApp(); + host.Log("Testing compiling services CS-Script Native API"); + Console.WriteLine("---------------------------------------------"); + + CodeDomSamples.LoadMethod_Instance(); + CodeDomSamples.LoadMethod_Static(); + CodeDomSamples.LoadDelegate(); + CodeDomSamples.CreateAction(); + CodeDomSamples.CreateFunc(); + CodeDomSamples.LoadCode(); + CodeDomSamples.LoadCode_WithInterface(host); + CodeDomSamples.LoadCode_WithDuckTypedInterface(host); + CodeDomSamples.ExecuteAndUnload(); + //CodeDomSamples.DebugTest(); //uncomment if want to fire an assertion during the script execution + } + + public class CodeDomSamples + { + public static void LoadMethod_Instance() + { + // 1- LoadMethod wraps method into a class definition, compiles it and returns loaded assembly + // 2 - CreateObject creates instance of a first class in the assembly + + dynamic script = CSScript.LoadMethod(@"int Sqr(int data) + { + return data * data; + }") + .CreateObject("*"); + + var result = script.Sqr(7); + } + + public static void LoadMethod_Static() + { + // 1 - LoadMethod wraps method into a class definition, compiles it and returns loaded assembly + // 2 - GetStaticMethod returns first found static method as a duck-typed delegate that + // accepts 'params object[]' arguments + // + // Note: you can use GetStaticMethodWithArgs for higher precision method search: GetStaticMethodWithArgs("*.SayHello", typeof(string)); + var sayHello = CSScript.LoadMethod(@"static void SayHello(string greeting) + { + Console.WriteLine(greeting); + }") + .GetStaticMethod(); + + sayHello("Hello World!"); + } + + public static void LoadDelegate() + { + // LoadDelegate wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns the method delegate for the method, which matches the delegate specified + // as the type parameter of LoadDelegate + + // The 'using System;' is optional; it demonstrates how to specify 'using' in the method-only syntax + + var sayHello = CSScript.LoadDelegate>( + @"void SayHello(string greeting) + { + Console.WriteLine(greeting); + }"); + + sayHello("Hello World!"); + } + + public static void CreateAction() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // without any specific return type. + + var sayHello = CSScript.CreateAction(@"void SayHello(string greeting) + { + Console.WriteLine(greeting); + }"); + + sayHello("Hello World!"); + } + + public static void CreateFunc() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // int as a return type. + + var Sqr = CSScript.CreateFunc(@"int Sqr(int a) + { + return a * a; + }"); + int r = Sqr(3); + } + + public static void LoadCode() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + dynamic script = CSScript.LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }") + .CreateObject("*"); + + int result = script.Sum(1, 2); + } + + public static void LoadCodeWithConfig() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + string file = Path.GetTempFileName(); + try + { + File.WriteAllText(file, @"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + var settings = new Settings(); + //settings = null; // set to null to foll back to defaults + + dynamic script = CSScript.LoadWithConfig(file, null, false, settings, "/define:TEST") + .CreateObject("*"); + + int result = script.Sum(1, 2); + } + finally + { + if (File.Exists(file)) + File.Delete(file); + } + } + + public static void LoadCode_WithInterface(HostApp host) + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly. + // 2 - The script class implements host app interface so the returned object can be type casted into it. + // 3 - In this sample host object is passed into script routine. + + var calc = (ICalc) CSScript.LoadCode(@"using CSScriptNativeApi; + public class Script : ICalc + { + public int Sum(int a, int b) + { + if(Host != null) + Host.Log(""Sum is invoked""); + return a + b; + } + + public HostApp Host { get; set; } + }") + .CreateObject("*"); + calc.Host = host; + int result = calc.Sum(1, 2); + } + + public static void LoadCode_WithDuckTypedInterface(HostApp host) + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2- The script class doesn't implement host app interface but it can still be aligned to + // one as long at it implements the interface members + // 3 - In this sample host object is passed into script routine. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc calc = CSScript.LoadCode(@"using CSScriptNativeApi; + public class Script + { + public int Sum(int a, int b) + { + if(Host != null) + Host.Log(""Sum is invoked""); + return a + b; + } + + public HostApp Host { get; set; } + }") + .CreateObject("*") + .AlignToInterface(); + calc.Host = host; + int result = calc.Sum(1, 2); + } + + public static void ExecuteAndUnload() + { + // The script will be loaded into a temporary AppDomain and unloaded after the execution. + + // Note: remote execution is a subject of some restrictions associated with the nature of the + // CLR cross-AppDomain interaction model: + // * the script class must be serializable or derived from MarshalByRefObject. + // + // * any object (call arguments, return objects) that crosses ApPDomain boundaries + // must be serializable or derived from MarshalByRefObject. + // + // * long living script class instances may get disposed in remote domain even if they are + // being referenced in the current AppDomain. You need to use the usual .NET techniques + // to prevent that. See LifetimeManagement.cs sample for details. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var code = @"using System; + public class Script : MarshalByRefObject + { + public void Hello(string greeting) + { + Console.WriteLine(greeting); + } + }"; + + //Note: usage of helper.CreateAndAlignToInterface("Script") is also acceptable + using (var helper = new AsmHelper(CSScript.CompileCode(code), null, deleteOnExit: true)) + { + IScript script = helper.CreateAndAlignToInterface("*"); + script.Hello("Hi there..."); + } + //from this point AsmHelper is disposed and the temp AppDomain is unloaded + } + + public static void DebugTest() + { + //pops up an assertion dialog + dynamic script = CSScript.LoadCode(@"using System; + using System.Diagnostics; + public class Script + { + public int Sum(int a, int b) + { + Debug.Assert(false,""Testing CS-Script debugging...""); + return a+b; + } + }", null, debugBuild: true).CreateObject("*"); + + int result = script.Sum(1, 2); + } + } + + public void Log(string message) + { + Console.WriteLine(message); + } + } + + public interface IScript + { + void Hello(string greeting); + } + + public interface ICalc + { + HostApp Host { get; set; } + + int Sum(int a, int b); + } + + public class Samples + { + static public void CompilingHistory() + { + string script = Path.GetTempFileName(); + string scriptAsm = script + ".dll"; + CSScript.KeepCompilingHistory = true; + + try + { + File.WriteAllText(script, @"using System; + using System.Windows.Forms; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + + + CSScript.CompileFile(script, scriptAsm, false, null); + + CompilingInfo info = CSScript.CompilingHistory + .Values + .FirstOrDefault(item => item.ScriptFile == script); + if (info != null) + { + Console.WriteLine("Script: " + info.ScriptFile); + + Console.WriteLine("Referenced assemblies:"); + foreach (string asm in info.Input.ReferencedAssemblies) + Console.WriteLine(asm); + + if (info.Result.Errors.HasErrors) + { + foreach (CompilerError err in info.Result.Errors) + if (!err.IsWarning) + Console.WriteLine("Error: " + err.ErrorText); + } + } + + CSScript.CompilingHistory.Clear(); + + } + finally + { + CSScript.KeepCompilingHistory = false; + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index eb57be6da..d5cb56a48 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -1,8 +1,30 @@ -using Artemis.Core.Services.Interfaces; +using System.Threading.Tasks; +using Artemis.Core.Services.Interfaces; namespace Artemis.Core.Services { public class CoreService : ICoreService { + private readonly IModuleService _moduleService; + + public CoreService(IModuleService moduleService) + { + _moduleService = moduleService; + Task.Run(Initialize); + } + + public void Dispose() + { + _moduleService.Dispose(); + } + + public bool IsInitialized { get; set; } + + private async Task Initialize() + { + await _moduleService.LoadModules(); + + IsInitialized = true; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index ed05a41fe..79ecdb825 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -1,6 +1,9 @@ -namespace Artemis.Core.Services.Interfaces +using System; + +namespace Artemis.Core.Services.Interfaces { - public interface ICoreService + public interface ICoreService: IArtemisService, IDisposable { + bool IsInitialized { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IModuleService.cs b/src/Artemis.Core/Services/Interfaces/IModuleService.cs index 279f1950e..47dd57750 100644 --- a/src/Artemis.Core/Services/Interfaces/IModuleService.cs +++ b/src/Artemis.Core/Services/Interfaces/IModuleService.cs @@ -1,6 +1,16 @@ -namespace Artemis.Core.Services.Interfaces +using System; +using System.Threading.Tasks; +using Artemis.Core.Events; +using Artemis.Core.Modules.Interfaces; + +namespace Artemis.Core.Services.Interfaces { - public interface IModuleService + public interface IModuleService : IArtemisService, IDisposable { + Task LoadModules(); + Task ReloadModule(IModule module); + + event EventHandler ModuleLoaded; + event EventHandler ModuleReloaded; } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/ModuleService.cs b/src/Artemis.Core/Services/ModuleService.cs index 5933c24cd..b2f48e14c 100644 --- a/src/Artemis.Core/Services/ModuleService.cs +++ b/src/Artemis.Core/Services/ModuleService.cs @@ -1,8 +1,120 @@ -using Artemis.Core.Services.Interfaces; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using Artemis.Core.Events; +using Artemis.Core.Exceptions; +using Artemis.Core.Models; +using Artemis.Core.Modules.Interfaces; +using Artemis.Core.Services.Interfaces; +using CSScriptLibrary; +using Newtonsoft.Json; namespace Artemis.Core.Services { public class ModuleService : IModuleService { + private readonly List _modules; + + public ModuleService() + { + _modules = new List(); + + if (!Directory.Exists(Constants.DataFolder + "modules")) + Directory.CreateDirectory(Constants.DataFolder + "modules"); + } + + public bool LoadingModules { get; private set; } + public ReadOnlyCollection Modules => _modules.AsReadOnly(); + + /// + /// Loads all installed modules. If modules already loaded this will reload them all + /// + /// + public async Task LoadModules() + { + if (LoadingModules) + throw new ArtemisCoreException("Cannot load modules while a previous load hasn't been completed yet."); + + OnStartedLoadingModules(); + + // Empty the list of modules + _modules.Clear(); + // Iterate all module folders + foreach (var directory in Directory.GetDirectories(Constants.DataFolder + "modules")) + // Load each module + _modules.Add(await LoadModuleFromFolder(directory)); + + + OnFinishedLoadedModules(); + } + + public async Task ReloadModule(IModule module) + { + } + + public void Dispose() + { + } + + private async Task LoadModuleFromFolder(string folder) + { + if (!folder.EndsWith("\\")) + folder += "\\"; + if (!File.Exists(folder + "module.json")) + throw new ArtemisModuleException(null, "Failed to load module, no module.json found in " + folder); + + var moduleInfo = JsonConvert.DeserializeObject(File.ReadAllText(folder + "module.json")); + // Load the main module which will contain a class implementing IModule + var module = await CSScript.Evaluator.LoadFileAsync(folder + moduleInfo.MainFile); + return module; + } + + #region Events + + /// + /// Occurs when a single module has loaded + /// + public event EventHandler ModuleLoaded; + + /// + /// Occurs when a single module has reloaded + /// + public event EventHandler ModuleReloaded; + + /// + /// Occurs when loading all modules has started + /// + public event EventHandler StartedLoadingModules; + + /// + /// Occurs when loading all modules has finished + /// + public event EventHandler FinishedLoadedModules; + + private void OnModuleLoaded(ModuleEventArgs e) + { + ModuleLoaded?.Invoke(this, e); + } + + private void OnModuleReloaded(ModuleEventArgs e) + { + ModuleReloaded?.Invoke(this, e); + } + + private void OnStartedLoadingModules() + { + LoadingModules = true; + StartedLoadingModules?.Invoke(this, EventArgs.Empty); + } + + private void OnFinishedLoadedModules() + { + LoadingModules = false; + FinishedLoadedModules?.Invoke(this, EventArgs.Empty); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/app.config b/src/Artemis.Core/app.config index a457a7c8d..64c2d2698 100644 --- a/src/Artemis.Core/app.config +++ b/src/Artemis.Core/app.config @@ -11,6 +11,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.Core/packages.config b/src/Artemis.Core/packages.config index 49638bdc5..51fe9d3d6 100644 --- a/src/Artemis.Core/packages.config +++ b/src/Artemis.Core/packages.config @@ -1,25 +1,73 @@  - - + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.Modules/Artemis.Modules.csproj b/src/Artemis.Modules/Artemis.Modules.csproj new file mode 100644 index 000000000..ddb2f94ca --- /dev/null +++ b/src/Artemis.Modules/Artemis.Modules.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {6B62C017-8ED8-4076-BDF9-555918266D43} + Library + Properties + Artemis.Modules + Artemis.Modules + v4.6 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + {9b811f9b-86b9-4771-87af-72bae7078a36} + Artemis.Core + + + + + + + \ No newline at end of file diff --git a/src/Artemis.Modules/Properties/AssemblyInfo.cs b/src/Artemis.Modules/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..16670c999 --- /dev/null +++ b/src/Artemis.Modules/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Artemis.Modules")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Artemis.Modules")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6b62c017-8ed8-4076-bdf9-555918266d43")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Artemis.Modules/TestModule.cs b/src/Artemis.Modules/TestModule.cs new file mode 100644 index 000000000..2fec3d455 --- /dev/null +++ b/src/Artemis.Modules/TestModule.cs @@ -0,0 +1,8 @@ +using Artemis.Core.Modules.Interfaces; + +namespace Artemis.Modules +{ + public class TestModule : IModule + { + } +} \ No newline at end of file diff --git a/src/Artemis.Modules/app.config b/src/Artemis.Modules/app.config new file mode 100644 index 000000000..c1979efa0 --- /dev/null +++ b/src/Artemis.Modules/app.config @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/App.config b/src/Artemis.UI/App.config index 9045fef60..2b8f64876 100644 --- a/src/Artemis.UI/App.config +++ b/src/Artemis.UI/App.config @@ -10,6 +10,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 02b510d1f..5c0d7cb28 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -147,6 +147,10 @@ + + {9b811f9b-86b9-4771-87af-72bae7078a36} + Artemis.Core + {e489e5e3-1a65-4af5-a1ea-f9805fd19a65} Artemis.Storage diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 2495ca0d9..5a4f82d85 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -1,4 +1,7 @@ -using Artemis.UI.Ninject; +using System.Windows; +using Artemis.Core.Ninject; +using Artemis.Core.Services.Interfaces; +using Artemis.UI.Ninject; using Artemis.UI.Stylet; using Artemis.UI.ViewModels; using Ninject; @@ -7,9 +10,26 @@ namespace Artemis.UI { public class Bootstrapper : NinjectBootstrapper { + private ICoreService _core; + + protected override void OnExit(ExitEventArgs e) + { + // Stop the Artemis core + _core.Dispose(); + + base.OnExit(e); + } + protected override void ConfigureIoC(IKernel kernel) { + // Load this assembly's module kernel.Load(); + // Load the core assembly's module + kernel.Load(); + + // Start the Artemis core, the core's constructor will initialize async + _core = Kernel.Get(); + base.ConfigureIoC(kernel); } } diff --git a/src/Artemis.UI/Stylet/NinjectBootstrapper.cs b/src/Artemis.UI/Stylet/NinjectBootstrapper.cs index 9d83df960..51a364424 100644 --- a/src/Artemis.UI/Stylet/NinjectBootstrapper.cs +++ b/src/Artemis.UI/Stylet/NinjectBootstrapper.cs @@ -9,17 +9,17 @@ namespace Artemis.UI.Stylet public class NinjectBootstrapper : BootstrapperBase where TRootViewModel : class { private object _rootViewModel; - private IKernel kernel; + protected IKernel Kernel; - protected virtual object RootViewModel => - _rootViewModel ?? (_rootViewModel = GetInstance(typeof(TRootViewModel))); + protected virtual object RootViewModel => _rootViewModel ?? (_rootViewModel = GetInstance(typeof(TRootViewModel))); protected override void ConfigureBootstrapper() { - kernel = new StandardKernel(); - DefaultConfigureIoC(kernel); - ConfigureIoC(kernel); + Kernel = new StandardKernel(); + DefaultConfigureIoC(Kernel); + ConfigureIoC(Kernel); } + /// /// Carries out default configuration of the IoC container. Override if you don't want to do this @@ -49,7 +49,7 @@ namespace Artemis.UI.Stylet public override object GetInstance(Type type) { - return kernel.Get(type); + return Kernel.Get(type); } protected override void Launch() @@ -61,8 +61,8 @@ namespace Artemis.UI.Stylet { base.Dispose(); ScreenExtensions.TryDispose(_rootViewModel); - if (kernel != null) - kernel.Dispose(); + if (Kernel != null) + Kernel.Dispose(); } } } \ No newline at end of file diff --git a/src/Artemis.sln b/src/Artemis.sln index 0e7c29e6f..7abd68f90 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Storage", "Artemis. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Core", "Artemis.Core\Artemis.Core.csproj", "{9B811F9B-86B9-4771-87AF-72BAE7078A36}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Modules", "Artemis.Modules\Artemis.Modules.csproj", "{6B62C017-8ED8-4076-BDF9-555918266D43}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|Any CPU.Build.0 = Release|Any CPU + {6B62C017-8ED8-4076-BDF9-555918266D43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B62C017-8ED8-4076-BDF9-555918266D43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B62C017-8ED8-4076-BDF9-555918266D43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B62C017-8ED8-4076-BDF9-555918266D43}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE