RobotNet/RobotNet.ScriptManager/Models/ScriptTask.cs
2025-10-15 15:15:53 +07:00

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);
}
}