#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui; /// /// Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is /// cross-platform but lacks things like file descriptor monitoring. /// /// This implementation is used for NetDriver. internal class NetMainLoop : IMainLoopDriver { internal NetEvents? _netEvents; /// Invoked when a Key is pressed. internal Action? ProcessInput; private readonly ManualResetEventSlim _eventReady = new (false); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private readonly BlockingCollection _resultQueue = new (new ConcurrentQueue ()); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private MainLoop? _mainLoop; /// Initializes the class with the console driver. /// Passing a consoleDriver is provided to capture windows resizing. /// The console driver used by this Net main loop. /// public NetMainLoop (ConsoleDriver consoleDriver) { ArgumentNullException.ThrowIfNull (consoleDriver); _netEvents = new (consoleDriver); } void IMainLoopDriver.Setup (MainLoop mainLoop) { _mainLoop = mainLoop; if (ConsoleDriver.RunningUnitTests) { return; } Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); } void IMainLoopDriver.Wakeup () { _eventReady.Set (); } bool IMainLoopDriver.EventsPending () { if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout)) { return true; } try { if (!_eventReadyTokenSource.IsCancellationRequested) { // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there // are no timers, but there IS an idle handler waiting. _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token); } } catch (OperationCanceledException) { return true; } finally { _eventReady.Reset (); } _eventReadyTokenSource.Token.ThrowIfCancellationRequested (); if (!_eventReadyTokenSource.IsCancellationRequested) { return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _); } // If cancellation was requested then always return true return true; } void IMainLoopDriver.Iteration () { while (_resultQueue.Count > 0) { // Always dequeue even if it's null and invoke if isn't null if (_resultQueue.TryTake (out NetEvents.InputResult dequeueResult)) { ProcessInput?.Invoke (dequeueResult); } } } void IMainLoopDriver.TearDown () { _inputHandlerTokenSource.Cancel (); _inputHandlerTokenSource.Dispose (); _eventReadyTokenSource.Cancel (); _eventReadyTokenSource.Dispose (); _eventReady.Dispose (); _resultQueue.Dispose(); _netEvents?.Dispose (); _netEvents = null; _mainLoop = null; } private void NetInputHandler () { while (_mainLoop is { }) { try { if (_inputHandlerTokenSource.IsCancellationRequested) { return; } if (_resultQueue?.Count == 0 || _netEvents!._forceRead) { NetEvents.InputResult? result = _netEvents!.DequeueInput (); if (result.HasValue) { _resultQueue?.Add (result.Value); } } if (!_inputHandlerTokenSource.IsCancellationRequested && _resultQueue?.Count > 0) { _eventReady.Set (); } } catch (OperationCanceledException) { return; } } } }