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 ConcurrentQueue _resultQueue = new (); internal readonly ManualResetEventSlim _waitForProbe = new (false); 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 = null) { if (consoleDriver is null) { throw new ArgumentNullException (nameof (consoleDriver)); } _netEvents = new NetEvents (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 () { _waitForProbe.Set (); if (_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 _); } return true; } void IMainLoopDriver.Iteration () { while (_resultQueue.Count > 0) { // Always dequeue even if it's null and invoke if isn't null if (_resultQueue.TryDequeue (out NetEvents.InputResult? dequeueResult)) { if (dequeueResult is { }) { ProcessInput?.Invoke (dequeueResult.Value); } } } } void IMainLoopDriver.TearDown () { _inputHandlerTokenSource?.Cancel (); _inputHandlerTokenSource?.Dispose (); _eventReadyTokenSource?.Cancel (); _eventReadyTokenSource?.Dispose (); _eventReady?.Dispose (); _resultQueue?.Clear (); _waitForProbe?.Dispose (); _netEvents?.Dispose (); _netEvents = null; _mainLoop = null; } private void NetInputHandler () { while (_mainLoop is { }) { try { if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested) { _waitForProbe.Wait (_inputHandlerTokenSource.Token); } } catch (OperationCanceledException) { return; } finally { if (_waitForProbe.IsSet) { _waitForProbe.Reset (); } } if (_inputHandlerTokenSource.IsCancellationRequested) { return; } _inputHandlerTokenSource.Token.ThrowIfCancellationRequested (); if (_resultQueue.Count == 0) { _resultQueue.Enqueue (_netEvents.DequeueInput ()); } try { while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out NetEvents.InputResult? result) && result is null) { // Dequeue null values _resultQueue.TryDequeue (out _); } } catch (InvalidOperationException) // Peek can raise an exception { } if (_resultQueue.Count > 0) { _eventReady.Set (); } } } }