Improves drivers responses to being more reliable.

This commit is contained in:
BDisp
2024-11-04 20:04:50 +00:00
parent e35b37fb4d
commit da21ef1320
3 changed files with 121 additions and 58 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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 })
{
_mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
lock (seqReqStatus!.AnsiRequest._responseLock)
{
seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
}
_retries = 0;
}
else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
{
if (_retries > 1)
{
_mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
lock (seqReqStatus!.AnsiRequest._responseLock)
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 ();
}
}