MoonSharp already has an extension for executing scripts asynchronously. However, that extension allows pretty much no control over the script's execution. Currently, the only way to do so is through coroutines, which involves writing a bunch of boilerplate every time, and which also makes it more difficult to implement, say, a C#-land binding that needs to pause the script thread (imagine a 'sleep' function). Coroutines also don't really allow scripts to be run in the background. They are still run in the foreground, but check back with the main thread every once in a while. In order to actually run them in the background AND be able to control their execution, one has to add more control code (Lua thread running coroutine and checking every x instructions for abort, main thread creating that separate Lua thread and controlling it via a reset event).
Right now, we want to:
- be able to stop the script without forcing an abort
- be able to pause the script without a Thread.Sleep, which makes the script thread unresponsive to any kind of abort code
- run a Lua script in the background and continue with the normal flow of the program
Additional functionality could obviously be added, like pausing & then resuming execution from within the main flow of the program.
Having support for this in the library makes sense considering it already has async methods. It is also better because it makes everyone else's code cleaner and requires less boilerplate to be written.
This is implemented as follows:
namespace MoonSharp.Interpreter
{
public class ExecutionControlToken
{
public ExecutionControlToken();
public void Terminate();
// ...
}
}
'ExecutionControlToken' provides control of the execution of a script.
Calling 'Terminate' will raise a ScriptTerminationRequestedException from the thread that is running the Lua script. All exceptions can be caught in the same way as with the existing async methods.
namespace MoonSharp.Interpreter
{
public class Script
{
// ...
public Task<DynValue> DoStringAsync(ExecutionControlToken ecToken, string code, ...);
public Task<DynValue> DoStreamAsync(ExecutionControlToken ecToken, ...);
public Task<DynValue> DoFileAsync(ExecutionControlToken ecToken, ...);
// ... other existing async methods
public Task<DynValue> CallAsync(ExecutionControlToken ecToken, ...);
// ...
}
}
The first three methods are modified from the original MoonSharp. They have an additional parameter in the 1st position, which is an 'ExecutionControlToken'. This 'ExecutionControlToken' becomes associated with the execution of the code specified, and it can be associated with multiple scripts.
... is parameters from the non-async methods.
Because 'ecToken' is added as a first parameter to these async methods, this will break compatibility if merged into the original MoonSharp.
namespace MoonSharp.Interpreter
{
public class ScriptExecutionContext
{
// ...
public void PauseExecution(TimeSpan timeSpan);
// ...
}
}
'PauseExecution' is added to 'ScriptExecutionContext' so that C#-land bindings can pause the script thread. This function responds to abort requests, so any call to 'PauseExecution' won't block the normal flow of the program.
Although async extensions are only supported on .NET 4.0+, 'PauseExecution' works on .NET 3.5 as well. On that platform, it simply calls Thread.Sleep. That is because there is no async support anyway, so the script execution is already blocking the thread. This is simply for uniformity.