Rewind cleanup.
This commit is contained in:
parent
54320865c0
commit
6cc5a914b7
|
@ -6,24 +6,23 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
public class RewindThreader : IDisposable
|
||||
{
|
||||
public static bool IsThreaded = false;
|
||||
|
||||
private readonly ConcurrentQueue<Job> _jobs = new ConcurrentQueue<Job>();
|
||||
private readonly bool _isThreaded;
|
||||
private readonly Action<byte[]> _performCapture;
|
||||
private readonly Action<int> _performRewind;
|
||||
private readonly BlockingCollection<Job> _jobs = new BlockingCollection<Job>(16);
|
||||
private readonly ConcurrentStack<byte[]> _stateBufferPool = new ConcurrentStack<byte[]>();
|
||||
private readonly EventWaitHandle _ewh;
|
||||
private readonly EventWaitHandle _ewh2;
|
||||
private readonly EventWaitHandle _rewindCompletedEvent;
|
||||
private readonly Thread _thread;
|
||||
private readonly Rewinder _rewinder;
|
||||
|
||||
public RewindThreader(Rewinder rewinder, bool isThreaded)
|
||||
public RewindThreader(Action<byte[]> performCapture, Action<int> performRewind, bool isThreaded)
|
||||
{
|
||||
IsThreaded = isThreaded;
|
||||
_rewinder = rewinder;
|
||||
_isThreaded = isThreaded;
|
||||
_performCapture = performCapture;
|
||||
_performRewind = performRewind;
|
||||
|
||||
if (IsThreaded)
|
||||
if (_isThreaded)
|
||||
{
|
||||
_ewh = new EventWaitHandle(false, EventResetMode.AutoReset);
|
||||
_ewh2 = new EventWaitHandle(false, EventResetMode.AutoReset);
|
||||
_rewindCompletedEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
|
||||
_thread = new Thread(ThreadProc) { IsBackground = true };
|
||||
_thread.Start();
|
||||
}
|
||||
|
@ -31,43 +30,37 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!IsThreaded)
|
||||
if (!_isThreaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var job = new Job { Type = JobType.Abort };
|
||||
_jobs.Enqueue(job);
|
||||
_ewh.Set();
|
||||
|
||||
_jobs.CompleteAdding();
|
||||
_thread.Join();
|
||||
_ewh.Dispose();
|
||||
_ewh2.Dispose();
|
||||
_rewindCompletedEvent.Dispose();
|
||||
}
|
||||
|
||||
public void Rewind(int frames)
|
||||
{
|
||||
if (!IsThreaded)
|
||||
if (!_isThreaded)
|
||||
{
|
||||
_rewinder._RunRewind(frames);
|
||||
_performRewind(frames);
|
||||
return;
|
||||
}
|
||||
|
||||
var job = new Job
|
||||
_jobs.Add(new Job
|
||||
{
|
||||
Type = JobType.Rewind,
|
||||
Frames = frames
|
||||
};
|
||||
_jobs.Enqueue(job);
|
||||
_ewh.Set();
|
||||
_ewh2.WaitOne();
|
||||
});
|
||||
_rewindCompletedEvent.WaitOne();
|
||||
}
|
||||
|
||||
public void Capture(byte[] coreSavestate)
|
||||
{
|
||||
if (!IsThreaded)
|
||||
if (!_isThreaded)
|
||||
{
|
||||
_rewinder.RunCapture(coreSavestate);
|
||||
_performCapture(coreSavestate);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -83,61 +76,34 @@ namespace BizHawk.Client.Common
|
|||
|
||||
Buffer.BlockCopy(coreSavestate, 0, savestateCopy, 0, coreSavestate.Length);
|
||||
|
||||
var job = new Job
|
||||
_jobs.Add(new Job
|
||||
{
|
||||
Type = JobType.Capture,
|
||||
CoreState = savestateCopy
|
||||
};
|
||||
DoSafeEnqueue(job);
|
||||
});
|
||||
}
|
||||
|
||||
private void ThreadProc()
|
||||
{
|
||||
for (;; )
|
||||
foreach (Job job in _jobs.GetConsumingEnumerable())
|
||||
{
|
||||
_ewh.WaitOne();
|
||||
while (_jobs.Count != 0)
|
||||
if (job.Type == JobType.Capture)
|
||||
{
|
||||
Job job;
|
||||
if (_jobs.TryDequeue(out job))
|
||||
{
|
||||
if (job.Type == JobType.Abort)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (job.Type == JobType.Capture)
|
||||
{
|
||||
_rewinder.RunCapture(job.CoreState);
|
||||
_stateBufferPool.Push(job.CoreState);
|
||||
}
|
||||
|
||||
if (job.Type == JobType.Rewind)
|
||||
{
|
||||
_rewinder._RunRewind(job.Frames);
|
||||
_ewh2.Set();
|
||||
}
|
||||
}
|
||||
_performCapture(job.CoreState);
|
||||
_stateBufferPool.Push(job.CoreState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DoSafeEnqueue(Job job)
|
||||
{
|
||||
_jobs.Enqueue(job);
|
||||
_ewh.Set();
|
||||
|
||||
// just in case... we're getting really behind.. slow it down here
|
||||
// if this gets backed up too much, then the rewind will seem to malfunction since it requires all the captures in the queue to complete first
|
||||
while (_jobs.Count > 15)
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
if (job.Type == JobType.Rewind)
|
||||
{
|
||||
_performRewind(job.Frames);
|
||||
_rewindCompletedEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum JobType
|
||||
{
|
||||
Capture, Rewind, Abort
|
||||
Capture, Rewind
|
||||
}
|
||||
|
||||
private sealed class Job
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
using System.IO;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Common.IEmulatorExtensions;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
|
@ -14,7 +13,6 @@ namespace BizHawk.Client.Common
|
|||
private long? _overrideMemoryLimit;
|
||||
private RewindThreader _rewindThread;
|
||||
private byte[] _lastState;
|
||||
private bool _rewindImpossible;
|
||||
private int _rewindFrequency = 1;
|
||||
private bool _rewindDeltaEnable;
|
||||
private byte[] _deltaBuffer = new byte[0];
|
||||
|
@ -25,12 +23,12 @@ namespace BizHawk.Client.Common
|
|||
}
|
||||
|
||||
public Action<string> MessageCallback { get; set; }
|
||||
|
||||
public bool RewindActive { get; set; }
|
||||
|
||||
// TODO: make RewindBuf never be null
|
||||
public float FullnessRatio
|
||||
{
|
||||
get { return _rewindBuffer.FullnessRatio; }
|
||||
get { return _rewindBuffer != null ? _rewindBuffer.FullnessRatio : 0; }
|
||||
}
|
||||
|
||||
public int Count
|
||||
|
@ -58,114 +56,86 @@ namespace BizHawk.Client.Common
|
|||
get { return _rewindFrequency; }
|
||||
}
|
||||
|
||||
bool IsRewindEnabledAtAll
|
||||
private bool IsRewindEnabledAtAll
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Global.Config.RewindEnabledLarge && !Global.Config.RewindEnabledMedium && !Global.Config.RewindEnabledSmall)
|
||||
return false;
|
||||
else return true;
|
||||
}
|
||||
get { return Global.Config.RewindEnabledLarge || Global.Config.RewindEnabledMedium || Global.Config.RewindEnabledSmall; }
|
||||
}
|
||||
|
||||
// TOOD: this should not be parameterless?! It is only possible due to passing a static context in
|
||||
public void CaptureRewindState()
|
||||
public void Capture()
|
||||
{
|
||||
if (!IsRewindEnabledAtAll)
|
||||
if (!IsRewindEnabledAtAll || !Global.Emulator.HasSavestates())
|
||||
{
|
||||
return;
|
||||
|
||||
if (Global.Emulator.HasSavestates())
|
||||
{
|
||||
if (_rewindImpossible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastState == null)
|
||||
{
|
||||
DoRewindSettings();
|
||||
}
|
||||
|
||||
// log a frame
|
||||
if (_lastState != null && Global.Emulator.Frame % _rewindFrequency == 0)
|
||||
{
|
||||
_rewindThread.Capture(Global.Emulator.AsStatable().SaveStateBinary());
|
||||
}
|
||||
}
|
||||
|
||||
if (_rewindThread == null)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
if (_rewindThread == null || Global.Emulator.Frame % _rewindFrequency != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rewindThread.Capture(Global.Emulator.AsStatable().SaveStateBinary());
|
||||
}
|
||||
|
||||
public void DoRewindSettings()
|
||||
public void Initialize()
|
||||
{
|
||||
if (_rewindThread != null)
|
||||
{
|
||||
_rewindThread.Dispose();
|
||||
_rewindThread = null;
|
||||
}
|
||||
Clear();
|
||||
|
||||
if (Global.Emulator.HasSavestates())
|
||||
{
|
||||
// This is the first frame. Capture the state, and put it in LastState for future deltas to be compared against.
|
||||
_lastState = (byte[])Global.Emulator.AsStatable().SaveStateBinary().Clone();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRewindParams(false, 1);
|
||||
}
|
||||
|
||||
var 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;
|
||||
|
||||
_rewindDeltaEnable = Global.Config.Rewind_UseDelta;
|
||||
if (!RewindActive || !_rewindDeltaEnable)
|
||||
{
|
||||
_lastState = null;
|
||||
}
|
||||
|
||||
if (rewind_enabled)
|
||||
{
|
||||
var capacity = Global.Config.Rewind_BufferSize * (long)1024 * 1024;
|
||||
if (RewindActive)
|
||||
{
|
||||
var capacity = Global.Config.Rewind_BufferSize * (long)1024 * 1024;
|
||||
|
||||
if (_rewindBuffer != null)
|
||||
{
|
||||
_rewindBuffer.Dispose();
|
||||
}
|
||||
_rewindBuffer = new StreamBlobDatabase(Global.Config.Rewind_OnDisk, capacity, BufferManage);
|
||||
|
||||
_rewindBuffer = new StreamBlobDatabase(Global.Config.Rewind_OnDisk, capacity, BufferManage);
|
||||
|
||||
_rewindThread = new RewindThreader(this, Global.Config.Rewind_IsThreaded);
|
||||
}
|
||||
_rewindThread = new RewindThreader(CaptureInternal, RewindInternal, Global.Config.Rewind_IsThreaded);
|
||||
}
|
||||
}
|
||||
|
||||
public void Rewind(int frames)
|
||||
{
|
||||
if (Global.Emulator.HasSavestates() && _rewindThread != null)
|
||||
if (!Global.Emulator.HasSavestates() || _rewindThread == null)
|
||||
{
|
||||
_rewindThread.Rewind(frames);
|
||||
return;
|
||||
}
|
||||
|
||||
_rewindThread.Rewind(frames);
|
||||
}
|
||||
|
||||
// TODO remove me
|
||||
public void _RunRewind(int frames)
|
||||
private void RewindInternal(int frames)
|
||||
{
|
||||
for (int i = 0; i < frames; i++)
|
||||
{
|
||||
|
@ -174,31 +144,36 @@ namespace BizHawk.Client.Common
|
|||
return;
|
||||
}
|
||||
|
||||
RewindOne();
|
||||
LoadPreviousState();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: only run by RewindThreader, refactor
|
||||
public void RunCapture(byte[] coreSavestate)
|
||||
private void CaptureInternal(byte[] coreSavestate)
|
||||
{
|
||||
if (_rewindDeltaEnable)
|
||||
{
|
||||
CaptureRewindStateDelta(coreSavestate);
|
||||
CaptureStateDelta(coreSavestate);
|
||||
}
|
||||
else
|
||||
{
|
||||
CaptureRewindStateNonDelta(coreSavestate);
|
||||
CaptureStateNonDelta(coreSavestate);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetRewindBuffer()
|
||||
public void Clear()
|
||||
{
|
||||
if (_rewindBuffer != null)
|
||||
if (_rewindThread != null)
|
||||
{
|
||||
_rewindBuffer.Clear();
|
||||
_rewindThread.Dispose();
|
||||
_rewindThread = null;
|
||||
}
|
||||
|
||||
if (_rewindBuffer != null)
|
||||
{
|
||||
_rewindBuffer.Dispose();
|
||||
_rewindBuffer = null;
|
||||
}
|
||||
|
||||
_rewindImpossible = false;
|
||||
_lastState = null;
|
||||
}
|
||||
|
||||
|
@ -224,11 +199,6 @@ namespace BizHawk.Client.Common
|
|||
|
||||
RewindActive = enabled;
|
||||
_rewindFrequency = frequency;
|
||||
|
||||
if (!RewindActive)
|
||||
{
|
||||
_lastState = null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] BufferManage(byte[] inbuf, ref long size, bool allocate)
|
||||
|
@ -283,7 +253,7 @@ namespace BizHawk.Client.Common
|
|||
}
|
||||
}
|
||||
|
||||
private void CaptureRewindStateNonDelta(byte[] state)
|
||||
private void CaptureStateNonDelta(byte[] state)
|
||||
{
|
||||
long offset = _rewindBuffer.Enqueue(0, state.Length + 1);
|
||||
var stream = _rewindBuffer.Stream;
|
||||
|
@ -308,12 +278,12 @@ namespace BizHawk.Client.Common
|
|||
UpdateLastState(state, 0, state.Length);
|
||||
}
|
||||
|
||||
private unsafe void CaptureRewindStateDelta(byte[] currentState)
|
||||
private unsafe void CaptureStateDelta(byte[] currentState)
|
||||
{
|
||||
// in case the state sizes mismatch, capture a full state rather than trying to do anything clever
|
||||
if (currentState.Length != _lastState.Length)
|
||||
{
|
||||
CaptureRewindStateNonDelta(_lastState);
|
||||
CaptureStateNonDelta(_lastState);
|
||||
UpdateLastState(currentState);
|
||||
return;
|
||||
}
|
||||
|
@ -356,7 +326,7 @@ namespace BizHawk.Client.Common
|
|||
if (index + length + maxHeaderSize >= stateLength)
|
||||
{
|
||||
// If the delta ends up being larger than the full state, capture the full state instead
|
||||
CaptureRewindStateNonDelta(_lastState);
|
||||
CaptureStateNonDelta(_lastState);
|
||||
UpdateLastState(currentState);
|
||||
return;
|
||||
}
|
||||
|
@ -381,46 +351,50 @@ namespace BizHawk.Client.Common
|
|||
UpdateLastState(currentState);
|
||||
}
|
||||
|
||||
private void RewindOne()
|
||||
private void LoadPreviousState()
|
||||
{
|
||||
if (!Global.Emulator.HasSavestates()) return;
|
||||
|
||||
var ms = _rewindBuffer.PopMemoryStream();
|
||||
byte[] buf = ms.GetBuffer();
|
||||
var reader = new BinaryReader(ms);
|
||||
var fullstate = reader.ReadBoolean();
|
||||
if (fullstate)
|
||||
using (var reader = new BinaryReader(_rewindBuffer.PopMemoryStream()))
|
||||
{
|
||||
byte[] buf = ((MemoryStream)reader.BaseStream).GetBuffer();
|
||||
var fullState = reader.ReadByte() == 1;
|
||||
if (_rewindDeltaEnable)
|
||||
{
|
||||
UpdateLastState(buf, 1, buf.Length - 1);
|
||||
using (var lastStateReader = new BinaryReader(new MemoryStream(_lastState)))
|
||||
{
|
||||
Global.Emulator.AsStatable().LoadStateBinary(lastStateReader);
|
||||
}
|
||||
|
||||
if (fullState)
|
||||
{
|
||||
UpdateLastState(buf, 1, buf.Length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = 1;
|
||||
int offset = 0;
|
||||
|
||||
while (index < buf.Length)
|
||||
{
|
||||
int offsetDelta = (int)VLInteger.ReadUnsigned(buf, ref index);
|
||||
int length = (int)VLInteger.ReadUnsigned(buf, ref index);
|
||||
|
||||
offset += offsetDelta;
|
||||
|
||||
Buffer.BlockCopy(buf, index, _lastState, offset, length);
|
||||
index += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Global.Emulator.AsStatable().LoadStateBinary(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
var output = new MemoryStream(_lastState);
|
||||
int index = 1;
|
||||
int offset = 0;
|
||||
|
||||
while (index < buf.Length)
|
||||
else
|
||||
{
|
||||
int offsetDelta = (int)VLInteger.ReadUnsigned(buf, ref index);
|
||||
int length = (int)VLInteger.ReadUnsigned(buf, ref index);
|
||||
if (!fullState)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
offset += offsetDelta;
|
||||
|
||||
output.Position = offset;
|
||||
output.Write(buf, index, length);
|
||||
index += length;
|
||||
Global.Emulator.AsStatable().LoadStateBinary(reader);
|
||||
}
|
||||
|
||||
output.Position = 0;
|
||||
Global.Emulator.AsStatable().LoadStateBinary(new BinaryReader(output));
|
||||
output.Close();
|
||||
}
|
||||
reader.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2724,7 +2724,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
var runFrame = false;
|
||||
_runloopFrameadvance = false;
|
||||
var currentTimestamp = Stopwatch.GetTimestamp();
|
||||
var suppressCaptureRewind = false;
|
||||
|
||||
double frameAdvanceTimestampDeltaMs = (double)(currentTimestamp - _frameAdvanceTimestamp) / Stopwatch.Frequency * 1000.0;
|
||||
bool frameProgressTimeElapsed = frameAdvanceTimestampDeltaMs >= Global.Config.FrameProgressDelayMs;
|
||||
|
@ -2772,8 +2771,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
runFrame = true;
|
||||
}
|
||||
|
||||
bool isRewinding = suppressCaptureRewind = Rewind(ref runFrame, currentTimestamp);
|
||||
|
||||
bool isRewinding = Rewind(ref runFrame, currentTimestamp);
|
||||
|
||||
float atten = 0;
|
||||
|
||||
if (runFrame || force)
|
||||
|
@ -2814,7 +2813,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
UpdateFpsDisplay(currentTimestamp, isRewinding, isFastForwarding);
|
||||
|
||||
CaptureRewind(suppressCaptureRewind);
|
||||
CaptureRewind(isRewinding);
|
||||
|
||||
// Set volume, if enabled
|
||||
if (Global.Config.SoundEnabledNormal)
|
||||
|
@ -3503,8 +3502,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
Global.Rewinder.ResetRewindBuffer();
|
||||
|
||||
if (Emulator.CoreComm.RomStatusDetails == null && loader.Rom != null)
|
||||
{
|
||||
Emulator.CoreComm.RomStatusDetails = string.Format(
|
||||
|
@ -3560,7 +3557,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
SetMainformMovieInfo();
|
||||
CurrentlyOpenRomArgs = args;
|
||||
|
||||
Global.Rewinder.CaptureRewindState();
|
||||
Global.Rewinder.Initialize();
|
||||
Global.Rewinder.Capture();
|
||||
|
||||
Global.StickyXORAdapter.ClearStickies();
|
||||
Global.StickyXORAdapter.ClearStickyFloats();
|
||||
|
@ -3695,7 +3693,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
GlobalWin.Tools.Restart();
|
||||
RewireSound();
|
||||
Global.Rewinder.ResetRewindBuffer();
|
||||
Global.Rewinder.Clear();
|
||||
Text = "BizHawk" + (VersionInfo.DeveloperBuild ? " (interim) " : string.Empty);
|
||||
HandlePlatformMenus();
|
||||
_stateSlots.Clear();
|
||||
|
@ -3736,7 +3734,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void ClearRewindData()
|
||||
{
|
||||
Global.Rewinder.ResetRewindBuffer();
|
||||
Global.Rewinder.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -4116,7 +4114,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
else if (!suppressCaptureRewind && Global.Rewinder.RewindActive)
|
||||
{
|
||||
Global.Rewinder.CaptureRewindState();
|
||||
Global.Rewinder.Capture();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
PutRewindSetting(ref Global.Config.Rewind_LargeStateSize, (int)LargeStateUpDown.Value * 1024);
|
||||
if (TriggerRewindSettingsReload)
|
||||
{
|
||||
Global.Rewinder.DoRewindSettings();
|
||||
Global.Rewinder.Initialize();
|
||||
}
|
||||
|
||||
// These settings are not used by DoRewindSettings
|
||||
|
|
Loading…
Reference in New Issue