diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj
index cf2c1cb7d..5c1f0cb18 100644
--- a/src/Artemis.Core/Artemis.Core.csproj
+++ b/src/Artemis.Core/Artemis.Core.csproj
@@ -35,8 +35,8 @@
..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll
-
- ..\packages\CS-Script.bin.3.27.2.0\lib\net46\CSScriptLibrary.dll
+
+ ..\packages\CS-Script.bin.3.28.0.1\lib\net46\CSScriptLibrary.dll
..\packages\HidSharp.1.5\lib\net35\HidSharp.dll
@@ -54,10 +54,10 @@
..\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\CS-Script.bin.3.28.0.1\lib\net46\Mono.CSharp.dll
-
- ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.11.0.1\lib\net45\Newtonsoft.Json.dll
..\packages\Ninject.3.3.4\lib\net45\Ninject.dll
@@ -104,8 +104,8 @@
..\packages\RGB.NET.Input.0.0.1.26\lib\net45\RGB.NET.Input.dll
-
- ..\packages\Sanford.Multimedia.Midi.6.4.2\lib\net20\Sanford.Multimedia.Midi.dll
+
+ ..\packages\Sanford.Multimedia.Midi.6.6.0\lib\net20\Sanford.Multimedia.Midi.dll
..\packages\Stylet.1.1.21\lib\net45\Stylet.dll
@@ -196,6 +196,9 @@
+
+
+
@@ -209,10 +212,6 @@
-
-
-
-
{cd23bc5e-57f0-46ce-a007-24d031146219}
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 bd3c697f3..42de5fab1 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -1,7 +1,10 @@
using System;
+using System.Linq;
using System.Threading.Tasks;
using Artemis.Core.Exceptions;
using Artemis.Core.Services.Interfaces;
+using Artemis.Plugins.Interfaces;
+using RGB.NET.Core;
namespace Artemis.Core.Services
{
@@ -14,6 +17,7 @@ namespace Artemis.Core.Services
{
_pluginService = pluginService;
_rgbService = rgbService;
+ _rgbService.Surface.Updating += SurfaceOnUpdating;
Task.Run(Initialize);
}
@@ -34,10 +38,27 @@ namespace Artemis.Core.Services
// Initialize the services
await _pluginService.LoadPlugins();
await _rgbService.LoadDevices();
-
+
OnInitialized();
}
+ private void SurfaceOnUpdating(UpdatingEventArgs args)
+ {
+ try
+ {
+ // Update all active modules
+ foreach (var module in _pluginService.Plugins.OfType())
+ module.Update(args.DeltaTime);
+ // Render all active modules
+ foreach (var module in _pluginService.Plugins.OfType())
+ module.Render(args.DeltaTime);
+ }
+ catch (Exception e)
+ {
+ throw new ArtemisCoreException("Exception during update", e);
+ }
+ }
+
#region Events
public event EventHandler Initialized;
diff --git a/src/Artemis.Core/Services/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs
index 112c01053..f174f00bd 100644
--- a/src/Artemis.Core/Services/Interfaces/IPluginService.cs
+++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs
@@ -9,17 +9,32 @@ namespace Artemis.Core.Services.Interfaces
{
public interface IPluginService : IArtemisService, IDisposable
{
+ ///
+ /// Indicates wether or not plugins are currently being loaded
+ ///
bool LoadingPlugins { get; }
+
+ ///
+ /// All loaded plugins
+ ///
ReadOnlyCollection Plugins { get; }
///
/// Loads all installed plugins. If plugins already loaded this will reload them all
///
- ///
Task LoadPlugins();
+ ///
+ /// Reloads the plugin accompanying the provided plugin info
+ ///
+ /// The plugin info containing the plugin to reload
Task ReloadPlugin(PluginInfo pluginInfo);
- Task GetPluginViewModel(PluginInfo pluginInfo);
+
+ ///
+ /// Gets the view model of the module accompanying the provided plugin info
+ ///
+ /// The plugin info containing the module for which to load the view model
+ Task GetModuleViewModel(PluginInfo pluginInfo);
///
/// Occurs when a single plugin has loaded
diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs
index 4458128f5..cade01e89 100644
--- a/src/Artemis.Core/Services/PluginService.cs
+++ b/src/Artemis.Core/Services/PluginService.cs
@@ -33,10 +33,6 @@ namespace Artemis.Core.Services
public ReadOnlyCollection Plugins => _plugins.AsReadOnly();
///
- ///
- /// Loads all installed plugins. If plugins already loaded this will reload them all
- ///
- ///
public async Task LoadPlugins()
{
if (LoadingPlugins)
@@ -56,18 +52,22 @@ namespace Artemis.Core.Services
public async Task ReloadPlugin(PluginInfo pluginInfo)
{
+ throw new NotImplementedException();
}
- public async Task GetPluginViewModel(PluginInfo pluginInfo)
+ public async Task GetModuleViewModel(PluginInfo pluginInfo)
{
+ // Don't attempt to locave VMs for something other than a module
+ if (pluginInfo.Plugin is IModule)
+ throw new ArtemisPluginException(pluginInfo, "Cannot locate a view model for this plugin as it's not a module.");
// Compile the ViewModel and get the type
var compile = await Task.Run(() => CSScript.LoadFile(pluginInfo.Folder + pluginInfo.ViewModel));
- var vmType = compile.ExportedTypes.FirstOrDefault(t => typeof(IPluginViewModel).IsAssignableFrom(t));
+ var vmType = compile.ExportedTypes.FirstOrDefault(t => typeof(IModuleViewModel).IsAssignableFrom(t));
if (vmType == null)
- throw new ArtemisPluginException(pluginInfo, "Cannot locate a view model for this plugin.");
+ throw new ArtemisPluginException(pluginInfo, "Cannot locate a view model for this module.");
// Instantiate the ViewModel with Ninject
- var vm = (IPluginViewModel) _kernel.Get(vmType);
+ var vm = (IModuleViewModel) _kernel.Get(vmType);
vm.PluginInfo = pluginInfo;
return vm;
}
@@ -94,25 +94,10 @@ namespace Artemis.Core.Services
}
#region Events
-
- ///
- /// Occurs when a single plugin has loaded
- ///
+
public event EventHandler PluginLoaded;
-
- ///
- /// Occurs when a single plugin has reloaded
- ///
public event EventHandler PluginReloaded;
-
- ///
- /// Occurs when loading all plugins has started
- ///
public event EventHandler StartedLoadingPlugins;
-
- ///
- /// Occurs when loading all plugins has finished
- ///
public event EventHandler FinishedLoadedPlugins;
private void OnPluginLoaded(PluginEventArgs e)
diff --git a/src/Artemis.Core/packages.config b/src/Artemis.Core/packages.config
index 693215aa2..ef2b52f54 100644
--- a/src/Artemis.Core/packages.config
+++ b/src/Artemis.Core/packages.config
@@ -1,15 +1,15 @@
-
-
+
+
-
+
-
+
@@ -28,7 +28,7 @@
-
+
diff --git a/src/Artemis.Plugins/Abstract/PluginViewModel.cs b/src/Artemis.Plugins/Abstract/ModuleViewModel.cs
similarity index 74%
rename from src/Artemis.Plugins/Abstract/PluginViewModel.cs
rename to src/Artemis.Plugins/Abstract/ModuleViewModel.cs
index cba287e00..338f9f911 100644
--- a/src/Artemis.Plugins/Abstract/PluginViewModel.cs
+++ b/src/Artemis.Plugins/Abstract/ModuleViewModel.cs
@@ -4,7 +4,7 @@ using Stylet;
namespace Artemis.Plugins.Abstract
{
- public abstract class PluginViewModel : Screen, IPluginViewModel
+ public abstract class ModuleViewModel : Screen, IModuleViewModel
{
public PluginInfo PluginInfo { get; set; }
}
diff --git a/src/Artemis.Plugins/Artemis.Plugins.csproj b/src/Artemis.Plugins/Artemis.Plugins.csproj
index 04adf6b1f..9c2269b82 100644
--- a/src/Artemis.Plugins/Artemis.Plugins.csproj
+++ b/src/Artemis.Plugins/Artemis.Plugins.csproj
@@ -30,8 +30,8 @@
4
-
- ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.11.0.1\lib\net45\Newtonsoft.Json.dll
..\packages\Ninject.3.3.4\lib\net45\Ninject.dll
@@ -49,13 +49,13 @@
-
+
-
+
diff --git a/src/Artemis.Plugins/Interfaces/IModule.cs b/src/Artemis.Plugins/Interfaces/IModule.cs
index 0eac4ca47..d75b99ee7 100644
--- a/src/Artemis.Plugins/Interfaces/IModule.cs
+++ b/src/Artemis.Plugins/Interfaces/IModule.cs
@@ -6,5 +6,16 @@
///
public interface IModule : IPlugin
{
+ ///
+ /// Called each frame when the module must update
+ ///
+ /// Time since the last update
+ void Update(double deltaTime);
+
+ ///
+ /// Called each frame when the module must render
+ ///
+ /// Time since the last render
+ void Render(double deltaTime);
}
}
\ No newline at end of file
diff --git a/src/Artemis.Plugins/Interfaces/IPluginViewModel.cs b/src/Artemis.Plugins/Interfaces/IModuleViewModel.cs
similarity index 65%
rename from src/Artemis.Plugins/Interfaces/IPluginViewModel.cs
rename to src/Artemis.Plugins/Interfaces/IModuleViewModel.cs
index 711fb20b7..83ef20391 100644
--- a/src/Artemis.Plugins/Interfaces/IPluginViewModel.cs
+++ b/src/Artemis.Plugins/Interfaces/IModuleViewModel.cs
@@ -5,9 +5,9 @@ namespace Artemis.Plugins.Interfaces
{
///
///
- /// Allows you to create a view model for a plugin
+ /// Allows you to create a view model for a module
///
- public interface IPluginViewModel : IScreen
+ public interface IModuleViewModel : IScreen
{
PluginInfo PluginInfo { get; set; }
}
diff --git a/src/Artemis.Plugins/Interfaces/IPlugin.cs b/src/Artemis.Plugins/Interfaces/IPlugin.cs
index 1c6da9f41..fc923e8ae 100644
--- a/src/Artemis.Plugins/Interfaces/IPlugin.cs
+++ b/src/Artemis.Plugins/Interfaces/IPlugin.cs
@@ -4,7 +4,8 @@
/// This is the base plugin type, use the other interfaces such as IModule to create plugins
///
public interface IPlugin
- {///
+ {
+ ///
/// Called when the plugin is loaded
///
void LoadPlugin();
diff --git a/src/Artemis.Plugins/packages.config b/src/Artemis.Plugins/packages.config
index 9295a4bb5..ba9e759e2 100644
--- a/src/Artemis.Plugins/packages.config
+++ b/src/Artemis.Plugins/packages.config
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj
index e09810ea4..c71aff2ca 100644
--- a/src/Artemis.UI/Artemis.UI.csproj
+++ b/src/Artemis.UI/Artemis.UI.csproj
@@ -39,11 +39,14 @@
..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll
+
+ ..\packages\ControlzEx.3.0.2.4\lib\net45\ControlzEx.dll
+
..\packages\HidSharp.1.5\lib\net35\HidSharp.dll
-
- ..\packages\MahApps.Metro.1.5.0\lib\net45\MahApps.Metro.dll
+
+ ..\packages\MahApps.Metro.1.6.0\lib\net45\MahApps.Metro.dll
..\packages\MaterialDesignColors.1.1.3\lib\net45\MaterialDesignColors.dll
@@ -63,8 +66,8 @@
..\packages\Ninject.Extensions.Factory.3.3.2\lib\net45\Ninject.Extensions.Factory.dll
-
- ..\packages\PropertyChanged.Fody.2.2.4.0\lib\net452\PropertyChanged.dll
+
+ ..\packages\PropertyChanged.Fody.2.2.6\lib\net452\PropertyChanged.dll
..\packages\RGB.NET.Brushes.0.0.1.26\lib\net45\RGB.NET.Brushes.dll
@@ -99,8 +102,8 @@
..\packages\RGB.NET.Groups.0.0.1.26\lib\net45\RGB.NET.Groups.dll
-
- ..\packages\Sanford.Multimedia.Midi.6.4.1\lib\net20\Sanford.Multimedia.Midi.dll
+
+ ..\packages\Sanford.Multimedia.Midi.6.6.0\lib\net20\Sanford.Multimedia.Midi.dll
..\packages\Stylet.1.1.21\lib\net45\Stylet.dll
@@ -113,7 +116,7 @@
..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll
- ..\packages\MahApps.Metro.1.5.0\lib\net45\System.Windows.Interactivity.dll
+ ..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll
@@ -235,18 +238,17 @@
-
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}.
-
+
@@ -254,4 +256,5 @@
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Stylet/ArtemisViewManager.cs b/src/Artemis.UI/Stylet/ArtemisViewManager.cs
index f9ab1316d..859375f00 100644
--- a/src/Artemis.UI/Stylet/ArtemisViewManager.cs
+++ b/src/Artemis.UI/Stylet/ArtemisViewManager.cs
@@ -14,7 +14,7 @@ namespace Artemis.UI.Stylet
public override UIElement CreateViewForModel(object model)
{
- if (model is IPluginViewModel)
+ if (model is IModuleViewModel)
return CreateViewForPlugin(model);
return base.CreateViewForModel(model);
@@ -22,7 +22,7 @@ namespace Artemis.UI.Stylet
private UIElement CreateViewForPlugin(object model)
{
- var pluginInfo = ((IPluginViewModel) model).PluginInfo;
+ var pluginInfo = ((IModuleViewModel) model).PluginInfo;
var viewPath = pluginInfo.Folder + pluginInfo.ViewModel.Replace("ViewModel", "View").Replace(".cs", ".xaml");
// There doesn't have to be a view so make sure one exists
if (!File.Exists(viewPath))
diff --git a/src/Artemis.UI/ViewModels/RootViewModel.cs b/src/Artemis.UI/ViewModels/RootViewModel.cs
index 2f1ac82ba..5361a49d7 100644
--- a/src/Artemis.UI/ViewModels/RootViewModel.cs
+++ b/src/Artemis.UI/ViewModels/RootViewModel.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Controls;
using Artemis.Core.Services.Interfaces;
using Artemis.Plugins.Interfaces;
using Artemis.Plugins.Models;
-using Artemis.UI.Services.Interfaces;
using Artemis.UI.ViewModels.Interfaces;
using Stylet;
@@ -19,7 +21,7 @@ namespace Artemis.UI.ViewModels
{
_artemisViewModels = artemisViewModels;
_pluginService = pluginService;
-
+
// Add the built-in items
Items.AddRange(artemisViewModels);
// Activate the home item
@@ -33,52 +35,78 @@ namespace Artemis.UI.ViewModels
if (!LoadingPlugins)
Modules.AddRange(_pluginService.Plugins.Where(p => p.Plugin is IModule));
+
+ PropertyChanged += OnSelectedModuleChanged;
+ PropertyChanged += OnSelectedPageChanged;
}
+
public IObservableCollection Modules { get; set; }
public bool MenuOpen { get; set; }
public bool LoadingPlugins { get; set; }
+ public ListBoxItem SelectedPage { get; set; }
+ public PluginInfo SelectedModule { get; set; }
private void PluginServiceOnStartedLoadingPlugins(object sender, EventArgs eventArgs)
{
LoadingPlugins = true;
+
Modules.Clear();
+ SelectedModule = null;
}
private void PluginServiceOnFinishedLoadedPlugins(object sender, EventArgs eventArgs)
{
- LoadingPlugins = false;
Modules.AddRange(_pluginService.Plugins.Where(p => p.Plugin is IModule));
- NavigateToModule(Modules.First());
+ SelectedModule = null;
+
+ LoadingPlugins = false;
}
- public void NavigateToHome()
+ public async Task NavigateToSelectedModule()
{
- ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(HomeViewModel)));
- MenuOpen = false;
- }
+ if (SelectedModule == null || LoadingPlugins)
+ return;
- public void NavigateToNews()
- {
- }
-
- public void NavigateToWorkshop()
- {
- }
-
- public void NavigateToSettings()
- {
- ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(SettingsViewModel)));
- MenuOpen = false;
- }
-
- public async void NavigateToModule(PluginInfo pluginInfo)
- {
// Create a view model for the given plugin info (which will be a module)
- var viewModel = await _pluginService.GetPluginViewModel(pluginInfo);
+ var viewModel = await _pluginService.GetModuleViewModel(SelectedModule);
// Tell Stylet to active the view model, the view manager will compile and show the XAML
ActivateItem(viewModel);
+
+ SelectedPage = null;
+ MenuOpen = false;
+ }
+
+ private async void OnSelectedModuleChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "SelectedModule")
+ await NavigateToSelectedModule();
+ }
+
+ private void OnSelectedPageChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName != "SelectedPage" || SelectedPage == null)
+ return;
+
+ switch (SelectedPage.Name)
+ {
+ case "Home":
+ ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(HomeViewModel)));
+ break;
+ case "News":
+ // ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(NewsViewModel)));
+ break;
+ case "Workshop":
+ // ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(WorkshopViewModel)));
+ break;
+ case "Settings":
+ ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(SettingsViewModel)));
+ break;
+ }
+
+ SelectedModule = null;
+ MenuOpen = false;
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/ViewModels/SettingsViewModel.cs b/src/Artemis.UI/ViewModels/SettingsViewModel.cs
index c884cc9c1..97f6dd8ec 100644
--- a/src/Artemis.UI/ViewModels/SettingsViewModel.cs
+++ b/src/Artemis.UI/ViewModels/SettingsViewModel.cs
@@ -1,37 +1,10 @@
-using System.Linq;
-using Artemis.Core.Services.Interfaces;
-using Artemis.UI.ViewModels.Interfaces;
-using RGB.NET.Core;
+using Artemis.UI.ViewModels.Interfaces;
using Stylet;
namespace Artemis.UI.ViewModels
{
public class SettingsViewModel : Screen, ISettingsViewModel
{
- private readonly IRgbService _rgbService;
-
- public SettingsViewModel(IRgbService rgbService)
- {
- _rgbService = rgbService;
- _rgbService.FinishedLoadedDevices += (sender, args) => SetTestDevice();
- }
-
- protected override void OnActivate()
- {
- SetTestDevice();
- base.OnActivate();
- }
-
- private void SetTestDevice()
- {
- if (!IsActive)
- return;
-
- if (!_rgbService.LoadingDevices)
- TestDevice = _rgbService.Surface.Devices.FirstOrDefault(d => d.DeviceInfo.DeviceType == RGBDeviceType.Keyboard);
- }
-
- public IRGBDevice TestDevice { get; set; }
public string Title => "Settings";
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Views/RootView.xaml b/src/Artemis.UI/Views/RootView.xaml
index f1169de2c..6ec42fb6f 100644
--- a/src/Artemis.UI/Views/RootView.xaml
+++ b/src/Artemis.UI/Views/RootView.xaml
@@ -7,6 +7,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:vms="clr-namespace:Artemis.UI.ViewModels"
+ xmlns:models="clr-namespace:Artemis.Plugins.Models;assembly=Artemis.Plugins"
mc:Ignorable="d"
GlowBrush="{DynamicResource AccentColorBrush}"
FontFamily="{StaticResource DefaultFont}"
@@ -80,29 +81,30 @@
-
+ DockPanel.Dock="Top"
+ SelectedItem="{Binding SelectedPage}">
+
Home
-
+
News
-
+
Workshop
-
+
@@ -118,39 +120,23 @@
-
-
-
-
+
+
+
+
- Home
-
-
-
-
-
- News
-
-
-
-
-
- Workshop
-
-
-
-
-
- Settings
-
-
-
-
+
+
+
+
+
@@ -185,4 +171,4 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Views/SettingsView.xaml b/src/Artemis.UI/Views/SettingsView.xaml
index f8cf757eb..df815cf4b 100644
--- a/src/Artemis.UI/Views/SettingsView.xaml
+++ b/src/Artemis.UI/Views/SettingsView.xaml
@@ -3,11 +3,24 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:local="clr-namespace:Artemis.UI.Views"
- xmlns:visualizers="clr-namespace:Artemis.UI.Controls.Visualizers"
mc:Ignorable="d"
- d:DesignHeight="300" d:DesignWidth="300">
-
-
-
+ d:DesignHeight="600" d:DesignWidth="600">
+
+
+
+
+
+
+
+
+ General
+
+ General settings like start up with Windows etc.
+
+
+ Plugins
+
+ A list of plugins and options to disable them
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/packages.config b/src/Artemis.UI/packages.config
index e1f77e99a..d22cb7eaa 100644
--- a/src/Artemis.UI/packages.config
+++ b/src/Artemis.UI/packages.config
@@ -1,16 +1,17 @@
-
+
+
-
+
-
+
@@ -25,7 +26,7 @@
-
+
\ No newline at end of file