#nullable enable
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 _eventReadyTokenSource = new ();
private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
private readonly ManualResetEventSlim _waitForProbe = new (false);
private readonly ConcurrentQueue _resultQueue = new ();
private MainLoop? _mainLoop;
/// Initializes the class with the console driver.
/// Passing a IConsoleDriver is provided to capture windows resizing.
/// The console driver used by this Net main loop.
///
public NetMainLoop (IConsoleDriver consoleDriver)
{
ArgumentNullException.ThrowIfNull (consoleDriver);
_netEvents = new (consoleDriver);
}
void IMainLoopDriver.Setup (MainLoop mainLoop)
{
_mainLoop = mainLoop;
if (!ConsoleDriver.RunningUnitTests)
{
Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
}
}
void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
bool IMainLoopDriver.EventsPending ()
{
if (ConsoleDriver.RunningUnitTests)
{
return true;
}
_waitForProbe.Set ();
if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.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.TimedEvents.CheckTimersAndIdleHandlers (out _);
}
// If cancellation was requested then always return true
return true;
}
void IMainLoopDriver.Iteration ()
{
while (!ConsoleDriver.RunningUnitTests && _resultQueue.TryDequeue (out NetEvents.InputResult inputRecords))
{
ProcessInput?.Invoke (inputRecords);
}
}
void IMainLoopDriver.TearDown ()
{
_inputHandlerTokenSource.Cancel ();
_inputHandlerTokenSource.Dispose ();
_eventReadyTokenSource.Cancel ();
_eventReadyTokenSource.Dispose ();
_eventReady.Dispose ();
_waitForProbe.Dispose ();
_resultQueue.Clear ();
_netEvents?.Dispose ();
_netEvents = null;
_mainLoop = null;
}
private void NetInputHandler ()
{
while (_mainLoop is { })
{
try
{
if (!_inputHandlerTokenSource.IsCancellationRequested)
{
try
{
_waitForProbe.Wait (_inputHandlerTokenSource.Token);
}
catch (Exception ex)
{
if (ex is OperationCanceledException or ObjectDisposedException)
{
return;
}
throw;
}
_waitForProbe.Reset ();
}
ProcessInputQueue ();
}
catch (OperationCanceledException)
{
return;
}
}
}
private void ProcessInputQueue ()
{
if (_resultQueue.Count == 0)
{
NetEvents.InputResult? result = _netEvents!.DequeueInput ();
if (result.HasValue)
{
_resultQueue.Enqueue (result.Value);
_eventReady.Set ();
}
}
}
}