NetMainLoop.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. using System.Collections.Concurrent;
  2. namespace Terminal.Gui;
  3. /// <summary>
  4. /// Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is
  5. /// cross-platform but lacks things like file descriptor monitoring.
  6. /// </summary>
  7. /// <remarks>This implementation is used for NetDriver.</remarks>
  8. internal class NetMainLoop : IMainLoopDriver
  9. {
  10. internal NetEvents _netEvents;
  11. /// <summary>Invoked when a Key is pressed.</summary>
  12. internal Action<NetEvents.InputResult> ProcessInput;
  13. private readonly ManualResetEventSlim _eventReady = new (false);
  14. private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
  15. private readonly ConcurrentQueue<NetEvents.InputResult?> _resultQueue = new ();
  16. internal readonly ManualResetEventSlim _waitForProbe = new (false);
  17. private readonly CancellationTokenSource _eventReadyTokenSource = new ();
  18. private MainLoop _mainLoop;
  19. /// <summary>Initializes the class with the console driver.</summary>
  20. /// <remarks>Passing a consoleDriver is provided to capture windows resizing.</remarks>
  21. /// <param name="consoleDriver">The console driver used by this Net main loop.</param>
  22. /// <exception cref="ArgumentNullException"></exception>
  23. public NetMainLoop (ConsoleDriver consoleDriver = null)
  24. {
  25. if (consoleDriver is null)
  26. {
  27. throw new ArgumentNullException (nameof (consoleDriver));
  28. }
  29. _netEvents = new NetEvents (consoleDriver);
  30. }
  31. void IMainLoopDriver.Setup (MainLoop mainLoop)
  32. {
  33. _mainLoop = mainLoop;
  34. if (ConsoleDriver.RunningUnitTests)
  35. {
  36. return;
  37. }
  38. Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
  39. }
  40. void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
  41. bool IMainLoopDriver.EventsPending ()
  42. {
  43. _waitForProbe.Set ();
  44. if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout))
  45. {
  46. return true;
  47. }
  48. try
  49. {
  50. if (!_eventReadyTokenSource.IsCancellationRequested)
  51. {
  52. // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there
  53. // are no timers, but there IS an idle handler waiting.
  54. _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
  55. }
  56. }
  57. catch (OperationCanceledException)
  58. {
  59. return true;
  60. }
  61. finally
  62. {
  63. _eventReady.Reset ();
  64. }
  65. _eventReadyTokenSource.Token.ThrowIfCancellationRequested ();
  66. if (!_eventReadyTokenSource.IsCancellationRequested)
  67. {
  68. return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
  69. }
  70. return true;
  71. }
  72. void IMainLoopDriver.Iteration ()
  73. {
  74. while (_resultQueue.Count > 0)
  75. {
  76. // Always dequeue even if it's null and invoke if isn't null
  77. if (_resultQueue.TryDequeue (out NetEvents.InputResult? dequeueResult))
  78. {
  79. if (dequeueResult is { })
  80. {
  81. ProcessInput?.Invoke (dequeueResult.Value);
  82. }
  83. }
  84. }
  85. }
  86. void IMainLoopDriver.TearDown ()
  87. {
  88. _inputHandlerTokenSource?.Cancel ();
  89. _inputHandlerTokenSource?.Dispose ();
  90. _eventReadyTokenSource?.Cancel ();
  91. _eventReadyTokenSource?.Dispose ();
  92. _eventReady?.Dispose ();
  93. _resultQueue?.Clear ();
  94. _waitForProbe?.Dispose ();
  95. _netEvents?.Dispose ();
  96. _netEvents = null;
  97. _mainLoop = null;
  98. }
  99. private void NetInputHandler ()
  100. {
  101. while (_mainLoop is { })
  102. {
  103. try
  104. {
  105. if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested)
  106. {
  107. _waitForProbe.Wait (_inputHandlerTokenSource.Token);
  108. }
  109. }
  110. catch (OperationCanceledException)
  111. {
  112. return;
  113. }
  114. finally
  115. {
  116. if (_waitForProbe.IsSet)
  117. {
  118. _waitForProbe.Reset ();
  119. }
  120. }
  121. if (_inputHandlerTokenSource.IsCancellationRequested)
  122. {
  123. return;
  124. }
  125. _inputHandlerTokenSource.Token.ThrowIfCancellationRequested ();
  126. if (_resultQueue.Count == 0)
  127. {
  128. _resultQueue.Enqueue (_netEvents.DequeueInput ());
  129. }
  130. try
  131. {
  132. while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out NetEvents.InputResult? result) && result is null)
  133. {
  134. // Dequeue null values
  135. _resultQueue.TryDequeue (out _);
  136. }
  137. }
  138. catch (InvalidOperationException) // Peek can raise an exception
  139. { }
  140. if (_resultQueue.Count > 0)
  141. {
  142. _eventReady.Set ();
  143. }
  144. }
  145. }
  146. }