using System.Collections.Concurrent;
namespace Terminal.Gui;
///
/// Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is
/// cross-platform but lacks things like file descriptor monitoring.
///
/// This implementation is used for NetDriver.
internal class NetMainLoop : IMainLoopDriver
{
internal NetEvents _netEvents;
/// Invoked when a Key is pressed.
internal Action ProcessInput;
private readonly ManualResetEventSlim _eventReady = new (false);
private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
private readonly ConcurrentQueue _resultQueue = new ();
internal readonly ManualResetEventSlim _waitForProbe = new (false);
private readonly CancellationTokenSource _eventReadyTokenSource = new ();
private MainLoop _mainLoop;
/// Initializes the class with the console driver.
/// Passing a consoleDriver is provided to capture windows resizing.
/// The console driver used by this Net main loop.
///
public NetMainLoop (ConsoleDriver consoleDriver = null)
{
if (consoleDriver is null)
{
throw new ArgumentNullException (nameof (consoleDriver));
}
_netEvents = new NetEvents (consoleDriver);
}
void IMainLoopDriver.Setup (MainLoop mainLoop)
{
_mainLoop = mainLoop;
if (ConsoleDriver.RunningUnitTests)
{
return;
}
Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
}
void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
bool IMainLoopDriver.EventsPending ()
{
_waitForProbe.Set ();
if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout))
{
return true;
}
try
{
if (!_eventReadyTokenSource.IsCancellationRequested)
{
// Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there
// are no timers, but there IS an idle handler waiting.
_eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
}
}
catch (OperationCanceledException)
{
return true;
}
finally
{
_eventReady.Reset ();
}
_eventReadyTokenSource.Token.ThrowIfCancellationRequested ();
if (!_eventReadyTokenSource.IsCancellationRequested)
{
return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
}
return true;
}
void IMainLoopDriver.Iteration ()
{
while (_resultQueue.Count > 0)
{
// Always dequeue even if it's null and invoke if isn't null
if (_resultQueue.TryDequeue (out NetEvents.InputResult? dequeueResult))
{
if (dequeueResult is { })
{
ProcessInput?.Invoke (dequeueResult.Value);
}
}
}
}
void IMainLoopDriver.TearDown ()
{
_inputHandlerTokenSource?.Cancel ();
_inputHandlerTokenSource?.Dispose ();
_eventReadyTokenSource?.Cancel ();
_eventReadyTokenSource?.Dispose ();
_eventReady?.Dispose ();
_resultQueue?.Clear ();
_waitForProbe?.Dispose ();
_netEvents?.Dispose ();
_netEvents = null;
_mainLoop = null;
}
private void NetInputHandler ()
{
while (_mainLoop is { })
{
try
{
if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested)
{
_waitForProbe.Wait (_inputHandlerTokenSource.Token);
}
}
catch (OperationCanceledException)
{
return;
}
finally
{
if (_waitForProbe.IsSet)
{
_waitForProbe.Reset ();
}
}
if (_inputHandlerTokenSource.IsCancellationRequested)
{
return;
}
_inputHandlerTokenSource.Token.ThrowIfCancellationRequested ();
if (_resultQueue.Count == 0)
{
_resultQueue.Enqueue (_netEvents.DequeueInput ());
}
try
{
while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out NetEvents.InputResult? result) && result is null)
{
// Dequeue null values
_resultQueue.TryDequeue (out _);
}
}
catch (InvalidOperationException) // Peek can raise an exception
{ }
if (_resultQueue.Count > 0)
{
_eventReady.Set ();
}
}
}
}