using Microsoft.UI.Dispatching; using RblxTool.Interop; using RblxTool.Models; namespace RblxTool.Services; public sealed class JobRunner { private readonly RblxEngine _engine; private readonly DispatcherQueue _dispatcher; private CancellationTokenSource? _pump; public JobRunner(RblxEngine engine, DispatcherQueue dispatcher) { _engine = engine; _dispatcher = dispatcher; } public ulong JobId { get; private set; } public bool Running => _pump is not null && !_pump.IsCancellationRequested; public event Action? EventReceived; public event Action? Completed; public void Start(ulong jobId, int pollIntervalMs = 120) { if (jobId == 0) { Completed?.Invoke("engine refused the job"); return; } Stop(); JobId = jobId; var source = new CancellationTokenSource(); _pump = source; _ = Task.Run(() => Pump(jobId, pollIntervalMs, source.Token), source.Token); } private async Task Pump(ulong jobId, int pollIntervalMs, CancellationToken token) { var idle = 0; while (!token.IsCancellationRequested) { var poll = _engine.Poll(jobId); if (poll.Events.Count > 0) { idle = 0; var batch = poll.Events; _dispatcher.TryEnqueue(DispatcherQueuePriority.Low, () => { foreach (var item in batch) { EventReceived?.Invoke(item); } }); } else { idle++; } if (poll.Done && poll.Events.Count == 0) { var result = _engine.Result(jobId); _dispatcher.TryEnqueue(() => Completed?.Invoke(result)); break; } var delay = idle > 20 ? pollIntervalMs * 4 : pollIntervalMs; try { await Task.Delay(delay, token).ConfigureAwait(false); } catch (TaskCanceledException) { break; } } } public void Cancel() { if (JobId != 0) { _engine.Cancel(JobId); } } public void Stop() { _pump?.Cancel(); _pump?.Dispose(); _pump = null; } }