#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 (); }
}