Add evicting and xmldoc

This commit is contained in:
tznind
2024-10-22 04:24:13 +01:00
parent 6de203290d
commit 23af0d9fd3
3 changed files with 109 additions and 14 deletions

View File

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

View File

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

View File

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