Create `DeltaSerializer`, uses simple RLE encoding so the serialized delta isn't huge, use it for C64 EasyFlash and Disks
This commit is contained in:
parent
9a3cd21bc4
commit
00e2fea901
|
@ -0,0 +1,155 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BizHawk.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes deltas between data, mainly for ROM like structures which are actually writable, and therefore the differences need to be saved
|
||||
/// Uses simple RLE encoding to keep the size down
|
||||
/// </summary>
|
||||
public static class DeltaSerializer
|
||||
{
|
||||
public static byte[] GetDelta<T>(ReadOnlySpan<T> original, ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var orignalAsBytes = MemoryMarshal.AsBytes(original);
|
||||
var dataAsBytes = MemoryMarshal.AsBytes(data);
|
||||
|
||||
if (orignalAsBytes.Length != dataAsBytes.Length)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(dataAsBytes.Length)}");
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
var end = dataAsBytes.Length;
|
||||
var ret = new byte[end + 4].AsSpan(); // worst case scenario size (i.e. everything is different)
|
||||
var retSize = 0;
|
||||
|
||||
while (index < end)
|
||||
{
|
||||
var blockStart = index;
|
||||
while (index < end && orignalAsBytes[index] == dataAsBytes[index])
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
var same = index - blockStart;
|
||||
|
||||
if (same < 4) // something changed, or we hit end of spans, count how many different bytes come after
|
||||
{
|
||||
var different = 0;
|
||||
while (index < end && same < 8) // in case we hit end of span before, this does nothing and different is 0
|
||||
{
|
||||
if (orignalAsBytes[index] != dataAsBytes[index])
|
||||
{
|
||||
different++;
|
||||
same = 0; // note: same is set to 0 on first iteration
|
||||
}
|
||||
else
|
||||
{
|
||||
// we don't end on hitting a same byte, only after a sufficent number of same bytes are encountered
|
||||
// this would help against possibly having a few stray same bytes splattered around changes
|
||||
same++;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
if (different > 0) // only not 0 if index == end immediately
|
||||
{
|
||||
if (same < 4) // we have different bytes, but we hit the end of the spans before the 8 limit, and we have less than what a same block will save
|
||||
{
|
||||
different += same;
|
||||
same = 0;
|
||||
}
|
||||
|
||||
different = -different; // negative is a signal that these are different bytes
|
||||
MemoryMarshal.Write(ret.Slice(retSize, 4), ref different);
|
||||
retSize += 4;
|
||||
for (var i = blockStart; i < index - same; i++)
|
||||
{
|
||||
ret[retSize++] = (byte)(orignalAsBytes[i] ^ dataAsBytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (same > 0) // same is 4-8, 8 indicates we hit the 8 same bytes threshold, 4-7 indicate hit end of span
|
||||
{
|
||||
if (same == 8)
|
||||
{
|
||||
while (index < end && orignalAsBytes[index] == dataAsBytes[index])
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
same = index - blockStart;
|
||||
MemoryMarshal.Write(ret.Slice(retSize, 4), ref same);
|
||||
retSize += 4;
|
||||
}
|
||||
}
|
||||
else // count amount of same bytes in this block
|
||||
{
|
||||
MemoryMarshal.Write(ret.Slice(retSize, 4), ref same);
|
||||
retSize += 4;
|
||||
}
|
||||
}
|
||||
|
||||
return ret.Slice(0, retSize).ToArray();
|
||||
}
|
||||
|
||||
public static void ApplyDelta<T>(ReadOnlySpan<T> original, Span<T> data, ReadOnlySpan<byte> delta)
|
||||
where T : unmanaged
|
||||
{
|
||||
var orignalAsBytes = MemoryMarshal.AsBytes(original);
|
||||
var dataAsBytes = MemoryMarshal.AsBytes(data);
|
||||
|
||||
if (orignalAsBytes.Length != dataAsBytes.Length)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(dataAsBytes.Length)}");
|
||||
}
|
||||
|
||||
var dataPos = 0;
|
||||
var dataEnd = dataAsBytes.Length;
|
||||
var deltaPos = 0;
|
||||
var deltaEnd = delta.Length;
|
||||
|
||||
while (deltaPos < deltaEnd)
|
||||
{
|
||||
if (deltaEnd - deltaPos < 4)
|
||||
{
|
||||
throw new InvalidOperationException("Hit end of delta unexpectingly!");
|
||||
}
|
||||
|
||||
var header = MemoryMarshal.Read<int>(delta.Slice(deltaPos, 4));
|
||||
deltaPos += 4;
|
||||
if (header < 0) // difference block
|
||||
{
|
||||
header = -header;
|
||||
|
||||
if (header < dataEnd - dataPos || header < deltaEnd - deltaPos)
|
||||
{
|
||||
throw new InvalidOperationException("Corrupt delta header!");
|
||||
}
|
||||
|
||||
for (var i = 0; i < header; i++)
|
||||
{
|
||||
dataAsBytes[dataPos + i] = (byte)(orignalAsBytes[dataPos + i] ^ delta[deltaPos + i]);
|
||||
}
|
||||
|
||||
deltaPos += header;
|
||||
}
|
||||
else // sameness block
|
||||
{
|
||||
if (header < dataEnd - dataPos)
|
||||
{
|
||||
throw new InvalidOperationException("Corrupt delta header!");
|
||||
}
|
||||
|
||||
orignalAsBytes.Slice(dataPos, header).CopyTo(dataAsBytes.Slice(dataPos, header));
|
||||
}
|
||||
|
||||
dataPos += header;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -748,6 +748,22 @@ namespace BizHawk.Common
|
|||
}
|
||||
}
|
||||
|
||||
public void SyncDelta<T>(string name, T[] original, T[] data)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (IsReader)
|
||||
{
|
||||
var delta = Array.Empty<byte>();
|
||||
Sync(name, ref delta, useNull: false);
|
||||
DeltaSerializer.ApplyDelta<T>(original, data, delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
var delta = DeltaSerializer.GetDelta<T>(original, data);
|
||||
Sync(name, ref delta, useNull: false);
|
||||
}
|
||||
}
|
||||
|
||||
private BinaryReader _br;
|
||||
private BinaryWriter _bw;
|
||||
private TextReader _tr;
|
||||
|
|
|
@ -100,8 +100,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
|
|||
ser.Sync("CommandLatch55", ref _commandLatchAa);
|
||||
ser.Sync("CommandLatchAA", ref _commandLatchAa);
|
||||
ser.Sync("InternalROMState", ref _internalRomState);
|
||||
SaveState.SyncDelta("MediaStateA", ser, _originalMediaA, ref _banksA);
|
||||
SaveState.SyncDelta("MediaStateB", ser, _originalMediaB, ref _banksB);
|
||||
ser.SyncDelta("MediaStateA", _originalMediaA, _banksA);
|
||||
ser.SyncDelta("MediaStateB", _originalMediaB, _banksB);
|
||||
DriveLightOn = _boardLed;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Linq;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
|
||||
|
@ -11,7 +11,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
|
|||
public const int FluxBitsPerTrack = 16000000 / 5;
|
||||
public const int FluxEntriesPerTrack = FluxBitsPerTrack / FluxBitsPerEntry;
|
||||
private readonly int[][] _tracks;
|
||||
private readonly int[] _originalMedia;
|
||||
private readonly int[][] _originalMedia;
|
||||
public bool Valid;
|
||||
public bool WriteProtected;
|
||||
|
||||
|
@ -23,7 +23,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
|
|||
WriteProtected = false;
|
||||
_tracks = new int[trackCapacity][];
|
||||
FillMissingTracks();
|
||||
_originalMedia = SerializeTracks(_tracks);
|
||||
_originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray();
|
||||
Valid = true;
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
|
|||
|
||||
FillMissingTracks();
|
||||
Valid = true;
|
||||
_originalMedia = SerializeTracks(_tracks);
|
||||
_originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray();
|
||||
}
|
||||
|
||||
private int[] ConvertToFluxTransitions(int density, byte[] bytes, int fluxBitOffset)
|
||||
|
@ -136,59 +136,14 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
|
|||
return _tracks[halftrack];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine the tracks into a single bitstream.
|
||||
/// </summary>
|
||||
private int[] SerializeTracks(int[][] tracks)
|
||||
{
|
||||
var trackCount = tracks.Length;
|
||||
var result = new int[trackCount * FluxEntriesPerTrack];
|
||||
for (var i = 0; i < trackCount; i++)
|
||||
{
|
||||
Array.Copy(tracks[i], 0, result, i * FluxEntriesPerTrack, FluxEntriesPerTrack);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split a bitstream into tracks.
|
||||
/// </summary>
|
||||
private int[][] DeserializeTracks(int[] data)
|
||||
{
|
||||
var trackCount = data.Length / FluxEntriesPerTrack;
|
||||
var result = new int[trackCount][];
|
||||
for (var i = 0; i < trackCount; i++)
|
||||
{
|
||||
result[i] = new int[FluxEntriesPerTrack];
|
||||
Array.Copy(data, i * FluxEntriesPerTrack, result[i], 0, FluxEntriesPerTrack);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync(nameof(WriteProtected), ref WriteProtected);
|
||||
|
||||
// cpp: the below comment is wrong (at least now), writes are implemented (see ExecuteFlux() in Drive1541)
|
||||
// I'm not yet going to uncomment this, due to the noted performance issues
|
||||
// Not sure where performance issues would truly lie, suppose it's probably due to new array spam
|
||||
// Something just a bit smarter would fix such issues
|
||||
|
||||
// Currently nothing actually writes to _tracks and so it is always the same as _originalMedia
|
||||
// So commenting out this (very slow) code for now
|
||||
// If/when disk writing is implemented, Disk.cs should implement ISaveRam as a means of file storage of the new disk state
|
||||
// And this code needs to be rethought to be reasonably performant
|
||||
//if (ser.IsReader)
|
||||
//{
|
||||
// var mediaState = new int[_originalMedia.Length];
|
||||
// SaveState.SyncDelta("MediaState", ser, _originalMedia, ref mediaState);
|
||||
// _tracks = DeserializeTracks(mediaState);
|
||||
//}
|
||||
//else if (ser.IsWriter)
|
||||
//{
|
||||
// var mediaState = SerializeTracks(_tracks);
|
||||
// SaveState.SyncDelta("MediaState", ser, _originalMedia, ref mediaState);
|
||||
//}
|
||||
for (var i = 0; i < _tracks.Length; i++)
|
||||
{
|
||||
ser.SyncDelta("MediaState", _originalMedia[i], _tracks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
||||
{
|
||||
internal static class SaveState
|
||||
{
|
||||
private static int[] GetDelta(IList<int> source, IList<int> data)
|
||||
{
|
||||
var length = Math.Min(source.Count, data.Count);
|
||||
var delta = new int[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
delta[i] = source[i] ^ data[i];
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
public static void SyncDelta(string name, Serializer ser, int[] source, ref int[] data)
|
||||
{
|
||||
int[] delta = null;
|
||||
if (ser.IsWriter && data != null)
|
||||
{
|
||||
delta = GetDelta(source, data);
|
||||
}
|
||||
|
||||
ser.Sync(name, ref delta, false);
|
||||
if (ser.IsReader && delta != null)
|
||||
{
|
||||
data = GetDelta(source, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
|||
private int _cpuClockNum;
|
||||
private int _ratioDifference;
|
||||
private int _driveLightOffTime;
|
||||
private int[] _trackImageData = new int[1];
|
||||
private int[] _trackImageData;
|
||||
public Func<int> ReadIec = () => 0xFF;
|
||||
public Action DebuggerStep;
|
||||
public readonly Chip23128 DriveRom;
|
||||
|
@ -100,8 +100,9 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
|||
ser.Sync("SystemCpuClockNumerator", ref _cpuClockNum);
|
||||
ser.Sync("SystemDriveCpuRatioDifference", ref _ratioDifference);
|
||||
ser.Sync("DriveLightOffTime", ref _driveLightOffTime);
|
||||
// feos: drop 400KB of ROM data from savestates
|
||||
//ser.Sync("TrackImageData", ref _trackImageData, useNull: false);
|
||||
|
||||
// set _trackImageData back to the correct reference
|
||||
_trackImageData = _disk?.GetDataForTrack(_trackNumber);
|
||||
|
||||
ser.Sync("DiskDensityCounter", ref _diskDensityCounter);
|
||||
ser.Sync("DiskSupplementaryCounter", ref _diskSupplementaryCounter);
|
||||
|
|
Loading…
Reference in New Issue