tsm: redo the algo from scratch, probably not last time

This commit is contained in:
feos 2018-02-27 01:28:24 +03:00
parent 13a49b00d0
commit 0fe703c9b7
2 changed files with 80 additions and 41 deletions

View File

@ -47,21 +47,13 @@ namespace BizHawk.Client.Common
private readonly TasMovie _movie; private readonly TasMovie _movie;
private ulong _expectedStateSize; private ulong _expectedStateSize;
private int _stateFrequency;
private readonly int _minFrequency = 1; private readonly int _minFrequency = 1;
private readonly int _maxFrequency = 16; private readonly int _maxFrequency = 16;
private int _maxStates => (int)(Settings.Cap / _expectedStateSize) + private int _maxStates => (int)(Settings.Cap / _expectedStateSize) +
(int)((ulong)Settings.DiskCapacitymb * 1024 * 1024 / _expectedStateSize); (int)((ulong)Settings.DiskCapacitymb * 1024 * 1024 / _expectedStateSize);
private int _fileStateGap => 1 << Settings.FileStateGap; private int _fileStateGap => 1 << Settings.FileStateGap;
private int _greenzoneDecayCall = 0;
private int StateFrequency
{
get
{
return NumberExtensions.Clamp(
((int)_expectedStateSize / Settings.MemStateGapDivider / 1024),
_minFrequency, _maxFrequency);
}
}
public TasStateManager(TasMovie movie) public TasStateManager(TasMovie movie)
{ {
@ -82,6 +74,13 @@ namespace BizHawk.Client.Common
NdbDatabase?.Dispose(); NdbDatabase?.Dispose();
} }
public void UpdateStateFrequency()
{
_stateFrequency = NumberExtensions.Clamp(
((int)_expectedStateSize / Settings.MemStateGapDivider / 1024),
_minFrequency, _maxFrequency);
}
/// <summary> /// <summary>
/// Mounts this instance for write access. Prior to that it's read-only /// Mounts this instance for write access. Prior to that it's read-only
/// </summary> /// </summary>
@ -95,6 +94,7 @@ namespace BizHawk.Client.Common
int limit = 0; int limit = 0;
_isMountedForWrite = true; _isMountedForWrite = true;
_expectedStateSize = (ulong)Core.SaveStateBinary().Length; _expectedStateSize = (ulong)Core.SaveStateBinary().Length;
UpdateStateFrequency();
if (_expectedStateSize > 0) if (_expectedStateSize > 0)
{ {
@ -176,7 +176,7 @@ namespace BizHawk.Client.Common
} }
else else
{ {
shouldCapture = frame % StateFrequency == 0; shouldCapture = frame % _stateFrequency == 0;
} }
if (shouldCapture) if (shouldCapture)
@ -287,55 +287,93 @@ namespace BizHawk.Client.Common
} }
/// <summary> /// <summary>
/// Deletes/moves states to follow the state storage size limits. /// Deletes states to follow the state storage size limits.
/// Used after changing the settings too. /// Used after changing the settings too.
/// TODO: don't miss states on region borders
/// </summary> /// </summary>
public void LimitStateCount() public void LimitStateCount()
{ {
if (Used + _expectedStateSize > Settings.Cap || DiskUsed > (ulong)Settings.DiskCapacitymb * 1024 * 1024) if (Used + _expectedStateSize > Settings.Cap || DiskUsed > (ulong)Settings.DiskCapacitymb * 1024 * 1024)
{ {
// we have 5 greenzone regions (by powers of 2), the closest one we do not touch // feos: this GREENZONE DECAY algo is critically important (and crazy), so I'll explain it fully here
int regionSize = _maxStates / 5; // we force decay gap between memory-based states that increases for every new region
int lastClearedFrame = 1; // regions start from the state right above the current frame (or right below for forward decay)
// we use powers of 2 to determine decay gap size and region length
// amount of regions and their lengths depend on how many powers of 2 we want to use
// we use 5 powers of 2, from 0 to 4. decay gap goes 0, 1, 3, 7, 15 (in reality, not perfectly so)
// 1 decay gap unit is 1 frame * minimal state frequency
// first region has no decay gaps, the length of that region in fceux is called "greenzone capacity"
// every next region is twice longer than its predecessor, but it has the same amount of states (approximately)
// states beyond last region are erased, except for state at frame 0
// algo works in both directions, alternating between them on every call
// it removes as many states is its pattern needs, which allows for cooldown before cap is about to get hit again
// todo: this is still imperfect, even though probably usable already
// iterate through regions (region frame sizes increase by 2) _greenzoneDecayCall++;
for (int borderIndex = GetStateIndexByFrame(Global.Emulator.Frame) - regionSize, mult = 2; borderIndex > 0; mult *= 2)
int regionStates = _maxStates / 5;
int baseIndex = GetStateIndexByFrame(Global.Emulator.Frame);
int direction = 1; // negative for forward decay
if (_greenzoneDecayCall % 2 == 0)
{ {
int nextBorderIndex = borderIndex - regionSize * mult; baseIndex++;
if (nextBorderIndex <= 0) direction = -1;
nextBorderIndex = 1; // reached greenzone end }
// iterate through states. i > 0 because nextBorderIndex > 0 int lastStateFrame = -1;
for (int i = borderIndex; i > nextBorderIndex; i--)
for (int mult = 2, currentStateIndex = baseIndex - regionStates * direction; mult <= 16; mult *= 2)
{
int gap = _stateFrequency * mult;
int regionFrames = regionStates * gap;
for (; ; currentStateIndex -= direction)
{ {
int minStep = mult * StateFrequency; // are we out of states yet?
int curFrame = GetStateFrameByIndex(i); if (direction > 0 && currentStateIndex <= 1 ||
int nextFrame = GetStateFrameByIndex(i - 1); direction < 0 && currentStateIndex >= _states.Count - 1)
return;
if (curFrame - nextFrame < minStep) int nextStateIndex = currentStateIndex - direction;
NumberExtensions.Clamp(nextStateIndex, 1, _states.Count - 1);
int currentStateFrame = GetStateFrameByIndex(currentStateIndex);
int nextStateFrame = GetStateFrameByIndex(nextStateIndex);
int frameDiff = Math.Max(currentStateFrame, nextStateFrame) - Math.Min(currentStateFrame, nextStateFrame);
lastStateFrame = currentStateFrame;
if (frameDiff < gap)
{ {
RemoveState(nextFrame); RemoveState(nextStateFrame);
lastClearedFrame = nextFrame;
// when going forward, we don't remove the state before current
// but current changes anyway, so compensate for that here
if (direction < 0)
currentStateIndex--;
} }
else else
continue; {
regionFrames -= frameDiff;
if (regionFrames <= 0)
break;
}
} }
if (nextBorderIndex == 1)
return;
borderIndex = nextBorderIndex;
} }
// finish off whatever we've missed // finish off whatever we've missed
List<KeyValuePair<int, StateManagerState>> leftoverStates = _states if (lastStateFrame > -1)
.Where(s => s.Key > 0 && s.Key < lastClearedFrame)
.ToList();
foreach (var state in leftoverStates)
{ {
RemoveState(state.Key); List<KeyValuePair<int, StateManagerState>> leftoverStates;
if (direction > 0)
leftoverStates = _states.Where(s => s.Key > 0 && s.Key < lastStateFrame).ToList();
else
leftoverStates = _states.Where(s => s.Key > lastStateFrame && s.Key < LastEmulatedFrame).ToList();
foreach (var state in leftoverStates)
{
RemoveState(state.Key);
}
} }
} }
} }

View File

@ -1014,6 +1014,7 @@ namespace BizHawk.Client.EmuHawk
Location = this.ChildPointToScreen(TasView), Location = this.ChildPointToScreen(TasView),
Statable = this.StatableEmulator Statable = this.StatableEmulator
}.ShowDialog(); }.ShowDialog();
CurrentTasMovie.TasStateManager.UpdateStateFrequency();
CurrentTasMovie.TasStateManager.LimitStateCount(); CurrentTasMovie.TasStateManager.LimitStateCount();
UpdateChangesIndicator(); UpdateChangesIndicator();
} }