From 6cc5a914b73c7bd231c47fba58d8992647e9542e Mon Sep 17 00:00:00 2001 From: "J.D. Purcell" Date: Sat, 18 Mar 2017 21:22:02 -0400 Subject: [PATCH] Rewind cleanup. --- .../rewind/RewindThreader.cs | 100 +++----- BizHawk.Client.Common/rewind/Rewinder.cs | 214 ++++++++---------- BizHawk.Client.EmuHawk/MainForm.cs | 18 +- BizHawk.Client.EmuHawk/config/RewindConfig.cs | 2 +- 4 files changed, 136 insertions(+), 198 deletions(-) diff --git a/BizHawk.Client.Common/rewind/RewindThreader.cs b/BizHawk.Client.Common/rewind/RewindThreader.cs index 12924ef325..7fc2d83a1c 100644 --- a/BizHawk.Client.Common/rewind/RewindThreader.cs +++ b/BizHawk.Client.Common/rewind/RewindThreader.cs @@ -6,24 +6,23 @@ namespace BizHawk.Client.Common { public class RewindThreader : IDisposable { - public static bool IsThreaded = false; - - private readonly ConcurrentQueue _jobs = new ConcurrentQueue(); + private readonly bool _isThreaded; + private readonly Action _performCapture; + private readonly Action _performRewind; + private readonly BlockingCollection _jobs = new BlockingCollection(16); private readonly ConcurrentStack _stateBufferPool = new ConcurrentStack(); - 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 performCapture, Action 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 diff --git a/BizHawk.Client.Common/rewind/Rewinder.cs b/BizHawk.Client.Common/rewind/Rewinder.cs index 03c01aa2ed..8da6dc251f 100644 --- a/BizHawk.Client.Common/rewind/Rewinder.cs +++ b/BizHawk.Client.Common/rewind/Rewinder.cs @@ -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 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(); } } } diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 46f2719b73..680e9ca2ea 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -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(); } } diff --git a/BizHawk.Client.EmuHawk/config/RewindConfig.cs b/BizHawk.Client.EmuHawk/config/RewindConfig.cs index d8a9f4d6cc..6b1a246f37 100644 --- a/BizHawk.Client.EmuHawk/config/RewindConfig.cs +++ b/BizHawk.Client.EmuHawk/config/RewindConfig.cs @@ -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