mainloop.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. //
  2. // mainloop.cs: Simple managed mainloop implementation.
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. // Copyright (C) 2011 Novell (http://www.novell.com)
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining
  10. // a copy of this software and associated documentation files (the
  11. // "Software"), to deal in the Software without restriction, including
  12. // without limitation the rights to use, copy, modify, merge, publish,
  13. // distribute, sublicense, and/or sell copies of the Software, and to
  14. // permit persons to whom the Software is furnished to do so, subject to
  15. // the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be
  18. // included in all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  22. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  24. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  25. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27. //
  28. using System.Collections.Generic;
  29. using System;
  30. using System.Runtime.InteropServices;
  31. using System.Threading;
  32. namespace Mono.Terminal {
  33. /// <summary>
  34. /// Simple main loop implementation that can be used to monitor
  35. /// file descriptor, run timers and idle handlers.
  36. /// </summary>
  37. public class MainLoop {
  38. static bool useUnix = true;
  39. /// <summary>
  40. /// Condition on which to wake up from file descriptor activity. These match the Linux/BSD poll definitions.
  41. /// </summary>
  42. [Flags]
  43. public enum Condition : short {
  44. /// <summary>
  45. /// There is data to read
  46. /// </summary>
  47. PollIn = 1,
  48. /// <summary>
  49. /// Writing to the specified descriptor will not block
  50. /// </summary>
  51. PollOut = 4,
  52. /// <summary>
  53. /// There is urgent data to read
  54. /// </summary>
  55. PollPri = 2,
  56. /// <summary>
  57. /// Error condition on output
  58. /// </summary>
  59. PollErr = 8,
  60. /// <summary>
  61. /// Hang-up on output
  62. /// </summary>
  63. PollHup = 16,
  64. /// <summary>
  65. /// File descriptor is not open.
  66. /// </summary>
  67. PollNval = 32
  68. }
  69. class Watch {
  70. public int File;
  71. public Condition Condition;
  72. public Func<MainLoop,bool> Callback;
  73. }
  74. class Timeout {
  75. public TimeSpan Span;
  76. public Func<MainLoop,bool> Callback;
  77. }
  78. Dictionary <int, Watch> descriptorWatchers = new Dictionary<int,Watch>();
  79. SortedList <double, Timeout> timeouts = new SortedList<double,Timeout> ();
  80. List<Func<bool>> idleHandlers = new List<Func<bool>> ();
  81. [StructLayout(LayoutKind.Sequential)]
  82. struct Pollfd {
  83. public int fd;
  84. public short events, revents;
  85. }
  86. [DllImport ("libc")]
  87. extern static int poll ([In,Out]Pollfd[] ufds, uint nfds, int timeout);
  88. [DllImport ("libc")]
  89. extern static int pipe ([In,Out]int [] pipes);
  90. [DllImport ("libc")]
  91. extern static int read (int fd, IntPtr buf, IntPtr n);
  92. [DllImport ("libc")]
  93. extern static int write (int fd, IntPtr buf, IntPtr n);
  94. Pollfd [] pollmap;
  95. bool poll_dirty = true;
  96. int [] wakeupPipes = new int [2];
  97. static IntPtr ignore = Marshal.AllocHGlobal (1);
  98. /// <summary>
  99. /// Default constructor
  100. /// </summary>
  101. public MainLoop ()
  102. {
  103. pipe (wakeupPipes);
  104. AddWatch (wakeupPipes [0], Condition.PollIn, ml => {
  105. read (wakeupPipes [0], ignore, (IntPtr) 1);
  106. return true;
  107. });
  108. }
  109. void Wakeup ()
  110. {
  111. write (wakeupPipes [1], ignore, (IntPtr) 1);
  112. }
  113. /// <summary>
  114. /// Runs @action on the thread that is processing events
  115. /// </summary>
  116. public void Invoke (Action action)
  117. {
  118. AddIdle (()=> {
  119. action ();
  120. return false;
  121. });
  122. Wakeup ();
  123. }
  124. /// <summary>
  125. /// Executes the specified @idleHandler on the idle loop. The return value is a token to remove it.
  126. /// </summary>
  127. public Func<bool> AddIdle (Func<bool> idleHandler)
  128. {
  129. lock (idleHandlers)
  130. idleHandlers.Add (idleHandler);
  131. return idleHandler;
  132. }
  133. /// <summary>
  134. /// Removes the specified idleHandler from processing.
  135. /// </summary>
  136. public void RemoveIdle (Func<bool> idleHandler)
  137. {
  138. lock (idleHandler)
  139. idleHandlers.Remove (idleHandler);
  140. }
  141. /// <summary>
  142. /// Watches a file descriptor for activity.
  143. /// </summary>
  144. /// <remarks>
  145. /// When the condition is met, the provided callback
  146. /// is invoked. If the callback returns false, the
  147. /// watch is automatically removed.
  148. ///
  149. /// The return value is a token that represents this watch, you can
  150. /// use this token to remove the watch by calling RemoveWatch.
  151. /// </remarks>
  152. public object AddWatch (int fileDescriptor, Condition condition, Func<MainLoop,bool> callback)
  153. {
  154. if (callback == null)
  155. throw new ArgumentNullException ("callback");
  156. if (!useUnix)
  157. throw new Exception ("AddWatch is only supported for Unix");
  158. var watch = new Watch () { Condition = condition, Callback = callback, File = fileDescriptor };
  159. descriptorWatchers [fileDescriptor] = watch;
  160. poll_dirty = true;
  161. return watch;
  162. }
  163. /// <summary>
  164. /// This event is raised when a key is pressed when using the Windows driver.
  165. /// </summary>
  166. public Action<ConsoleKeyInfo> WindowsKeyPressed;
  167. /// <summary>
  168. /// Removes an active watch from the mainloop.
  169. /// </summary>
  170. /// <remarks>
  171. /// The token parameter is the value returned from AddWatch
  172. /// </remarks>
  173. public void RemoveWatch (object token)
  174. {
  175. var watch = token as Watch;
  176. if (watch == null)
  177. return;
  178. descriptorWatchers.Remove (watch.File);
  179. }
  180. void AddTimeout (TimeSpan time, Timeout timeout)
  181. {
  182. timeouts.Add ((DateTime.UtcNow + time).Ticks, timeout);
  183. }
  184. /// <summary>
  185. /// Adds a timeout to the mainloop.
  186. /// </summary>
  187. /// <remarks>
  188. /// When time time specified passes, the callback will be invoked.
  189. /// If the callback returns true, the timeout will be reset, repeating
  190. /// the invocation. If it returns false, the timeout will stop.
  191. ///
  192. /// The returned value is a token that can be used to stop the timeout
  193. /// by calling RemoveTimeout.
  194. /// </remarks>
  195. public object AddTimeout (TimeSpan time, Func<MainLoop,bool> callback)
  196. {
  197. if (callback == null)
  198. throw new ArgumentNullException ("callback");
  199. var timeout = new Timeout () {
  200. Span = time,
  201. Callback = callback
  202. };
  203. AddTimeout (time, timeout);
  204. return timeout;
  205. }
  206. /// <summary>
  207. /// Removes a previously scheduled timeout
  208. /// </summary>
  209. /// <remarks>
  210. /// The token parameter is the value returned by AddTimeout.
  211. /// </remarks>
  212. public void RemoveTimeout (object token)
  213. {
  214. var idx = timeouts.IndexOfValue (token as Timeout);
  215. if (idx == -1)
  216. return;
  217. timeouts.RemoveAt (idx);
  218. }
  219. void UpdatePollMap ()
  220. {
  221. if (!poll_dirty)
  222. return;
  223. poll_dirty = false;
  224. pollmap = new Pollfd [descriptorWatchers.Count];
  225. int i = 0;
  226. foreach (var fd in descriptorWatchers.Keys){
  227. pollmap [i].fd = fd;
  228. pollmap [i].events = (short) descriptorWatchers [fd].Condition;
  229. i++;
  230. }
  231. }
  232. void RunTimers ()
  233. {
  234. long now = DateTime.UtcNow.Ticks;
  235. var copy = timeouts;
  236. timeouts = new SortedList<double,Timeout> ();
  237. foreach (var k in copy.Keys){
  238. var timeout = copy [k];
  239. if (k < now) {
  240. if (timeout.Callback (this))
  241. AddTimeout (timeout.Span, timeout);
  242. } else
  243. timeouts.Add (k, timeout);
  244. }
  245. }
  246. void RunIdle ()
  247. {
  248. List<Func<bool>> iterate;
  249. lock (idleHandlers){
  250. iterate = idleHandlers;
  251. idleHandlers = new List<Func<bool>> ();
  252. }
  253. foreach (var idle in iterate){
  254. if (idle ())
  255. lock (idleHandlers)
  256. idleHandlers.Add (idle);
  257. }
  258. }
  259. bool running;
  260. /// <summary>
  261. /// Stops the mainloop.
  262. /// </summary>
  263. public void Stop ()
  264. {
  265. running = false;
  266. Wakeup ();
  267. }
  268. AutoResetEvent keyReady = new AutoResetEvent (false);
  269. AutoResetEvent waitForProbe = new AutoResetEvent (false);
  270. ConsoleKeyInfo? windowsKeyResult = null;
  271. void WindowsKeyReader ()
  272. {
  273. while (true) {
  274. waitForProbe.WaitOne ();
  275. var result = Console.ReadKey ();
  276. keyReady.Set ();
  277. }
  278. }
  279. /// <summary>
  280. /// Determines whether there are pending events to be processed.
  281. /// </summary>
  282. /// <remarks>
  283. /// You can use this method if you want to probe if events are pending.
  284. /// Typically used if you need to flush the input queue while still
  285. /// running some of your own code in your main thread.
  286. /// </remarks>
  287. public bool EventsPending (bool wait = false)
  288. {
  289. long now = DateTime.UtcNow.Ticks;
  290. if (useUnix) {
  291. int pollTimeout, n;
  292. if (timeouts.Count > 0)
  293. pollTimeout = (int)((timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
  294. else
  295. pollTimeout = -1;
  296. if (!wait)
  297. pollTimeout = 0;
  298. UpdatePollMap ();
  299. n = poll (pollmap, (uint)pollmap.Length, pollTimeout);
  300. int ic;
  301. lock (idleHandlers)
  302. ic = idleHandlers.Count;
  303. return n > 0 || timeouts.Count > 0 && ((timeouts.Keys [0] - DateTime.UtcNow.Ticks) < 0) || ic > 0;
  304. } else {
  305. windowsKeyResult = null;
  306. waitForProbe.Set ();
  307. keyReady.WaitOne ();
  308. return false;
  309. }
  310. }
  311. /// <summary>
  312. /// Runs one iteration of timers and file watches
  313. /// </summary>
  314. /// <remarks>
  315. /// You use this to process all pending events (timers, idle handlers and file watches).
  316. ///
  317. /// You can use it like this:
  318. /// while (main.EvensPending ()) MainIteration ();
  319. /// </remarks>
  320. public void MainIteration ()
  321. {
  322. if (timeouts.Count > 0)
  323. RunTimers ();
  324. if (useUnix) {
  325. foreach (var p in pollmap) {
  326. Watch watch;
  327. if (p.revents == 0)
  328. continue;
  329. if (!descriptorWatchers.TryGetValue (p.fd, out watch))
  330. continue;
  331. if (!watch.Callback (this))
  332. descriptorWatchers.Remove (p.fd);
  333. }
  334. } else {
  335. if (windowsKeyResult.HasValue) {
  336. if (WindowsKeyPressed != null)
  337. WindowsKeyPressed (windowsKeyResult.Value);
  338. windowsKeyResult = null;
  339. }
  340. }
  341. if (idleHandlers.Count > 0)
  342. RunIdle ();
  343. }
  344. /// <summary>
  345. /// Runs the mainloop.
  346. /// </summary>
  347. public void Run ()
  348. {
  349. bool prev = running;
  350. running = true;
  351. while (running){
  352. EventsPending (true);
  353. MainIteration ();
  354. }
  355. running = prev;
  356. }
  357. }
  358. }