mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-01 16:59:35 +01:00
Add evicting and xmldoc
This commit is contained in:
@@ -5,7 +5,7 @@ namespace Terminal.Gui;
|
||||
|
||||
public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
||||
{
|
||||
private readonly List<AnsiEscapeSequenceRequest> _requests = new ();
|
||||
private readonly List<Tuple<AnsiEscapeSequenceRequest,DateTime>> _requests = new ();
|
||||
|
||||
/// <summary>
|
||||
///<para>
|
||||
@@ -24,6 +24,13 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
||||
private TimeSpan _throttle = TimeSpan.FromMilliseconds (100);
|
||||
private TimeSpan _runScheduleThrottle = TimeSpan.FromMilliseconds (100);
|
||||
|
||||
/// <summary>
|
||||
/// If console has not responded to a request after this period of time, we assume that it is never going
|
||||
/// to respond. Only affects when we try to send a new request with the same terminator - at which point
|
||||
/// we tell the parser to stop expecting the old request and start expecting the new request.
|
||||
/// </summary>
|
||||
private TimeSpan _staleTimeout = TimeSpan.FromSeconds (5);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the <paramref name="request"/> immediately or queues it if there is already
|
||||
/// an outstanding request for the given <see cref="AnsiEscapeSequenceRequest.Terminator"/>.
|
||||
@@ -32,17 +39,51 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
||||
/// <returns><see langword="true"/> if request was sent immediately. <see langword="false"/> if it was queued.</returns>
|
||||
public bool SendOrSchedule (AnsiEscapeSequenceRequest request )
|
||||
{
|
||||
if (CanSend(request))
|
||||
|
||||
if (CanSend(request, out var reason))
|
||||
{
|
||||
Send (request);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
if (reason == ReasonCannotSend.OutstandingRequest)
|
||||
{
|
||||
_requests.Add (request);
|
||||
return false;
|
||||
EvictStaleRequests (request.Terminator);
|
||||
|
||||
// Try again after
|
||||
if (CanSend (request, out _))
|
||||
{
|
||||
Send (request);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_requests.Add (Tuple.Create(request,DateTime.Now));
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks to see if the last time we sent <paramref name="withTerminator"/>
|
||||
/// is a long time ago. If so we assume that we will never get a response and
|
||||
/// can proceed with a new request for this terminator (returning <see langword="true"/>).
|
||||
/// </summary>
|
||||
/// <param name="withTerminator"></param>
|
||||
/// <returns></returns>
|
||||
private bool EvictStaleRequests (string withTerminator)
|
||||
{
|
||||
if (_lastSend.TryGetValue (withTerminator, out var dt))
|
||||
{
|
||||
// TODO: If debugging this can cause problem becuase we stop expecting response but one comes in anyway
|
||||
// causing parser to ignore and it to fall through to default console iteration which typically crashes.
|
||||
if (DateTime.Now - dt > _staleTimeout)
|
||||
{
|
||||
parser.StopExpecting (withTerminator);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private DateTime _lastRun = DateTime.Now;
|
||||
@@ -62,12 +103,12 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
||||
return false;
|
||||
}
|
||||
|
||||
var opportunity = _requests.FirstOrDefault (CanSend);
|
||||
var opportunity = _requests.FirstOrDefault (r=>CanSend(r.Item1, out _));
|
||||
|
||||
if (opportunity != null)
|
||||
{
|
||||
_requests.Remove (opportunity);
|
||||
Send (opportunity);
|
||||
Send (opportunity.Item1);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -82,14 +123,22 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
||||
r.Send ();
|
||||
}
|
||||
|
||||
public bool CanSend (AnsiEscapeSequenceRequest r)
|
||||
private bool CanSend (AnsiEscapeSequenceRequest r, out ReasonCannotSend reason)
|
||||
{
|
||||
if (ShouldThrottle (r))
|
||||
{
|
||||
reason = ReasonCannotSend.TooManyRequests;
|
||||
return false;
|
||||
}
|
||||
|
||||
return !parser.IsExpecting (r.Terminator);
|
||||
if (parser.IsExpecting (r.Terminator))
|
||||
{
|
||||
reason = ReasonCannotSend.OutstandingRequest;
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = default;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShouldThrottle (AnsiEscapeSequenceRequest r)
|
||||
@@ -102,3 +151,22 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum ReasonCannotSend
|
||||
{
|
||||
/// <summary>
|
||||
/// No reason given.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The parser is already waiting for a request to complete with the given terminator.
|
||||
/// </summary>
|
||||
OutstandingRequest,
|
||||
|
||||
/// <summary>
|
||||
/// There have been too many requests sent recently, new requests will be put into
|
||||
/// queue to prevent console becoming unresponsive.
|
||||
/// </summary>
|
||||
TooManyRequests
|
||||
}
|
||||
|
||||
@@ -224,10 +224,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
ResetState ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new expected ANSI response with a specific terminator and a callback for when the response is
|
||||
/// completed.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void ExpectResponse (string terminator, Action<string> response) { expectedResponses.Add ((terminator, response)); }
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -236,6 +233,12 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
// If any of the new terminator matches any existing terminators characters it's a collision so true.
|
||||
return expectedResponses.Any (r => r.terminator.Intersect (requestTerminator).Any());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopExpecting (string requestTerminator)
|
||||
{
|
||||
expectedResponses.RemoveAll (r => r.terminator == requestTerminator);
|
||||
}
|
||||
}
|
||||
|
||||
internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
#nullable enable
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// When implemented in a derived class, allows watching an input stream of characters
|
||||
/// (i.e. console input) for ANSI response sequences.
|
||||
/// </summary>
|
||||
public interface IAnsiResponseParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Current state of the parser based on what sequence of characters it has
|
||||
/// read from the console input stream.
|
||||
/// </summary>
|
||||
AnsiResponseParserState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the parser that you are expecting a response to come in
|
||||
/// with the given <paramref name="terminator"/> (i.e. because you have
|
||||
/// sent an ANSI request out).
|
||||
/// </summary>
|
||||
/// <param name="terminator">The terminator you expect to see on response.</param>
|
||||
/// <param name="response">Callback to invoke when the response is seen in console input.</param>
|
||||
void ExpectResponse (string terminator, Action<string> response);
|
||||
|
||||
/// <summary>
|
||||
@@ -13,4 +29,12 @@ public interface IAnsiResponseParser
|
||||
/// <param name="requestTerminator"></param>
|
||||
/// <returns></returns>
|
||||
bool IsExpecting (string requestTerminator);
|
||||
|
||||
/// <summary>
|
||||
/// Removes callback and expectation that we will get a response for the
|
||||
/// given <pararef name="requestTerminator"/>. Use to give up on very old
|
||||
/// requests e.g. if you want to send a different one with the same terminator.
|
||||
/// </summary>
|
||||
/// <param name="requestTerminator"></param>
|
||||
void StopExpecting (string requestTerminator);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user