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 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 _globals, IDictionary _robotnet) { public IDictionary globals = _globals; public IDictionary robotnet = _robotnet; } public ScriptTask(ScriptTaskData task, ScriptOptions options, IDictionary _globals, IDictionary _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 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); } }