using System.Collections.Concurrent; using Microsoft.Extensions.Logging; namespace Terminal.Gui; /// /// /// Handles creating the input loop thread and bootstrapping the /// that handles layout/drawing/events etc. /// /// This class is designed to be managed by /// /// internal class MainLoopCoordinator : IMainLoopCoordinator { private readonly Func> _inputFactory; private readonly ConcurrentQueue _inputBuffer; private readonly IInputProcessor _inputProcessor; private readonly IMainLoop _loop; private readonly CancellationTokenSource _tokenSource = new (); private readonly Func _outputFactory; private IConsoleInput _input; private IConsoleOutput _output; private readonly object _oLockInitialization = new (); private ConsoleDriverFacade _facade; private Task _inputTask; private readonly ITimedEvents _timedEvents; private readonly SemaphoreSlim _startupSemaphore = new (0, 1); /// /// Creates a new coordinator /// /// /// /// Function to create a new input. This must call /// explicitly and cannot return an existing instance. This requirement arises because Windows /// console screen buffer APIs are thread-specific for certain operations. /// /// /// /// /// Function to create a new output. This must call /// explicitly and cannot return an existing instance. This requirement arises because Windows /// console screen buffer APIs are thread-specific for certain operations. /// /// public MainLoopCoordinator ( ITimedEvents timedEvents, Func> inputFactory, ConcurrentQueue inputBuffer, IInputProcessor inputProcessor, Func outputFactory, IMainLoop loop ) { _timedEvents = timedEvents; _inputFactory = inputFactory; _inputBuffer = inputBuffer; _inputProcessor = inputProcessor; _outputFactory = outputFactory; _loop = loop; } /// /// Starts the input loop thread in separate task (returning immediately). /// public async Task StartAsync () { Logging.Logger.LogInformation ("Main Loop Coordinator booting..."); _inputTask = Task.Run (RunInput); // Main loop is now booted on same thread as rest of users application BootMainLoop (); // Wait asynchronously for the semaphore or task failure. Task waitForSemaphore = _startupSemaphore.WaitAsync (); // Wait for either the semaphore to be released or the input task to crash. // ReSharper disable once UseConfigureAwaitFalse Task completedTask = await Task.WhenAny (waitForSemaphore, _inputTask); // Check if the task was the input task and if it has failed. if (completedTask == _inputTask) { if (_inputTask.IsFaulted) { throw _inputTask.Exception; } throw new ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)"); } Logging.Logger.LogInformation ("Main Loop Coordinator booting complete"); } private void RunInput () { try { lock (_oLockInitialization) { // Instance must be constructed on the thread in which it is used. _input = _inputFactory.Invoke (); _input.Initialize (_inputBuffer); BuildFacadeIfPossible (); } try { _input.Run (_tokenSource.Token); } catch (OperationCanceledException) { } _input.Dispose (); } catch (Exception e) { Logging.Logger.LogCritical (e, "Input loop crashed"); throw; } if (_stopCalled) { Logging.Logger.LogInformation ("Input loop exited cleanly"); } else { Logging.Logger.LogCritical ("Input loop exited early (stop not called)"); } } /// public void RunIteration () { _loop.Iteration (); } private void BootMainLoop () { lock (_oLockInitialization) { // Instance must be constructed on the thread in which it is used. _output = _outputFactory.Invoke (); _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output); BuildFacadeIfPossible (); } } private void BuildFacadeIfPossible () { if (_input != null && _output != null) { _facade = new ( _inputProcessor, _loop.OutputBuffer, _output, _loop.AnsiRequestScheduler, _loop.WindowSizeMonitor); Application.Driver = _facade; _startupSemaphore.Release (); } } private bool _stopCalled; /// public void Stop () { // Ignore repeated calls to Stop - happens if user spams Application.Shutdown(). if (_stopCalled) { return; } _stopCalled = true; _tokenSource.Cancel (); _output.Dispose (); // Wait for input infinite loop to exit _inputTask.Wait (); } }