Add delta compression rewinder (#2733)
Good size reduction for some cores, less useful for others. Typically moderately slower. Has some threading built in (cannot be disabled). Example speeds with PSX core: 104 fps with delta rewinder 112 fps normal rewinder (no compression) 124 fps without rewinder
This commit is contained in:
parent
3ea71a2dda
commit
547bf6d308
|
@ -4,6 +4,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="../MainSlnCommon.props" />
|
<Import Project="../MainSlnCommon.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -7,6 +7,13 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool UseCompression { get; }
|
bool UseCompression { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether or not to delta compress savestates before storing them
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
|
// TODO: This is in here for frontend reasons, but the buffer itself doesn't interact with this.
|
||||||
|
bool UseDelta { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Buffer space to use in MB
|
/// Buffer space to use in MB
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -29,6 +36,7 @@
|
||||||
public class RewindConfig : IRewindSettings
|
public class RewindConfig : IRewindSettings
|
||||||
{
|
{
|
||||||
public bool UseCompression { get; set; }
|
public bool UseCompression { get; set; }
|
||||||
|
public bool UseDelta { get; set; }
|
||||||
public bool Enabled { get; set; } = true;
|
public bool Enabled { get; set; } = true;
|
||||||
public long BufferSize { get; set; } = 512; // in mb
|
public long BufferSize { get; set; } = 512; // in mb
|
||||||
public int TargetFrameLength { get; set; } = 600;
|
public int TargetFrameLength { get; set; } = 600;
|
||||||
|
|
|
@ -0,0 +1,308 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Client.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A rewinder that uses Zelda compression, built on top of a ring buffer
|
||||||
|
/// </summary>
|
||||||
|
public class ZeldaWinder : IRewinder
|
||||||
|
{
|
||||||
|
private const int IS_GAP = unchecked((int)0x80000000);
|
||||||
|
|
||||||
|
private readonly ZwinderBuffer _buffer;
|
||||||
|
private readonly IStatable _stateSource;
|
||||||
|
|
||||||
|
private byte[] _master = new byte[0];
|
||||||
|
private int _masterFrame = -1;
|
||||||
|
private int _masterLength = 0;
|
||||||
|
private byte[] _scratch = new byte[0];
|
||||||
|
private int _count;
|
||||||
|
|
||||||
|
private Task _activeTask = null;
|
||||||
|
private bool _active;
|
||||||
|
|
||||||
|
private void Sync()
|
||||||
|
{
|
||||||
|
_activeTask?.Wait();
|
||||||
|
_activeTask = null;
|
||||||
|
}
|
||||||
|
private void Work(Action work)
|
||||||
|
{
|
||||||
|
_activeTask = Task.Run(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZeldaWinder(IStatable stateSource, IRewindSettings settings)
|
||||||
|
{
|
||||||
|
_buffer = new ZwinderBuffer(settings);
|
||||||
|
_stateSource = stateSource;
|
||||||
|
_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many states are actually in the state ringbuffer
|
||||||
|
/// </summary>
|
||||||
|
public int Count { get { Sync(); return _count; } }
|
||||||
|
|
||||||
|
public float FullnessRatio { get { Sync(); return _buffer.Used / (float)_buffer.Size; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total size of the _buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
|
public long Size => _buffer.Size;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TODO: This is not a frequency, it's the reciprocal
|
||||||
|
/// </summary>
|
||||||
|
public int RewindFrequency { get { Sync(); return _buffer.RewindFrequency; } }
|
||||||
|
|
||||||
|
public bool Active
|
||||||
|
{
|
||||||
|
get { Sync(); return _active; }
|
||||||
|
private set { Sync(); _active = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Suspend()
|
||||||
|
{
|
||||||
|
Active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resume()
|
||||||
|
{
|
||||||
|
Active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Sync();
|
||||||
|
_buffer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Sync();
|
||||||
|
_buffer.InvalidateEnd(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Capture(int frame)
|
||||||
|
{
|
||||||
|
Sync();
|
||||||
|
if (!_active)
|
||||||
|
return;
|
||||||
|
if (_masterFrame == -1)
|
||||||
|
{
|
||||||
|
var sss = new SaveStateStream(this);
|
||||||
|
_stateSource.SaveStateBinary(new BinaryWriter(sss));
|
||||||
|
(_master, _scratch) = (_scratch, _master);
|
||||||
|
_masterLength = (int)sss.Position;
|
||||||
|
_masterFrame = frame;
|
||||||
|
_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_buffer.WillCapture(_masterFrame))
|
||||||
|
return;
|
||||||
|
|
||||||
|
{
|
||||||
|
var sss = new SaveStateStream(this);
|
||||||
|
_stateSource.SaveStateBinary(new BinaryWriter(sss));
|
||||||
|
|
||||||
|
Work(() =>
|
||||||
|
{
|
||||||
|
_buffer.Capture(_masterFrame, underlyingStream_ =>
|
||||||
|
{
|
||||||
|
var zeldas = SpanStream.GetOrBuild(underlyingStream_);
|
||||||
|
if (_master.Length < _scratch.Length)
|
||||||
|
{
|
||||||
|
var replacement = new byte[_scratch.Length];
|
||||||
|
Array.Copy(_master, replacement, _master.Length);
|
||||||
|
_master = replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lengthHolder = _masterLength;
|
||||||
|
var lengthHolderSpan = new ReadOnlySpan<byte>(&lengthHolder, 4);
|
||||||
|
|
||||||
|
zeldas.Write(lengthHolderSpan);
|
||||||
|
|
||||||
|
fixed (byte* older_ = _master)
|
||||||
|
fixed (byte* newer_ = _scratch)
|
||||||
|
{
|
||||||
|
int* older = (int*)older_;
|
||||||
|
int* newer = (int*)newer_;
|
||||||
|
int lastIndex = (Math.Min(_masterLength, (int)sss.Position) + 3) / 4;
|
||||||
|
int lastOldIndex = (_masterLength + 3) / 4;
|
||||||
|
int* olderEnd = older + lastIndex;
|
||||||
|
|
||||||
|
int* from = older;
|
||||||
|
int* to = older;
|
||||||
|
|
||||||
|
while (older < olderEnd)
|
||||||
|
{
|
||||||
|
if (*older++ == *newer++)
|
||||||
|
{
|
||||||
|
if (to < from)
|
||||||
|
{
|
||||||
|
// Save on [to, from]
|
||||||
|
lengthHolder = (int)(from - to);
|
||||||
|
zeldas.Write(lengthHolderSpan);
|
||||||
|
zeldas.Write(new ReadOnlySpan<byte>(to, lengthHolder * 4));
|
||||||
|
}
|
||||||
|
to = older;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (from < to)
|
||||||
|
{
|
||||||
|
// encode gap [from, to]
|
||||||
|
lengthHolder = (int)(to - from) | IS_GAP;
|
||||||
|
zeldas.Write(lengthHolderSpan);
|
||||||
|
}
|
||||||
|
from = older;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (from < to)
|
||||||
|
{
|
||||||
|
// encode gap [from, to]
|
||||||
|
lengthHolder = (int)(to - from) | IS_GAP;
|
||||||
|
zeldas.Write(lengthHolderSpan);
|
||||||
|
}
|
||||||
|
if (lastOldIndex > lastIndex)
|
||||||
|
{
|
||||||
|
from += lastOldIndex - lastIndex;
|
||||||
|
}
|
||||||
|
if (to < from)
|
||||||
|
{
|
||||||
|
// Save on [to, from]
|
||||||
|
lengthHolder = (int)(from - to);
|
||||||
|
zeldas.Write(lengthHolderSpan);
|
||||||
|
zeldas.Write(new ReadOnlySpan<byte>(to, lengthHolder * 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(_master, _scratch) = (_scratch, _master);
|
||||||
|
_masterLength = (int)sss.Position;
|
||||||
|
_masterFrame = frame;
|
||||||
|
_count++;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void RefillMaster(ZwinderBuffer.StateInformation state)
|
||||||
|
{
|
||||||
|
var lengthHolder = 0;
|
||||||
|
var lengthHolderSpan = new Span<byte>(&lengthHolder, 4);
|
||||||
|
var zeldas = SpanStream.GetOrBuild(state.GetReadStream());
|
||||||
|
zeldas.Read(lengthHolderSpan);
|
||||||
|
_masterLength = lengthHolder;
|
||||||
|
fixed (byte* buffer_ = _master)
|
||||||
|
{
|
||||||
|
int* buffer = (int*)buffer_;
|
||||||
|
while (zeldas.Read(lengthHolderSpan) == 4)
|
||||||
|
{
|
||||||
|
if ((lengthHolder & IS_GAP) != 0)
|
||||||
|
{
|
||||||
|
buffer += lengthHolder & ~IS_GAP;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
zeldas.Read(new Span<byte>(buffer, lengthHolder * 4));
|
||||||
|
buffer += lengthHolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_masterFrame = state.Frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Rewind(int frameToAvoid)
|
||||||
|
{
|
||||||
|
Sync();
|
||||||
|
if (!_active || _count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_masterFrame == frameToAvoid && _count > 1)
|
||||||
|
{
|
||||||
|
var index = _buffer.Count - 1;
|
||||||
|
RefillMaster(_buffer.GetState(index));
|
||||||
|
_buffer.InvalidateEnd(index);
|
||||||
|
_stateSource.LoadStateBinary(new BinaryReader(new MemoryStream(_master, 0, _masterLength, false)));
|
||||||
|
_count--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stateSource.LoadStateBinary(new BinaryReader(new MemoryStream(_master, 0, _masterLength, false)));
|
||||||
|
Work(() =>
|
||||||
|
{
|
||||||
|
var index = _buffer.Count - 1;
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
RefillMaster(_buffer.GetState(index));
|
||||||
|
_buffer.InvalidateEnd(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_masterFrame = -1;
|
||||||
|
}
|
||||||
|
_count--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SaveStateStream : Stream, ISpanStream
|
||||||
|
{
|
||||||
|
public SaveStateStream(ZeldaWinder owner)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
}
|
||||||
|
private ZeldaWinder _owner;
|
||||||
|
private byte[] _dest
|
||||||
|
{
|
||||||
|
get => _owner._scratch;
|
||||||
|
set => _owner._scratch = value;
|
||||||
|
}
|
||||||
|
private int _position;
|
||||||
|
public override bool CanRead => false;
|
||||||
|
public override bool CanSeek => false;
|
||||||
|
public override bool CanWrite => true;
|
||||||
|
public override long Length => _position;
|
||||||
|
public override long Position { get => _position; set => throw new IOException(); }
|
||||||
|
public override void Flush() { }
|
||||||
|
public override int Read(byte[] buffer, int offset, int count) => throw new IOException();
|
||||||
|
public override long Seek(long offset, SeekOrigin origin) => throw new IOException();
|
||||||
|
public override void SetLength(long value) => throw new IOException();
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
Write(new ReadOnlySpan<byte>(buffer, offset, count));
|
||||||
|
}
|
||||||
|
private void MaybeResize(int requestedSize)
|
||||||
|
{
|
||||||
|
if (requestedSize > _dest.Length)
|
||||||
|
{
|
||||||
|
var replacement = new byte[(Math.Max(_dest.Length * 2, requestedSize) + 3) & ~3];
|
||||||
|
Array.Copy(_dest, replacement, _dest.Length);
|
||||||
|
_dest = replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void Write(ReadOnlySpan<byte> buffer)
|
||||||
|
{
|
||||||
|
var requestedSize = _position + buffer.Length;
|
||||||
|
MaybeResize(requestedSize);
|
||||||
|
buffer.CopyTo(new Span<byte>(_dest, _position, buffer.Length));
|
||||||
|
_position = requestedSize;
|
||||||
|
}
|
||||||
|
public override void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
var requestedSize = _position + 1;
|
||||||
|
MaybeResize(requestedSize);
|
||||||
|
_dest[_position] = value;
|
||||||
|
_position = requestedSize;
|
||||||
|
}
|
||||||
|
public int Read(Span<byte> buffer) => throw new IOException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -164,6 +164,17 @@ namespace BizHawk.Client.Common
|
||||||
return frameDiff >= ComputeIdealRewindInterval();
|
return frameDiff >= ComputeIdealRewindInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Predict whether Capture() will capture a state. Useful if expensive work needs to happen before
|
||||||
|
/// Capture() is called.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="frame">The same frame number to be passed to capture</param>
|
||||||
|
/// <returns>Whether capture will happen, assuming Capture() is passed the same frame and force = false</returns>
|
||||||
|
public bool WillCapture(int frame)
|
||||||
|
{
|
||||||
|
return ShouldCapture(frame);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maybe captures a state, if the conditions are favorable
|
/// Maybe captures a state, if the conditions are favorable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -949,7 +949,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
Rewinder?.Dispose();
|
Rewinder?.Dispose();
|
||||||
Rewinder = Emulator.HasSavestates() && Config.Rewind.Enabled
|
Rewinder = Emulator.HasSavestates() && Config.Rewind.Enabled
|
||||||
? new Zwinder(Emulator.AsStatable(), Config.Rewind)
|
? Config.Rewind.UseDelta
|
||||||
|
? new ZeldaWinder(Emulator.AsStatable(), Config.Rewind)
|
||||||
|
: new Zwinder(Emulator.AsStatable(), Config.Rewind)
|
||||||
: null;
|
: null;
|
||||||
AddOnScreenMessage(Rewinder?.Active == true ? "Rewind started" : "Rewind disabled");
|
AddOnScreenMessage(Rewinder?.Active == true ? "Rewind started" : "Rewind disabled");
|
||||||
}
|
}
|
||||||
|
|
17
src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs
generated
Normal file → Executable file
17
src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs
generated
Normal file → Executable file
|
@ -41,6 +41,7 @@
|
||||||
this.label6 = new BizHawk.WinForms.Controls.LocLabelEx();
|
this.label6 = new BizHawk.WinForms.Controls.LocLabelEx();
|
||||||
this.FullnessLabel = new BizHawk.WinForms.Controls.LocLabelEx();
|
this.FullnessLabel = new BizHawk.WinForms.Controls.LocLabelEx();
|
||||||
this.groupBox4 = new System.Windows.Forms.GroupBox();
|
this.groupBox4 = new System.Windows.Forms.GroupBox();
|
||||||
|
this.cbDeltaCompression = new System.Windows.Forms.CheckBox();
|
||||||
this.TargetFrameLengthNumeric = new System.Windows.Forms.NumericUpDown();
|
this.TargetFrameLengthNumeric = new System.Windows.Forms.NumericUpDown();
|
||||||
this.label2 = new BizHawk.WinForms.Controls.LocLabelEx();
|
this.label2 = new BizHawk.WinForms.Controls.LocLabelEx();
|
||||||
this.EstTimeLabel = new BizHawk.WinForms.Controls.LocLabelEx();
|
this.EstTimeLabel = new BizHawk.WinForms.Controls.LocLabelEx();
|
||||||
|
@ -118,7 +119,7 @@
|
||||||
this.UseCompression.Name = "UseCompression";
|
this.UseCompression.Name = "UseCompression";
|
||||||
this.UseCompression.Size = new System.Drawing.Size(306, 17);
|
this.UseCompression.Size = new System.Drawing.Size(306, 17);
|
||||||
this.UseCompression.TabIndex = 5;
|
this.UseCompression.TabIndex = 5;
|
||||||
this.UseCompression.Text = "Use compression (economizes buffer usage at cost of CPU)";
|
this.UseCompression.Text = "Use zlib compression (economizes buffer usage at cost of CPU)";
|
||||||
this.UseCompression.UseVisualStyleBackColor = true;
|
this.UseCompression.UseVisualStyleBackColor = true;
|
||||||
this.UseCompression.CheckedChanged += new System.EventHandler(this.UseCompression_CheckedChanged);
|
this.UseCompression.CheckedChanged += new System.EventHandler(this.UseCompression_CheckedChanged);
|
||||||
//
|
//
|
||||||
|
@ -183,6 +184,7 @@
|
||||||
//
|
//
|
||||||
// groupBox4
|
// groupBox4
|
||||||
//
|
//
|
||||||
|
this.groupBox4.Controls.Add(this.cbDeltaCompression);
|
||||||
this.groupBox4.Controls.Add(this.TargetFrameLengthNumeric);
|
this.groupBox4.Controls.Add(this.TargetFrameLengthNumeric);
|
||||||
this.groupBox4.Controls.Add(this.label2);
|
this.groupBox4.Controls.Add(this.label2);
|
||||||
this.groupBox4.Controls.Add(this.label4);
|
this.groupBox4.Controls.Add(this.label4);
|
||||||
|
@ -202,11 +204,21 @@
|
||||||
this.groupBox4.Controls.Add(this.StateSizeLabel);
|
this.groupBox4.Controls.Add(this.StateSizeLabel);
|
||||||
this.groupBox4.Location = new System.Drawing.Point(12, 12);
|
this.groupBox4.Location = new System.Drawing.Point(12, 12);
|
||||||
this.groupBox4.Name = "groupBox4";
|
this.groupBox4.Name = "groupBox4";
|
||||||
this.groupBox4.Size = new System.Drawing.Size(371, 205);
|
this.groupBox4.Size = new System.Drawing.Size(371, 218);
|
||||||
this.groupBox4.TabIndex = 2;
|
this.groupBox4.TabIndex = 2;
|
||||||
this.groupBox4.TabStop = false;
|
this.groupBox4.TabStop = false;
|
||||||
this.groupBox4.Text = "RewindSettings";
|
this.groupBox4.Text = "RewindSettings";
|
||||||
//
|
//
|
||||||
|
// cbDeltaCompression
|
||||||
|
//
|
||||||
|
this.cbDeltaCompression.AutoSize = true;
|
||||||
|
this.cbDeltaCompression.Location = new System.Drawing.Point(15, 193);
|
||||||
|
this.cbDeltaCompression.Name = "cbDeltaCompression";
|
||||||
|
this.cbDeltaCompression.Size = new System.Drawing.Size(149, 17);
|
||||||
|
this.cbDeltaCompression.TabIndex = 35;
|
||||||
|
this.cbDeltaCompression.Text = "Use delta compression (economizes buffer usage at cost of CPU)";
|
||||||
|
this.cbDeltaCompression.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
// TargetFrameLengthNumeric
|
// TargetFrameLengthNumeric
|
||||||
//
|
//
|
||||||
this.TargetFrameLengthNumeric.Location = new System.Drawing.Point(125, 135);
|
this.TargetFrameLengthNumeric.Location = new System.Drawing.Point(125, 135);
|
||||||
|
@ -538,5 +550,6 @@
|
||||||
private BizHawk.WinForms.Controls.LocLabelEx label20;
|
private BizHawk.WinForms.Controls.LocLabelEx label20;
|
||||||
private System.Windows.Forms.NumericUpDown TargetFrameLengthNumeric;
|
private System.Windows.Forms.NumericUpDown TargetFrameLengthNumeric;
|
||||||
private BizHawk.WinForms.Controls.LocLabelEx label2;
|
private BizHawk.WinForms.Controls.LocLabelEx label2;
|
||||||
|
private System.Windows.Forms.CheckBox cbDeltaCompression;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -47,6 +47,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
RewindEnabledBox.Checked = _config.Rewind.Enabled;
|
RewindEnabledBox.Checked = _config.Rewind.Enabled;
|
||||||
UseCompression.Checked = _config.Rewind.UseCompression;
|
UseCompression.Checked = _config.Rewind.UseCompression;
|
||||||
|
cbDeltaCompression.Checked = _config.Rewind.UseDelta;
|
||||||
BufferSizeUpDown.Value = Math.Max(_config.Rewind.BufferSize, BufferSizeUpDown.Minimum);
|
BufferSizeUpDown.Value = Math.Max(_config.Rewind.BufferSize, BufferSizeUpDown.Minimum);
|
||||||
TargetFrameLengthNumeric.Value = Math.Max(_config.Rewind.TargetFrameLength, TargetFrameLengthNumeric.Minimum);
|
TargetFrameLengthNumeric.Value = Math.Max(_config.Rewind.TargetFrameLength, TargetFrameLengthNumeric.Minimum);
|
||||||
StateSizeLabel.Text = FormatKB(_avgStateSize);
|
StateSizeLabel.Text = FormatKB(_avgStateSize);
|
||||||
|
@ -111,6 +112,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
_config.Rewind.Enabled = PutRewindSetting(_config.Rewind.Enabled, RewindEnabledBox.Checked);
|
_config.Rewind.Enabled = PutRewindSetting(_config.Rewind.Enabled, RewindEnabledBox.Checked);
|
||||||
_config.Rewind.BufferSize = PutRewindSetting(_config.Rewind.BufferSize, (int)BufferSizeUpDown.Value);
|
_config.Rewind.BufferSize = PutRewindSetting(_config.Rewind.BufferSize, (int)BufferSizeUpDown.Value);
|
||||||
_config.Rewind.TargetFrameLength = PutRewindSetting(_config.Rewind.TargetFrameLength, (int)TargetFrameLengthNumeric.Value);
|
_config.Rewind.TargetFrameLength = PutRewindSetting(_config.Rewind.TargetFrameLength, (int)TargetFrameLengthNumeric.Value);
|
||||||
|
_config.Rewind.UseDelta = PutRewindSetting(_config.Rewind.UseDelta, cbDeltaCompression.Checked);
|
||||||
|
|
||||||
// These settings are not used by DoRewindSettings
|
// These settings are not used by DoRewindSettings
|
||||||
_config.Savestates.CompressionLevelNormal = (int)nudCompression.Value;
|
_config.Savestates.CompressionLevelNormal = (int)nudCompression.Value;
|
||||||
|
|
Loading…
Reference in New Issue