Use variable length integers for the rewind state deltas to reduce size.

This commit is contained in:
jdpurcell 2015-01-17 19:16:22 +00:00
parent 1a266a0518
commit 4a40408f1d
2 changed files with 75 additions and 50 deletions

View File

@ -263,17 +263,19 @@ namespace BizHawk.Client.Common
return; return;
} }
var beginChangeSequence = -1; int index = 0;
var inChangeSequence = false; int stateLength = Math.Min(currentState.Length, _lastState.Length);
bool inChangeSequence = false;
int changeSequenceStartOffset = 0;
int lastChangeSequenceStartOffset = 0;
if (_tempBuf.Length < currentState.Length) if (_tempBuf.Length < stateLength)
{ {
_tempBuf = new byte[currentState.Length]; _tempBuf = new byte[stateLength];
} }
int offset = 0; _tempBuf[index++] = 0; // Full state (false = delta)
_tempBuf[offset++] = 0; // Full state (false = delta)
int stateLength = Math.Min(currentState.Length, _lastState.Length); // Just to be safe :)
fixed (byte* pCurrentState = &currentState[0]) fixed (byte* pCurrentState = &currentState[0])
fixed (byte* pLastState = &_lastState[0]) fixed (byte* pLastState = &_lastState[0])
for (int i = 0; i < stateLength; i++) for (int i = 0; i < stateLength; i++)
@ -288,54 +290,39 @@ namespace BizHawk.Client.Common
} }
inChangeSequence = true; inChangeSequence = true;
beginChangeSequence = i; changeSequenceStartOffset = i;
} }
if (thisByteMatches || i - beginChangeSequence == 254 || i == currentState.Length - 1) if (thisByteMatches || i == stateLength - 1)
{ {
const int maxHeaderSize = 5; const int maxHeaderSize = 10;
int length = i - beginChangeSequence + (thisByteMatches ? 0 : 1); int length = i - changeSequenceStartOffset + (thisByteMatches ? 0 : 1);
if (offset + length + maxHeaderSize >= stateLength) if (index + length + maxHeaderSize >= stateLength)
{ {
// If the delta ends up being larger than the full state, capture the full state instead // If the delta ends up being larger than the full state, capture the full state instead
CaptureRewindStateNonDelta(currentState); CaptureRewindStateNonDelta(currentState);
return; return;
} }
// Length // Delta Offset
_tempBuf[offset++] = (byte)length; VLInteger.WriteUnsigned((uint)(changeSequenceStartOffset - lastChangeSequenceStartOffset), _tempBuf, ref index);
// Offset // Length
if (isSmall) VLInteger.WriteUnsigned((uint)length, _tempBuf, ref index);
{
BitConverterLE.WriteBytes((ushort)beginChangeSequence, _tempBuf, offset);
offset += 2;
}
else
{
BitConverterLE.WriteBytes((uint)beginChangeSequence, _tempBuf, offset);
offset += 4;
}
// Data // Data
Buffer.BlockCopy(_lastState, beginChangeSequence, _tempBuf, offset, length); Buffer.BlockCopy(_lastState, changeSequenceStartOffset, _tempBuf, index, length);
offset += length; index += length;
inChangeSequence = false; inChangeSequence = false;
lastChangeSequenceStartOffset = changeSequenceStartOffset;
} }
} }
if (_lastState.Length == currentState.Length) Buffer.BlockCopy(currentState, 0, _lastState, 0, _lastState.Length);
{
Buffer.BlockCopy(currentState, 0, _lastState, 0, _lastState.Length);
}
else
{
_lastState = (byte[])currentState.Clone();
}
_rewindBuffer.Push(new ArraySegment<byte>(_tempBuf, 0, offset)); _rewindBuffer.Push(new ArraySegment<byte>(_tempBuf, 0, index));
} }
private void RewindLarge() private void RewindLarge()
@ -361,15 +348,21 @@ namespace BizHawk.Client.Common
} }
else else
{ {
byte[] buf = ms.GetBuffer();
var output = new MemoryStream(_lastState); var output = new MemoryStream(_lastState);
while (ms.Position < ms.Length) int index = 1;
int offset = 0;
while (index < buf.Length)
{ {
var len = reader.ReadByte(); int deltaOffset = (int)VLInteger.ReadUnsigned(buf, ref index);
int offset = isSmall ? reader.ReadUInt16() : reader.ReadInt32(); int length = (int)VLInteger.ReadUnsigned(buf, ref index);
offset += deltaOffset;
output.Position = offset; output.Position = offset;
output.Write(ms.GetBuffer(), (int)ms.Position, len); output.Write(buf, index, length);
ms.Position += len; index += length;
} }
reader.Close(); reader.Close();

View File

@ -376,18 +376,50 @@ namespace BizHawk.Common
public static class BitConverterLE public static class BitConverterLE
{ {
public static void WriteBytes(ushort value, byte[] dst, int startIndex) public static void WriteBytes(ushort value, byte[] dst, int index)
{ {
dst[startIndex ] = (byte)(value ); dst[index ] = (byte)(value );
dst[startIndex + 1] = (byte)(value >> 8); dst[index + 1] = (byte)(value >> 8);
} }
public static void WriteBytes(uint value, byte[] dst, int startIndex) public static void WriteBytes(uint value, byte[] dst, int index)
{ {
dst[startIndex ] = (byte)(value ); dst[index ] = (byte)(value );
dst[startIndex + 1] = (byte)(value >> 8); dst[index + 1] = (byte)(value >> 8);
dst[startIndex + 2] = (byte)(value >> 16); dst[index + 2] = (byte)(value >> 16);
dst[startIndex + 3] = (byte)(value >> 24); dst[index + 3] = (byte)(value >> 24);
}
}
public static class VLInteger
{
public static void WriteUnsigned(uint value, byte[] data, ref int index)
{
// This is optimized for good performance on both the x86 and x64 JITs. Don't change anything without benchmarking.
do
{
uint x = value & 0x7FU;
value >>= 7;
data[index++] = (byte)((value != 0U ? 0x80U : 0U) | x);
}
while (value != 0U);
}
public static uint ReadUnsigned(byte[] data, ref int index)
{
// This is optimized for good performance on both the x86 and x64 JITs. Don't change anything without benchmarking.
uint value = 0U;
int shiftCount = 0;
bool isLastByte; // Negating the comparison and moving it earlier in the loop helps a lot on x86 for some reason
do
{
uint x = (uint)data[index++];
isLastByte = (x & 0x80U) == 0U;
value |= (x & 0x7FU) << shiftCount;
shiftCount += 7;
}
while (!isLastByte);
return value;
} }
} }