Refactor ZWinderBuffer to allow use of arbitrary backing Streams (#2536)
This commit is contained in:
parent
bcf5347823
commit
f4e98fd9bd
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.BizInvoke
|
||||
{
|
||||
|
@ -8,7 +9,7 @@ namespace BizHawk.BizInvoke
|
|||
/// Create a stream that allows read/write over a set of unmanaged memory pointers
|
||||
/// The validity and lifetime of those pointers is YOUR responsibility
|
||||
/// </summary>
|
||||
public class MemoryViewStream : Stream
|
||||
public unsafe class MemoryViewStream : Stream, ISpanStream
|
||||
{
|
||||
public MemoryViewStream(bool readable, bool writable, long ptr, long length)
|
||||
{
|
||||
|
@ -50,20 +51,38 @@ namespace BizHawk.BizInvoke
|
|||
|
||||
public override void Flush() {}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
private byte* CurrentPointer() => (byte*)Z.SS(_ptr + _pos);
|
||||
|
||||
public int Read(Span<byte> buffer)
|
||||
{
|
||||
if (!_readable)
|
||||
throw new InvalidOperationException();
|
||||
if (count < 0 || offset + count > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
throw new IOException();
|
||||
EnsureNotDisposed();
|
||||
|
||||
count = (int)Math.Min(count, _length - _pos);
|
||||
Marshal.Copy(Z.SS(_ptr + _pos), buffer, offset, count);
|
||||
var count = (int)Math.Min(buffer.Length, _length - _pos);
|
||||
new ReadOnlySpan<byte>(CurrentPointer(), count).CopyTo(buffer);
|
||||
_pos += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return Read(new Span<byte>(buffer, offset, count));
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (_pos < _length)
|
||||
{
|
||||
var ret = *CurrentPointer();
|
||||
_pos++;
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
long newpos;
|
||||
|
@ -86,19 +105,36 @@ namespace BizHawk.BizInvoke
|
|||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
if (!_writable)
|
||||
throw new IOException();
|
||||
EnsureNotDisposed();
|
||||
if (_pos + buffer.Length > _length)
|
||||
throw new IOException("End of non-resizable stream");
|
||||
buffer.CopyTo(new Span<byte>(CurrentPointer(), buffer.Length));
|
||||
_pos += buffer.Length;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (!_writable)
|
||||
throw new InvalidOperationException();
|
||||
if (count < 0 || _pos + count > _length || offset + count > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
EnsureNotDisposed();
|
||||
Write(new ReadOnlySpan<byte>(buffer, offset, count));
|
||||
}
|
||||
|
||||
Marshal.Copy(buffer, offset, Z.SS(_ptr + _pos), count);
|
||||
_pos += count;
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
if (_pos < _length)
|
||||
{
|
||||
*CurrentPointer() = value;
|
||||
_pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("End of non-resizable stream");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
bool UseCompression { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Max amount of buffer space to use in MB
|
||||
/// Buffer space to use in MB
|
||||
/// </summary>
|
||||
long BufferSize { get; }
|
||||
|
||||
|
@ -16,6 +16,14 @@
|
|||
/// Desired frame length (number of emulated frames you can go back before running out of buffer)
|
||||
/// </summary>
|
||||
int TargetFrameLength { get; }
|
||||
|
||||
public enum BackingStoreType
|
||||
{
|
||||
Memory,
|
||||
TempFile,
|
||||
}
|
||||
|
||||
public BackingStoreType BackingStore { get; }
|
||||
}
|
||||
|
||||
public class RewindConfig : IRewindSettings
|
||||
|
@ -24,5 +32,6 @@
|
|||
public bool Enabled { get; set; } = true;
|
||||
public long BufferSize { get; set; } = 512; // in mb
|
||||
public int TargetFrameLength { get; set; } = 600;
|
||||
public IRewindSettings.BackingStoreType BackingStore { get; set; } = IRewindSettings.BackingStoreType.Memory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using BizHawk.BizInvoke;
|
||||
using BizHawk.Common;
|
||||
|
||||
|
@ -26,8 +28,29 @@ namespace BizHawk.Client.Common
|
|||
|
||||
Size = 1L << (int)Math.Floor(Math.Log(targetSize, 2));
|
||||
_sizeMask = Size - 1;
|
||||
_buffer = new MemoryBlock((ulong)Size);
|
||||
_buffer.Protect(_buffer.Start, _buffer.Size, MemoryBlock.Protection.RW);
|
||||
switch (settings.BackingStore)
|
||||
{
|
||||
case IRewindSettings.BackingStoreType.Memory:
|
||||
{
|
||||
var buffer = new MemoryBlock((ulong)Size);
|
||||
buffer.Protect(buffer.Start, buffer.Size, MemoryBlock.Protection.RW);
|
||||
_disposables.Add(buffer);
|
||||
_backingStore = new MemoryViewStream(true, true, (long)buffer.Start, (long)buffer.Size);
|
||||
_disposables.Add(_backingStore);
|
||||
break;
|
||||
}
|
||||
case IRewindSettings.BackingStoreType.TempFile:
|
||||
{
|
||||
var filename = TempFileManager.GetTempFilename("ZwinderBuffer");
|
||||
var filestream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
|
||||
filestream.SetLength(Size);
|
||||
_backingStore = filestream;
|
||||
_disposables.Add(filestream);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
_targetFrameLength = settings.TargetFrameLength;
|
||||
_states = new StateInfo[STATEMASK + 1];
|
||||
_useCompression = settings.UseCompression;
|
||||
|
@ -35,9 +58,12 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
_buffer.Dispose();
|
||||
foreach (var d in (_disposables as IEnumerable<IDisposable>).Reverse())
|
||||
d.Dispose();
|
||||
_disposables.Clear();
|
||||
}
|
||||
|
||||
private readonly List<IDisposable> _disposables = new List<IDisposable>();
|
||||
|
||||
/// <summary>
|
||||
/// Number of states that could be in the state ringbuffer, Mask for the state ringbuffer
|
||||
|
@ -67,7 +93,6 @@ namespace BizHawk.Client.Common
|
|||
public long Size { get; }
|
||||
|
||||
private readonly long _sizeMask;
|
||||
private readonly MemoryBlock _buffer;
|
||||
|
||||
private readonly int _targetFrameLength;
|
||||
|
||||
|
@ -78,6 +103,8 @@ namespace BizHawk.Client.Common
|
|||
public int Frame;
|
||||
}
|
||||
|
||||
private readonly Stream _backingStore;
|
||||
|
||||
private readonly StateInfo[] _states;
|
||||
private int _firstStateIndex;
|
||||
private int _nextStateIndex;
|
||||
|
@ -164,7 +191,7 @@ namespace BizHawk.Client.Common
|
|||
? (_states[_firstStateIndex].Start - start) & _sizeMask
|
||||
: Size;
|
||||
};
|
||||
var stream = new SaveStateStream(_buffer, start, _sizeMask, initialMaxSize, notifySizeReached);
|
||||
var stream = new SaveStateStream(_backingStore, start, _sizeMask, initialMaxSize, notifySizeReached);
|
||||
|
||||
if (_useCompression)
|
||||
{
|
||||
|
@ -186,7 +213,7 @@ namespace BizHawk.Client.Common
|
|||
|
||||
private Stream MakeLoadStream(int index)
|
||||
{
|
||||
Stream stream = new LoadStateStream(_buffer, _states[index].Start, _states[index].Size, _sizeMask);
|
||||
Stream stream = new LoadStateStream(_backingStore, _states[index].Start, _states[index].Size, _sizeMask);
|
||||
if (_useCompression)
|
||||
stream = new DeflateStream(stream, CompressionMode.Decompress, leaveOpen: true);
|
||||
return stream;
|
||||
|
@ -256,18 +283,16 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
var startByte = _states[_firstStateIndex].Start;
|
||||
var endByte = (_states[HeadStateIndex].Start + _states[HeadStateIndex].Size) & _sizeMask;
|
||||
// TODO: Use spans to avoid these extra copies in .net core
|
||||
var destStream = SpanStream.GetOrBuild(writer.BaseStream);
|
||||
if (startByte > endByte)
|
||||
{
|
||||
{
|
||||
var stream = _buffer.GetStream(_buffer.Start + (ulong)startByte, (ulong)(Size - startByte), false);
|
||||
stream.CopyTo(writer.BaseStream);
|
||||
}
|
||||
_backingStore.Position = startByte;
|
||||
WaterboxUtils.CopySome(_backingStore, writer.BaseStream, Size - startByte);
|
||||
startByte = 0;
|
||||
}
|
||||
{
|
||||
var stream = _buffer.GetStream(_buffer.Start + (ulong)startByte, (ulong)(endByte - startByte), false);
|
||||
stream.CopyTo(writer.BaseStream);
|
||||
_backingStore.Position = startByte;
|
||||
WaterboxUtils.CopySome(_backingStore, writer.BaseStream, endByte - startByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -284,9 +309,8 @@ namespace BizHawk.Client.Common
|
|||
_states[i].Start = nextByte;
|
||||
nextByte += _states[i].Size;
|
||||
}
|
||||
// TODO: Use spans to avoid this extra copy in .net core
|
||||
var dest = _buffer.GetStream(_buffer.Start, (ulong)nextByte, true);
|
||||
WaterboxUtils.CopySome(reader.BaseStream, dest, nextByte);
|
||||
_backingStore.Position = 0;
|
||||
WaterboxUtils.CopySome(reader.BaseStream, _backingStore, nextByte);
|
||||
}
|
||||
|
||||
public static ZwinderBuffer Create(BinaryReader reader)
|
||||
|
@ -314,7 +338,7 @@ namespace BizHawk.Client.Common
|
|||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="buffer">The ringbuffer to write into</param>
|
||||
/// <param name="backingStore">The ringbuffer to write into</param>
|
||||
/// <param name="offset">Offset into the buffer to start writing (and treat as position 0 in the stream)</param>
|
||||
/// <param name="mask">Buffer size mask, used to wrap values in the ringbuffer correctly</param>
|
||||
/// <param name="notifySize">
|
||||
|
@ -325,16 +349,18 @@ namespace BizHawk.Client.Common
|
|||
/// or abort processing with an IOException. This must fail if size is going to exceed buffer.Length, as nothing else
|
||||
/// is preventing that case.
|
||||
/// </param>
|
||||
public SaveStateStream(MemoryBlock buffer, long offset, long mask, long notifySize, Func<long> notifySizeReached)
|
||||
public SaveStateStream(Stream backingStore, long offset, long mask, long notifySize, Func<long> notifySizeReached)
|
||||
{
|
||||
_ptr = (byte*)Z.US(buffer.Start);
|
||||
_backingStore = backingStore;
|
||||
_backingStoreSS = SpanStream.GetOrBuild(backingStore);
|
||||
_offset = offset;
|
||||
_mask = mask;
|
||||
_notifySize = notifySize;
|
||||
_notifySizeReached = notifySizeReached;
|
||||
}
|
||||
|
||||
private readonly byte* _ptr;
|
||||
|
||||
private readonly Stream _backingStore;
|
||||
private readonly ISpanStream _backingStoreSS;
|
||||
private readonly long _offset;
|
||||
private readonly long _mask;
|
||||
private long _position;
|
||||
|
@ -371,25 +397,20 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
var start = (_position + _offset) & _mask;
|
||||
var end = (start + n) & _mask;
|
||||
_backingStore.Position = start;
|
||||
if (end < start)
|
||||
{
|
||||
long m = BufferLength - start;
|
||||
|
||||
// Array.Copy(buffer, offset, _buffer, start, m);
|
||||
buffer.Slice(0, (int)m).CopyTo(new Span<byte>(_ptr + start, (int)m));
|
||||
|
||||
// offset += (int)m;
|
||||
_backingStoreSS.Write(buffer.Slice(0, (int)m));
|
||||
buffer = buffer.Slice((int)m);
|
||||
|
||||
n -= m;
|
||||
_position += m;
|
||||
start = 0;
|
||||
_backingStore.Position = start;
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
// Array.Copy(buffer, offset, _buffer, start, n);
|
||||
buffer.CopyTo(new Span<byte>(_ptr + start, (int)n));
|
||||
|
||||
_backingStoreSS.Write(buffer);
|
||||
_position += n;
|
||||
}
|
||||
}
|
||||
|
@ -400,21 +421,26 @@ namespace BizHawk.Client.Common
|
|||
long requestedSize = _position + 1;
|
||||
while (requestedSize > _notifySize)
|
||||
_notifySize = _notifySizeReached();
|
||||
_ptr[(_position++ + _offset) & _mask] = value;
|
||||
_backingStore.WriteByte(value);
|
||||
_position++;
|
||||
if (_position + _offset == BufferLength)
|
||||
_backingStore.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe class LoadStateStream : Stream, ISpanStream
|
||||
{
|
||||
public LoadStateStream(MemoryBlock buffer, long offset, long size, long mask)
|
||||
public LoadStateStream(Stream backingStore, long offset, long size, long mask)
|
||||
{
|
||||
_ptr = (byte*)Z.US(buffer.Start);
|
||||
_backingStore = backingStore;
|
||||
_backingStoreSS = SpanStream.GetOrBuild(backingStore);
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_mask = mask;
|
||||
}
|
||||
|
||||
private readonly byte* _ptr;
|
||||
private readonly Stream _backingStore;
|
||||
private readonly ISpanStream _backingStoreSS;
|
||||
private readonly long _offset;
|
||||
private readonly long _size;
|
||||
private long _position;
|
||||
|
@ -430,8 +456,7 @@ namespace BizHawk.Client.Common
|
|||
get => _position;
|
||||
set => throw new IOException();
|
||||
}
|
||||
public override void Flush()
|
||||
{}
|
||||
public override void Flush() {}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
|
@ -446,24 +471,22 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
var start = (_position + _offset) & _mask;
|
||||
var end = (start + n) & _mask;
|
||||
_backingStore.Position = start;
|
||||
if (end < start)
|
||||
{
|
||||
long m = BufferLength - start;
|
||||
|
||||
// Array.Copy(_buffer, start, buffer, offset, m);
|
||||
new ReadOnlySpan<byte>(_ptr + start, (int)m).CopyTo(buffer);
|
||||
|
||||
// offset += (int)m;
|
||||
if (_backingStoreSS.Read(buffer.Slice(0, (int)m)) != (int)m)
|
||||
throw new IOException("Unexpected end of underlying buffer");
|
||||
buffer = buffer.Slice((int)m);
|
||||
|
||||
n -= m;
|
||||
_position += m;
|
||||
start = 0;
|
||||
_backingStore.Position = start;
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
// Array.Copy(_buffer, start, buffer, offset, n);
|
||||
new ReadOnlySpan<byte>(_ptr + start, (int)n).CopyTo(buffer);
|
||||
if (_backingStoreSS.Read(buffer.Slice(0, (int)n)) != (int)n)
|
||||
throw new IOException("Unexpected end of underlying buffer");
|
||||
_position += n;
|
||||
}
|
||||
}
|
||||
|
@ -472,9 +495,20 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public override int ReadByte()
|
||||
{
|
||||
return _position < _size
|
||||
? _ptr[(_position++ + _offset) & _mask]
|
||||
: -1;
|
||||
if (_position < _size)
|
||||
{
|
||||
var ret = _backingStore.ReadByte();
|
||||
if (ret == -1)
|
||||
throw new IOException("Unexpected end of underlying buffer");
|
||||
_position++;
|
||||
if (_position + _offset == BufferLength)
|
||||
_backingStore.Position = 0;
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new IOException();
|
||||
|
|
Loading…
Reference in New Issue