diff --git a/src/BizHawk.Client.Common/config/MovieConfig.cs b/src/BizHawk.Client.Common/config/MovieConfig.cs index ae2d283172..709f075509 100644 --- a/src/BizHawk.Client.Common/config/MovieConfig.cs +++ b/src/BizHawk.Client.Common/config/MovieConfig.cs @@ -8,7 +8,7 @@ public int MovieCompressionLevel { get; } public bool VBAStyleMovieLoadState { get; } public bool PlaySoundOnMovieEnd { get; set; } - PagedStateManager.PagedSettings DefaultTasStateManagerSettings { get; } + IStateManagerSettings DefaultTasStateManagerSettings { get; } } public class MovieConfig : IMovieConfig @@ -20,6 +20,6 @@ public bool VBAStyleMovieLoadState { get; set; } public bool PlaySoundOnMovieEnd { get; set; } - public PagedStateManager.PagedSettings DefaultTasStateManagerSettings { get; set; } = new(); + public IStateManagerSettings DefaultTasStateManagerSettings { get; set; } = new PagedStateManager.PagedSettings(); } } diff --git a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs index 5a6340ce23..df3c9fc6be 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs @@ -9,7 +9,7 @@ namespace BizHawk.Client.Common bool BindMarkersToInput { get; set; } IMovieChangeLog ChangeLog { get; } - IStateManager TasStateManager { get; set; } + IStateManager TasStateManager { get; set; } Func InputRollSettingsForSave { get; set; } string InputRollSettings { get; } ITasMovieRecord this[int index] { get; } diff --git a/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs b/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs index 2cf979c2ac..4a3f24d1df 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs @@ -4,9 +4,9 @@ using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { - public interface IStateManager : IDisposable + public interface IStateManager : IDisposable { - TSettings Settings { get; } + IStateManagerSettings Settings { get; } /// /// Requests that the current emulator state be captured @@ -44,8 +44,9 @@ namespace BizHawk.Client.Common /// /// Updates the internal state saving logic settings + /// May create a new state manager /// - IStateManager UpdateSettings(TSettings settings, bool keepOldStates = false); + IStateManager UpdateSettings(IStateManagerSettings settings, bool keepOldStates = false); /// /// Serializes the current state of the instance for persisting to disk @@ -58,7 +59,7 @@ namespace BizHawk.Client.Common void LoadStateHistory(BinaryReader br); /// - /// Enables the instance to be used. An instance of should not + /// Enables the instance to be used. An instance of should not /// be useable until this method is called /// void Engage(byte[] frameZeroState); diff --git a/src/BizHawk.Client.Common/movie/tasproj/IStateManagerSettings.cs b/src/BizHawk.Client.Common/movie/tasproj/IStateManagerSettings.cs new file mode 100644 index 0000000000..cefeb4ed5f --- /dev/null +++ b/src/BizHawk.Client.Common/movie/tasproj/IStateManagerSettings.cs @@ -0,0 +1,9 @@ +namespace BizHawk.Client.Common +{ + public interface IStateManagerSettings + { + IStateManager CreateManager(Func reserveCallback); + + IStateManagerSettings Clone(); + } +} diff --git a/src/BizHawk.Client.Common/movie/tasproj/StatableStream.cs b/src/BizHawk.Client.Common/movie/tasproj/StatableStream.cs new file mode 100644 index 0000000000..e67c5d27bd --- /dev/null +++ b/src/BizHawk.Client.Common/movie/tasproj/StatableStream.cs @@ -0,0 +1,36 @@ +using System.IO; +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common +{ + internal class StatableStream : IStatable + { + public bool AvoidRewind => false; + public void LoadStateBinary(BinaryReader reader) => throw new NotImplementedException(); + + private Stream _stream; + private int _length; + public StatableStream(Stream stream, int length) + { + _stream = stream; + _length = length; + } + public void SaveStateBinary(BinaryWriter writer) + { + int copied = 0; + const int bufferSize = 81920; // It's the default of CopyTo's buffer size + byte[] buffer = new byte[bufferSize]; + while (copied < _length - bufferSize) + { + if (_stream.Read(buffer, 0, bufferSize) != bufferSize) + throw new Exception("Unexpected end of stream."); + writer.Write(buffer); + copied += bufferSize; + } + int remaining = _length - copied; + if (_stream.Read(buffer, 0, remaining) != remaining) + throw new Exception("Unexpected end of stream."); + writer.Write(buffer, 0, remaining); + } + } +} diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs index c96b29ce0a..0149ffbca2 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs @@ -135,17 +135,20 @@ namespace BizHawk.Client.Common } }); - PagedStateManager.PagedSettings settings = new(); + IStateManagerSettings settings = Session.Settings.DefaultTasStateManagerSettings; bl.GetLump(BinaryStateLump.StateHistorySettings, abort: false, tr => { var json = tr.ReadToEnd(); try { - settings = JsonConvert.DeserializeObject(json); + settings = JsonConvert.DeserializeObject(json, new JsonSerializerSettings() + { + TypeNameHandling = TypeNameHandling.Objects, + }); } catch { - // Do nothing, and use default settings instead + settings = Session.Settings.DefaultTasStateManagerSettings; } }); @@ -155,7 +158,7 @@ namespace BizHawk.Client.Common { try { - TasStateManager = new PagedStateManager(settings, IsReserved); + TasStateManager = settings.CreateManager(IsReserved); TasStateManager.LoadStateHistory(br); } catch @@ -172,13 +175,11 @@ namespace BizHawk.Client.Common { try { - TasStateManager = new PagedStateManager(settings, IsReserved); + TasStateManager = settings.CreateManager(IsReserved); } catch { - TasStateManager = new PagedStateManager( - Session.Settings.DefaultTasStateManagerSettings, - IsReserved); + TasStateManager = Session.Settings.DefaultTasStateManagerSettings.CreateManager(IsReserved); } } } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index b2266b58fd..14669cafea 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -41,7 +41,7 @@ namespace BizHawk.Client.Common _inputPollable = emulator.AsInputPollable(); - TasStateManager ??= new PagedStateManager(Session.Settings.DefaultTasStateManagerSettings, IsReserved); + TasStateManager ??= Session.Settings.DefaultTasStateManagerSettings.CreateManager(IsReserved); if (StartsFromSavestate) { TasStateManager.Engage(BinarySavestate); @@ -81,7 +81,7 @@ namespace BizHawk.Client.Common public TasLagLog LagLog { get; } = new TasLagLog(); public override string PreferredExtension => Extension; - public IStateManager TasStateManager { get; set; } + public IStateManager TasStateManager { get; set; } public Action GreenzoneInvalidated { get; set; } diff --git a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs index 155f778fdb..a1a2649e66 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs @@ -8,7 +8,7 @@ using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { - public class ZwinderStateManager : IStateManager, IDisposable + public class ZwinderStateManager : IStateManager, IDisposable { private static readonly byte[] NonState = Array.Empty(); @@ -36,12 +36,6 @@ namespace BizHawk.Client.Common _reserveCallback = reserveCallback; } - /// Called when deciding to evict a state for the given frame, if true is returned, the state will be reserved - public ZwinderStateManager(Func reserveCallback) - : this(new ZwinderStateManagerSettings(), reserveCallback) - { - } - public void Engage(byte[] frameZeroState) { if (!_reserved.ContainsKey(0)) @@ -64,20 +58,37 @@ namespace BizHawk.Client.Common } public ZwinderStateManagerSettings Settings { get; private set; } + IStateManagerSettings IStateManager.Settings => Settings; - public ZwinderStateManager UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldStates = false) + public IStateManager UpdateSettings(IStateManagerSettings settings, bool keepOldStates = false) { - bool makeNewReserved = Settings?.AncientStoreType != settings.AncientStoreType; - Settings = settings; + if (settings is not ZwinderStateManagerSettings zSettings) + { + IStateManager newManager = settings.CreateManager(_reserveCallback); + newManager.Engage(GetStateClosestToFrame(0).Value.ReadAllBytes()); + if (keepOldStates) + { + foreach (int frame in StateCache) + { + Stream ss = GetStateClosestToFrame(frame).Value; + newManager.Capture(frame, new StatableStream(ss, (int)ss.Length)); + } + } + Dispose(); + return newManager; + } - _current = UpdateBuffer(_current, settings.Current(), keepOldStates); - _recent = UpdateBuffer(_recent, settings.Recent(), keepOldStates); - _gapFiller = UpdateBuffer(_gapFiller, settings.GapFiller(), keepOldStates); + bool makeNewReserved = Settings?.AncientStoreType != zSettings.AncientStoreType; + Settings = zSettings; + + _current = UpdateBuffer(_current, zSettings.Current(), keepOldStates); + _recent = UpdateBuffer(_recent, zSettings.Recent(), keepOldStates); + _gapFiller = UpdateBuffer(_gapFiller, zSettings.GapFiller(), keepOldStates); if (keepOldStates) { // For ancients, let's throw out states if doing so still satisfies the ancient state interval. - if (settings.AncientStateInterval > _ancientInterval) + if (zSettings.AncientStateInterval > _ancientInterval) { List reservedFrames = _reserved.Keys.ToList(); reservedFrames.Sort(); @@ -86,7 +97,7 @@ namespace BizHawk.Client.Common if (_reserveCallback(reservedFrames[i])) continue; - if (reservedFrames[i + 1] - reservedFrames[i - 1] <= settings.AncientStateInterval) + if (reservedFrames[i + 1] - reservedFrames[i - 1] <= zSettings.AncientStateInterval) { EvictReserved(reservedFrames[i]); reservedFrames.RemoveAt(i); @@ -113,12 +124,11 @@ namespace BizHawk.Client.Common if (makeNewReserved) RebuildReserved(); - _ancientInterval = settings.AncientStateInterval; + _ancientInterval = zSettings.AncientStateInterval; RebuildStateCache(); return this; } - IStateManager IStateManager.UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldStates) => UpdateSettings(settings, keepOldStates); private void RebuildReserved() { diff --git a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs index 9bb67ed348..579fea5321 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs @@ -5,7 +5,7 @@ using BizHawk.Common; namespace BizHawk.Client.Common { - public class ZwinderStateManagerSettings + public class ZwinderStateManagerSettings : IStateManagerSettings { public ZwinderStateManagerSettings() { } @@ -109,6 +109,13 @@ namespace BizHawk.Client.Common [Description("Where to keep the reserved states.")] public IRewindSettings.BackingStoreType AncientStoreType { get; set; } = IRewindSettings.BackingStoreType.Memory; + public IStateManager CreateManager(Func reserveCallback) + { + return new ZwinderStateManager(this, reserveCallback); + } + + public IStateManagerSettings Clone() => new ZwinderStateManagerSettings(this); + // Just to simplify some other code. public RewindConfig Current() { diff --git a/src/BizHawk.Client.Common/tools/TAStudio/PagedStateManager.cs b/src/BizHawk.Client.Common/tools/TAStudio/PagedStateManager.cs index 98811d5213..70aae47265 100644 --- a/src/BizHawk.Client.Common/tools/TAStudio/PagedStateManager.cs +++ b/src/BizHawk.Client.Common/tools/TAStudio/PagedStateManager.cs @@ -26,10 +26,11 @@ namespace BizHawk.Client.Common /// 1. No copies, ever. States are deposited directly to, and read directly from, one giant buffer. (assuming no TempFile storage which is not currently implemented) /// 2. Support for arbitrary and changeable state sizes. /// - public class PagedStateManager : IStateManager, IDisposable + public class PagedStateManager : IStateManager, IDisposable { public PagedSettings Settings { get; private set; } - public class PagedSettings + IStateManagerSettings IStateManager.Settings => Settings; + public class PagedSettings : IStateManagerSettings { // Instead of the user giving a set of memory limits, the user will give just one. // That will be the limit for ALL managed states combined. @@ -95,6 +96,13 @@ namespace BizHawk.Client.Common ForceSaveMarkerStates = other.ForceSaveMarkerStates; } + + public IStateManager CreateManager(Func reserveCallback) + { + return new PagedStateManager(this, reserveCallback); + } + + public IStateManagerSettings Clone() => new PagedSettings(this); } public int Count => _states.Count; @@ -553,25 +561,25 @@ namespace BizHawk.Client.Common } } - public PagedStateManager UpdateSettings(PagedSettings settings, bool keepOldStates = false) + public IStateManager UpdateSettings(IStateManagerSettings settings, bool keepOldStates = false) { - if (settings.TotalMemoryLimitMB != this.Settings.TotalMemoryLimitMB) + if (settings is not PagedSettings pSettings || pSettings.TotalMemoryLimitMB != this.Settings.TotalMemoryLimitMB) { - PagedStateManager newManager = new(settings, _reserveCallback); + IStateManager newManager = settings.CreateManager(_reserveCallback); newManager.Engage(GetStateClosestToFrame(0).Value.ReadAllBytes()); if (keepOldStates) foreach (StateInfo state in _states) - { - Stream s = GetStateClosestToFrame(state.Frame).Value; - newManager.Capture(state.Frame, new StatableStream(s, (int) s.Length)); - } + { + Stream s = GetStateClosestToFrame(state.Frame).Value; + newManager.Capture(state.Frame, new StatableStream(s, (int)s.Length)); + } Dispose(); return newManager; } else { - bool recaptureOld = settings.FramesBetweenOldStates > this.Settings.FramesBetweenOldStates; - this.Settings = settings; + bool recaptureOld = pSettings.FramesBetweenOldStates > this.Settings.FramesBetweenOldStates; + this.Settings = pSettings; if (recaptureOld) { foreach (StateInfo state in _states) @@ -586,7 +594,6 @@ namespace BizHawk.Client.Common return this; } } - IStateManager IStateManager.UpdateSettings(PagedSettings settings, bool keepOldStates) => UpdateSettings(settings, keepOldStates); public void SaveStateHistory(BinaryWriter bw) { bw.Write((byte)2); // version @@ -654,36 +661,5 @@ namespace BizHawk.Client.Common public StatableArray(byte[] array) => _array = array; public void SaveStateBinary(BinaryWriter writer) => writer.Write(_array); } - - private class StatableStream: IStatable - { - public bool AvoidRewind => false; - public void LoadStateBinary(BinaryReader reader) => throw new NotImplementedException(); - - private Stream _stream; - private int _length; - public StatableStream(Stream stream, int length) - { - _stream = stream; - _length = length; - } - public void SaveStateBinary(BinaryWriter writer) - { - int copied = 0; - const int bufferSize = 81920; // It's the default of CopyTo's buffer size - byte[] buffer = new byte[bufferSize]; - while (copied < _length - bufferSize) - { - if (_stream.Read(buffer, 0, bufferSize) != bufferSize) - throw new Exception("Unexpected end of stream."); - writer.Write(buffer); - copied += bufferSize; - } - int remaining = _length - copied; - if (_stream.Read(buffer, 0, remaining) != remaining) - throw new Exception("Unexpected end of stream."); - writer.Write(buffer, 0, remaining); - } - } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs index 440ac74a76..b0b56ee5c8 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs @@ -6,13 +6,13 @@ namespace BizHawk.Client.EmuHawk { public partial class GreenzoneSettings : Form, IDialogParent { - private readonly Action _saveSettings; - private PagedStateManager.PagedSettings _settings; + private readonly Action _saveSettings; + private IStateManagerSettings _settings; private readonly bool _isDefault; public IDialogController DialogController { get; } - public GreenzoneSettings(IDialogController dialogController, PagedStateManager.PagedSettings settings, Action saveSettings, bool isDefault) + public GreenzoneSettings(IDialogController dialogController, IStateManagerSettings settings, Action saveSettings, bool isDefault) { DialogController = dialogController; InitializeComponent(); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 931958180e..bb9bf8870f 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -915,7 +915,7 @@ namespace BizHawk.Client.EmuHawk { using GreenzoneSettings form = new( DialogController, - new PagedStateManager.PagedSettings(CurrentTasMovie.TasStateManager.Settings), + CurrentTasMovie.TasStateManager.Settings.Clone(), (s, k) => { CurrentTasMovie.TasStateManager = CurrentTasMovie.TasStateManager.UpdateSettings(s, k); }, false) { @@ -955,7 +955,7 @@ namespace BizHawk.Client.EmuHawk { using GreenzoneSettings form = new( DialogController, - new PagedStateManager.PagedSettings(Config.Movies.DefaultTasStateManagerSettings), + Config.Movies.DefaultTasStateManagerSettings.Clone(), (s, k) => { Config.Movies.DefaultTasStateManagerSettings = s; }, true) {