1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00
Artemis/src/Artemis.Core/Scripting.Extensions.cs
SpoinkyNL aada338dcc Further expanded plugins
Added navigation between modules
2018-02-25 19:48:22 +01:00

295 lines
15 KiB
C#

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<Action, string> 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<Func<int, int, int>>(
@"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<ICalc>(
@"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>(
@"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>(
@"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>(@"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<ICalc>(
@"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<ICalc>(
@"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<IFullCalc>(
@"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);
}
}