InputImpl.cs 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. #nullable enable
  2. using System.Collections.Concurrent;
  3. namespace Terminal.Gui.Drivers;
  4. /// <summary>
  5. /// Base class for reading console input in perpetual loop.
  6. /// The <see cref="Peek"/> and <see cref="Read"/> methods are executed
  7. /// on the input thread created by <see cref="MainLoopCoordinator{TInputRecord}.StartInputTaskAsync"/>.
  8. /// </summary>
  9. /// <typeparam name="TInputRecord"></typeparam>
  10. public abstract class InputImpl<TInputRecord> : IInput<TInputRecord>
  11. {
  12. private ConcurrentQueue<TInputRecord>? _inputQueue;
  13. /// <summary>
  14. /// Determines how to get the current system type, adjust
  15. /// in unit tests to simulate specific timings.
  16. /// </summary>
  17. public Func<DateTime> Now { get; set; } = () => DateTime.Now;
  18. /// <inheritdoc />
  19. public CancellationTokenSource? ExternalCancellationTokenSource { get; set; }
  20. /// <inheritdoc/>
  21. public void Initialize (ConcurrentQueue<TInputRecord> inputQueue) { _inputQueue = inputQueue; }
  22. /// <inheritdoc/>
  23. public void Run (CancellationToken runCancellationToken)
  24. {
  25. // Create a linked token source if we have an external one
  26. CancellationTokenSource? linkedCts = null;
  27. CancellationToken effectiveToken = runCancellationToken;
  28. if (ExternalCancellationTokenSource != null)
  29. {
  30. linkedCts = CancellationTokenSource.CreateLinkedTokenSource (runCancellationToken, ExternalCancellationTokenSource.Token);
  31. effectiveToken = linkedCts.Token;
  32. }
  33. try
  34. {
  35. if (_inputQueue == null)
  36. {
  37. throw new ("Cannot run input before Initialization");
  38. }
  39. do
  40. {
  41. DateTime dt = Now ();
  42. while (Peek ())
  43. {
  44. foreach (TInputRecord r in Read ())
  45. {
  46. _inputQueue.Enqueue (r);
  47. }
  48. }
  49. effectiveToken.ThrowIfCancellationRequested ();
  50. }
  51. while (!effectiveToken.IsCancellationRequested);
  52. }
  53. catch (OperationCanceledException)
  54. { }
  55. finally
  56. {
  57. Logging.Trace($"Stopping input processing");
  58. linkedCts?.Dispose ();
  59. }
  60. }
  61. /// <summary>
  62. /// When implemented in a derived class, returns true if there is data available
  63. /// to read from console.
  64. /// </summary>
  65. /// <returns></returns>
  66. public abstract bool Peek ();
  67. /// <summary>
  68. /// Returns the available data without blocking, called when <see cref="Peek"/>
  69. /// returns <see langword="true"/>.
  70. /// </summary>
  71. /// <returns></returns>
  72. public abstract IEnumerable<TInputRecord> Read ();
  73. /// <inheritdoc/>
  74. public virtual void Dispose () { }
  75. }