BizHawk/BizHawk.Client.Common/rewind/Rewinder.cs

385 lines
8.7 KiB
C#
Raw Normal View History

using System;
using System.IO;
2013-12-27 01:14:17 +00:00
namespace BizHawk.Client.Common
{
public class Rewinder
{
public bool RewindActive = true;
2013-12-27 01:14:17 +00:00
private StreamBlobDatabase RewindBuffer;
private RewindThreader RewindThread;
private byte[] LastState;
private bool RewindImpossible;
private int RewindFrequency = 1;
private bool RewindDeltaEnable = false;
private byte[] RewindFellationBuf;
private byte[] TempBuf = new byte[0];
2013-12-27 01:14:17 +00:00
public Action<string> MessageCallback;
// TODO: make RewindBuf never be null
public float FullnessRatio
{
get { return RewindBuffer.FullnessRatio; }
}
public int Count
{
get { return RewindBuffer != null ? RewindBuffer.Count : 0; }
}
public long Size
{
get { return RewindBuffer != null ? RewindBuffer.Size : 0; }
}
public int BufferCount
{
get { return RewindBuffer != null ? RewindBuffer.Count : 0; }
}
public bool HasBuffer
{
get { return RewindBuffer != null; }
}
// TOOD: this should not be parameterless?! It is only possible due to passing a static context in
public void CaptureRewindState()
{
if (RewindImpossible)
{
return;
}
if (LastState == null)
{
DoRewindSettings();
}
//log a frame
if (LastState != null && Global.Emulator.Frame % RewindFrequency == 0)
{
byte[] CurrentState = Global.Emulator.SaveStateBinary();
RewindThread.Capture(CurrentState);
}
}
public void DoRewindSettings()
{
// This is the first frame. Capture the state, and put it in LastState for future deltas to be compared against.
LastState = (byte[])Global.Emulator.SaveStateBinary().Clone();
int state_size = 0;
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;
}
bool rewind_enabled = false;
if (state_size == 1)
{
rewind_enabled = Global.Config.RewindEnabledSmall;
}
else if (state_size == 2)
{
rewind_enabled = Global.Config.RewindEnabledMedium;
}
else if (state_size == 3)
{
rewind_enabled = Global.Config.RewindEnabledLarge;
}
RewindDeltaEnable = Global.Config.Rewind_UseDelta;
if (rewind_enabled)
{
long cap = Global.Config.Rewind_BufferSize * (long)1024 * (long)1024;
if (RewindBuffer != null)
2013-12-27 01:14:17 +00:00
{
RewindBuffer.Dispose();
2013-12-27 01:14:17 +00:00
}
RewindBuffer = new StreamBlobDatabase(Global.Config.Rewind_OnDisk, cap, BufferManage);
if (RewindThread != null)
2013-12-27 01:14:17 +00:00
{
RewindThread.Dispose();
2013-12-27 01:14:17 +00:00
}
RewindThread = new RewindThreader(this, Global.Config.Rewind_IsThreaded);
}
}
public void Rewind(int frames)
{
RewindThread.Rewind(frames);
}
// TODO remove me
public void _RunRewind(int frames)
{
for (int i = 0; i < frames; i++)
{
if (RewindBuffer.Count == 0 || (Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie.InputLogLength == 0))
{
return;
}
if (LastState.Length < 0x10000)
{
Rewind64K();
}
else
{
RewindLarge();
}
}
}
// TODO: only run by RewindThreader, refactor
public void RunCapture(byte[] coreSavestate)
{
if (RewindDeltaEnable)
{
if (LastState.Length <= 0x10000)
{
CaptureRewindStateDelta(coreSavestate, true);
}
else
{
CaptureRewindStateDelta(coreSavestate, false);
}
}
else
{
CaptureRewindStateNonDelta(coreSavestate);
}
}
public void ResetRewindBuffer()
{
if (RewindBuffer != null)
{
RewindBuffer.Clear();
}
RewindImpossible = false;
LastState = null;
}
2013-12-27 01:14:17 +00:00
private void DoMessage(string message)
{
if (MessageCallback != null)
{
MessageCallback(message);
}
}
private void SetRewindParams(bool enabled, int frequency)
{
if (RewindActive != enabled)
{
2013-12-27 01:14:17 +00:00
DoMessage("Rewind " + (enabled ? "Enabled" : "Disabled"));
}
if (RewindFrequency != frequency && enabled)
{
2013-12-27 01:14:17 +00:00
DoMessage("Rewind frequency set to " + frequency);
}
RewindActive = enabled;
RewindFrequency = frequency;
if (!RewindActive)
{
LastState = null;
}
}
private byte[] BufferManage(byte[] inbuf, long size, bool allocate)
{
if (allocate)
{
//if we have an appropriate buffer free, return it
if (RewindFellationBuf != null && RewindFellationBuf.LongLength == size)
{
byte[] ret = RewindFellationBuf;
RewindFellationBuf = null;
return ret;
}
//otherwise, allocate it
return new byte[size];
}
else
{
RewindFellationBuf = inbuf;
return null;
}
}
private void CaptureRewindStateNonDelta(byte[] CurrentState)
{
long offset = RewindBuffer.Enqueue(0, CurrentState.Length + 1);
Stream stream = RewindBuffer.Stream;
stream.Position = offset;
//write the header for a non-delta frame
stream.WriteByte(1); //i.e. true
stream.Write(CurrentState, 0, CurrentState.Length);
}
private void CaptureRewindStateDelta(byte[] CurrentState, bool isSmall)
{
//in case the state sizes mismatch, capture a full state rather than trying to do anything clever
if (CurrentState.Length != LastState.Length)
{
CaptureRewindStateNonDelta(CurrentState);
return;
}
int beginChangeSequence = -1;
bool inChangeSequence = false;
MemoryStream ms;
// try to set up the buffer in advance so we dont ever have exceptions in here
if (TempBuf.Length < CurrentState.Length)
{
TempBuf = new byte[CurrentState.Length * 2];
}
ms = new MemoryStream(TempBuf, 0, TempBuf.Length, true, true);
RETRY:
try
{
var writer = new BinaryWriter(ms);
writer.Write(false); // delta state
for (int i = 0; i < CurrentState.Length; i++)
{
if (inChangeSequence == false)
{
if (i >= LastState.Length)
{
continue;
}
if (CurrentState[i] == LastState[i])
{
continue;
}
inChangeSequence = true;
beginChangeSequence = i;
continue;
}
if (i - beginChangeSequence == 254 || i == CurrentState.Length - 1)
{
writer.Write((byte)(i - beginChangeSequence + 1));
if (isSmall)
{
writer.Write((ushort)beginChangeSequence);
}
else
{
writer.Write(beginChangeSequence);
}
writer.Write(LastState, beginChangeSequence, i - beginChangeSequence + 1);
inChangeSequence = false;
continue;
}
if (CurrentState[i] == LastState[i])
{
writer.Write((byte)(i - beginChangeSequence));
if (isSmall)
{
writer.Write((ushort)beginChangeSequence);
}
else
{
writer.Write(beginChangeSequence);
}
writer.Write(LastState, beginChangeSequence, i - beginChangeSequence);
inChangeSequence = false;
}
}
}
catch (NotSupportedException)
{
//ok... we had an exception after all
//if we did actually run out of room in the memorystream, then try it again with a bigger buffer
TempBuf = new byte[TempBuf.Length * 2];
goto RETRY;
}
if (LastState != null && LastState.Length == CurrentState.Length)
{
Buffer.BlockCopy(CurrentState, 0, LastState, 0, LastState.Length);
}
else
{
LastState = (byte[])CurrentState.Clone();
}
var seg = new ArraySegment<byte>(TempBuf, 0, (int)ms.Position);
RewindBuffer.Push(seg);
}
private void RewindLarge()
{
RewindDelta(false);
}
private void Rewind64K()
{
RewindDelta(true);
}
private void RewindDelta(bool isSmall)
{
var ms = RewindBuffer.PopMemoryStream();
var reader = new BinaryReader(ms);
bool fullstate = reader.ReadBoolean();
if (fullstate)
{
Global.Emulator.LoadStateBinary(reader);
}
else
{
var output = new MemoryStream(LastState);
while (ms.Position < ms.Length - 1)
{
byte len = reader.ReadByte();
int offset;
if(isSmall)
offset = reader.ReadUInt16();
else offset = reader.ReadInt32();
output.Position = offset;
output.Write(ms.GetBuffer(), (int)ms.Position, len);
ms.Position += len;
}
reader.Close();
output.Position = 0;
Global.Emulator.LoadStateBinary(new BinaryReader(output));
}
}
}
}