#nullable enable // // MainLoop.cs: IMainLoopDriver and MainLoop for Terminal.Gui // // Authors: // Miguel de Icaza (miguel@gnome.org) // using System.Collections.ObjectModel; namespace Terminal.Gui; /// Interface to create a platform specific driver. internal interface IMainLoopDriver { /// Must report whether there are any events pending, or even block waiting for events. /// , if there were pending events, otherwise. bool EventsPending (); /// The iteration function. void Iteration (); /// Initializes the , gets the calling main loop for the initialization. /// Call to release resources. /// Main loop. void Setup (MainLoop mainLoop); /// Tears down the driver. Releases resources created in . void TearDown (); /// Wakes up the that might be waiting on input, must be thread safe. void Wakeup (); } /// The MainLoop monitors timers and idle handlers. /// /// Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this /// on Windows. /// public class MainLoop : IDisposable { /// /// Gets the class responsible for handling idles and timeouts /// public ITimedEvents TimedEvents { get; } = new TimedEvents(); /// Creates a new MainLoop. /// Use to release resources. /// /// The instance (one of the implementations FakeMainLoop, UnixMainLoop, /// NetMainLoop or WindowsMainLoop). /// internal MainLoop (IMainLoopDriver driver) { MainLoopDriver = driver; driver.Setup (this); } /// The current in use. /// The main loop driver. internal IMainLoopDriver? MainLoopDriver { get; private set; } /// Used for unit tests. internal bool Running { get; set; } /// public void Dispose () { GC.SuppressFinalize (this); Stop (); Running = false; MainLoopDriver?.TearDown (); MainLoopDriver = null; } /// /// Adds specified idle handler function to processing. The handler function will be called /// once per iteration of the main loop after other events have been handled. /// /// /// Remove an idle handler by calling with the token this method returns. /// /// If the returns it will be removed and not called /// subsequently. /// /// /// Token that can be used to remove the idle handler with . // QUESTION: Why are we re-inventing the event wheel here? // PERF: This is heavy. // CONCURRENCY: Race conditions exist here. // CONCURRENCY: null delegates will hose this. // internal Func AddIdle (Func idleHandler) { TimedEvents.AddIdle (idleHandler); MainLoopDriver?.Wakeup (); return idleHandler; } /// Determines whether there are pending events to be processed. /// /// You can use this method if you want to probe if events are pending. Typically used if you need to flush the /// input queue while still running some of your own code in your main thread. /// internal bool EventsPending () { return MainLoopDriver!.EventsPending (); } /// Runs the . Used only for unit tests. internal void Run () { bool prev = Running; Running = true; while (Running) { EventsPending (); RunIteration (); } Running = prev; } /// Runs one iteration of timers and file watches /// /// Use this to process all pending events (timers, idle handlers and file watches). /// /// while (main.EventsPending ()) RunIteration (); /// /// internal void RunIteration () { RunAnsiScheduler (); MainLoopDriver?.Iteration (); TimedEvents.LockAndRunTimers (); TimedEvents.LockAndRunIdles (); } private void RunAnsiScheduler () { Application.Driver?.GetRequestScheduler ().RunSchedule (); } /// Stops the main loop driver and calls . Used only for unit tests. internal void Stop () { Running = false; Wakeup (); } /// Wakes up the that might be waiting on input. internal void Wakeup () { MainLoopDriver?.Wakeup (); } }