Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/BizHawk.Client.Common/config/MovieConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public interface IMovieConfig
public int MovieCompressionLevel { get; }
public bool VBAStyleMovieLoadState { get; }
public bool PlaySoundOnMovieEnd { get; set; }
ZwinderStateManagerSettings DefaultTasStateManagerSettings { get; }
IStateManagerSettings DefaultTasStateManagerSettings { get; }
}

public class MovieConfig : IMovieConfig
Expand All @@ -20,6 +20,6 @@ public class MovieConfig : IMovieConfig
public bool VBAStyleMovieLoadState { get; set; }
public bool PlaySoundOnMovieEnd { get; set; }

public ZwinderStateManagerSettings DefaultTasStateManagerSettings { get; set; } = new ZwinderStateManagerSettings();
public IStateManagerSettings DefaultTasStateManagerSettings { get; set; } = new PagedStateManager.PagedSettings();
}
}
2 changes: 1 addition & 1 deletion src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public interface ITasMovie : IMovie, INotifyPropertyChanged, IDisposable
bool BindMarkersToInput { get; set; }

IMovieChangeLog ChangeLog { get; }
IStateManager TasStateManager { get; }
IStateManager TasStateManager { get; set; }
Func<string> InputRollSettingsForSave { get; set; }
string InputRollSettings { get; }
ITasMovieRecord this[int index] { get; }
Expand Down
18 changes: 8 additions & 10 deletions src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,7 @@ namespace BizHawk.Client.Common
{
public interface IStateManager : IDisposable
{
/// <summary>
/// Retrieves the savestate for the given frame,
/// If this frame does not have a state currently, will return an empty array.false
/// Try not to use this as it is not fast.
/// </summary>
/// <returns>A savestate for the given frame or an empty array if there isn't one</returns>
byte[] this[int frame] { get; }

ZwinderStateManagerSettings Settings { get; }
IStateManagerSettings Settings { get; }

/// <summary>
/// Requests that the current emulator state be captured
Expand Down Expand Up @@ -52,14 +44,20 @@ public interface IStateManager : IDisposable

/// <summary>
/// Updates the internal state saving logic settings
/// May create a new state manager
/// </summary>
void UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldStates = false);
IStateManager UpdateSettings(IStateManagerSettings settings, bool keepOldStates = false);

/// <summary>
/// Serializes the current state of the instance for persisting to disk
/// </summary>
void SaveStateHistory(BinaryWriter bw);

/// <summary>
/// Deserializes the state of the instance that was persisted to disk
/// </summary>
void LoadStateHistory(BinaryReader br);

/// <summary>
/// Enables the instance to be used. An instance of <see cref="IStateManager"/> should not
/// be useable until this method is called
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace BizHawk.Client.Common
{
public interface IStateManagerSettings
{
IStateManager CreateManager(Func<int, bool> reserveCallback);

IStateManagerSettings Clone();
}
}
36 changes: 36 additions & 0 deletions src/BizHawk.Client.Common/movie/tasproj/StatableStream.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
25 changes: 13 additions & 12 deletions src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,50 +135,51 @@ private void LoadTasprojExtras(ZipStateLoader bl)
}
});

var settings = new ZwinderStateManagerSettings();
IStateManagerSettings settings = Session.Settings.DefaultTasStateManagerSettings;
bl.GetLump(BinaryStateLump.StateHistorySettings, abort: false, tr =>
{
var json = tr.ReadToEnd();
try
{
settings = JsonConvert.DeserializeObject<ZwinderStateManagerSettings>(json);
settings = JsonConvert.DeserializeObject<IStateManagerSettings>(json, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects,
});
}
catch
{
// Do nothing, and use default settings instead
settings = Session.Settings.DefaultTasStateManagerSettings;
}
});

TasStateManager?.Dispose();
bool badHistory = false;
var hasHistory = bl.GetLump(BinaryStateLump.StateHistory, abort: false, br =>
{
try
{
TasStateManager = ZwinderStateManager.Create(br, settings, IsReserved);
TasStateManager = settings.CreateManager(IsReserved);
TasStateManager.LoadStateHistory(br);
}
catch
{
// Continue with a fresh manager. If state history got corrupted, the file is still very much useable
// and we would want the user to be able to load, and regenerate their state history
// however, we still have an issue of how state history got corrupted
TasStateManager = new ZwinderStateManager(
Session.Settings.DefaultTasStateManagerSettings,
IsReserved);
badHistory = true;
Session.PopupMessage("State history was corrupted, clearing and working with a fresh history.");
}
});

if (!hasHistory)
if (!hasHistory || badHistory)
{
try
{
TasStateManager = new ZwinderStateManager(settings, IsReserved);
TasStateManager = settings.CreateManager(IsReserved);
}
catch
{
TasStateManager = new ZwinderStateManager(
Session.Settings.DefaultTasStateManagerSettings,
IsReserved);
TasStateManager = Session.Settings.DefaultTasStateManagerSettings.CreateManager(IsReserved);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public override void Attach(IEmulator emulator)

_inputPollable = emulator.AsInputPollable();

TasStateManager ??= new ZwinderStateManager(Session.Settings.DefaultTasStateManagerSettings, IsReserved);
TasStateManager ??= Session.Settings.DefaultTasStateManagerSettings.CreateManager(IsReserved);
if (StartsFromSavestate)
{
TasStateManager.Engage(BinarySavestate);
Expand Down Expand Up @@ -81,7 +81,7 @@ public override bool StartsFromSavestate
public TasLagLog LagLog { get; } = new TasLagLog();

public override string PreferredExtension => Extension;
public IStateManager TasStateManager { get; private set; }
public IStateManager TasStateManager { get; set; }

public Action<int> GreenzoneInvalidated { get; set; }

Expand Down
75 changes: 32 additions & 43 deletions src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ internal ZwinderStateManager(ZwinderStateManagerSettings settings, Func<int, boo
_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))
Expand All @@ -63,38 +57,38 @@ private ZwinderStateManager(ZwinderBuffer current, ZwinderBuffer recent, Zwinder
RebuildReserved();
}

public byte[] this[int frame]
public ZwinderStateManagerSettings Settings { get; private set; }
IStateManagerSettings IStateManager.Settings => Settings;

public IStateManager UpdateSettings(IStateManagerSettings settings, bool keepOldStates = false)
{
get
if (settings is not ZwinderStateManagerSettings zSettings)
{
var (f, dataStream) = GetStateClosestToFrame(frame);
if (f != frame)
IStateManager newManager = settings.CreateManager(_reserveCallback);
newManager.Engage(GetStateClosestToFrame(0).Value.ReadAllBytes());
if (keepOldStates)
{
dataStream.Dispose();
return NonState;
foreach (int frame in StateCache)
{
Stream ss = GetStateClosestToFrame(frame).Value;
newManager.Capture(frame, new StatableStream(ss, (int)ss.Length));
}
}

var data = dataStream.ReadAllBytes();
dataStream.Dispose();
return data;
Dispose();
return newManager;
}
}

public ZwinderStateManagerSettings Settings { get; private set; }
bool makeNewReserved = Settings?.AncientStoreType != zSettings.AncientStoreType;
Settings = zSettings;

public void UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldStates = false)
{
bool makeNewReserved = Settings?.AncientStoreType != settings.AncientStoreType;
Settings = settings;

_current = UpdateBuffer(_current, settings.Current(), keepOldStates);
_recent = UpdateBuffer(_recent, settings.Recent(), keepOldStates);
_gapFiller = UpdateBuffer(_gapFiller, settings.GapFiller(), keepOldStates);
_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();
Expand All @@ -103,7 +97,7 @@ public void UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldSta
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);
Expand All @@ -130,8 +124,10 @@ public void UpdateSettings(ZwinderStateManagerSettings settings, bool keepOldSta
if (makeNewReserved)
RebuildReserved();

_ancientInterval = settings.AncientStateInterval;
_ancientInterval = zSettings.AncientStateInterval;
RebuildStateCache();

return this;
}

private void RebuildReserved()
Expand Down Expand Up @@ -523,32 +519,25 @@ public bool InvalidateAfter(int frame)
return b1 || b2 || b3;
}

public static ZwinderStateManager Create(BinaryReader br, ZwinderStateManagerSettings settings, Func<int, bool> reserveCallback)
public void LoadStateHistory(BinaryReader br)
{
// Initial format had no version number, but I think it's a safe bet no valid file has buffer size 2^56 or more so this should work.
int version = br.ReadByte();
if (version == 0) throw new Exception("Unsupported GreenZone version.");

var current = ZwinderBuffer.Create(br, settings.Current(), version == 0);
var recent = ZwinderBuffer.Create(br, settings.Recent());
var gaps = ZwinderBuffer.Create(br, settings.GapFiller());

if (version == 0)
settings.AncientStateInterval = br.ReadInt32();

var ret = new ZwinderStateManager(current, recent, gaps, reserveCallback, settings);
_current.Load(br);
_recent.Load(br);
_gapFiller.Load(br);

var ancientCount = br.ReadInt32();
for (var i = 0; i < ancientCount; i++)
{
var key = br.ReadInt32();
var length = br.ReadInt32();
var data = br.ReadBytes(length);
ret._reserved.Add(key, data);
_reserved.Add(key, data);
}

ret.RebuildStateCache();

return ret;
RebuildStateCache();
}

public void SaveStateHistory(BinaryWriter bw)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace BizHawk.Client.Common
{
public class ZwinderStateManagerSettings
public class ZwinderStateManagerSettings : IStateManagerSettings
{
public ZwinderStateManagerSettings() { }

Expand Down Expand Up @@ -109,6 +109,13 @@ public ZwinderStateManagerSettings(ZwinderStateManagerSettings settings)
[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()
{
Expand Down
Loading
Loading