using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using NodaTime; namespace PluralKit.Bot { public class HandlerQueue { private readonly List _handlers = new List(); public HandlerEntry Add(Func> handler) { var entry = new HandlerEntry {Handler = handler}; _handlers.Add(entry); return entry; } public async Task WaitFor(Func predicate, Duration? timeout = null, CancellationToken ct = default) { var timeoutTask = Task.Delay(timeout?.ToTimeSpan() ?? TimeSpan.FromMilliseconds(-1), ct); var tcs = new TaskCompletionSource(); Task Handler(T e) { var matches = predicate(e); if (matches) tcs.SetResult(e); return Task.FromResult(matches); } var entry = new HandlerEntry {Handler = Handler}; _handlers.Add(entry); // Wait for either the event task or the timeout task // If the timeout task finishes first, raise, otherwise pass event through try { var theTask = await Task.WhenAny(timeoutTask, tcs.Task); if (theTask == timeoutTask) throw new TimeoutException(); } finally { entry.Remove(); } return await tcs.Task; } public async Task TryHandle(T evt) { _handlers.RemoveAll(he => !he.Alive); var now = SystemClock.Instance.GetCurrentInstant(); foreach (var entry in _handlers) { if (entry.Expiry < now) entry.Alive = false; else if (entry.Alive && await entry.Handler(evt)) { entry.Alive = false; return true; } } return false; } public class HandlerEntry { internal Func> Handler; internal bool Alive = true; internal Instant Expiry = SystemClock.Instance.GetCurrentInstant() + Duration.FromMinutes(30); public void Remove() => Alive = false; } } }