MainLoop.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. #nullable enable
  2. //
  3. // MainLoop.cs: IMainLoopDriver and MainLoop for Terminal.Gui
  4. //
  5. // Authors:
  6. // Miguel de Icaza ([email protected])
  7. //
  8. using System.Collections.ObjectModel;
  9. namespace Terminal.Gui;
  10. /// <summary>Interface to create a platform specific <see cref="MainLoop"/> driver.</summary>
  11. internal interface IMainLoopDriver
  12. {
  13. /// <summary>Must report whether there are any events pending, or even block waiting for events.</summary>
  14. /// <returns><see langword="true"/>, if there were pending events, <see langword="false"/> otherwise.</returns>
  15. bool EventsPending ();
  16. /// <summary>The iteration function.</summary>
  17. void Iteration ();
  18. /// <summary>Initializes the <see cref="MainLoop"/>, gets the calling main loop for the initialization.</summary>
  19. /// <remarks>Call <see cref="TearDown"/> to release resources.</remarks>
  20. /// <param name="mainLoop">Main loop.</param>
  21. void Setup (MainLoop mainLoop);
  22. /// <summary>Tears down the <see cref="MainLoop"/> driver. Releases resources created in <see cref="Setup"/>.</summary>
  23. void TearDown ();
  24. /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input, must be thread safe.</summary>
  25. void Wakeup ();
  26. }
  27. /// <summary>The MainLoop monitors timers and idle handlers.</summary>
  28. /// <remarks>
  29. /// Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this
  30. /// on Windows.
  31. /// </remarks>
  32. public class MainLoop : IDisposable
  33. {
  34. /// <summary>
  35. /// Gets the class responsible for handling idles and timeouts
  36. /// </summary>
  37. public ITimedEvents TimedEvents { get; } = new TimedEvents();
  38. /// <summary>Creates a new MainLoop.</summary>
  39. /// <remarks>Use <see cref="Dispose"/> to release resources.</remarks>
  40. /// <param name="driver">
  41. /// The <see cref="IConsoleDriver"/> instance (one of the implementations FakeMainLoop, UnixMainLoop,
  42. /// NetMainLoop or WindowsMainLoop).
  43. /// </param>
  44. internal MainLoop (IMainLoopDriver driver)
  45. {
  46. MainLoopDriver = driver;
  47. driver.Setup (this);
  48. }
  49. /// <summary>The current <see cref="IMainLoopDriver"/> in use.</summary>
  50. /// <value>The main loop driver.</value>
  51. internal IMainLoopDriver? MainLoopDriver { get; private set; }
  52. /// <summary>Used for unit tests.</summary>
  53. internal bool Running { get; set; }
  54. /// <inheritdoc/>
  55. public void Dispose ()
  56. {
  57. GC.SuppressFinalize (this);
  58. Stop ();
  59. Running = false;
  60. MainLoopDriver?.TearDown ();
  61. MainLoopDriver = null;
  62. }
  63. /// <summary>
  64. /// Adds specified idle handler function to <see cref="MainLoop"/> processing. The handler function will be called
  65. /// once per iteration of the main loop after other events have been handled.
  66. /// </summary>
  67. /// <remarks>
  68. /// <para>Remove an idle handler by calling <see cref="TimedEvents.RemoveIdle(Func{bool})"/> with the token this method returns.</para>
  69. /// <para>
  70. /// If the <paramref name="idleHandler"/> returns <see langword="false"/> it will be removed and not called
  71. /// subsequently.
  72. /// </para>
  73. /// </remarks>
  74. /// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="TimedEvents.RemoveIdle(Func{bool})"/> .</param>
  75. // QUESTION: Why are we re-inventing the event wheel here?
  76. // PERF: This is heavy.
  77. // CONCURRENCY: Race conditions exist here.
  78. // CONCURRENCY: null delegates will hose this.
  79. //
  80. internal Func<bool> AddIdle (Func<bool> idleHandler)
  81. {
  82. TimedEvents.AddIdle (idleHandler);
  83. MainLoopDriver?.Wakeup ();
  84. return idleHandler;
  85. }
  86. /// <summary>Determines whether there are pending events to be processed.</summary>
  87. /// <remarks>
  88. /// You can use this method if you want to probe if events are pending. Typically used if you need to flush the
  89. /// input queue while still running some of your own code in your main thread.
  90. /// </remarks>
  91. internal bool EventsPending () { return MainLoopDriver!.EventsPending (); }
  92. /// <summary>Runs the <see cref="MainLoop"/>. Used only for unit tests.</summary>
  93. internal void Run ()
  94. {
  95. bool prev = Running;
  96. Running = true;
  97. while (Running)
  98. {
  99. EventsPending ();
  100. RunIteration ();
  101. }
  102. Running = prev;
  103. }
  104. /// <summary>Runs one iteration of timers and file watches</summary>
  105. /// <remarks>
  106. /// Use this to process all pending events (timers, idle handlers and file watches).
  107. /// <code>
  108. /// while (main.EventsPending ()) RunIteration ();
  109. /// </code>
  110. /// </remarks>
  111. internal void RunIteration ()
  112. {
  113. RunAnsiScheduler ();
  114. MainLoopDriver?.Iteration ();
  115. TimedEvents.LockAndRunTimers ();
  116. TimedEvents.LockAndRunIdles ();
  117. }
  118. private void RunAnsiScheduler ()
  119. {
  120. Application.Driver?.GetRequestScheduler ().RunSchedule ();
  121. }
  122. /// <summary>Stops the main loop driver and calls <see cref="IMainLoopDriver.Wakeup"/>. Used only for unit tests.</summary>
  123. internal void Stop ()
  124. {
  125. Running = false;
  126. Wakeup ();
  127. }
  128. /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input.</summary>
  129. internal void Wakeup () { MainLoopDriver?.Wakeup (); }
  130. }