diff --git a/BizHawk.Client.Common/rewind/Rewinder.cs b/BizHawk.Client.Common/rewind/Rewinder.cs index 33cc41c98a..0897d41fb8 100644 --- a/BizHawk.Client.Common/rewind/Rewinder.cs +++ b/BizHawk.Client.Common/rewind/Rewinder.cs @@ -263,17 +263,19 @@ namespace BizHawk.Client.Common return; } - var beginChangeSequence = -1; - var inChangeSequence = false; + int index = 0; + 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[offset++] = 0; // Full state (false = delta) - int stateLength = Math.Min(currentState.Length, _lastState.Length); // Just to be safe :) + _tempBuf[index++] = 0; // Full state (false = delta) + fixed (byte* pCurrentState = ¤tState[0]) fixed (byte* pLastState = &_lastState[0]) for (int i = 0; i < stateLength; i++) @@ -288,54 +290,39 @@ namespace BizHawk.Client.Common } inChangeSequence = true; - beginChangeSequence = i; + changeSequenceStartOffset = i; } - if (thisByteMatches || i - beginChangeSequence == 254 || i == currentState.Length - 1) + if (thisByteMatches || i == stateLength - 1) { - const int maxHeaderSize = 5; - int length = i - beginChangeSequence + (thisByteMatches ? 0 : 1); + const int maxHeaderSize = 10; + 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 CaptureRewindStateNonDelta(currentState); return; } - // Length - _tempBuf[offset++] = (byte)length; + // Delta Offset + VLInteger.WriteUnsigned((uint)(changeSequenceStartOffset - lastChangeSequenceStartOffset), _tempBuf, ref index); - // Offset - if (isSmall) - { - BitConverterLE.WriteBytes((ushort)beginChangeSequence, _tempBuf, offset); - offset += 2; - } - else - { - BitConverterLE.WriteBytes((uint)beginChangeSequence, _tempBuf, offset); - offset += 4; - } + // Length + VLInteger.WriteUnsigned((uint)length, _tempBuf, ref index); // Data - Buffer.BlockCopy(_lastState, beginChangeSequence, _tempBuf, offset, length); - offset += length; + Buffer.BlockCopy(_lastState, changeSequenceStartOffset, _tempBuf, index, length); + index += length; inChangeSequence = false; + lastChangeSequenceStartOffset = changeSequenceStartOffset; } } - if (_lastState.Length == currentState.Length) - { - Buffer.BlockCopy(currentState, 0, _lastState, 0, _lastState.Length); - } - else - { - _lastState = (byte[])currentState.Clone(); - } + Buffer.BlockCopy(currentState, 0, _lastState, 0, _lastState.Length); - _rewindBuffer.Push(new ArraySegment(_tempBuf, 0, offset)); + _rewindBuffer.Push(new ArraySegment(_tempBuf, 0, index)); } private void RewindLarge() @@ -361,15 +348,21 @@ namespace BizHawk.Client.Common } else { + byte[] buf = ms.GetBuffer(); var output = new MemoryStream(_lastState); - while (ms.Position < ms.Length) - { - var len = reader.ReadByte(); - int offset = isSmall ? reader.ReadUInt16() : reader.ReadInt32(); + int index = 1; + int offset = 0; + while (index < buf.Length) + { + int deltaOffset = (int)VLInteger.ReadUnsigned(buf, ref index); + int length = (int)VLInteger.ReadUnsigned(buf, ref index); + + offset += deltaOffset; + output.Position = offset; - output.Write(ms.GetBuffer(), (int)ms.Position, len); - ms.Position += len; + output.Write(buf, index, length); + index += length; } reader.Close(); diff --git a/BizHawk.Common/Util.cs b/BizHawk.Common/Util.cs index 5f23d96d0a..fc87baa6e8 100644 --- a/BizHawk.Common/Util.cs +++ b/BizHawk.Common/Util.cs @@ -376,18 +376,50 @@ namespace BizHawk.Common 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[startIndex + 1] = (byte)(value >> 8); + dst[index ] = (byte)(value ); + 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[startIndex + 1] = (byte)(value >> 8); - dst[startIndex + 2] = (byte)(value >> 16); - dst[startIndex + 3] = (byte)(value >> 24); + dst[index ] = (byte)(value ); + dst[index + 1] = (byte)(value >> 8); + dst[index + 2] = (byte)(value >> 16); + 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; } }