From 0fe703c9b70bd6a2f8e8545c3e92185cf28065f6 Mon Sep 17 00:00:00 2001 From: feos Date: Tue, 27 Feb 2018 01:28:24 +0300 Subject: [PATCH] tsm: redo the algo from scratch, probably not last time --- .../movie/tasproj/TasStateManager.cs | 120 ++++++++++++------ .../tools/TAStudio/TAStudio.MenuItems.cs | 1 + 2 files changed, 80 insertions(+), 41 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index 2bb78d5d0a..d549cb9aab 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -47,21 +47,13 @@ namespace BizHawk.Client.Common private readonly TasMovie _movie; private ulong _expectedStateSize; + private int _stateFrequency; private readonly int _minFrequency = 1; private readonly int _maxFrequency = 16; private int _maxStates => (int)(Settings.Cap / _expectedStateSize) + (int)((ulong)Settings.DiskCapacitymb * 1024 * 1024 / _expectedStateSize); private int _fileStateGap => 1 << Settings.FileStateGap; - - private int StateFrequency - { - get - { - return NumberExtensions.Clamp( - ((int)_expectedStateSize / Settings.MemStateGapDivider / 1024), - _minFrequency, _maxFrequency); - } - } + private int _greenzoneDecayCall = 0; public TasStateManager(TasMovie movie) { @@ -82,6 +74,13 @@ namespace BizHawk.Client.Common NdbDatabase?.Dispose(); } + public void UpdateStateFrequency() + { + _stateFrequency = NumberExtensions.Clamp( + ((int)_expectedStateSize / Settings.MemStateGapDivider / 1024), + _minFrequency, _maxFrequency); + } + /// /// Mounts this instance for write access. Prior to that it's read-only /// @@ -95,6 +94,7 @@ namespace BizHawk.Client.Common int limit = 0; _isMountedForWrite = true; _expectedStateSize = (ulong)Core.SaveStateBinary().Length; + UpdateStateFrequency(); if (_expectedStateSize > 0) { @@ -176,7 +176,7 @@ namespace BizHawk.Client.Common } else { - shouldCapture = frame % StateFrequency == 0; + shouldCapture = frame % _stateFrequency == 0; } if (shouldCapture) @@ -287,55 +287,93 @@ namespace BizHawk.Client.Common } /// - /// 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. - /// TODO: don't miss states on region borders /// public void LimitStateCount() { 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 - int regionSize = _maxStates / 5; - int lastClearedFrame = 1; + // feos: this GREENZONE DECAY algo is critically important (and crazy), so I'll explain it fully here + // we force decay gap between memory-based states that increases for every new region + // 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) - for (int borderIndex = GetStateIndexByFrame(Global.Emulator.Frame) - regionSize, mult = 2; borderIndex > 0; mult *= 2) + _greenzoneDecayCall++; + + 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; - if (nextBorderIndex <= 0) - nextBorderIndex = 1; // reached greenzone end + baseIndex++; + direction = -1; + } - // iterate through states. i > 0 because nextBorderIndex > 0 - for (int i = borderIndex; i > nextBorderIndex; i--) + int lastStateFrame = -1; + + 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; - int curFrame = GetStateFrameByIndex(i); - int nextFrame = GetStateFrameByIndex(i - 1); + // are we out of states yet? + if (direction > 0 && currentStateIndex <= 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); - lastClearedFrame = nextFrame; + RemoveState(nextStateFrame); + + // 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 - continue; + { + regionFrames -= frameDiff; + if (regionFrames <= 0) + break; + } } - - if (nextBorderIndex == 1) - return; - - borderIndex = nextBorderIndex; } // finish off whatever we've missed - List> leftoverStates = _states - .Where(s => s.Key > 0 && s.Key < lastClearedFrame) - .ToList(); - - foreach (var state in leftoverStates) + if (lastStateFrame > -1) { - RemoveState(state.Key); + List> 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); + } } } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index e1ef5b53a2..1f9cb3447a 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -1014,6 +1014,7 @@ namespace BizHawk.Client.EmuHawk Location = this.ChildPointToScreen(TasView), Statable = this.StatableEmulator }.ShowDialog(); + CurrentTasMovie.TasStateManager.UpdateStateFrequency(); CurrentTasMovie.TasStateManager.LimitStateCount(); UpdateChangesIndicator(); }