Pass new test: ensure we don't create gaps larger than the ancient state interval.

This commit is contained in:
SuuperW 2025-05-24 02:34:10 -05:00
parent 4672192d3f
commit c2beb017ed
5 changed files with 50 additions and 42 deletions

View File

@ -23,9 +23,9 @@ namespace BizHawk.Client.Common
void Capture(int frame, IStatable source, bool force = false); void Capture(int frame, IStatable source, bool force = false);
/// <summary> /// <summary>
/// Commands the state manager to remove a reserved state for the given frame, if it is exists /// Tell the state manager we no longer wish to reserve the state for the given frame.
/// </summary> /// </summary>
void EvictReserved(int frame); void Unreserve(int frame);
bool HasState(int frame); bool HasState(int frame);

View File

@ -88,7 +88,7 @@ namespace BizHawk.Client.Common
if (newBranch.UserText.Length is 0) newBranch.UserText = old.UserText; if (newBranch.UserText.Length is 0) newBranch.UserText = old.UserText;
this[index] = newBranch; this[index] = newBranch;
if (!_movie.IsReserved(old.Frame)) if (!_movie.IsReserved(old.Frame))
_movie.TasStateManager.EvictReserved(old.Frame); _movie.TasStateManager.Unreserve(old.Frame);
_movie.FlagChanges(); _movie.FlagChanges();
} }
@ -120,7 +120,7 @@ namespace BizHawk.Client.Common
if (result) if (result)
{ {
if (!_movie.IsReserved(item!.Frame)) if (!_movie.IsReserved(item!.Frame))
_movie.TasStateManager.EvictReserved(item.Frame); _movie.TasStateManager.Unreserve(item.Frame);
_movie.FlagChanges(); _movie.FlagChanges();
} }

View File

@ -206,7 +206,7 @@ namespace BizHawk.Client.Common
return; return;
} }
_movie.TasStateManager.EvictReserved(item.Frame - 1); _movie.TasStateManager.Unreserve(item.Frame - 1);
_movie.ChangeLog.AddMarkerChange(null, item.Frame, item.Message); _movie.ChangeLog.AddMarkerChange(null, item.Frame, item.Message);
base.Remove(item); base.Remove(item);
@ -221,7 +221,7 @@ namespace BizHawk.Client.Common
if (match.Invoke(m)) if (match.Invoke(m))
{ {
_movie.ChangeLog.AddMarkerChange(null, m.Frame, m.Message); _movie.ChangeLog.AddMarkerChange(null, m.Frame, m.Message);
_movie.TasStateManager.EvictReserved(m.Frame - 1); _movie.TasStateManager.Unreserve(m.Frame - 1);
} }
} }

View File

@ -93,22 +93,22 @@ namespace BizHawk.Client.Common
if (keepOldStates) if (keepOldStates)
{ {
// For ancients ... lets just make sure we aren't keeping states with a gap below the new interval // For ancients, let's throw out states if doing so still satisfies the ancient state interval.
if (settings.AncientStateInterval > _ancientInterval) if (settings.AncientStateInterval > _ancientInterval)
{ {
int lastReserved = 0; List<int> reservedFrames = _reserved.Keys.ToList();
List<int> framesToRemove = new List<int>(); reservedFrames.Sort();
foreach (int f in _reserved.Keys) for (int i = 1; i < reservedFrames.Count - 1; i++)
{ {
if (!_reserveCallback(f) && f - lastReserved < settings.AncientStateInterval) if (_reserveCallback(reservedFrames[i]))
framesToRemove.Add(f); continue;
else
lastReserved = f; if (reservedFrames[i + 1] - reservedFrames[i - 1] <= settings.AncientStateInterval)
{
EvictReserved(reservedFrames[i]);
reservedFrames.RemoveAt(i);
i--;
} }
foreach (int f in framesToRemove)
{
if (f != 0)
EvictReserved(f);
} }
} }
} }
@ -297,7 +297,7 @@ namespace BizHawk.Client.Common
} }
} }
public void EvictReserved(int frame) private void EvictReserved(int frame)
{ {
if (frame == 0) if (frame == 0)
{ {
@ -311,6 +311,16 @@ namespace BizHawk.Client.Common
} }
} }
public void Unreserve(int frame)
{
// Before removing the state, check if it should be still be reserved.
// For now, this just means checking if we need to keep this state to satisfy the ancient interval.
if (ShouldKeepForAncient(frame))
return;
EvictReserved(frame);
}
public void Capture(int frame, IStatable source, bool force = false) public void Capture(int frame, IStatable source, bool force = false)
{ {
// We already have this state, no need to capture // We already have this state, no need to capture
@ -366,42 +376,40 @@ namespace BizHawk.Client.Common
index2 => index2 =>
{ {
var state2 = _recent.GetState(index2); var state2 = _recent.GetState(index2);
StateCache.Remove(state2.Frame);
var isReserved = _reserveCallback(state2.Frame); var isReserved = _reserveCallback(state2.Frame);
// Add to reserved if reserved, or if it matches an "ancient" state consideration // Add to reserved if reserved, or if it matches an "ancient" state consideration
if (isReserved || !HasNearByReserved(state2.Frame)) if (isReserved || ShouldKeepForAncient(state2.Frame))
{ {
AddToReserved(state2); AddToReserved(state2);
} }
else
StateCache.Remove(state2.Frame);
}); });
}); });
} }
// Returns whether or not a frame has a reserved state within the frame interval on either side of it /// <summary>
private bool HasNearByReserved(int frame) /// Will removing the state on this leave us with a gap larger than the ancient interval?
/// </summary>
private bool ShouldKeepForAncient(int frame)
{ {
// An easy optimization, we know frame 0 always exists int index = StateCache.BinarySearch(frame + 1);
if (frame < _ancientInterval) if (index < 0)
index = ~index;
if (index == StateCache.Count)
{ {
return true; // There is no future state... so why are we wanting to evict this state?
} // We aren't decaying from _recent, that's for sure. So,
// Has nearby before
if (_reserved.Any(kvp => kvp.Key < frame && kvp.Key > frame - _ancientInterval))
{
return true;
}
// Has nearby after
if (_reserved.Any(kvp => kvp.Key > frame && kvp.Key < frame + _ancientInterval))
{
return true;
}
return false; return false;
} }
if (index <= 1)
return true; // This should never happen.
int nextState = StateCache[index];
int previousState = StateCache[index - 2]; // assume StateCache[index - 1] == frame
return nextState - previousState > _ancientInterval;
}
private bool NeedsGap(int frame) private bool NeedsGap(int frame)
{ {

View File

@ -413,7 +413,7 @@ namespace BizHawk.Tests.Client.Common.Movie
for (int i = 400; i < 1000; i += 400) for (int i = 400; i < 1000; i += 400)
{ {
zw.EvictReserved(i); zw.Unreserve(i);
} }
for (int i = 0; i < 10000; i++) for (int i = 0; i < 10000; i++)