#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui; public class AnsiRequestScheduler(IAnsiResponseParser parser) { private readonly List _requests = new (); /// /// /// Dictionary where key is ansi request terminator and value is when we last sent a request for /// this terminator. Combined with this prevents hammering the console /// with too many requests in sequence which can cause console to freeze as there is no space for /// regular screen drawing / mouse events etc to come in. /// /// /// When user exceeds the throttle, new requests accumulate in (i.e. remain /// queued). /// /// private ConcurrentDictionary _lastSend = new (); private TimeSpan _throttle = TimeSpan.FromMilliseconds (100); private TimeSpan _runScheduleThrottle = TimeSpan.FromMilliseconds (100); /// /// Sends the immediately or queues it if there is already /// an outstanding request for the given . /// /// /// if request was sent immediately. if it was queued. public bool SendOrSchedule (AnsiEscapeSequenceRequest request ) { if (CanSend(request)) { Send (request); return true; } else { _requests.Add (request); return false; } } private DateTime _lastRun = DateTime.Now; /// /// Identifies and runs any that can be sent based on the /// current outstanding requests of the parser. /// /// Repeated requests to run the schedule over short period of time will be ignored. /// Pass to override this behaviour and force evaluation of outstanding requests. /// if a request was found and run. /// if no outstanding requests or all have existing outstanding requests underway in parser. public bool RunSchedule (bool force = false) { if (!force && DateTime.Now - _lastRun < _runScheduleThrottle) { return false; } var opportunity = _requests.FirstOrDefault (CanSend); if (opportunity != null) { _requests.Remove (opportunity); Send (opportunity); return true; } return false; } private void Send (AnsiEscapeSequenceRequest r) { _lastSend.AddOrUpdate (r.Terminator,(s)=>DateTime.Now,(s,v)=>DateTime.Now); parser.ExpectResponse (r.Terminator,r.ResponseReceived); r.Send (); } public bool CanSend (AnsiEscapeSequenceRequest r) { if (ShouldThrottle (r)) { return false; } return !parser.IsExpecting (r.Terminator); } private bool ShouldThrottle (AnsiEscapeSequenceRequest r) { if (_lastSend.TryGetValue (r.Terminator, out DateTime value)) { return DateTime.Now - value < _throttle; } return false; } }