#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 _inputHandlerTokenSource = new ();
private readonly BlockingCollection _resultQueue = new (new ConcurrentQueue ());
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)
{
ArgumentNullException.ThrowIfNull (consoleDriver);
_netEvents = new (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 ()
{
if (_resultQueue.Count > 0 || _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 _);
}
// If cancellation was requested then always return true
return true;
}
void IMainLoopDriver.Iteration ()
{
while (_resultQueue.Count > 0)
{
// Always dequeue even if it's null and invoke if isn't null
if (_resultQueue.TryTake (out NetEvents.InputResult dequeueResult))
{
ProcessInput?.Invoke (dequeueResult);
}
}
}
void IMainLoopDriver.TearDown ()
{
_inputHandlerTokenSource.Cancel ();
_inputHandlerTokenSource.Dispose ();
_eventReadyTokenSource.Cancel ();
_eventReadyTokenSource.Dispose ();
_eventReady.Dispose ();
_resultQueue.Dispose();
_netEvents?.Dispose ();
_netEvents = null;
_mainLoop = null;
}
private void NetInputHandler ()
{
while (_mainLoop is { })
{
try
{
if (_inputHandlerTokenSource.IsCancellationRequested)
{
return;
}
if (_resultQueue?.Count == 0 || _netEvents!._forceRead)
{
NetEvents.InputResult? result = _netEvents!.DequeueInput ();
if (result.HasValue)
{
_resultQueue?.Add (result.Value);
}
}
if (!_inputHandlerTokenSource.IsCancellationRequested && _resultQueue?.Count > 0)
{
_eventReady.Set ();
}
}
catch (OperationCanceledException)
{
return;
}
}
}
}