MainLoop.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. 
  2. using System;
  3. using System.Collections.Generic;
  4. namespace Terminal.Gui {
  5. /// <summary>
  6. /// Public interface to create your own platform specific main loop driver.
  7. /// </summary>
  8. public interface IMainLoopDriver {
  9. /// <summary>
  10. /// Initializes the main loop driver, gets the calling main loop for the initialization.
  11. /// </summary>
  12. /// <param name="mainLoop">Main loop.</param>
  13. void Setup (MainLoop mainLoop);
  14. /// <summary>
  15. /// Wakes up the mainloop that might be waiting on input, must be thread safe.
  16. /// </summary>
  17. void Wakeup ();
  18. /// <summary>
  19. /// Must report whether there are any events pending, or even block waiting for events.
  20. /// </summary>
  21. /// <returns><c>true</c>, if there were pending events, <c>false</c> otherwise.</returns>
  22. /// <param name="wait">If set to <c>true</c> wait until an event is available, otherwise return immediately.</param>
  23. bool EventsPending (bool wait);
  24. /// <summary>
  25. /// The interation function.
  26. /// </summary>
  27. void MainIteration ();
  28. }
  29. /// <summary>
  30. /// Simple main loop implementation that can be used to monitor
  31. /// file descriptor, run timers and idle handlers.
  32. /// </summary>
  33. /// <remarks>
  34. /// Monitoring of file descriptors is only available on Unix, there
  35. /// does not seem to be a way of supporting this on Windows.
  36. /// </remarks>
  37. public class MainLoop {
  38. internal class Timeout {
  39. public TimeSpan Span;
  40. public Func<MainLoop, bool> Callback;
  41. }
  42. internal SortedList<long, Timeout> timeouts = new SortedList<long, Timeout> ();
  43. internal List<Func<bool>> idleHandlers = new List<Func<bool>> ();
  44. IMainLoopDriver driver;
  45. /// <summary>
  46. /// The current IMainLoopDriver in use.
  47. /// </summary>
  48. /// <value>The driver.</value>
  49. public IMainLoopDriver Driver => driver;
  50. /// <summary>
  51. /// Creates a new Mainloop, to run it you must provide a driver, and choose
  52. /// one of the implementations UnixMainLoop, NetMainLoop or WindowsMainLoop.
  53. /// </summary>
  54. public MainLoop (IMainLoopDriver driver)
  55. {
  56. this.driver = driver;
  57. driver.Setup (this);
  58. }
  59. /// <summary>
  60. /// Runs @action on the thread that is processing events
  61. /// </summary>
  62. public void Invoke (Action action)
  63. {
  64. AddIdle (() => {
  65. action ();
  66. return false;
  67. });
  68. }
  69. /// <summary>
  70. /// Executes the specified @idleHandler on the idle loop. The return value is a token to remove it.
  71. /// </summary>
  72. public Func<bool> AddIdle (Func<bool> idleHandler)
  73. {
  74. lock (idleHandlers)
  75. idleHandlers.Add (idleHandler);
  76. return idleHandler;
  77. }
  78. /// <summary>
  79. /// Removes the specified idleHandler from processing.
  80. /// </summary>
  81. public void RemoveIdle (Func<bool> idleHandler)
  82. {
  83. lock (idleHandler)
  84. idleHandlers.Remove (idleHandler);
  85. }
  86. void AddTimeout (TimeSpan time, Timeout timeout)
  87. {
  88. timeouts.Add ((DateTime.UtcNow + time).Ticks, timeout);
  89. }
  90. /// <summary>
  91. /// Adds a timeout to the mainloop.
  92. /// </summary>
  93. /// <remarks>
  94. /// When time time specified passes, the callback will be invoked.
  95. /// If the callback returns true, the timeout will be reset, repeating
  96. /// the invocation. If it returns false, the timeout will stop.
  97. ///
  98. /// The returned value is a token that can be used to stop the timeout
  99. /// by calling RemoveTimeout.
  100. /// </remarks>
  101. public object AddTimeout (TimeSpan time, Func<MainLoop, bool> callback)
  102. {
  103. if (callback == null)
  104. throw new ArgumentNullException (nameof (callback));
  105. var timeout = new Timeout () {
  106. Span = time,
  107. Callback = callback
  108. };
  109. AddTimeout (time, timeout);
  110. return timeout;
  111. }
  112. /// <summary>
  113. /// Removes a previously scheduled timeout
  114. /// </summary>
  115. /// <remarks>
  116. /// The token parameter is the value returned by AddTimeout.
  117. /// </remarks>
  118. public void RemoveTimeout (object token)
  119. {
  120. var idx = timeouts.IndexOfValue (token as Timeout);
  121. if (idx == -1)
  122. return;
  123. timeouts.RemoveAt (idx);
  124. }
  125. void RunTimers ()
  126. {
  127. long now = DateTime.UtcNow.Ticks;
  128. var copy = timeouts;
  129. timeouts = new SortedList<long, Timeout> ();
  130. foreach (var k in copy.Keys) {
  131. var timeout = copy [k];
  132. if (k < now) {
  133. if (timeout.Callback (this))
  134. AddTimeout (timeout.Span, timeout);
  135. } else
  136. timeouts.Add (k, timeout);
  137. }
  138. }
  139. void RunIdle ()
  140. {
  141. List<Func<bool>> iterate;
  142. lock (idleHandlers) {
  143. iterate = idleHandlers;
  144. idleHandlers = new List<Func<bool>> ();
  145. }
  146. foreach (var idle in iterate) {
  147. if (idle ())
  148. lock (idleHandlers)
  149. idleHandlers.Add (idle);
  150. }
  151. }
  152. bool running;
  153. /// <summary>
  154. /// Stops the mainloop.
  155. /// </summary>
  156. public void Stop ()
  157. {
  158. running = false;
  159. driver.Wakeup ();
  160. }
  161. /// <summary>
  162. /// Determines whether there are pending events to be processed.
  163. /// </summary>
  164. /// <remarks>
  165. /// You can use this method if you want to probe if events are pending.
  166. /// Typically used if you need to flush the input queue while still
  167. /// running some of your own code in your main thread.
  168. /// </remarks>
  169. public bool EventsPending (bool wait = false)
  170. {
  171. return driver.EventsPending (wait);
  172. }
  173. /// <summary>
  174. /// Runs one iteration of timers and file watches
  175. /// </summary>
  176. /// <remarks>
  177. /// You use this to process all pending events (timers, idle handlers and file watches).
  178. ///
  179. /// You can use it like this:
  180. /// while (main.EvensPending ()) MainIteration ();
  181. /// </remarks>
  182. public void MainIteration ()
  183. {
  184. if (timeouts.Count > 0)
  185. RunTimers ();
  186. driver.MainIteration ();
  187. lock (idleHandlers) {
  188. if (idleHandlers.Count > 0)
  189. RunIdle ();
  190. }
  191. }
  192. /// <summary>
  193. /// Runs the mainloop.
  194. /// </summary>
  195. public void Run ()
  196. {
  197. bool prev = running;
  198. running = true;
  199. while (running) {
  200. EventsPending (true);
  201. MainIteration ();
  202. }
  203. running = prev;
  204. }
  205. }
  206. }