Skip to content

Commit 00e2fea

Browse files
Create DeltaSerializer, uses simple RLE encoding so the serialized delta isn't huge, use it for C64 EasyFlash and Disks
1 parent 9a3cd21 commit 00e2fea

File tree

6 files changed

+185
-95
lines changed

6 files changed

+185
-95
lines changed

src/BizHawk.Common/DeltaSerializer.cs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace BizHawk.Common
5+
{
6+
/// <summary>
7+
/// Serializes deltas between data, mainly for ROM like structures which are actually writable, and therefore the differences need to be saved
8+
/// Uses simple RLE encoding to keep the size down
9+
/// </summary>
10+
public static class DeltaSerializer
11+
{
12+
public static byte[] GetDelta<T>(ReadOnlySpan<T> original, ReadOnlySpan<T> data)
13+
where T : unmanaged
14+
{
15+
var orignalAsBytes = MemoryMarshal.AsBytes(original);
16+
var dataAsBytes = MemoryMarshal.AsBytes(data);
17+
18+
if (orignalAsBytes.Length != dataAsBytes.Length)
19+
{
20+
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(dataAsBytes.Length)}");
21+
}
22+
23+
var index = 0;
24+
var end = dataAsBytes.Length;
25+
var ret = new byte[end + 4].AsSpan(); // worst case scenario size (i.e. everything is different)
26+
var retSize = 0;
27+
28+
while (index < end)
29+
{
30+
var blockStart = index;
31+
while (index < end && orignalAsBytes[index] == dataAsBytes[index])
32+
{
33+
index++;
34+
}
35+
36+
var same = index - blockStart;
37+
38+
if (same < 4) // something changed, or we hit end of spans, count how many different bytes come after
39+
{
40+
var different = 0;
41+
while (index < end && same < 8) // in case we hit end of span before, this does nothing and different is 0
42+
{
43+
if (orignalAsBytes[index] != dataAsBytes[index])
44+
{
45+
different++;
46+
same = 0; // note: same is set to 0 on first iteration
47+
}
48+
else
49+
{
50+
// we don't end on hitting a same byte, only after a sufficent number of same bytes are encountered
51+
// this would help against possibly having a few stray same bytes splattered around changes
52+
same++;
53+
}
54+
55+
index++;
56+
}
57+
58+
if (different > 0) // only not 0 if index == end immediately
59+
{
60+
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
61+
{
62+
different += same;
63+
same = 0;
64+
}
65+
66+
different = -different; // negative is a signal that these are different bytes
67+
MemoryMarshal.Write(ret.Slice(retSize, 4), ref different);
68+
retSize += 4;
69+
for (var i = blockStart; i < index - same; i++)
70+
{
71+
ret[retSize++] = (byte)(orignalAsBytes[i] ^ dataAsBytes[i]);
72+
}
73+
}
74+
75+
if (same > 0) // same is 4-8, 8 indicates we hit the 8 same bytes threshold, 4-7 indicate hit end of span
76+
{
77+
if (same == 8)
78+
{
79+
while (index < end && orignalAsBytes[index] == dataAsBytes[index])
80+
{
81+
index++;
82+
}
83+
}
84+
85+
same = index - blockStart;
86+
MemoryMarshal.Write(ret.Slice(retSize, 4), ref same);
87+
retSize += 4;
88+
}
89+
}
90+
else // count amount of same bytes in this block
91+
{
92+
MemoryMarshal.Write(ret.Slice(retSize, 4), ref same);
93+
retSize += 4;
94+
}
95+
}
96+
97+
return ret.Slice(0, retSize).ToArray();
98+
}
99+
100+
public static void ApplyDelta<T>(ReadOnlySpan<T> original, Span<T> data, ReadOnlySpan<byte> delta)
101+
where T : unmanaged
102+
{
103+
var orignalAsBytes = MemoryMarshal.AsBytes(original);
104+
var dataAsBytes = MemoryMarshal.AsBytes(data);
105+
106+
if (orignalAsBytes.Length != dataAsBytes.Length)
107+
{
108+
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(dataAsBytes.Length)}");
109+
}
110+
111+
var dataPos = 0;
112+
var dataEnd = dataAsBytes.Length;
113+
var deltaPos = 0;
114+
var deltaEnd = delta.Length;
115+
116+
while (deltaPos < deltaEnd)
117+
{
118+
if (deltaEnd - deltaPos < 4)
119+
{
120+
throw new InvalidOperationException("Hit end of delta unexpectingly!");
121+
}
122+
123+
var header = MemoryMarshal.Read<int>(delta.Slice(deltaPos, 4));
124+
deltaPos += 4;
125+
if (header < 0) // difference block
126+
{
127+
header = -header;
128+
129+
if (header < dataEnd - dataPos || header < deltaEnd - deltaPos)
130+
{
131+
throw new InvalidOperationException("Corrupt delta header!");
132+
}
133+
134+
for (var i = 0; i < header; i++)
135+
{
136+
dataAsBytes[dataPos + i] = (byte)(orignalAsBytes[dataPos + i] ^ delta[deltaPos + i]);
137+
}
138+
139+
deltaPos += header;
140+
}
141+
else // sameness block
142+
{
143+
if (header < dataEnd - dataPos)
144+
{
145+
throw new InvalidOperationException("Corrupt delta header!");
146+
}
147+
148+
orignalAsBytes.Slice(dataPos, header).CopyTo(dataAsBytes.Slice(dataPos, header));
149+
}
150+
151+
dataPos += header;
152+
}
153+
}
154+
}
155+
}

src/BizHawk.Common/Serializer.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,22 @@ public void SyncFixedString(string name, ref string val, int length)
748748
}
749749
}
750750

751+
public void SyncDelta<T>(string name, T[] original, T[] data)
752+
where T : unmanaged
753+
{
754+
if (IsReader)
755+
{
756+
var delta = Array.Empty<byte>();
757+
Sync(name, ref delta, useNull: false);
758+
DeltaSerializer.ApplyDelta<T>(original, data, delta);
759+
}
760+
else
761+
{
762+
var delta = DeltaSerializer.GetDelta<T>(original, data);
763+
Sync(name, ref delta, useNull: false);
764+
}
765+
}
766+
751767
private BinaryReader _br;
752768
private BinaryWriter _bw;
753769
private TextReader _tr;

src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper0020.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ protected override void SyncStateInternal(Serializer ser)
100100
ser.Sync("CommandLatch55", ref _commandLatchAa);
101101
ser.Sync("CommandLatchAA", ref _commandLatchAa);
102102
ser.Sync("InternalROMState", ref _internalRomState);
103-
SaveState.SyncDelta("MediaStateA", ser, _originalMediaA, ref _banksA);
104-
SaveState.SyncDelta("MediaStateB", ser, _originalMediaB, ref _banksB);
103+
ser.SyncDelta("MediaStateA", _originalMediaA, _banksA);
104+
ser.SyncDelta("MediaStateB", _originalMediaB, _banksB);
105105
DriveLightOn = _boardLed;
106106
}
107107

src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3-
3+
using System.Linq;
44
using BizHawk.Common;
55

66
namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
@@ -11,7 +11,7 @@ public sealed class Disk
1111
public const int FluxBitsPerTrack = 16000000 / 5;
1212
public const int FluxEntriesPerTrack = FluxBitsPerTrack / FluxBitsPerEntry;
1313
private readonly int[][] _tracks;
14-
private readonly int[] _originalMedia;
14+
private readonly int[][] _originalMedia;
1515
public bool Valid;
1616
public bool WriteProtected;
1717

@@ -23,7 +23,7 @@ public Disk(int trackCapacity)
2323
WriteProtected = false;
2424
_tracks = new int[trackCapacity][];
2525
FillMissingTracks();
26-
_originalMedia = SerializeTracks(_tracks);
26+
_originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray();
2727
Valid = true;
2828
}
2929

@@ -45,7 +45,7 @@ public Disk(IList<byte[]> trackData, IList<int> trackNumbers, IList<int> trackDe
4545

4646
FillMissingTracks();
4747
Valid = true;
48-
_originalMedia = SerializeTracks(_tracks);
48+
_originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray();
4949
}
5050

5151
private int[] ConvertToFluxTransitions(int density, byte[] bytes, int fluxBitOffset)
@@ -136,59 +136,14 @@ public int[] GetDataForTrack(int halftrack)
136136
return _tracks[halftrack];
137137
}
138138

139-
/// <summary>
140-
/// Combine the tracks into a single bitstream.
141-
/// </summary>
142-
private int[] SerializeTracks(int[][] tracks)
143-
{
144-
var trackCount = tracks.Length;
145-
var result = new int[trackCount * FluxEntriesPerTrack];
146-
for (var i = 0; i < trackCount; i++)
147-
{
148-
Array.Copy(tracks[i], 0, result, i * FluxEntriesPerTrack, FluxEntriesPerTrack);
149-
}
150-
return result;
151-
}
152-
153-
/// <summary>
154-
/// Split a bitstream into tracks.
155-
/// </summary>
156-
private int[][] DeserializeTracks(int[] data)
157-
{
158-
var trackCount = data.Length / FluxEntriesPerTrack;
159-
var result = new int[trackCount][];
160-
for (var i = 0; i < trackCount; i++)
161-
{
162-
result[i] = new int[FluxEntriesPerTrack];
163-
Array.Copy(data, i * FluxEntriesPerTrack, result[i], 0, FluxEntriesPerTrack);
164-
}
165-
return result;
166-
}
167-
168139
public void SyncState(Serializer ser)
169140
{
170141
ser.Sync(nameof(WriteProtected), ref WriteProtected);
171142

172-
// cpp: the below comment is wrong (at least now), writes are implemented (see ExecuteFlux() in Drive1541)
173-
// I'm not yet going to uncomment this, due to the noted performance issues
174-
// Not sure where performance issues would truly lie, suppose it's probably due to new array spam
175-
// Something just a bit smarter would fix such issues
176-
177-
// Currently nothing actually writes to _tracks and so it is always the same as _originalMedia
178-
// So commenting out this (very slow) code for now
179-
// If/when disk writing is implemented, Disk.cs should implement ISaveRam as a means of file storage of the new disk state
180-
// And this code needs to be rethought to be reasonably performant
181-
//if (ser.IsReader)
182-
//{
183-
// var mediaState = new int[_originalMedia.Length];
184-
// SaveState.SyncDelta("MediaState", ser, _originalMedia, ref mediaState);
185-
// _tracks = DeserializeTracks(mediaState);
186-
//}
187-
//else if (ser.IsWriter)
188-
//{
189-
// var mediaState = SerializeTracks(_tracks);
190-
// SaveState.SyncDelta("MediaState", ser, _originalMedia, ref mediaState);
191-
//}
143+
for (var i = 0; i < _tracks.Length; i++)
144+
{
145+
ser.SyncDelta("MediaState", _originalMedia[i], _tracks[i]);
146+
}
192147
}
193148
}
194149
}

src/BizHawk.Emulation.Cores/Computers/Commodore64/SaveState.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public sealed partial class Drive1541 : SerialPortDevice
2626
private int _cpuClockNum;
2727
private int _ratioDifference;
2828
private int _driveLightOffTime;
29-
private int[] _trackImageData = new int[1];
29+
private int[] _trackImageData;
3030
public Func<int> ReadIec = () => 0xFF;
3131
public Action DebuggerStep;
3232
public readonly Chip23128 DriveRom;
@@ -100,8 +100,9 @@ public override void SyncState(Serializer ser)
100100
ser.Sync("SystemCpuClockNumerator", ref _cpuClockNum);
101101
ser.Sync("SystemDriveCpuRatioDifference", ref _ratioDifference);
102102
ser.Sync("DriveLightOffTime", ref _driveLightOffTime);
103-
// feos: drop 400KB of ROM data from savestates
104-
//ser.Sync("TrackImageData", ref _trackImageData, useNull: false);
103+
104+
// set _trackImageData back to the correct reference
105+
_trackImageData = _disk?.GetDataForTrack(_trackNumber);
105106

106107
ser.Sync("DiskDensityCounter", ref _diskDensityCounter);
107108
ser.Sync("DiskSupplementaryCounter", ref _diskSupplementaryCounter);

0 commit comments

Comments
 (0)