MainLoop.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #nullable enable
  2. using System.Collections.Concurrent;
  3. using System.Diagnostics;
  4. namespace Terminal.Gui.Drivers;
  5. /// <inheritdoc/>
  6. public class MainLoop<T> : IMainLoop<T>
  7. {
  8. private ITimedEvents? _timedEvents;
  9. private ConcurrentQueue<T>? _inputBuffer;
  10. private IInputProcessor? _inputProcessor;
  11. private IConsoleOutput? _out;
  12. private AnsiRequestScheduler? _ansiRequestScheduler;
  13. private IWindowSizeMonitor? _windowSizeMonitor;
  14. /// <inheritdoc/>
  15. public ITimedEvents TimedEvents
  16. {
  17. get => _timedEvents ?? throw new NotInitializedException (nameof (TimedEvents));
  18. private set => _timedEvents = value;
  19. }
  20. // TODO: follow above pattern for others too
  21. /// <summary>
  22. /// The input events thread-safe collection. This is populated on separate
  23. /// thread by a <see cref="IConsoleInput{T}"/>. Is drained as part of each
  24. /// <see cref="Iteration"/>
  25. /// </summary>
  26. public ConcurrentQueue<T> InputBuffer
  27. {
  28. get => _inputBuffer ?? throw new NotInitializedException (nameof (InputBuffer));
  29. private set => _inputBuffer = value;
  30. }
  31. /// <inheritdoc/>
  32. public IInputProcessor InputProcessor
  33. {
  34. get => _inputProcessor ?? throw new NotInitializedException (nameof (InputProcessor));
  35. private set => _inputProcessor = value;
  36. }
  37. /// <inheritdoc/>
  38. public IOutputBuffer OutputBuffer { get; } = new OutputBuffer ();
  39. /// <inheritdoc/>
  40. public IConsoleOutput Out
  41. {
  42. get => _out ?? throw new NotInitializedException (nameof (Out));
  43. private set => _out = value;
  44. }
  45. /// <inheritdoc/>
  46. public AnsiRequestScheduler AnsiRequestScheduler
  47. {
  48. get => _ansiRequestScheduler ?? throw new NotInitializedException (nameof (AnsiRequestScheduler));
  49. private set => _ansiRequestScheduler = value;
  50. }
  51. /// <inheritdoc/>
  52. public IWindowSizeMonitor WindowSizeMonitor
  53. {
  54. get => _windowSizeMonitor ?? throw new NotInitializedException (nameof (WindowSizeMonitor));
  55. private set => _windowSizeMonitor = value;
  56. }
  57. /// <summary>
  58. /// Handles raising events and setting required draw status etc when <see cref="Application.Top"/> changes
  59. /// </summary>
  60. public IToplevelTransitionManager ToplevelTransitionManager = new ToplevelTransitionManager ();
  61. /// <summary>
  62. /// Determines how to get the current system type, adjust
  63. /// in unit tests to simulate specific timings.
  64. /// </summary>
  65. public Func<DateTime> Now { get; set; } = () => DateTime.Now;
  66. /// <summary>
  67. /// Initializes the class with the provided subcomponents
  68. /// </summary>
  69. /// <param name="timedEvents"></param>
  70. /// <param name="inputBuffer"></param>
  71. /// <param name="inputProcessor"></param>
  72. /// <param name="consoleOutput"></param>
  73. /// <param name="componentFactory"></param>
  74. public void Initialize (
  75. ITimedEvents timedEvents,
  76. ConcurrentQueue<T> inputBuffer,
  77. IInputProcessor inputProcessor,
  78. IConsoleOutput consoleOutput,
  79. IComponentFactory<T> componentFactory
  80. )
  81. {
  82. InputBuffer = inputBuffer;
  83. Out = consoleOutput;
  84. InputProcessor = inputProcessor;
  85. TimedEvents = timedEvents;
  86. AnsiRequestScheduler = new (InputProcessor.GetParser ());
  87. WindowSizeMonitor = componentFactory.CreateWindowSizeMonitor (Out, OutputBuffer);
  88. }
  89. /// <inheritdoc/>
  90. public void Iteration ()
  91. {
  92. Application.RaiseIteration ();
  93. DateTime dt = Now ();
  94. int timeAllowed = 1000 / Math.Max(1,(int)Application.MaximumIterationsPerSecond);
  95. IterationImpl ();
  96. TimeSpan took = Now () - dt;
  97. TimeSpan sleepFor = TimeSpan.FromMilliseconds (timeAllowed) - took;
  98. Logging.TotalIterationMetric.Record (took.Milliseconds);
  99. if (sleepFor.Milliseconds > 0)
  100. {
  101. Task.Delay (sleepFor).Wait ();
  102. }
  103. }
  104. internal void IterationImpl ()
  105. {
  106. InputProcessor.ProcessQueue ();
  107. ToplevelTransitionManager.RaiseReadyEventIfNeeded ();
  108. ToplevelTransitionManager.HandleTopMaybeChanging ();
  109. if (Application.Top != null)
  110. {
  111. bool needsDrawOrLayout = AnySubViewsNeedDrawn (Application.Popover?.GetActivePopover () as View)
  112. || AnySubViewsNeedDrawn (Application.Top)
  113. || (Application.MouseGrabHandler.MouseGrabView != null && AnySubViewsNeedDrawn (Application.MouseGrabHandler.MouseGrabView));
  114. bool sizeChanged = WindowSizeMonitor.Poll ();
  115. if (needsDrawOrLayout || sizeChanged)
  116. {
  117. Logging.Redraws.Add (1);
  118. Application.LayoutAndDrawImpl (true);
  119. Out.Write (OutputBuffer);
  120. Out.SetCursorVisibility (CursorVisibility.Default);
  121. }
  122. SetCursor ();
  123. }
  124. var swCallbacks = Stopwatch.StartNew ();
  125. TimedEvents.RunTimers ();
  126. Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
  127. }
  128. private void SetCursor ()
  129. {
  130. View? mostFocused = Application.Top!.MostFocused;
  131. if (mostFocused == null)
  132. {
  133. return;
  134. }
  135. Point? to = mostFocused.PositionCursor ();
  136. if (to.HasValue)
  137. {
  138. // Translate to screen coordinates
  139. to = mostFocused.ViewportToScreen (to.Value);
  140. Out.SetCursorPosition (to.Value.X, to.Value.Y);
  141. Out.SetCursorVisibility (mostFocused.CursorVisibility);
  142. }
  143. else
  144. {
  145. Out.SetCursorVisibility (CursorVisibility.Invisible);
  146. }
  147. }
  148. private bool AnySubViewsNeedDrawn (View? v)
  149. {
  150. if (v is null)
  151. {
  152. return false;
  153. }
  154. if (v.NeedsDraw || v.NeedsLayout)
  155. {
  156. // Logging.Trace ($"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");
  157. return true;
  158. }
  159. foreach (View subview in v.SubViews)
  160. {
  161. if (AnySubViewsNeedDrawn (subview))
  162. {
  163. return true;
  164. }
  165. }
  166. return false;
  167. }
  168. /// <inheritdoc/>
  169. public void Dispose ()
  170. {
  171. // TODO release managed resources here
  172. }
  173. }