Browse Source

Improves drivers responses to being more reliable.

BDisp 8 months ago
parent
commit
da21ef1320

+ 36 - 12
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -176,6 +176,13 @@ internal class UnixMainLoop : IMainLoopDriver
             {
                 return;
             }
+            finally
+            {
+                if (!_inputHandlerTokenSource.IsCancellationRequested)
+                {
+                    _waitForInput.Reset ();
+                }
+            }
 
             if (_pollDataQueue?.Count == 0 || _forceRead)
             {
@@ -235,12 +242,15 @@ internal class UnixMainLoop : IMainLoopDriver
                     {
                         if (_retries > 1)
                         {
-                            EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
-
-                            lock (seqReqStatus!.AnsiRequest._responseLock)
+                            if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
                             {
-                                seqReqStatus.AnsiRequest.Response = string.Empty;
-                                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
+                                lock (seqReqStatus!.AnsiRequest._responseLock)
+                                {
+                                    EscSeqRequests.Statuses.TryDequeue (out _);
+
+                                    seqReqStatus.AnsiRequest.Response = string.Empty;
+                                    seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
+                                }
                             }
 
                             _retries = 0;
@@ -257,7 +267,10 @@ internal class UnixMainLoop : IMainLoopDriver
 
                     try
                     {
-                        Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
+                        if (!_forceRead)
+                        {
+                            Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
+                        }
                     }
                     catch (OperationCanceledException)
                     {
@@ -266,7 +279,6 @@ internal class UnixMainLoop : IMainLoopDriver
                 }
             }
 
-            _waitForInput.Reset ();
             _eventReady.Set ();
         }
     }
@@ -327,6 +339,22 @@ internal class UnixMainLoop : IMainLoopDriver
             return;
         }
 
+        if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator))
+        {
+            if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result))
+            {
+                lock (result.AnsiRequest._responseLock)
+                {
+                    result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator;
+                    result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator);
+
+                    EscSeqUtils.InvalidRequestTerminator = null;
+                }
+            }
+
+            return;
+        }
+
         if (newConsoleKeyInfo != default)
         {
             _pollDataQueue!.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo));
@@ -420,8 +448,7 @@ internal class UnixMainLoop : IMainLoopDriver
         // Write to stdout (fd 1)
         write (STDOUT_FILENO, ansiRequest, ansiRequest.Length);
 
-        // Flush the stdout buffer immediately using fsync
-        fsync (STDOUT_FILENO);
+        Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
     }
 
     [DllImport ("libc")]
@@ -436,9 +463,6 @@ internal class UnixMainLoop : IMainLoopDriver
     [DllImport ("libc")]
     private static extern int write (int fd, string buf, int n);
 
-    [DllImport ("libc", SetLastError = true)]
-    private static extern int fsync (int fd);
-
     [DllImport ("libc", SetLastError = true)]
     private static extern int ioctl (int fd, int request, ref Winsize ws);
 

+ 53 - 25
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -2,6 +2,7 @@
 // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
 //
 
+using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
@@ -140,7 +141,7 @@ internal class NetEvents : IDisposable
 
     //CancellationTokenSource _waitForStartCancellationTokenSource;
     private readonly ManualResetEventSlim _winChange = new (false);
-    private readonly Queue<InputResult?> _inputQueue = new ();
+    private readonly ConcurrentQueue<InputResult?> _inputQueue = new ();
     private readonly ConsoleDriver _consoleDriver;
     private ConsoleKeyInfo [] _cki;
     private bool _isEscSeq;
@@ -191,7 +192,10 @@ internal class NetEvents : IDisposable
 #endif
             if (_inputQueue.Count > 0)
             {
-                return _inputQueue.Dequeue ();
+                if (_inputQueue.TryDequeue (out InputResult? result))
+                {
+                    return result;
+                }
             }
         }
 
@@ -200,17 +204,10 @@ internal class NetEvents : IDisposable
 
     private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
     {
-        // if there is a key available, return it without waiting
-        //  (or dispatching work to the thread queue)
-        if (Console.KeyAvailable)
-        {
-            return Console.ReadKey (intercept);
-        }
-
         while (!cancellationToken.IsCancellationRequested)
         {
-            Task.Delay (100, cancellationToken).Wait (cancellationToken);
-
+            // if there is a key available, return it without waiting
+            //  (or dispatching work to the thread queue)
             if (Console.KeyAvailable)
             {
                 return Console.ReadKey (intercept);
@@ -220,12 +217,15 @@ internal class NetEvents : IDisposable
             {
                 if (_retries > 1)
                 {
-                    EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
-
-                    lock (seqReqStatus.AnsiRequest._responseLock)
+                    if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
                     {
-                        seqReqStatus.AnsiRequest.Response = string.Empty;
-                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
+                        lock (seqReqStatus!.AnsiRequest._responseLock)
+                        {
+                            EscSeqRequests.Statuses.TryDequeue (out _);
+
+                            seqReqStatus.AnsiRequest.Response = string.Empty;
+                            seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
+                        }
                     }
 
                     _retries = 0;
@@ -239,6 +239,11 @@ internal class NetEvents : IDisposable
             {
                 _retries = 0;
             }
+
+            if (!_forceRead)
+            {
+                Task.Delay (100, cancellationToken).Wait (cancellationToken);
+            }
         }
 
         cancellationToken.ThrowIfCancellationRequested ();
@@ -310,7 +315,10 @@ internal class NetEvents : IDisposable
 
                         _isEscSeq = true;
 
-                        if (_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
+                        if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
+                            || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127)
+                            || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))
+                            || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)))
                         {
                             ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
                             _cki = null;
@@ -510,7 +518,26 @@ internal class NetEvents : IDisposable
             return;
         }
 
-        HandleKeyboardEvent (newConsoleKeyInfo);
+        if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator))
+        {
+            if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result))
+            {
+                lock (result.AnsiRequest._responseLock)
+                {
+                    result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator;
+                    result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator);
+
+                    EscSeqUtils.InvalidRequestTerminator = null;
+                }
+            }
+
+            return;
+        }
+
+        if (newConsoleKeyInfo != default)
+        {
+            HandleKeyboardEvent (newConsoleKeyInfo);
+        }
     }
 
     [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
@@ -1822,7 +1849,7 @@ internal class NetMainLoop : IMainLoopDriver
 
     private readonly ManualResetEventSlim _eventReady = new (false);
     private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
-    private readonly Queue<InputResult?> _resultQueue = new ();
+    private readonly ConcurrentQueue<InputResult?> _resultQueue = new ();
     internal readonly ManualResetEventSlim _waitForProbe = new (false);
     private readonly CancellationTokenSource _eventReadyTokenSource = new ();
     private MainLoop _mainLoop;
@@ -1897,11 +1924,12 @@ internal class NetMainLoop : IMainLoopDriver
         while (_resultQueue.Count > 0)
         {
             // Always dequeue even if it's null and invoke if isn't null
-            InputResult? dequeueResult = _resultQueue.Dequeue ();
-
-            if (dequeueResult is { })
+            if (_resultQueue.TryDequeue (out InputResult? dequeueResult))
             {
-                ProcessInput?.Invoke (dequeueResult.Value);
+                if (dequeueResult is { })
+                {
+                    ProcessInput?.Invoke (dequeueResult.Value);
+                }
             }
         }
     }
@@ -1960,10 +1988,10 @@ internal class NetMainLoop : IMainLoopDriver
 
             try
             {
-                while (_resultQueue.Count > 0 && _resultQueue.Peek () is null)
+                while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out InputResult? result) && result is null)
                 {
                     // Dequeue null values
-                    _resultQueue.Dequeue ();
+                    _resultQueue.TryDequeue (out _);
                 }
             }
             catch (InvalidOperationException) // Peek can raise an exception

+ 32 - 21
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -8,13 +8,14 @@
 // 2) The values provided during Init (and the first WindowsConsole.EventType.WindowBufferSize) are not correct.
 //
 // If HACK_CHECK_WINCHANGED is defined then we ignore WindowsConsole.EventType.WindowBufferSize events
-// and instead check the console size every every 500ms in a thread in WidowsMainLoop. 
+// and instead check the console size every 500ms in a thread in WidowsMainLoop. 
 // As of Windows 11 23H2 25947.1000 and/or WT 1.19.2682 tearing no longer occurs when using 
 // the WindowsConsole.EventType.WindowBufferSize event. However, on Init the window size is
 // still incorrect so we still need this hack.
 
 #define HACK_CHECK_WINCHANGED
 
+using System.Collections.Concurrent;
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
@@ -920,6 +921,7 @@ internal class WindowsConsole
         uint numberEventsRead = 0;
         StringBuilder ansiSequence = new StringBuilder ();
         bool readingSequence = false;
+        bool raisedResponse = false;
 
         while (true)
         {
@@ -972,6 +974,7 @@ internal class WindowsConsole
 
                                     lock (seqReqStatus!.AnsiRequest._responseLock)
                                     {
+                                        raisedResponse = true;
                                         seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
                                         seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
                                         // Clear the terminator for not be enqueued
@@ -985,16 +988,31 @@ internal class WindowsConsole
                     }
                 }
 
-                if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
+                if (readingSequence && !raisedResponse && EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
                 {
-                    if (_retries > 1)
+                    _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
+
+                    lock (seqReqStatus!.AnsiRequest._responseLock)
                     {
-                        _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
+                        seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
+                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
+                    }
 
-                        lock (seqReqStatus!.AnsiRequest._responseLock)
+                    _retries = 0;
+                }
+                else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
+                {
+                    if (_retries > 1)
+                    {
+                        if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
                         {
-                            seqReqStatus.AnsiRequest.Response = string.Empty;
-                            seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
+                            lock (seqReqStatus!.AnsiRequest._responseLock)
+                            {
+                                _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _);
+
+                                seqReqStatus.AnsiRequest.Response = string.Empty;
+                                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
+                            }
                         }
 
                         _retries = 0;
@@ -2337,7 +2355,7 @@ internal class WindowsMainLoop : IMainLoopDriver
     private readonly ManualResetEventSlim _eventReady = new (false);
 
     // The records that we keep fetching
-    private readonly Queue<WindowsConsole.InputRecord []> _resultQueue = new ();
+    private readonly ConcurrentQueue<WindowsConsole.InputRecord []> _resultQueue = new ();
     internal readonly ManualResetEventSlim _waitForProbe = new (false);
     private readonly WindowsConsole _winConsole;
     private CancellationTokenSource _eventReadyTokenSource = new ();
@@ -2422,11 +2440,12 @@ internal class WindowsMainLoop : IMainLoopDriver
     {
         while (_resultQueue.Count > 0)
         {
-            WindowsConsole.InputRecord [] inputRecords = _resultQueue.Dequeue ();
-
-            if (inputRecords is { Length: > 0 })
+            if (_resultQueue.TryDequeue (out WindowsConsole.InputRecord [] inputRecords))
             {
-                ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]);
+                if (inputRecords is { Length: > 0 })
+                {
+                    ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]);
+                }
             }
         }
 #if HACK_CHECK_WINCHANGED
@@ -2524,15 +2543,7 @@ internal class WindowsMainLoop : IMainLoopDriver
                 }
             }
 
-            if (_eventReady.IsSet)
-            {
-                // it's already in an iteration and ensures set to iterate again
-                Application.Invoke (() => _eventReady.Set ());
-            }
-            else
-            {
-                _eventReady.Set ();
-            }
+            _eventReady.Set ();
         }
     }