Rewind: Fix subtle off-by-one-frame bugs (only really noticeable when frame advancing).

This commit is contained in:
J.D. Purcell 2017-04-02 18:50:34 -04:00
parent e767e1b673
commit 3f776dbf6f
3 changed files with 55 additions and 24 deletions

View File

@ -15,6 +15,7 @@ namespace BizHawk.Client.Common
private byte[] _lastState;
private int _rewindFrequency = 1;
private bool _rewindDeltaEnable;
private bool _lastRewindLoadedState;
private byte[] _deltaBuffer = new byte[0];
public Rewinder()
@ -30,21 +31,16 @@ namespace BizHawk.Client.Common
{
get { return _rewindBuffer != null ? _rewindBuffer.FullnessRatio : 0; }
}
public int Count
{
get { return _rewindBuffer != null ? _rewindBuffer.Count : 0; }
}
public long Size
public long Size
{
get { return _rewindBuffer != null ? _rewindBuffer.Size : 0; }
}
public int BufferCount
{
get { return _rewindBuffer != null ? _rewindBuffer.Count : 0; }
}
public bool HasBuffer
{
@ -125,26 +121,31 @@ namespace BizHawk.Client.Common
}
}
public void Rewind(int frames)
public bool Rewind(int frames)
{
if (!Global.Emulator.HasSavestates() || _rewindThread == null)
{
return;
return false;
}
_rewindThread.Rewind(frames);
return _lastRewindLoadedState;
}
private void RewindInternal(int frames)
{
_lastRewindLoadedState = false;
for (int i = 0; i < frames; i++)
{
if (_rewindBuffer.Count == 0 || (Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie.InputLogLength == 0))
if (_rewindBuffer.Count <= 1 || (Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie.InputLogLength == 0))
{
return;
break;
}
LoadPreviousState();
_lastRewindLoadedState = true;
}
}
@ -351,19 +352,27 @@ namespace BizHawk.Client.Common
UpdateLastState(currentState);
}
private MemoryStream GetPreviousStateMemoryStream()
{
if (_rewindDeltaEnable)
{
return _rewindBuffer.PopMemoryStream();
}
else
{
_rewindBuffer.Pop();
return _rewindBuffer.PeekMemoryStream();
}
}
private void LoadPreviousState()
{
using (var reader = new BinaryReader(_rewindBuffer.PopMemoryStream()))
using (var reader = new BinaryReader(GetPreviousStateMemoryStream()))
{
byte[] buf = ((MemoryStream)reader.BaseStream).GetBuffer();
var fullState = reader.ReadByte() == 1;
bool fullState = reader.ReadByte() == 1;
if (_rewindDeltaEnable)
{
using (var lastStateReader = new BinaryReader(new MemoryStream(_lastState)))
{
Global.Emulator.AsStatable().LoadStateBinary(lastStateReader);
}
if (fullState)
{
UpdateLastState(buf, 1, buf.Length - 1);
@ -384,6 +393,11 @@ namespace BizHawk.Client.Common
index += length;
}
}
using (var lastStateReader = new BinaryReader(new MemoryStream(_lastState)))
{
Global.Emulator.AsStatable().LoadStateBinary(lastStateReader);
}
}
else
{

View File

@ -95,12 +95,20 @@ namespace BizHawk.Client.Common
/// </summary>
public MemoryStream PopMemoryStream()
{
var item = Pop();
return CreateMemoryStream(Pop());
}
public MemoryStream PeekMemoryStream()
{
return CreateMemoryStream(Peek());
}
private MemoryStream CreateMemoryStream(ListItem item)
{
var buf = new byte[item.Length];
_mStream.Position = item.Index;
_mStream.Read(buf, 0, item.Length);
var ret = new MemoryStream(buf, 0, item.Length, false, true);
return ret;
return new MemoryStream(buf, 0, item.Length, false, true);
}
public long Enqueue(int timestamp, int amount)
@ -199,6 +207,16 @@ namespace BizHawk.Client.Common
return ret;
}
public ListItem Peek()
{
if (_mHead == null)
{
throw new InvalidOperationException("Attempted to peek from an empty data structure");
}
return _mHead.Value;
}
public ListItem Dequeue()
{
if (_mTail == null)

View File

@ -4186,8 +4186,7 @@ namespace BizHawk.Client.EmuHawk
if (isRewinding)
{
Global.Rewinder.Rewind(1);
runFrame = Global.Rewinder.Count != 0;
runFrame = Global.Rewinder.Rewind(1);
}
}
else