103 lines
2.5 KiB
C#
103 lines
2.5 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace LLib.Movement.Unstuck;
|
|
|
|
public sealed class RecoveryLadder
|
|
{
|
|
private readonly IRecoveryRung[] _rungs;
|
|
|
|
private readonly long[] _lastRunMs;
|
|
|
|
private readonly bool[] _hasRun;
|
|
|
|
private readonly List<string> _rungsFired;
|
|
|
|
private bool _hasRunAny;
|
|
|
|
private long _lastRunAnyMs;
|
|
|
|
private long _firstStallMs;
|
|
|
|
private long _lastStallMs;
|
|
|
|
public int Attempts { get; private set; }
|
|
|
|
public bool IsExhausted { get; private set; }
|
|
|
|
private int Cap => _003Coptions_003EP.Mode switch
|
|
{
|
|
RecoveryMode.Aggressive => _003Coptions_003EP.AggressiveCap,
|
|
RecoveryMode.Passive => _003Coptions_003EP.PassiveCap,
|
|
_ => _003Coptions_003EP.ConservativeCap,
|
|
};
|
|
|
|
public RecoveryLadder(RecoveryLadderOptions options, IReadOnlyList<IRecoveryRung> rungs)
|
|
{
|
|
_003Coptions_003EP = options;
|
|
_rungs = rungs.ToArray();
|
|
_lastRunMs = new long[rungs.Count];
|
|
_hasRun = new bool[rungs.Count];
|
|
_rungsFired = new List<string>();
|
|
base._002Ector();
|
|
}
|
|
|
|
public RecoveryDecision OnStall(long nowMs)
|
|
{
|
|
if (IsExhausted)
|
|
{
|
|
return new RecoveryDecision(RecoveryDecisionKind.None, null, null);
|
|
}
|
|
if (Attempts >= Cap)
|
|
{
|
|
IsExhausted = true;
|
|
return new RecoveryDecision(RecoveryDecisionKind.Exhausted, null, new RecoveryExhaustion(Attempts, _rungsFired.ToArray(), _firstStallMs, _lastStallMs));
|
|
}
|
|
if (_hasRunAny && nowMs - _lastRunAnyMs < _003Coptions_003EP.GlobalCooldownMs)
|
|
{
|
|
return new RecoveryDecision(RecoveryDecisionKind.None, null, null);
|
|
}
|
|
if (_003Coptions_003EP.Mode == RecoveryMode.Passive)
|
|
{
|
|
Consume(nowMs);
|
|
return new RecoveryDecision(RecoveryDecisionKind.Observe, null, null);
|
|
}
|
|
for (int i = 0; i < _rungs.Length; i++)
|
|
{
|
|
IRecoveryRung recoveryRung = _rungs[i];
|
|
if ((!_hasRun[i] || nowMs - _lastRunMs[i] >= recoveryRung.CooldownMs) && recoveryRung.CanRun(Attempts + 1, nowMs))
|
|
{
|
|
Consume(nowMs);
|
|
_rungsFired.Add(recoveryRung.Name);
|
|
_hasRun[i] = true;
|
|
_lastRunMs[i] = nowMs;
|
|
_hasRunAny = true;
|
|
_lastRunAnyMs = nowMs;
|
|
return new RecoveryDecision(RecoveryDecisionKind.Run, recoveryRung, null);
|
|
}
|
|
}
|
|
return new RecoveryDecision(RecoveryDecisionKind.None, null, null);
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
Attempts = 0;
|
|
IsExhausted = false;
|
|
_rungsFired.Clear();
|
|
_hasRunAny = false;
|
|
for (int i = 0; i < _hasRun.Length; i++)
|
|
{
|
|
_hasRun[i] = false;
|
|
}
|
|
}
|
|
|
|
private void Consume(long nowMs)
|
|
{
|
|
if (Attempts == 0)
|
|
{
|
|
_firstStallMs = nowMs;
|
|
}
|
|
Attempts++;
|
|
_lastStallMs = nowMs;
|
|
}
|
|
}
|