Add support for multiple state manager types.

This commit is contained in:
SuuperW 2025-07-24 19:03:34 -05:00
parent 62ddf36289
commit 15d93f6eb8
12 changed files with 123 additions and 83 deletions

View File

@ -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();
}
}

View File

@ -9,7 +9,7 @@ namespace BizHawk.Client.Common
bool BindMarkersToInput { get; set; }
IMovieChangeLog ChangeLog { get; }
IStateManager<PagedStateManager.PagedSettings> TasStateManager { get; set; }
IStateManager TasStateManager { get; set; }
Func<string> InputRollSettingsForSave { get; set; }
string InputRollSettings { get; }
ITasMovieRecord this[int index] { get; }

View File

@ -4,9 +4,9 @@ using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
public interface IStateManager<TSettings> : IDisposable
public interface IStateManager : IDisposable
{
TSettings Settings { get; }
IStateManagerSettings Settings { get; }
/// <summary>
/// Requests that the current emulator state be captured
@ -44,8 +44,9 @@ namespace BizHawk.Client.Common
/// <summary>
/// Updates the internal state saving logic settings
/// May create a new state manager
/// </summary>
IStateManager<TSettings> UpdateSettings(TSettings settings, bool keepOldStates = false);
IStateManager UpdateSettings(IStateManagerSettings settings, bool keepOldStates = false);
/// <summary>
/// Serializes the current state of the instance for persisting to disk
@ -58,7 +59,7 @@ namespace BizHawk.Client.Common
void LoadStateHistory(BinaryReader br);
/// <summary>
/// Enables the instance to be used. An instance of <see cref="IStateManager{T}"/> should not
/// Enables the instance to be used. An instance of <see cref="IStateManager"/> should not
/// be useable until this method is called
/// </summary>
void Engage(byte[] frameZeroState);

View File

@ -0,0 +1,9 @@
namespace BizHawk.Client.Common
{
public interface IStateManagerSettings
{
IStateManager CreateManager(Func<int, bool> reserveCallback);
IStateManagerSettings Clone();
}
}

View File

@ -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);
}
}
}

View File

@ -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<PagedStateManager.PagedSettings>(json);
settings = JsonConvert.DeserializeObject<IStateManagerSettings>(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);
}
}
}

View File

@ -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<PagedStateManager.PagedSettings> TasStateManager { get; set; }
public IStateManager TasStateManager { get; set; }
public Action<int> GreenzoneInvalidated { get; set; }

View File

@ -8,7 +8,7 @@ using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
public class ZwinderStateManager : IStateManager<ZwinderStateManagerSettings>, IDisposable
public class ZwinderStateManager : IStateManager, IDisposable
{
private static readonly byte[] NonState = Array.Empty<byte>();
@ -36,12 +36,6 @@ namespace BizHawk.Client.Common
_reserveCallback = reserveCallback;
}
/// <param name="reserveCallback">Called when deciding to evict a state for the given frame, if true is returned, the state will be reserved</param>
public ZwinderStateManager(Func<int, bool> 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<int> 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<ZwinderStateManagerSettings> IStateManager<ZwinderStateManagerSettings>.UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldStates) => UpdateSettings(settings, keepOldStates);
private void RebuildReserved()
{

View File

@ -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<int, bool> reserveCallback)
{
return new ZwinderStateManager(this, reserveCallback);
}
public IStateManagerSettings Clone() => new ZwinderStateManagerSettings(this);
// Just to simplify some other code.
public RewindConfig Current()
{

View File

@ -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.
/// </summary>
public class PagedStateManager : IStateManager<PagedStateManager.PagedSettings>, 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<int, bool> 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<PagedSettings> IStateManager<PagedSettings>.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);
}
}
}
}

View File

@ -6,13 +6,13 @@ namespace BizHawk.Client.EmuHawk
{
public partial class GreenzoneSettings : Form, IDialogParent
{
private readonly Action<PagedStateManager.PagedSettings, bool> _saveSettings;
private PagedStateManager.PagedSettings _settings;
private readonly Action<IStateManagerSettings, bool> _saveSettings;
private IStateManagerSettings _settings;
private readonly bool _isDefault;
public IDialogController DialogController { get; }
public GreenzoneSettings(IDialogController dialogController, PagedStateManager.PagedSettings settings, Action<PagedStateManager.PagedSettings, bool> saveSettings, bool isDefault)
public GreenzoneSettings(IDialogController dialogController, IStateManagerSettings settings, Action<IStateManagerSettings, bool> saveSettings, bool isDefault)
{
DialogController = dialogController;
InitializeComponent();

View File

@ -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)
{