InputImpl.cs 2.8 KB

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