156 lines
4.4 KiB
C#
156 lines
4.4 KiB
C#
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
|
using Microsoft.CodeAnalysis.Scripting;
|
|
using RobotNet.Script.Shares;
|
|
using System.Diagnostics;
|
|
|
|
namespace RobotNet.ScriptManager.Models;
|
|
|
|
public class ScriptTask : IDisposable
|
|
{
|
|
public bool Paused { get; private set; } = false;
|
|
private readonly ScriptRunner<object> scriptRunner;
|
|
private readonly ScriptTaskGlobals globals;
|
|
private readonly int Interval;
|
|
private readonly double ProcessTime;
|
|
private CancellationTokenSource? internalCts;
|
|
private Thread? thread;
|
|
private bool disposed;
|
|
private readonly string Name;
|
|
private readonly RobotNet.Script.ILogger? Logger;
|
|
private readonly bool AutoStart;
|
|
|
|
public class ScriptTaskGlobals(IDictionary<string, object?> _globals, IDictionary<string, object?> _robotnet)
|
|
{
|
|
public IDictionary<string, object?> globals = _globals;
|
|
public IDictionary<string, object?> robotnet = _robotnet;
|
|
}
|
|
|
|
public ScriptTask(ScriptTaskData task, ScriptOptions options, IDictionary<string, object?> _globals, IDictionary<string, object?> _robotnet)
|
|
{
|
|
var script = CSharpScript.Create(task.Script, options, globalsType: typeof(ScriptTaskGlobals));
|
|
scriptRunner = script.CreateDelegate();
|
|
|
|
Name = task.Name;
|
|
globals = new ScriptTaskGlobals(_globals, _robotnet);
|
|
Interval = task.Interval;
|
|
ProcessTime = Interval * 0.8;
|
|
AutoStart = task.AutoStart;
|
|
Paused = !task.AutoStart;
|
|
|
|
if (globals.robotnet.TryGetValue($"get_{nameof(IRobotNetGlobals.Logger)}", out object? get_logger)
|
|
&& get_logger is Func<RobotNet.Script.ILogger> get_logger_func)
|
|
{
|
|
Logger = get_logger_func.Invoke();
|
|
}
|
|
}
|
|
|
|
public void Start(CancellationToken cancellationToken = default)
|
|
{
|
|
Stop(); // Ensure previous thread is stopped before starting a new one
|
|
|
|
internalCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
|
|
Paused = !AutoStart;
|
|
thread = new Thread(RunningHandler)
|
|
{
|
|
IsBackground = true,
|
|
Priority = ThreadPriority.Highest,
|
|
};
|
|
thread.Start();
|
|
}
|
|
|
|
public void Pause()
|
|
{
|
|
Paused = true;
|
|
}
|
|
|
|
public void Resume()
|
|
{
|
|
Paused = false;
|
|
}
|
|
|
|
private void RunningHandler()
|
|
{
|
|
if (internalCts == null || internalCts.IsCancellationRequested)
|
|
{
|
|
return;
|
|
}
|
|
var cts = new CancellationTokenSource();
|
|
var stopwatch = Stopwatch.StartNew();
|
|
int elapsed;
|
|
int remaining;
|
|
|
|
internalCts.Token.Register(() =>
|
|
{
|
|
cts.CancelAfter(TimeSpan.FromMilliseconds(Interval * 3));
|
|
});
|
|
|
|
try
|
|
{
|
|
while (!internalCts.IsCancellationRequested)
|
|
{
|
|
if (Paused)
|
|
{
|
|
Thread.Sleep(Interval); // Sleep briefly to avoid busy waiting
|
|
continue;
|
|
}
|
|
stopwatch.Restart();
|
|
scriptRunner.Invoke(globals, cts.Token).GetAwaiter().GetResult();
|
|
|
|
stopwatch.Stop();
|
|
elapsed = (int)stopwatch.ElapsedMilliseconds;
|
|
remaining = Interval - elapsed;
|
|
|
|
// If execution time exceeds ProcessTime, add another cycle
|
|
if (elapsed > ProcessTime)
|
|
{
|
|
remaining += Interval;
|
|
}
|
|
|
|
if (remaining > 0)
|
|
{
|
|
try
|
|
{
|
|
Thread.Sleep(remaining);
|
|
}
|
|
catch (ThreadInterruptedException)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
GC.Collect();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger?.LogError($"Task \"{Name}\" execution failed: {ex}");
|
|
}
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (internalCts != null && !internalCts.IsCancellationRequested)
|
|
{
|
|
internalCts.Cancel();
|
|
}
|
|
|
|
if (thread != null && thread.IsAlive)
|
|
{
|
|
//thread.Interrupt();
|
|
thread.Join();
|
|
}
|
|
|
|
internalCts?.Dispose();
|
|
internalCts = null;
|
|
thread = null;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (disposed) return;
|
|
Stop();
|
|
disposed = true;
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|