From 54f62645a238440b2a552a62a916479a89e99fe1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 23:18:56 +0000 Subject: [PATCH] Add ProcessAnsiRequestHandler method task. --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 163 ++++++++++++------ .../CursesDriver/CursesDriver.cs | 2 + .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 3 - .../ConsoleDrivers/NetDriver/NetDriver.cs | 5 + .../WindowsDriver/WindowsDriver.cs | 2 + 5 files changed, 115 insertions(+), 60 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 6caf6948f..3a54168fe 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Collections.Concurrent; using System.Diagnostics; namespace Terminal.Gui; @@ -12,6 +13,79 @@ namespace Terminal.Gui; /// public abstract class ConsoleDriver { + private readonly ManualResetEventSlim _waitAnsiRequest = new (false); + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private readonly CancellationTokenSource? _ansiRequestTokenSource = new (); + private readonly ConcurrentQueue _requestQueue = new (); + private readonly ConcurrentQueue _responseQueue = new (); + private IMainLoopDriver? _mainLoopDriver; + + internal void ProcessAnsiRequestHandler () + { + while (_ansiRequestTokenSource is { IsCancellationRequested: false}) + { + try + { + if (_requestQueue.Count == 0) + { + try + { + _waitAnsiRequest.Wait (_ansiRequestTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } + + _waitAnsiRequest.Reset (); + } + + while (_requestQueue.TryDequeue (out AnsiEscapeSequenceRequest? ansiRequest)) + { + try + { + lock (ansiRequest._responseLock) + { + AnsiEscapeSequenceRequest? request = ansiRequest; + + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == request); + Debug.Assert (e == request.AnsiEscapeSequenceResponse); + + _responseQueue.Enqueue (request); + + _waitAnsiResponse.Set (); + }; + + AnsiEscapeSequenceRequests.Add (ansiRequest); + + WriteRaw (ansiRequest.Request); + + _mainLoopDriver!._forceRead = true; + } + + if (!_ansiRequestTokenSource.IsCancellationRequested) + { + _mainLoopDriver._waitForInput.Set (); + + _waitAnsiRequest.Wait (_ansiRequestTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return; + } + + } + } + catch (OperationCanceledException) + { + return; + } + } + } + /// /// Set this to true in any unit tests that attempt to test drivers other than FakeDriver. /// @@ -32,82 +106,57 @@ public abstract class ConsoleDriver #region ANSI Esc Sequence Handling - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private CancellationTokenSource? _ansiResponseTokenSource; - - // QUESTION: Should this be virtual with a default implementation that does the common stuff? - // QUESTION: Looking at the implementations of this method, there is TONs of duplicated code. - // QUESTION: We should figure out how to find just the things that are unique to each driver and - // QUESTION: create more fine-grained APIs to handle those. /// - /// Provide handling for the terminal write ANSI escape sequence request. + /// Provide unique handling for all the terminal write ANSI escape sequence request. /// /// The object. /// The object. - /// The request response. - public virtual bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) + /// if the request response is valid, otherwise. + public bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) { - if (mainLoopDriver is null) - { - return false; - } + ArgumentNullException.ThrowIfNull (mainLoopDriver, nameof (mainLoopDriver)); + ArgumentNullException.ThrowIfNull (ansiRequest, nameof (ansiRequest)); - _ansiResponseTokenSource ??= new (); + lock (ansiRequest._responseLock) + { + _mainLoopDriver = mainLoopDriver; + _requestQueue.Enqueue (ansiRequest); + + _waitAnsiRequest.Set (); + } try { + _waitAnsiResponse.Wait (_ansiRequestTokenSource!.Token); + + _waitAnsiResponse.Reset (); + + _responseQueue.TryDequeue (out _); + lock (ansiRequest._responseLock) { - AnsiEscapeSequenceRequest? request = ansiRequest; + _mainLoopDriver._forceRead = false; - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == request); - Debug.Assert (e == request.AnsiEscapeSequenceResponse); + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) + { + if (AnsiEscapeSequenceRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + { + lock (request.AnsiRequest._responseLock) + { + // Bad request or no response at all + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); + } + } + } - _waitAnsiResponse.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - WriteRaw (ansiRequest.Request); - - mainLoopDriver._forceRead = true; - } - - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - mainLoopDriver._waitForInput.Set (); - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; } } catch (OperationCanceledException) { return false; } - - lock (ansiRequest._responseLock) - { - mainLoopDriver._forceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } } // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver. diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 84c74fa20..c56b7b352 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -679,6 +679,8 @@ internal class CursesDriver : ConsoleDriver { Curses.refresh (); } + + Task.Run (ProcessAnsiRequestHandler); } return new (_mainLoopDriver); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 64a3415da..7d82737cc 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -392,9 +392,6 @@ public class FakeDriver : ConsoleDriver MockKeyPressedHandler (new ConsoleKeyInfo (keyChar, key, shift, alt, control)); } - /// - public override bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } - /// internal override void WriteRaw (string ansi) { throw new NotImplementedException (); } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index f66d921bf..91746a218 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -294,6 +294,11 @@ internal class NetDriver : ConsoleDriver _mainLoopDriver = new (this); _mainLoopDriver.ProcessInput = ProcessInput; + if (!RunningUnitTests) + { + Task.Run (ProcessAnsiRequestHandler); + } + return new (_mainLoopDriver); } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 5eaefd618..51df2c4d8 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -481,6 +481,8 @@ internal class WindowsDriver : ConsoleDriver if (!RunningUnitTests) { WinConsole?.SetInitialCursorVisibility (); + + Task.Run (ProcessAnsiRequestHandler); } return new MainLoop (_mainLoopDriver);