MainLoopCoordinator.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. using System.Collections.Concurrent;
  2. using Microsoft.Extensions.Logging;
  3. namespace Terminal.Gui.Drivers;
  4. /// <summary>
  5. /// <para>
  6. /// Handles creating the input loop thread and bootstrapping the
  7. /// <see cref="MainLoop{T}"/> that handles layout/drawing/events etc.
  8. /// </para>
  9. /// <para>This class is designed to be managed by <see cref="ApplicationV2"/></para>
  10. /// </summary>
  11. /// <typeparam name="T"></typeparam>
  12. internal class MainLoopCoordinator<T> : IMainLoopCoordinator
  13. {
  14. private readonly ConcurrentQueue<T> _inputBuffer;
  15. private readonly IInputProcessor _inputProcessor;
  16. private readonly IMainLoop<T> _loop;
  17. private readonly IComponentFactory<T> _componentFactory;
  18. private readonly CancellationTokenSource _tokenSource = new ();
  19. private IConsoleInput<T> _input;
  20. private IConsoleOutput _output;
  21. private readonly object _oLockInitialization = new ();
  22. private ConsoleDriverFacade<T> _facade;
  23. private Task _inputTask;
  24. private readonly ITimedEvents _timedEvents;
  25. private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
  26. /// <summary>
  27. /// Creates a new coordinator
  28. /// </summary>
  29. /// <param name="timedEvents"></param>
  30. /// <param name="inputBuffer"></param>
  31. /// <param name="loop"></param>
  32. /// <param name="componentFactory">Factory for creating driver components
  33. /// (<see cref="IConsoleOutput"/>, <see cref="IConsoleInput{T}"/> etc)</param>
  34. public MainLoopCoordinator (
  35. ITimedEvents timedEvents,
  36. ConcurrentQueue<T> inputBuffer,
  37. IMainLoop<T> loop,
  38. IComponentFactory<T> componentFactory
  39. )
  40. {
  41. _timedEvents = timedEvents;
  42. _inputBuffer = inputBuffer;
  43. _inputProcessor = componentFactory.CreateInputProcessor (_inputBuffer);
  44. _loop = loop;
  45. _componentFactory = componentFactory;
  46. }
  47. /// <summary>
  48. /// Starts the input loop thread in separate task (returning immediately).
  49. /// </summary>
  50. public async Task StartAsync ()
  51. {
  52. Logging.Logger.LogInformation ("Main Loop Coordinator booting...");
  53. _inputTask = Task.Run (RunInput);
  54. // Main loop is now booted on same thread as rest of users application
  55. BootMainLoop ();
  56. // Wait asynchronously for the semaphore or task failure.
  57. Task waitForSemaphore = _startupSemaphore.WaitAsync ();
  58. // Wait for either the semaphore to be released or the input task to crash.
  59. // ReSharper disable once UseConfigureAwaitFalse
  60. Task completedTask = await Task.WhenAny (waitForSemaphore, _inputTask);
  61. // Check if the task was the input task and if it has failed.
  62. if (completedTask == _inputTask)
  63. {
  64. if (_inputTask.IsFaulted)
  65. {
  66. throw _inputTask.Exception;
  67. }
  68. Logging.Logger.LogCritical("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)");
  69. }
  70. Logging.Logger.LogInformation ("Main Loop Coordinator booting complete");
  71. }
  72. private void RunInput ()
  73. {
  74. try
  75. {
  76. lock (_oLockInitialization)
  77. {
  78. // Instance must be constructed on the thread in which it is used.
  79. _input = _componentFactory.CreateInput ();
  80. _input.Initialize (_inputBuffer);
  81. BuildFacadeIfPossible ();
  82. }
  83. try
  84. {
  85. _input.Run (_tokenSource.Token);
  86. }
  87. catch (OperationCanceledException)
  88. { }
  89. _input.Dispose ();
  90. }
  91. catch (Exception e)
  92. {
  93. Logging.Logger.LogCritical (e, "Input loop crashed");
  94. throw;
  95. }
  96. if (_stopCalled)
  97. {
  98. Logging.Logger.LogInformation ("Input loop exited cleanly");
  99. }
  100. else
  101. {
  102. Logging.Logger.LogCritical ("Input loop exited early (stop not called)");
  103. }
  104. }
  105. /// <inheritdoc/>
  106. public void RunIteration () { _loop.Iteration (); }
  107. private void BootMainLoop ()
  108. {
  109. lock (_oLockInitialization)
  110. {
  111. // Instance must be constructed on the thread in which it is used.
  112. _output = _componentFactory.CreateOutput ();
  113. _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output,_componentFactory);
  114. BuildFacadeIfPossible ();
  115. }
  116. }
  117. private void BuildFacadeIfPossible ()
  118. {
  119. if (_input != null && _output != null)
  120. {
  121. _facade = new (
  122. _inputProcessor,
  123. _loop.OutputBuffer,
  124. _output,
  125. _loop.AnsiRequestScheduler,
  126. _loop.WindowSizeMonitor);
  127. Application.Driver = _facade;
  128. _startupSemaphore.Release ();
  129. }
  130. }
  131. private bool _stopCalled;
  132. /// <inheritdoc/>
  133. public void Stop ()
  134. {
  135. // Ignore repeated calls to Stop - happens if user spams Application.Shutdown().
  136. if (_stopCalled)
  137. {
  138. return;
  139. }
  140. _stopCalled = true;
  141. _tokenSource.Cancel ();
  142. _output.Dispose ();
  143. // Wait for input infinite loop to exit
  144. _inputTask.Wait ();
  145. }
  146. }