2013-12-27 00:47:52 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
2015-01-17 06:20:00 +00:00
|
|
|
|
using BizHawk.Common;
|
2014-11-30 16:42:58 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
|
|
|
|
using BizHawk.Emulation.Common.IEmulatorExtensions;
|
|
|
|
|
|
2013-12-27 01:14:17 +00:00
|
|
|
|
namespace BizHawk.Client.Common
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
|
|
|
|
public class Rewinder
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
private StreamBlobDatabase _rewindBuffer;
|
|
|
|
|
private RewindThreader _rewindThread;
|
|
|
|
|
private byte[] _lastState;
|
|
|
|
|
private bool _rewindImpossible;
|
|
|
|
|
private int _rewindFrequency = 1;
|
|
|
|
|
private bool _rewindDeltaEnable;
|
|
|
|
|
private byte[] _rewindFellationBuf;
|
|
|
|
|
private byte[] _tempBuf = new byte[0];
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2014-02-04 03:08:20 +00:00
|
|
|
|
public Rewinder()
|
|
|
|
|
{
|
|
|
|
|
RewindActive = true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-30 01:17:11 +00:00
|
|
|
|
public Action<string> MessageCallback { get; set; }
|
|
|
|
|
public bool RewindActive { get; set; }
|
2013-12-27 01:14:17 +00:00
|
|
|
|
|
2013-12-27 00:47:52 +00:00
|
|
|
|
// TODO: make RewindBuf never be null
|
|
|
|
|
public float FullnessRatio
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
get { return _rewindBuffer.FullnessRatio; }
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Count
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
get { return _rewindBuffer != null ? _rewindBuffer.Count : 0; }
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public long Size
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
get { return _rewindBuffer != null ? _rewindBuffer.Size : 0; }
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int BufferCount
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
get { return _rewindBuffer != null ? _rewindBuffer.Count : 0; }
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool HasBuffer
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
get { return _rewindBuffer != null; }
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-12 05:30:27 +00:00
|
|
|
|
public int RewindFrequency
|
|
|
|
|
{
|
|
|
|
|
get { return _rewindFrequency; }
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-27 00:47:52 +00:00
|
|
|
|
// TOOD: this should not be parameterless?! It is only possible due to passing a static context in
|
|
|
|
|
public void CaptureRewindState()
|
|
|
|
|
{
|
2014-11-30 16:42:58 +00:00
|
|
|
|
if (Global.Emulator.HasSavestates())
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2014-11-30 16:42:58 +00:00
|
|
|
|
if (_rewindImpossible)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2014-11-30 16:42:58 +00:00
|
|
|
|
if (_lastState == null)
|
|
|
|
|
{
|
|
|
|
|
DoRewindSettings();
|
|
|
|
|
}
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2014-11-30 16:42:58 +00:00
|
|
|
|
// log a frame
|
|
|
|
|
if (_lastState != null && Global.Emulator.Frame % _rewindFrequency == 0)
|
|
|
|
|
{
|
2014-12-05 00:52:16 +00:00
|
|
|
|
_rewindThread.Capture(Global.Emulator.AsStatable().SaveStateBinary());
|
2014-11-30 16:42:58 +00:00
|
|
|
|
}
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DoRewindSettings()
|
|
|
|
|
{
|
2014-11-30 16:42:58 +00:00
|
|
|
|
if (Global.Emulator.HasSavestates())
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2014-11-30 16:42:58 +00:00
|
|
|
|
// This is the first frame. Capture the state, and put it in LastState for future deltas to be compared against.
|
2014-12-05 00:52:16 +00:00
|
|
|
|
_lastState = (byte[])Global.Emulator.AsStatable().SaveStateBinary().Clone();
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2014-11-30 16:42:58 +00:00
|
|
|
|
int state_size;
|
|
|
|
|
if (_lastState.Length >= Global.Config.Rewind_LargeStateSize)
|
|
|
|
|
{
|
|
|
|
|
SetRewindParams(Global.Config.RewindEnabledLarge, Global.Config.RewindFrequencyLarge);
|
|
|
|
|
state_size = 3;
|
|
|
|
|
}
|
|
|
|
|
else if (_lastState.Length >= Global.Config.Rewind_MediumStateSize)
|
|
|
|
|
{
|
|
|
|
|
SetRewindParams(Global.Config.RewindEnabledMedium, Global.Config.RewindFrequencyMedium);
|
|
|
|
|
state_size = 2;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SetRewindParams(Global.Config.RewindEnabledSmall, Global.Config.RewindFrequencySmall);
|
|
|
|
|
state_size = 1;
|
|
|
|
|
}
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2014-11-30 16:42:58 +00:00
|
|
|
|
var rewind_enabled = false;
|
|
|
|
|
if (state_size == 1)
|
|
|
|
|
{
|
|
|
|
|
rewind_enabled = Global.Config.RewindEnabledSmall;
|
|
|
|
|
}
|
|
|
|
|
else if (state_size == 2)
|
2013-12-27 01:14:17 +00:00
|
|
|
|
{
|
2014-11-30 16:42:58 +00:00
|
|
|
|
rewind_enabled = Global.Config.RewindEnabledMedium;
|
|
|
|
|
}
|
|
|
|
|
else if (state_size == 3)
|
|
|
|
|
{
|
|
|
|
|
rewind_enabled = Global.Config.RewindEnabledLarge;
|
2013-12-27 01:14:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-30 16:42:58 +00:00
|
|
|
|
_rewindDeltaEnable = Global.Config.Rewind_UseDelta;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2014-11-30 16:42:58 +00:00
|
|
|
|
if (rewind_enabled)
|
2013-12-27 01:14:17 +00:00
|
|
|
|
{
|
2014-11-30 16:42:58 +00:00
|
|
|
|
var cap = Global.Config.Rewind_BufferSize * (long)1024 * 1024;
|
|
|
|
|
|
|
|
|
|
if (_rewindBuffer != null)
|
|
|
|
|
{
|
|
|
|
|
_rewindBuffer.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_rewindBuffer = new StreamBlobDatabase(Global.Config.Rewind_OnDisk, cap, BufferManage);
|
2013-12-27 01:14:17 +00:00
|
|
|
|
|
2014-11-30 16:42:58 +00:00
|
|
|
|
if (_rewindThread != null)
|
|
|
|
|
{
|
|
|
|
|
_rewindThread.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_rewindThread = new RewindThreader(this, Global.Config.Rewind_IsThreaded);
|
|
|
|
|
}
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Rewind(int frames)
|
|
|
|
|
{
|
2014-11-30 19:58:32 +00:00
|
|
|
|
if (Global.Emulator.HasSavestates())
|
|
|
|
|
{
|
|
|
|
|
_rewindThread.Rewind(frames);
|
|
|
|
|
}
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO remove me
|
|
|
|
|
public void _RunRewind(int frames)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < frames; i++)
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
if (_rewindBuffer.Count == 0 || (Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie.InputLogLength == 0))
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-30 01:17:11 +00:00
|
|
|
|
if (_lastState.Length < 0x10000)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
|
|
|
|
Rewind64K();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RewindLarge();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: only run by RewindThreader, refactor
|
|
|
|
|
public void RunCapture(byte[] coreSavestate)
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
if (_rewindDeltaEnable)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2015-01-17 19:22:21 +00:00
|
|
|
|
CaptureRewindStateDelta(coreSavestate);
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CaptureRewindStateNonDelta(coreSavestate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ResetRewindBuffer()
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
if (_rewindBuffer != null)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
_rewindBuffer.Clear();
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-12-30 01:17:11 +00:00
|
|
|
|
_rewindImpossible = false;
|
|
|
|
|
_lastState = null;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-12-27 01:14:17 +00:00
|
|
|
|
private void DoMessage(string message)
|
|
|
|
|
{
|
|
|
|
|
if (MessageCallback != null)
|
|
|
|
|
{
|
|
|
|
|
MessageCallback(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-27 00:47:52 +00:00
|
|
|
|
private void SetRewindParams(bool enabled, int frequency)
|
|
|
|
|
{
|
|
|
|
|
if (RewindActive != enabled)
|
|
|
|
|
{
|
2013-12-27 01:14:17 +00:00
|
|
|
|
DoMessage("Rewind " + (enabled ? "Enabled" : "Disabled"));
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-12-30 01:17:11 +00:00
|
|
|
|
if (_rewindFrequency != frequency && enabled)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2013-12-27 01:14:17 +00:00
|
|
|
|
DoMessage("Rewind frequency set to " + frequency);
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RewindActive = enabled;
|
2013-12-30 01:17:11 +00:00
|
|
|
|
_rewindFrequency = frequency;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
|
|
|
|
if (!RewindActive)
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
_lastState = null;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private byte[] BufferManage(byte[] inbuf, long size, bool allocate)
|
|
|
|
|
{
|
|
|
|
|
if (allocate)
|
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
// if we have an appropriate buffer free, return it
|
|
|
|
|
if (_rewindFellationBuf != null && _rewindFellationBuf.LongLength == size)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
var ret = _rewindFellationBuf;
|
|
|
|
|
_rewindFellationBuf = null;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
2013-12-30 01:17:11 +00:00
|
|
|
|
|
|
|
|
|
// otherwise, allocate it
|
2013-12-27 00:47:52 +00:00
|
|
|
|
return new byte[size];
|
|
|
|
|
}
|
2014-02-04 03:08:20 +00:00
|
|
|
|
|
|
|
|
|
_rewindFellationBuf = inbuf;
|
|
|
|
|
return null;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-12-30 01:17:11 +00:00
|
|
|
|
private void CaptureRewindStateNonDelta(byte[] currentState)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
long offset = _rewindBuffer.Enqueue(0, currentState.Length + 1);
|
|
|
|
|
var stream = _rewindBuffer.Stream;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
stream.Position = offset;
|
|
|
|
|
|
2013-12-30 01:17:11 +00:00
|
|
|
|
// write the header for a non-delta frame
|
|
|
|
|
stream.WriteByte(1); // i.e. true
|
|
|
|
|
stream.Write(currentState, 0, currentState.Length);
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-17 19:22:21 +00:00
|
|
|
|
private unsafe void CaptureRewindStateDelta(byte[] currentState)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
// in case the state sizes mismatch, capture a full state rather than trying to do anything clever
|
|
|
|
|
if (currentState.Length != _lastState.Length)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2013-12-30 01:17:11 +00:00
|
|
|
|
CaptureRewindStateNonDelta(currentState);
|
2013-12-27 00:47:52 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-17 19:16:22 +00:00
|
|
|
|
int index = 0;
|
|
|
|
|
int stateLength = Math.Min(currentState.Length, _lastState.Length);
|
|
|
|
|
bool inChangeSequence = false;
|
|
|
|
|
int changeSequenceStartOffset = 0;
|
|
|
|
|
int lastChangeSequenceStartOffset = 0;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2015-01-17 19:16:22 +00:00
|
|
|
|
if (_tempBuf.Length < stateLength)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2015-01-17 19:16:22 +00:00
|
|
|
|
_tempBuf = new byte[stateLength];
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-17 19:16:22 +00:00
|
|
|
|
_tempBuf[index++] = 0; // Full state (false = delta)
|
|
|
|
|
|
2015-01-17 06:19:13 +00:00
|
|
|
|
fixed (byte* pCurrentState = ¤tState[0])
|
|
|
|
|
fixed (byte* pLastState = &_lastState[0])
|
|
|
|
|
for (int i = 0; i < stateLength; i++)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2015-01-17 06:19:13 +00:00
|
|
|
|
bool thisByteMatches = *(pCurrentState + i) == *(pLastState + i);
|
2015-01-17 03:55:41 +00:00
|
|
|
|
|
2015-01-17 06:19:13 +00:00
|
|
|
|
if (inChangeSequence == false)
|
|
|
|
|
{
|
|
|
|
|
if (thisByteMatches)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2015-01-17 06:19:13 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inChangeSequence = true;
|
2015-01-17 19:16:22 +00:00
|
|
|
|
changeSequenceStartOffset = i;
|
2015-01-17 06:19:13 +00:00
|
|
|
|
}
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2015-01-17 19:16:22 +00:00
|
|
|
|
if (thisByteMatches || i == stateLength - 1)
|
2015-01-17 06:19:13 +00:00
|
|
|
|
{
|
2015-01-17 19:16:22 +00:00
|
|
|
|
const int maxHeaderSize = 10;
|
|
|
|
|
int length = i - changeSequenceStartOffset + (thisByteMatches ? 0 : 1);
|
2015-01-17 06:19:13 +00:00
|
|
|
|
|
2015-01-17 19:16:22 +00:00
|
|
|
|
if (index + length + maxHeaderSize >= stateLength)
|
2015-01-17 06:19:13 +00:00
|
|
|
|
{
|
|
|
|
|
// If the delta ends up being larger than the full state, capture the full state instead
|
|
|
|
|
CaptureRewindStateNonDelta(currentState);
|
|
|
|
|
return;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-17 19:22:21 +00:00
|
|
|
|
// Offset Delta
|
2015-01-17 19:16:22 +00:00
|
|
|
|
VLInteger.WriteUnsigned((uint)(changeSequenceStartOffset - lastChangeSequenceStartOffset), _tempBuf, ref index);
|
2015-01-17 06:19:13 +00:00
|
|
|
|
|
2015-01-17 19:16:22 +00:00
|
|
|
|
// Length
|
|
|
|
|
VLInteger.WriteUnsigned((uint)length, _tempBuf, ref index);
|
2015-01-17 06:19:13 +00:00
|
|
|
|
|
|
|
|
|
// Data
|
2015-01-17 19:16:22 +00:00
|
|
|
|
Buffer.BlockCopy(_lastState, changeSequenceStartOffset, _tempBuf, index, length);
|
|
|
|
|
index += length;
|
2015-01-17 06:19:13 +00:00
|
|
|
|
|
|
|
|
|
inChangeSequence = false;
|
2015-01-17 19:16:22 +00:00
|
|
|
|
lastChangeSequenceStartOffset = changeSequenceStartOffset;
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-17 19:16:22 +00:00
|
|
|
|
Buffer.BlockCopy(currentState, 0, _lastState, 0, _lastState.Length);
|
2015-01-17 06:19:13 +00:00
|
|
|
|
|
2015-01-17 19:16:22 +00:00
|
|
|
|
_rewindBuffer.Push(new ArraySegment<byte>(_tempBuf, 0, index));
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RewindLarge()
|
|
|
|
|
{
|
|
|
|
|
RewindDelta(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Rewind64K()
|
|
|
|
|
{
|
|
|
|
|
RewindDelta(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RewindDelta(bool isSmall)
|
|
|
|
|
{
|
2014-11-30 16:42:58 +00:00
|
|
|
|
if (Global.Emulator.HasSavestates())
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2014-11-30 16:42:58 +00:00
|
|
|
|
var ms = _rewindBuffer.PopMemoryStream();
|
|
|
|
|
var reader = new BinaryReader(ms);
|
|
|
|
|
var fullstate = reader.ReadBoolean();
|
|
|
|
|
if (fullstate)
|
2013-12-27 00:47:52 +00:00
|
|
|
|
{
|
2014-12-05 00:52:16 +00:00
|
|
|
|
Global.Emulator.AsStatable().LoadStateBinary(reader);
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
2014-11-30 16:42:58 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2015-01-17 19:16:22 +00:00
|
|
|
|
byte[] buf = ms.GetBuffer();
|
2014-11-30 16:42:58 +00:00
|
|
|
|
var output = new MemoryStream(_lastState);
|
2015-01-17 19:16:22 +00:00
|
|
|
|
int index = 1;
|
|
|
|
|
int offset = 0;
|
|
|
|
|
|
|
|
|
|
while (index < buf.Length)
|
2014-11-30 16:42:58 +00:00
|
|
|
|
{
|
2015-01-17 19:22:21 +00:00
|
|
|
|
int offsetDelta = (int)VLInteger.ReadUnsigned(buf, ref index);
|
2015-01-17 19:16:22 +00:00
|
|
|
|
int length = (int)VLInteger.ReadUnsigned(buf, ref index);
|
2013-12-27 00:47:52 +00:00
|
|
|
|
|
2015-01-17 19:22:21 +00:00
|
|
|
|
offset += offsetDelta;
|
2015-01-17 19:16:22 +00:00
|
|
|
|
|
2014-11-30 16:42:58 +00:00
|
|
|
|
output.Position = offset;
|
2015-01-17 19:16:22 +00:00
|
|
|
|
output.Write(buf, index, length);
|
|
|
|
|
index += length;
|
2014-11-30 16:42:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reader.Close();
|
|
|
|
|
output.Position = 0;
|
2014-12-05 00:52:16 +00:00
|
|
|
|
Global.Emulator.AsStatable().LoadStateBinary(new BinaryReader(output));
|
2014-11-30 16:42:58 +00:00
|
|
|
|
}
|
2013-12-27 00:47:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|