|
@@ -105,15 +105,19 @@ class NetWinVTConsole {
|
|
}
|
|
}
|
|
|
|
|
|
internal class NetEvents : IDisposable {
|
|
internal class NetEvents : IDisposable {
|
|
- ManualResetEventSlim _inputReady = new ManualResetEventSlim (false);
|
|
|
|
- ManualResetEventSlim _waitForStart = new ManualResetEventSlim (false);
|
|
|
|
- ManualResetEventSlim _winChange = new ManualResetEventSlim (false);
|
|
|
|
- Queue<InputResult?> _inputResultQueue = new Queue<InputResult?> ();
|
|
|
|
- ConsoleDriver _consoleDriver;
|
|
|
|
|
|
+ readonly ManualResetEventSlim _inputReady = new ManualResetEventSlim (false);
|
|
|
|
+ CancellationTokenSource _inputReadyCancellationTokenSource;
|
|
|
|
+
|
|
|
|
+ readonly ManualResetEventSlim _waitForStart = new ManualResetEventSlim (false);
|
|
|
|
+ //CancellationTokenSource _waitForStartCancellationTokenSource;
|
|
|
|
+
|
|
|
|
+ readonly ManualResetEventSlim _winChange = new ManualResetEventSlim (false);
|
|
|
|
+
|
|
|
|
+ readonly Queue<InputResult?> _inputQueue = new Queue<InputResult?> ();
|
|
|
|
+
|
|
|
|
+ readonly ConsoleDriver _consoleDriver;
|
|
ConsoleKeyInfo [] _cki;
|
|
ConsoleKeyInfo [] _cki;
|
|
bool _isEscSeq;
|
|
bool _isEscSeq;
|
|
- CancellationTokenSource _cancellationTokenSource;
|
|
|
|
- CancellationToken _cancellationToken;
|
|
|
|
|
|
|
|
#if PROCESS_REQUEST
|
|
#if PROCESS_REQUEST
|
|
bool _neededProcessRequest;
|
|
bool _neededProcessRequest;
|
|
@@ -123,32 +127,40 @@ internal class NetEvents : IDisposable {
|
|
public NetEvents (ConsoleDriver consoleDriver)
|
|
public NetEvents (ConsoleDriver consoleDriver)
|
|
{
|
|
{
|
|
_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
|
|
_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
|
|
- _cancellationTokenSource = new CancellationTokenSource ();
|
|
|
|
- _cancellationToken = _cancellationTokenSource.Token;
|
|
|
|
- Task.Run (ProcessInputResultQueue);
|
|
|
|
- Task.Run (CheckWindowSizeChange);
|
|
|
|
|
|
+ _inputReadyCancellationTokenSource = new CancellationTokenSource ();
|
|
|
|
+
|
|
|
|
+ Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
|
|
|
|
+
|
|
|
|
+ Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token);
|
|
}
|
|
}
|
|
|
|
|
|
- public InputResult? ReadConsoleInput ()
|
|
|
|
|
|
+ public InputResult? DequeueInput ()
|
|
{
|
|
{
|
|
- while (true) {
|
|
|
|
- if (_cancellationToken.IsCancellationRequested) {
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
|
|
+ while (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) {
|
|
_waitForStart.Set ();
|
|
_waitForStart.Set ();
|
|
_winChange.Set ();
|
|
_winChange.Set ();
|
|
|
|
|
|
- if (_inputResultQueue.Count == 0) {
|
|
|
|
- _inputReady.Wait ();
|
|
|
|
|
|
+ try {
|
|
|
|
+ if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) {
|
|
|
|
+ if (_inputQueue.Count == 0) {
|
|
|
|
+ _inputReady.Wait (_inputReadyCancellationTokenSource.Token);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } catch (OperationCanceledException) {
|
|
|
|
+ return null;
|
|
|
|
+ } finally {
|
|
_inputReady.Reset ();
|
|
_inputReady.Reset ();
|
|
}
|
|
}
|
|
|
|
+
|
|
#if PROCESS_REQUEST
|
|
#if PROCESS_REQUEST
|
|
_neededProcessRequest = false;
|
|
_neededProcessRequest = false;
|
|
#endif
|
|
#endif
|
|
- if (_inputResultQueue.Count > 0) {
|
|
|
|
- return _inputResultQueue.Dequeue ();
|
|
|
|
|
|
+ if (_inputQueue.Count > 0) {
|
|
|
|
+ return _inputQueue.Dequeue ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
|
|
static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
|
|
@@ -169,24 +181,25 @@ internal class NetEvents : IDisposable {
|
|
return default;
|
|
return default;
|
|
}
|
|
}
|
|
|
|
|
|
- void ProcessInputResultQueue ()
|
|
|
|
|
|
+ void ProcessInputQueue ()
|
|
{
|
|
{
|
|
- while (true) {
|
|
|
|
- _waitForStart.Wait ();
|
|
|
|
|
|
+ while (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) {
|
|
|
|
+
|
|
|
|
+ _waitForStart.Wait (_inputReadyCancellationTokenSource.Token);
|
|
_waitForStart.Reset ();
|
|
_waitForStart.Reset ();
|
|
|
|
|
|
- if (_inputResultQueue.Count == 0) {
|
|
|
|
|
|
+ if (_inputQueue.Count == 0) {
|
|
ConsoleKey key = 0;
|
|
ConsoleKey key = 0;
|
|
ConsoleModifiers mod = 0;
|
|
ConsoleModifiers mod = 0;
|
|
ConsoleKeyInfo newConsoleKeyInfo = default;
|
|
ConsoleKeyInfo newConsoleKeyInfo = default;
|
|
|
|
|
|
while (true) {
|
|
while (true) {
|
|
- if (_cancellationToken.IsCancellationRequested) {
|
|
|
|
|
|
+ if (_inputReadyCancellationTokenSource.Token.IsCancellationRequested) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
ConsoleKeyInfo consoleKeyInfo;
|
|
ConsoleKeyInfo consoleKeyInfo;
|
|
try {
|
|
try {
|
|
- consoleKeyInfo = ReadConsoleKeyInfo (_cancellationToken, true);
|
|
|
|
|
|
+ consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token, true);
|
|
} catch (OperationCanceledException) {
|
|
} catch (OperationCanceledException) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -226,7 +239,7 @@ internal class NetEvents : IDisposable {
|
|
|
|
|
|
void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
|
|
void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
|
|
{
|
|
{
|
|
- _inputResultQueue.Enqueue (new InputResult {
|
|
|
|
|
|
+ _inputQueue.Enqueue (new InputResult {
|
|
EventType = EventType.Key,
|
|
EventType = EventType.Key,
|
|
ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
|
|
ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
|
|
});
|
|
});
|
|
@@ -263,13 +276,13 @@ internal class NetEvents : IDisposable {
|
|
}
|
|
}
|
|
|
|
|
|
while (true) {
|
|
while (true) {
|
|
- if (_cancellationToken.IsCancellationRequested) {
|
|
|
|
|
|
+ if (_inputReadyCancellationTokenSource.IsCancellationRequested) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
- _winChange.Wait ();
|
|
|
|
|
|
+ _winChange.Wait (_inputReadyCancellationTokenSource.Token);
|
|
_winChange.Reset ();
|
|
_winChange.Reset ();
|
|
try {
|
|
try {
|
|
- RequestWindowSize (_cancellationToken);
|
|
|
|
|
|
+ RequestWindowSize (_inputReadyCancellationTokenSource.Token);
|
|
} catch (OperationCanceledException) {
|
|
} catch (OperationCanceledException) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -290,7 +303,7 @@ internal class NetEvents : IDisposable {
|
|
if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) return false;
|
|
if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) return false;
|
|
var w = Math.Max (winWidth, 0);
|
|
var w = Math.Max (winWidth, 0);
|
|
var h = Math.Max (winHeight, 0);
|
|
var h = Math.Max (winHeight, 0);
|
|
- _inputResultQueue.Enqueue (new InputResult () {
|
|
|
|
|
|
+ _inputQueue.Enqueue (new InputResult () {
|
|
EventType = EventType.WindowSize,
|
|
EventType = EventType.WindowSize,
|
|
WindowSizeEvent = new WindowSizeEvent () {
|
|
WindowSizeEvent = new WindowSizeEvent () {
|
|
Size = new Size (w, h)
|
|
Size = new Size (w, h)
|
|
@@ -437,7 +450,7 @@ internal class NetEvents : IDisposable {
|
|
var winPositionEv = new WindowPositionEvent () {
|
|
var winPositionEv = new WindowPositionEvent () {
|
|
CursorPosition = point
|
|
CursorPosition = point
|
|
};
|
|
};
|
|
- _inputResultQueue.Enqueue (new InputResult () {
|
|
|
|
|
|
+ _inputQueue.Enqueue (new InputResult () {
|
|
EventType = eventType,
|
|
EventType = eventType,
|
|
WindowPositionEvent = winPositionEv
|
|
WindowPositionEvent = winPositionEv
|
|
});
|
|
});
|
|
@@ -474,7 +487,7 @@ internal class NetEvents : IDisposable {
|
|
var requestRespEv = new RequestResponseEvent () {
|
|
var requestRespEv = new RequestResponseEvent () {
|
|
ResultTuple = (c1Control, code, values, terminating)
|
|
ResultTuple = (c1Control, code, values, terminating)
|
|
};
|
|
};
|
|
- _inputResultQueue.Enqueue (new InputResult () {
|
|
|
|
|
|
+ _inputQueue.Enqueue (new InputResult () {
|
|
EventType = eventType,
|
|
EventType = eventType,
|
|
RequestResponseEvent = requestRespEv
|
|
RequestResponseEvent = requestRespEv
|
|
});
|
|
});
|
|
@@ -487,7 +500,7 @@ internal class NetEvents : IDisposable {
|
|
ButtonState = buttonState,
|
|
ButtonState = buttonState,
|
|
};
|
|
};
|
|
|
|
|
|
- _inputResultQueue.Enqueue (new InputResult () {
|
|
|
|
|
|
+ _inputQueue.Enqueue (new InputResult () {
|
|
EventType = EventType.Mouse,
|
|
EventType = EventType.Mouse,
|
|
MouseEvent = mouseEvent
|
|
MouseEvent = mouseEvent
|
|
});
|
|
});
|
|
@@ -571,14 +584,15 @@ internal class NetEvents : IDisposable {
|
|
ConsoleKeyInfo = cki
|
|
ConsoleKeyInfo = cki
|
|
};
|
|
};
|
|
|
|
|
|
- _inputResultQueue.Enqueue (inputResult);
|
|
|
|
|
|
+ _inputQueue.Enqueue (inputResult);
|
|
}
|
|
}
|
|
|
|
|
|
public void Dispose ()
|
|
public void Dispose ()
|
|
{
|
|
{
|
|
- _cancellationTokenSource?.Cancel ();
|
|
|
|
- _cancellationTokenSource?.Dispose ();
|
|
|
|
- _cancellationTokenSource = null;
|
|
|
|
|
|
+ _inputReadyCancellationTokenSource?.Cancel ();
|
|
|
|
+ _inputReadyCancellationTokenSource?.Dispose ();
|
|
|
|
+ _inputReadyCancellationTokenSource = null;
|
|
|
|
+
|
|
try {
|
|
try {
|
|
// throws away any typeahead that has been typed by
|
|
// throws away any typeahead that has been typed by
|
|
// the user and has not yet been read by the program.
|
|
// the user and has not yet been read by the program.
|
|
@@ -616,8 +630,6 @@ internal class NetDriver : ConsoleDriver {
|
|
|
|
|
|
public override void End ()
|
|
public override void End ()
|
|
{
|
|
{
|
|
- _mainLoop?._netEvents.Dispose ();
|
|
|
|
-
|
|
|
|
if (IsWinPlatform) {
|
|
if (IsWinPlatform) {
|
|
NetWinConsole?.Cleanup ();
|
|
NetWinConsole?.Cleanup ();
|
|
}
|
|
}
|
|
@@ -1116,10 +1128,10 @@ internal class NetDriver : ConsoleDriver {
|
|
_keyUpHandler = keyUpHandler;
|
|
_keyUpHandler = keyUpHandler;
|
|
_mouseHandler = mouseHandler;
|
|
_mouseHandler = mouseHandler;
|
|
|
|
|
|
- var mLoop = _mainLoop = mainLoop.MainLoopDriver as NetMainLoop;
|
|
|
|
|
|
+ _mainLoop = mainLoop.MainLoopDriver as NetMainLoop;
|
|
|
|
|
|
// Note: .Net API doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called.
|
|
// Note: .Net API doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called.
|
|
- mLoop.ProcessInput = (e) => ProcessInput (e);
|
|
|
|
|
|
+ _mainLoop.ProcessInput = ProcessInput;
|
|
}
|
|
}
|
|
|
|
|
|
volatile bool _winSizeChanging;
|
|
volatile bool _winSizeChanging;
|
|
@@ -1301,11 +1313,12 @@ internal class NetDriver : ConsoleDriver {
|
|
/// This implementation is used for NetDriver.
|
|
/// This implementation is used for NetDriver.
|
|
/// </remarks>
|
|
/// </remarks>
|
|
internal class NetMainLoop : IMainLoopDriver {
|
|
internal class NetMainLoop : IMainLoopDriver {
|
|
- readonly ManualResetEventSlim _keyReady = new ManualResetEventSlim (false);
|
|
|
|
|
|
+ readonly ManualResetEventSlim _eventReady = new ManualResetEventSlim (false);
|
|
readonly ManualResetEventSlim _waitForProbe = new ManualResetEventSlim (false);
|
|
readonly ManualResetEventSlim _waitForProbe = new ManualResetEventSlim (false);
|
|
- readonly Queue<NetEvents.InputResult?> _inputResult = new Queue<NetEvents.InputResult?> ();
|
|
|
|
|
|
+ readonly Queue<NetEvents.InputResult?> _resultQueue = new Queue<NetEvents.InputResult?> ();
|
|
MainLoop _mainLoop;
|
|
MainLoop _mainLoop;
|
|
- CancellationTokenSource _tokenSource = new CancellationTokenSource ();
|
|
|
|
|
|
+ CancellationTokenSource _eventReadyTokenSource = new CancellationTokenSource ();
|
|
|
|
+ readonly CancellationTokenSource _inputHandlerTokenSource = new CancellationTokenSource ();
|
|
internal NetEvents _netEvents;
|
|
internal NetEvents _netEvents;
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -1331,32 +1344,43 @@ internal class NetMainLoop : IMainLoopDriver {
|
|
|
|
|
|
void NetInputHandler ()
|
|
void NetInputHandler ()
|
|
{
|
|
{
|
|
- while (true) {
|
|
|
|
- _waitForProbe.Wait ();
|
|
|
|
- _waitForProbe.Reset ();
|
|
|
|
- if (_inputResult.Count == 0) {
|
|
|
|
- _inputResult.Enqueue (_netEvents.ReadConsoleInput ());
|
|
|
|
|
|
+ while (_mainLoop != null) {
|
|
|
|
+ try {
|
|
|
|
+ if (!_inputHandlerTokenSource.IsCancellationRequested) {
|
|
|
|
+ _waitForProbe.Wait (_inputHandlerTokenSource.Token);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } catch (OperationCanceledException) {
|
|
|
|
+ return;
|
|
|
|
+ } finally {
|
|
|
|
+ _waitForProbe.Reset ();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (_resultQueue.Count == 0) {
|
|
|
|
+ _resultQueue.Enqueue (_netEvents.DequeueInput ());
|
|
}
|
|
}
|
|
try {
|
|
try {
|
|
- while (_inputResult.Peek () == null) {
|
|
|
|
- _inputResult.Dequeue ();
|
|
|
|
|
|
+ while (_resultQueue.Peek () == null) {
|
|
|
|
+ _resultQueue.Dequeue ();
|
|
}
|
|
}
|
|
- if (_inputResult.Count > 0) {
|
|
|
|
- _keyReady.Set ();
|
|
|
|
|
|
+ if (_resultQueue.Count > 0) {
|
|
|
|
+ _eventReady.Set ();
|
|
}
|
|
}
|
|
- } catch (InvalidOperationException) { }
|
|
|
|
|
|
+ } catch (InvalidOperationException) {
|
|
|
|
+ // Ignore
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void IMainLoopDriver.Setup (MainLoop mainLoop)
|
|
void IMainLoopDriver.Setup (MainLoop mainLoop)
|
|
{
|
|
{
|
|
_mainLoop = mainLoop;
|
|
_mainLoop = mainLoop;
|
|
- Task.Run (NetInputHandler);
|
|
|
|
|
|
+ Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
|
|
}
|
|
}
|
|
|
|
|
|
void IMainLoopDriver.Wakeup ()
|
|
void IMainLoopDriver.Wakeup ()
|
|
{
|
|
{
|
|
- _keyReady.Set ();
|
|
|
|
|
|
+ _eventReady.Set ();
|
|
}
|
|
}
|
|
|
|
|
|
bool IMainLoopDriver.EventsPending ()
|
|
bool IMainLoopDriver.EventsPending ()
|
|
@@ -1368,40 +1392,46 @@ internal class NetMainLoop : IMainLoopDriver {
|
|
}
|
|
}
|
|
|
|
|
|
try {
|
|
try {
|
|
- if (!_tokenSource.IsCancellationRequested) {
|
|
|
|
|
|
+ if (!_eventReadyTokenSource.IsCancellationRequested) {
|
|
// Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there
|
|
// 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.
|
|
// are no timers, but there IS an idle handler waiting.
|
|
- _keyReady.Wait (waitTimeout, _tokenSource.Token);
|
|
|
|
|
|
+ _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
|
|
}
|
|
}
|
|
} catch (OperationCanceledException) {
|
|
} catch (OperationCanceledException) {
|
|
return true;
|
|
return true;
|
|
} finally {
|
|
} finally {
|
|
- _keyReady.Reset ();
|
|
|
|
|
|
+ _eventReady.Reset ();
|
|
}
|
|
}
|
|
|
|
|
|
- if (!_tokenSource.IsCancellationRequested) {
|
|
|
|
- return _inputResult.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
|
|
|
|
|
|
+ if (!_eventReadyTokenSource.IsCancellationRequested) {
|
|
|
|
+ return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
|
|
}
|
|
}
|
|
|
|
|
|
- _tokenSource.Dispose ();
|
|
|
|
- _tokenSource = new CancellationTokenSource ();
|
|
|
|
|
|
+ _eventReadyTokenSource.Dispose ();
|
|
|
|
+ _eventReadyTokenSource = new CancellationTokenSource ();
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
void IMainLoopDriver.Iteration ()
|
|
void IMainLoopDriver.Iteration ()
|
|
{
|
|
{
|
|
- while (_inputResult.Count > 0) {
|
|
|
|
- ProcessInput?.Invoke (_inputResult.Dequeue ().Value);
|
|
|
|
|
|
+ while (_resultQueue.Count > 0) {
|
|
|
|
+ ProcessInput?.Invoke (_resultQueue.Dequeue ().Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public void TearDown ()
|
|
|
|
|
|
+ void IMainLoopDriver.TearDown ()
|
|
{
|
|
{
|
|
- _inputResult?.Clear ();
|
|
|
|
- _tokenSource?.Cancel ();
|
|
|
|
- _keyReady?.Dispose ();
|
|
|
|
|
|
+ _inputHandlerTokenSource?.Cancel ();
|
|
|
|
+ _inputHandlerTokenSource?.Dispose ();
|
|
|
|
+ _eventReadyTokenSource?.Cancel ();
|
|
|
|
+ _eventReadyTokenSource?.Dispose ();
|
|
|
|
+
|
|
|
|
+ _eventReady?.Dispose ();
|
|
|
|
+
|
|
|
|
+ _resultQueue?.Clear ();
|
|
_waitForProbe?.Dispose ();
|
|
_waitForProbe?.Dispose ();
|
|
_netEvents?.Dispose ();
|
|
_netEvents?.Dispose ();
|
|
|
|
+ _netEvents = null;
|
|
|
|
|
|
_mainLoop = null;
|
|
_mainLoop = null;
|
|
}
|
|
}
|