diff --git a/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs b/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs index c8000adb54..86cc34298a 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs @@ -60,7 +60,7 @@ namespace BizHawk.Client.Common /// /// Updates the internal state saving logic settings /// - void UpdateSettings(ZwinderStateManagerSettings settings); + void UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldStates = false); /// /// Serializes the current state of the instance for persisting to disk diff --git a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs index f7eabb8c89..ce750aee89 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs @@ -25,33 +25,12 @@ namespace BizHawk.Client.Common // When recent states are evicted this interval is used to determine if we need to reserve the state // We always want to keep some states throughout the movie - private readonly int _ancientInterval; + private int _ancientInterval; internal ZwinderStateManager(ZwinderStateManagerSettings settings, Func reserveCallback) { - Settings = settings; + UpdateSettings(settings, false); - _current = new ZwinderBuffer(new RewindConfig - { - UseCompression = settings.CurrentUseCompression, - BufferSize = settings.CurrentBufferSize, - TargetFrameLength = settings.CurrentTargetFrameLength - }); - _recent = new ZwinderBuffer(new RewindConfig - { - UseCompression = settings.RecentUseCompression, - BufferSize = settings.RecentBufferSize, - TargetFrameLength = settings.RecentTargetFrameLength - }); - - _gapFiller = new ZwinderBuffer(new RewindConfig - { - UseCompression = settings.GapsUseCompression, - BufferSize = settings.GapsBufferSize, - TargetFrameLength = settings.GapsTargetFrameLength - }); - - _ancientInterval = settings.AncientStateInterval; _reserveCallback = reserveCallback; } @@ -95,8 +74,83 @@ namespace BizHawk.Client.Common } } - // TODO: private set, refactor LoadTasprojExtras to hold onto a settings object and pass it in to Create() method - public ZwinderStateManagerSettings Settings { get; set; } + public ZwinderStateManagerSettings Settings { get; private set; } + + public void UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldStates = false) + { + Settings = settings; + + _current = UpdateBuffer(_current, settings.Current(), keepOldStates); + _recent = UpdateBuffer(_recent, settings.Recent(), keepOldStates); + _gapFiller = UpdateBuffer(_gapFiller, settings.GapFiller(), keepOldStates); + + if (keepOldStates) + { + // For ancients ... lets just make sure we aren't keeping states with a gap below the new interval + if (settings.AncientStateInterval > _ancientInterval) + { + int lastReserved = 0; + List framesToRemove = new List(); + foreach (int f in _reserved.Keys) + { + if (!_reserveCallback(f) && f - lastReserved < settings.AncientStateInterval) + framesToRemove.Add(f); + else + lastReserved = f; + } + foreach (int f in framesToRemove) + EvictReserved(f); + } + } + else + { + foreach (int f in _reserved.Keys) + { + if (f != 0 && !_reserveCallback(f)) + EvictReserved(f); + } + } + + _ancientInterval = settings.AncientStateInterval; + RebuildStateCache(); + } + private ZwinderBuffer UpdateBuffer(ZwinderBuffer buffer, RewindConfig newConfig, bool keepOldStates) + { + if (buffer == null) // just make a new one, plain and simple + buffer = new ZwinderBuffer(newConfig); + else if (!buffer.MatchesSettings(newConfig)) // no need to do anything if these settings are already in use + { + if (keepOldStates) + { + // force capture all the old states, let the buffer handle decay if they don't all fit + ZwinderBuffer old = buffer; + buffer = new ZwinderBuffer(newConfig); + for (int i = 0; i < old.Count; i++) + { + ZwinderBuffer.StateInformation si = old.GetState(i); + // don't allow states that should be reserved to decay here, where we don't attempt re-capture + if (_reserveCallback(si.Frame)) + AddToReserved(si); + else + buffer.Capture(si.Frame, s => si.GetReadStream().CopyTo(s), null, true); + } + old.Dispose(); + } + else + { + buffer.Dispose(); + buffer = new ZwinderBuffer(newConfig); + } + } + return buffer; + } + + private void RebuildStateCache() + { + StateCache.Clear(); + foreach (StateInfo state in AllStates()) + StateCache.Add(state.Frame); + } public int Count => _current.Count + _recent.Count + _gapFiller.Count + _reserved.Count; @@ -383,8 +437,6 @@ namespace BizHawk.Client.Common return _reserved.Count < origCount; } - public void UpdateSettings(ZwinderStateManagerSettings settings) => Settings = settings; - public bool InvalidateAfter(int frame) { if (frame < 0) @@ -418,11 +470,7 @@ namespace BizHawk.Client.Common ret._reserved.Add(key, data); } - var allStates = ret.AllStates().ToList(); - foreach (var state in allStates) - { - ret.StateCache.Add(state.Frame); - } + ret.RebuildStateCache(); return ret; } diff --git a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs index 055fc470c8..4ca00196c5 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs @@ -68,5 +68,34 @@ namespace BizHawk.Client.Common [DisplayName("Ancient State Interval")] [Description("How often to maintain states when outside of Current and Recent intervals")] public int AncientStateInterval { get; set; } = 5000; + + // Just to simplify some other code. + public RewindConfig Current() + { + return new RewindConfig() + { + UseCompression = CurrentUseCompression, + BufferSize = CurrentBufferSize, + TargetFrameLength = CurrentTargetFrameLength + }; + } + public RewindConfig Recent() + { + return new RewindConfig() + { + UseCompression = RecentUseCompression, + BufferSize = RecentBufferSize, + TargetFrameLength = RecentTargetFrameLength + }; + } + public RewindConfig GapFiller() + { + return new RewindConfig() + { + UseCompression = GapsUseCompression, + BufferSize = GapsBufferSize, + TargetFrameLength = GapsTargetFrameLength + }; + } } } diff --git a/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs b/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs index 9ee5c7e5de..90c0deab61 100644 --- a/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs +++ b/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs @@ -106,6 +106,14 @@ namespace BizHawk.Client.Common return Math.Max(idealInterval, 1); } + public bool MatchesSettings(RewindConfig settings) + { + long size = 1L << (int)Math.Floor(Math.Log(settings.BufferSize, 2)); + return Size == size && + _useCompression == settings.UseCompression && + _targetFrameLength == settings.TargetFrameLength; + } + private bool ShouldCapture(int frame) { if (Count == 0) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 7a75aaa90c..d44395e8a5 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -996,17 +996,11 @@ namespace BizHawk.Client.EmuHawk }.Show(); } - private void UpdateStateSettings(ZwinderStateManagerSettings settings) - { - Config.Movies.DefaultTasStateManagerSettings = settings; - CurrentTasMovie.TasStateManager.UpdateSettings(settings); - } - private void StateHistorySettingsMenuItem_Click(object sender, EventArgs e) { new DefaultGreenzoneSettings( - new ZwinderStateManagerSettings(Config.Movies.DefaultTasStateManagerSettings), - UpdateStateSettings) + new ZwinderStateManagerSettings(CurrentTasMovie.TasStateManager.Settings), + s => { CurrentTasMovie.TasStateManager.UpdateSettings(s, true); }) { Location = this.ChildPointToScreen(TasView), Text = "Savestate History Settings",