Add in ISaveRam implementation for C64, using the deltas of disks.
Add in better docs for `DeltaSerializer`. Fix C64 not remembering disk changes when swapping disks (swapping disks essentially just reset the disk previously)
This commit is contained in:
parent
ece5d2548a
commit
b8f3f089f2
|
@ -202,7 +202,7 @@ namespace BizHawk.Client.Common
|
|||
|
||||
CommonEntriesFor(VSystemID.Raw.Arcade, basePath: Path.Combine(".", "Arcade")),
|
||||
|
||||
CommonEntriesFor(VSystemID.Raw.C64, basePath: Path.Combine(".", "C64"), omitSaveRAM: true),
|
||||
CommonEntriesFor(VSystemID.Raw.C64, basePath: Path.Combine(".", "C64")),
|
||||
|
||||
CommonEntriesFor(VSystemID.Raw.ChannelF, basePath: Path.Combine(".", "Channel F"), omitSaveRAM: true),
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ using BizHawk.Emulation.Common.Base_Implementations;
|
|||
using BizHawk.Emulation.Cores;
|
||||
using BizHawk.Emulation.Cores.Arcades.MAME;
|
||||
using BizHawk.Emulation.Cores.Calculators.TI83;
|
||||
using BizHawk.Emulation.Cores.Computers.Commodore64;
|
||||
using BizHawk.Emulation.Cores.Consoles.NEC.PCE;
|
||||
using BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64;
|
||||
using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES;
|
||||
|
@ -1921,7 +1922,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
byte[] sram;
|
||||
|
||||
// some cores might not know how big the saveram ought to be, so just send it the whole file
|
||||
if (Emulator is MGBAHawk || Emulator is NeoGeoPort || (Emulator is NES && (Emulator as NES).BoardName == "FDS"))
|
||||
if (Emulator is C64 or MGBAHawk or NeoGeoPort or NES { BoardName: "FDS" })
|
||||
{
|
||||
sram = File.ReadAllBytes(saveRamPath);
|
||||
}
|
||||
|
|
|
@ -5,30 +5,42 @@ 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
|
||||
/// Uses a simple delta format in order to keep size down
|
||||
/// DELTA FORMAT DETAILS FOLLOWS
|
||||
/// The format comprises of an indeterminate amount of blocks. These blocks start with a 4 byte header. This header is read as a native endian 32-bit two's complement signed integer.
|
||||
/// If the header is positive, then the header indicates the amount of bytes which are identical between the original and current spans.
|
||||
/// Positive headers are blocks by themselves, so the next header will proceed immediately after a positive header.
|
||||
/// If the header is negative, then the header indicates the negation of the amount of bytes which differ between the original and current spans.
|
||||
/// A negative header will have the negated header amount of bytes proceed it, which will be the bitwise XOR between the original and differing bytes.
|
||||
/// A header of -0x80000000 is considered ill-formed.
|
||||
/// This format does not stipulate requirements for whether blocks of non-differing bytes necessarily will use a positive header.
|
||||
/// Thus, an implementation is free to use negative headers only, although without combination of positive headers, this will obviously not have great results wrt final size.
|
||||
/// More practically, an implementation may want to avoid using positive headers when the block is rather small (e.g. smaller than the header itself, and thus not shrinking the result).
|
||||
/// Subsequently, it may not mind putting some identical bytes within the negative header's block.
|
||||
/// XORing the same values result in 0, so doing this will not leave trace of the original data.
|
||||
/// </summary>
|
||||
public static class DeltaSerializer
|
||||
{
|
||||
public static ReadOnlySpan<byte> GetDelta<T>(ReadOnlySpan<T> original, ReadOnlySpan<T> data)
|
||||
public static ReadOnlySpan<byte> GetDelta<T>(ReadOnlySpan<T> original, ReadOnlySpan<T> current)
|
||||
where T : unmanaged
|
||||
{
|
||||
var orignalAsBytes = MemoryMarshal.AsBytes(original);
|
||||
var dataAsBytes = MemoryMarshal.AsBytes(data);
|
||||
var currentAsBytes = MemoryMarshal.AsBytes(current);
|
||||
|
||||
if (orignalAsBytes.Length != dataAsBytes.Length)
|
||||
if (orignalAsBytes.Length != currentAsBytes.Length)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(dataAsBytes.Length)}");
|
||||
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(currentAsBytes.Length)}");
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
var end = dataAsBytes.Length;
|
||||
var end = currentAsBytes.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])
|
||||
while (index < end && orignalAsBytes[index] == currentAsBytes[index])
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
@ -40,7 +52,7 @@ namespace BizHawk.Common
|
|||
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])
|
||||
if (orignalAsBytes[index] != currentAsBytes[index])
|
||||
{
|
||||
different++;
|
||||
same = 0; // note: same is set to 0 on first iteration
|
||||
|
@ -68,7 +80,7 @@ namespace BizHawk.Common
|
|||
retSize += 4;
|
||||
for (var i = blockStart; i < index - same; i++)
|
||||
{
|
||||
ret[retSize++] = (byte)(orignalAsBytes[i] ^ dataAsBytes[i]);
|
||||
ret[retSize++] = (byte)(orignalAsBytes[i] ^ currentAsBytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +88,7 @@ namespace BizHawk.Common
|
|||
{
|
||||
if (same == 8)
|
||||
{
|
||||
while (index < end && orignalAsBytes[index] == dataAsBytes[index])
|
||||
while (index < end && orignalAsBytes[index] == currentAsBytes[index])
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
@ -97,19 +109,19 @@ namespace BizHawk.Common
|
|||
return ret.Slice(0, retSize);
|
||||
}
|
||||
|
||||
public static void ApplyDelta<T>(ReadOnlySpan<T> original, Span<T> data, ReadOnlySpan<byte> delta)
|
||||
public static void ApplyDelta<T>(ReadOnlySpan<T> original, Span<T> current, ReadOnlySpan<byte> delta)
|
||||
where T : unmanaged
|
||||
{
|
||||
var orignalAsBytes = MemoryMarshal.AsBytes(original);
|
||||
var dataAsBytes = MemoryMarshal.AsBytes(data);
|
||||
var currentAsBytes = MemoryMarshal.AsBytes(current);
|
||||
|
||||
if (orignalAsBytes.Length != dataAsBytes.Length)
|
||||
if (orignalAsBytes.Length != currentAsBytes.Length)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(dataAsBytes.Length)}");
|
||||
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(currentAsBytes.Length)}");
|
||||
}
|
||||
|
||||
var dataPos = 0;
|
||||
var dataEnd = dataAsBytes.Length;
|
||||
var dataEnd = currentAsBytes.Length;
|
||||
var deltaPos = 0;
|
||||
var deltaEnd = delta.Length;
|
||||
|
||||
|
@ -126,14 +138,14 @@ namespace BizHawk.Common
|
|||
{
|
||||
header = -header;
|
||||
|
||||
if (dataEnd - dataPos < header || deltaEnd - deltaPos < header)
|
||||
if (header == int.MinValue || dataEnd - dataPos < header || deltaEnd - deltaPos < header)
|
||||
{
|
||||
throw new InvalidOperationException("Corrupt delta header!");
|
||||
}
|
||||
|
||||
for (var i = 0; i < header; i++)
|
||||
{
|
||||
dataAsBytes[dataPos + i] = (byte)(orignalAsBytes[dataPos + i] ^ delta[deltaPos + i]);
|
||||
currentAsBytes[dataPos + i] = (byte)(orignalAsBytes[dataPos + i] ^ delta[deltaPos + i]);
|
||||
}
|
||||
|
||||
deltaPos += header;
|
||||
|
@ -145,7 +157,7 @@ namespace BizHawk.Common
|
|||
throw new InvalidOperationException("Corrupt delta header!");
|
||||
}
|
||||
|
||||
orignalAsBytes.Slice(dataPos, header).CopyTo(dataAsBytes.Slice(dataPos, header));
|
||||
orignalAsBytes.Slice(dataPos, header).CopyTo(currentAsBytes.Slice(dataPos, header));
|
||||
}
|
||||
|
||||
dataPos += header;
|
||||
|
|
|
@ -748,18 +748,18 @@ namespace BizHawk.Common
|
|||
}
|
||||
}
|
||||
|
||||
public void SyncDelta<T>(string name, T[] original, T[] data)
|
||||
public void SyncDelta<T>(string name, T[] original, T[] current)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (IsReader)
|
||||
{
|
||||
var delta = Array.Empty<byte>();
|
||||
Sync(name, ref delta, useNull: false);
|
||||
DeltaSerializer.ApplyDelta<T>(original, data, delta);
|
||||
DeltaSerializer.ApplyDelta<T>(original, current, delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
var delta = DeltaSerializer.GetDelta<T>(original, data).ToArray(); // TODO: don't create array here (need .net update to write span to binary writer)
|
||||
var delta = DeltaSerializer.GetDelta<T>(original, current).ToArray(); // TODO: don't create array here (need .net update to write span to binary writer)
|
||||
Sync(name, ref delta, useNull: false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
|||
ser.Sync(nameof(CurrentDisk), ref _currentDisk);
|
||||
if (oldDisk != _currentDisk)
|
||||
{
|
||||
InitDisk();
|
||||
// don't use InitDisk here, no need to load in soon to be overwritten deltas
|
||||
InitMedia(_roms[_currentDisk]);
|
||||
}
|
||||
ser.Sync("PreviousDiskPressed", ref _prevPressed);
|
||||
ser.Sync("NextDiskPressed", ref _nextPressed);
|
||||
|
|
|
@ -129,7 +129,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
|||
{
|
||||
case C64.DiskDriveType.Commodore1541:
|
||||
case C64.DiskDriveType.Commodore1541II:
|
||||
DiskDrive = new Drive1541(ClockNumerator, ClockDenominator);
|
||||
DiskDrive = new Drive1541(ClockNumerator, ClockDenominator, () => _c64.CurrentDisk);
|
||||
Serial.Connect(DiskDrive);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,12 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
|||
_cyclesPerFrame = _board.Vic.CyclesPerFrame;
|
||||
_memoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
|
||||
|
||||
if (_board.DiskDrive != null)
|
||||
{
|
||||
_board.DiskDrive.InitSaveRam(_roms.Count);
|
||||
ser.Register<ISaveRam>(_board.DiskDrive);
|
||||
}
|
||||
|
||||
InitMedia(_roms[_currentDisk]);
|
||||
HardReset();
|
||||
|
||||
|
@ -172,6 +178,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
|||
|
||||
private void IncrementDisk()
|
||||
{
|
||||
_board.DiskDrive.SaveDeltas();
|
||||
_currentDisk++;
|
||||
if (CurrentDisk >= _roms.Count)
|
||||
{
|
||||
|
@ -183,6 +190,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
|||
|
||||
private void DecrementDisk()
|
||||
{
|
||||
_board.DiskDrive.SaveDeltas();
|
||||
_currentDisk--;
|
||||
if (_currentDisk < 0)
|
||||
{
|
||||
|
@ -195,12 +203,14 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
|||
private void InitDisk()
|
||||
{
|
||||
InitMedia(_roms[_currentDisk]);
|
||||
_board.DiskDrive.LoadDeltas();
|
||||
}
|
||||
|
||||
public void SetDisk(int discNum)
|
||||
{
|
||||
if (_currentDisk != discNum)
|
||||
{
|
||||
_board.DiskDrive.SaveDeltas();
|
||||
_currentDisk = discNum;
|
||||
InitDisk();
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
|
|||
_tracks = new int[trackCapacity][];
|
||||
FillMissingTracks();
|
||||
_originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray();
|
||||
_usedTracks = new bool[trackCapacity];
|
||||
Valid = true;
|
||||
}
|
||||
|
||||
|
@ -49,7 +48,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
|
|||
FillMissingTracks();
|
||||
Valid = true;
|
||||
_originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray();
|
||||
_usedTracks = new bool[trackCapacity];
|
||||
}
|
||||
|
||||
private int[] ConvertToFluxTransitions(int density, byte[] bytes, int fluxBitOffset)
|
||||
|
@ -135,29 +133,41 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
|
|||
}
|
||||
}
|
||||
|
||||
public void AttachTracker(bool[] usedTracks)
|
||||
{
|
||||
if (_tracks.Length != usedTracks.Length)
|
||||
{
|
||||
throw new InvalidOperationException("track and tracker length mismatch! (this should be impossible, please report)");
|
||||
}
|
||||
|
||||
_usedTracks = usedTracks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic update of the deltas stored in Drive1541's ISaveRam implementation.
|
||||
/// deltaUpdateCallback will be called for each track which has been possibly dirtied
|
||||
/// </summary>
|
||||
/// <param name="deltaUpdateCallback">callback</param>
|
||||
public void DeltaUpdate(Action<int, int[], int[]> deltaUpdateCallback)
|
||||
{
|
||||
for (var i = 0; i < _tracks.Length; i++)
|
||||
{
|
||||
if (_usedTracks[i])
|
||||
{
|
||||
deltaUpdateCallback(i, _originalMedia[i], _tracks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int[] GetDataForTrack(int halftrack)
|
||||
{
|
||||
_usedTracks[halftrack] = true; // TODO: probably can be smarter about this with the WriteProtected flag
|
||||
_usedTracks[halftrack] = true;
|
||||
return _tracks[halftrack];
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync(nameof(WriteProtected), ref WriteProtected);
|
||||
var oldUsedTracks = _usedTracks; // Sync changes reference if loading state (we don't care in the saving state case)
|
||||
ser.Sync(nameof(_usedTracks), ref _usedTracks, useNull: false);
|
||||
|
||||
for (var i = 0; i < _tracks.Length; i++)
|
||||
{
|
||||
if (_usedTracks[i])
|
||||
{
|
||||
ser.SyncDelta($"MediaState{i}", _originalMedia[i], _tracks[i]);
|
||||
}
|
||||
else if (ser.IsReader && oldUsedTracks[i]) // _tracks[i] might be different, but in the state it wasn't, so just copy _originalMedia[i]
|
||||
{
|
||||
_originalMedia[i].AsSpan().CopyTo(_tracks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Components.M6502;
|
||||
using BizHawk.Emulation.Cores.Computers.Commodore64.Media;
|
||||
using BizHawk.Emulation.Cores.Computers.Commodore64.MOS;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
||||
{
|
||||
public sealed partial class Drive1541 : SerialPortDevice
|
||||
public sealed partial class Drive1541 : SerialPortDevice, ISaveRam
|
||||
{
|
||||
private Disk _disk;
|
||||
private int _bitHistory;
|
||||
|
@ -51,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
|||
public void WriteMemory(ushort address, byte value) => _drive.Write(address, value);
|
||||
}
|
||||
|
||||
public Drive1541(int clockNum, int clockDen)
|
||||
public Drive1541(int clockNum, int clockDen, Func<int> getCurrentDiskNumber)
|
||||
{
|
||||
DriveRom = new Chip23128();
|
||||
_cpu = new MOS6502X<CpuLink>(new CpuLink(this))
|
||||
|
@ -65,6 +67,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
|||
|
||||
_cpuClockNum = clockNum;
|
||||
_driveCpuClockNum = clockDen * 16000000; // 16mhz
|
||||
_getCurrentDiskNumber = getCurrentDiskNumber;
|
||||
}
|
||||
|
||||
public override void SyncState(Serializer ser)
|
||||
|
@ -101,9 +104,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
|||
ser.Sync("SystemDriveCpuRatioDifference", ref _ratioDifference);
|
||||
ser.Sync("DriveLightOffTime", ref _driveLightOffTime);
|
||||
|
||||
// set _trackImageData back to the correct reference
|
||||
_trackImageData = _disk?.GetDataForTrack(_trackNumber);
|
||||
|
||||
ser.Sync("DiskDensityCounter", ref _diskDensityCounter);
|
||||
ser.Sync("DiskSupplementaryCounter", ref _diskSupplementaryCounter);
|
||||
ser.Sync("DiskFluxReversalDetected", ref _diskFluxReversalDetected);
|
||||
|
@ -123,6 +123,34 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
|||
ser.Sync("DiskWriteLatch", ref _diskWriteLatch);
|
||||
ser.Sync("DiskOutputBits", ref _diskOutputBits);
|
||||
ser.Sync("DiskWriteProtected", ref _diskWriteProtected);
|
||||
|
||||
if (ser.IsReader)
|
||||
{
|
||||
ResetDeltas();
|
||||
}
|
||||
else
|
||||
{
|
||||
SaveDeltas();
|
||||
}
|
||||
|
||||
for (var i = 0; i < _usedDiskTracks.Length; i++)
|
||||
{
|
||||
ser.Sync($"_usedDiskTracks{i}", ref _usedDiskTracks[i], useNull: false);
|
||||
for (var j = 0; j < 84; j++)
|
||||
{
|
||||
ser.Sync($"DiskDeltas{i},{j}", ref _diskDeltas[i, j], useNull: true);
|
||||
}
|
||||
}
|
||||
|
||||
_disk?.AttachTracker(_usedDiskTracks[_getCurrentDiskNumber()]);
|
||||
|
||||
if (ser.IsReader)
|
||||
{
|
||||
LoadDeltas();
|
||||
}
|
||||
|
||||
// set _trackImageData back to the correct reference
|
||||
_trackImageData = _disk?.GetDataForTrack(_trackNumber);
|
||||
}
|
||||
|
||||
public override void ExecutePhase()
|
||||
|
@ -206,6 +234,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
|||
public void InsertMedia(Disk disk)
|
||||
{
|
||||
_disk = disk;
|
||||
_disk?.AttachTracker(_usedDiskTracks[_getCurrentDiskNumber()]);
|
||||
UpdateMediaData();
|
||||
}
|
||||
|
||||
|
@ -229,5 +258,96 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
|
|||
_trackImageData = null;
|
||||
_diskBits = 0;
|
||||
}
|
||||
|
||||
// ISaveRam implementation
|
||||
|
||||
// this is some extra state used to keep savestate size down, as most tracks don't get used
|
||||
// we keep it here for all disks as we need to remember it when swapping disks around
|
||||
// _usedDiskTracks.Length also doubles as a way to remember the disk count
|
||||
private bool[][] _usedDiskTracks;
|
||||
private byte[,][] _diskDeltas;
|
||||
private readonly Func<int> _getCurrentDiskNumber;
|
||||
|
||||
public void InitSaveRam(int diskCount)
|
||||
{
|
||||
_usedDiskTracks = new bool[diskCount][];
|
||||
_diskDeltas = new byte[diskCount, 84][];
|
||||
for (var i = 0; i < diskCount; i++)
|
||||
{
|
||||
_usedDiskTracks[i] = new bool[84];
|
||||
}
|
||||
}
|
||||
|
||||
public bool SaveRamModified => true;
|
||||
|
||||
public byte[] CloneSaveRam()
|
||||
{
|
||||
SaveDeltas(); // update the current deltas
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
using var bw = new BinaryWriter(ms);
|
||||
bw.Write(_usedDiskTracks.Length);
|
||||
for (var i = 0; i < _usedDiskTracks.Length; i++)
|
||||
{
|
||||
bw.WriteByteBuffer(_usedDiskTracks[i].ToUByteBuffer());
|
||||
for (var j = 0; j < 84; j++)
|
||||
{
|
||||
bw.WriteByteBuffer(_diskDeltas[i, j]);
|
||||
}
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
public void StoreSaveRam(byte[] data)
|
||||
{
|
||||
using var ms = new MemoryStream(data, false);
|
||||
using var br = new BinaryReader(ms);
|
||||
|
||||
var ndisks = br.ReadInt32();
|
||||
if (ndisks != _usedDiskTracks.Length)
|
||||
{
|
||||
throw new InvalidOperationException("Disk count mismatch!");
|
||||
}
|
||||
|
||||
ResetDeltas();
|
||||
|
||||
for (var i = 0; i < _usedDiskTracks.Length; i++)
|
||||
{
|
||||
_usedDiskTracks[i] = br.ReadByteBuffer(returnNull: false)!.ToBoolBuffer();
|
||||
for (var j = 0; j < 84; j++)
|
||||
{
|
||||
_diskDeltas[i, j] = br.ReadByteBuffer(returnNull: true);
|
||||
}
|
||||
}
|
||||
|
||||
_disk?.AttachTracker(_usedDiskTracks[_getCurrentDiskNumber()]);
|
||||
LoadDeltas(); // load up new deltas
|
||||
_usedDiskTracks[_getCurrentDiskNumber()][_trackNumber] = true; // make sure this gets set to true now
|
||||
}
|
||||
|
||||
public void SaveDeltas()
|
||||
{
|
||||
_disk?.DeltaUpdate((tracknum, original, current) =>
|
||||
{
|
||||
_diskDeltas[_getCurrentDiskNumber(), tracknum] = DeltaSerializer.GetDelta<int>(original, current).ToArray();
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadDeltas()
|
||||
{
|
||||
_disk?.DeltaUpdate((tracknum, original, current) =>
|
||||
{
|
||||
DeltaSerializer.ApplyDelta<int>(original, current, _diskDeltas[_getCurrentDiskNumber(), tracknum]);
|
||||
});
|
||||
}
|
||||
|
||||
private void ResetDeltas()
|
||||
{
|
||||
_disk?.DeltaUpdate(static (_, original, current) =>
|
||||
{
|
||||
original.AsSpan().CopyTo(current);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue