From c76a3f4a9bff27ddffa0769d84a3159c9c1477d6 Mon Sep 17 00:00:00 2001 From: saxxonpike Date: Wed, 1 Jan 2025 11:04:22 -0600 Subject: [PATCH 1/3] [C64] Move SaveRam implementation to its own partial --- .../Commodore64/Serial/Drive1541.SaveRam.cs | 103 ++++++++++++++++++ .../Computers/Commodore64/Serial/Drive1541.cs | 95 +--------------- 2 files changed, 104 insertions(+), 94 deletions(-) create mode 100644 src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs new file mode 100644 index 00000000000..6786ffc72c0 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs @@ -0,0 +1,103 @@ +using System.IO; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial; + +public sealed partial class Drive1541 : ISaveRam +{ + // 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 _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(original, current) + .ToArray(); + }); + } + + public void LoadDeltas() + { + _disk?.DeltaUpdate((tracknum, original, current) => + { + DeltaSerializer.ApplyDelta(original, current, _diskDeltas[_getCurrentDiskNumber(), tracknum]); + }); + } + + private void ResetDeltas() + { + _disk?.DeltaUpdate(static (_, original, current) => + { + original.AsSpan() + .CopyTo(current); + }); + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs index 129f496a0a6..b9aaa241f38 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs @@ -1,14 +1,11 @@ -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, ISaveRam + public sealed partial class Drive1541 : SerialPortDevice { private Disk _disk; private int _bitHistory; @@ -258,95 +255,5 @@ public void RemoveMedia() _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 _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(original, current).ToArray(); - }); - } - - public void LoadDeltas() - { - _disk?.DeltaUpdate((tracknum, original, current) => - { - DeltaSerializer.ApplyDelta(original, current, _diskDeltas[_getCurrentDiskNumber(), tracknum]); - }); - } - - private void ResetDeltas() - { - _disk?.DeltaUpdate(static (_, original, current) => - { - original.AsSpan().CopyTo(current); - }); - } } } From 228172a4afefb8b455becb29defedd8806f8fee7 Mon Sep 17 00:00:00 2001 From: saxxonpike Date: Mon, 6 Jan 2025 18:51:14 +1000 Subject: [PATCH 2/3] [C64] DiskTrack implementation to simplify delta tracking --- .../Computers/Commodore64/Media/Disk.cs | 109 ++------- .../Computers/Commodore64/Media/DiskTrack.cs | 217 ++++++++++++++++++ .../Serial/Drive1541.FluxTransitions.cs | 9 +- .../Commodore64/Serial/Drive1541.SaveRam.cs | 54 +++-- .../Computers/Commodore64/Serial/Drive1541.cs | 21 +- 5 files changed, 284 insertions(+), 126 deletions(-) create mode 100644 src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskTrack.cs diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs index 5405b078565..d148d1971e0 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.Linq; + using BizHawk.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media @@ -9,8 +9,7 @@ public sealed class Disk public const int FluxBitsPerEntry = 32; public const int FluxBitsPerTrack = 16000000 / 5; public const int FluxEntriesPerTrack = FluxBitsPerTrack / FluxBitsPerEntry; - private readonly int[][] _tracks; - private readonly int[][] _originalMedia; + private readonly DiskTrack[] _tracks; private bool[] _usedTracks; public bool Valid; public bool WriteProtected; @@ -18,12 +17,12 @@ public sealed class Disk /// /// Create a blank, unformatted disk. /// - public Disk(int trackCapacity) + public Disk(int trackCount) { WriteProtected = false; - _tracks = new int[trackCapacity][]; + _tracks = new DiskTrack[trackCount]; + _usedTracks = new bool[trackCount]; FillMissingTracks(); - _originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray(); Valid = true; } @@ -37,76 +36,17 @@ public Disk(int trackCapacity) public Disk(IList trackData, IList trackNumbers, IList trackDensities, int trackCapacity) { WriteProtected = true; - _tracks = new int[trackCapacity][]; + _tracks = new DiskTrack[trackCapacity]; + _usedTracks = new bool[trackCapacity]; for (var i = 0; i < trackData.Count; i++) { - _tracks[trackNumbers[i]] = ConvertToFluxTransitions(trackDensities[i], trackData[i], 0); + var track = new DiskTrack(); + track.ReadFromGCR(trackDensities[i], trackData[i], 0); + _tracks[trackNumbers[i]] = track; } FillMissingTracks(); Valid = true; - _originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray(); - } - - private int[] ConvertToFluxTransitions(int density, byte[] bytes, int fluxBitOffset) - { - var paddedLength = bytes.Length; - switch (density) - { - case 3: - paddedLength = Math.Max(bytes.Length, 7692); - break; - case 2: - paddedLength = Math.Max(bytes.Length, 7142); - break; - case 1: - paddedLength = Math.Max(bytes.Length, 6666); - break; - case 0: - paddedLength = Math.Max(bytes.Length, 6250); - break; - } - - paddedLength++; - var paddedBytes = new byte[paddedLength]; - Array.Copy(bytes, paddedBytes, bytes.Length); - for (var i = bytes.Length; i < paddedLength; i++) - { - paddedBytes[i] = 0xAA; - } - var result = new int[FluxEntriesPerTrack]; - var lengthBits = (paddedLength * 8) - 7; - var offsets = new List(); - var remainingBits = lengthBits; - - const long bitsNum = FluxEntriesPerTrack * FluxBitsPerEntry; - long bitsDen = lengthBits; - - for (var i = 0; i < paddedLength; i++) - { - var byteData = paddedBytes[i]; - for (var j = 0; j < 8; j++) - { - var offset = fluxBitOffset + ((i * 8 + j) * bitsNum / bitsDen); - var byteOffset = (int)(offset / FluxBitsPerEntry); - var bitOffset = (int)(offset % FluxBitsPerEntry); - offsets.Add(offset); - result[byteOffset] |= ((byteData & 0x80) != 0 ? 1 : 0) << bitOffset; - byteData <<= 1; - remainingBits--; - if (remainingBits <= 0) - { - break; - } - } - - if (remainingBits <= 0) - { - break; - } - } - - return result; } private void FillMissingTracks() @@ -116,29 +56,15 @@ private void FillMissingTracks() { if (_tracks[i] == null && _tracks[i - 1] != null) { - _tracks[i] = new int[FluxEntriesPerTrack]; - Array.Copy(_tracks[i - 1], _tracks[i], FluxEntriesPerTrack); + _tracks[i] = _tracks[i - 1].Clone(); } } // Fill vacant tracks for (var i = 0; i < _tracks.Length; i++) { - if (_tracks[i] == null) - { - _tracks[i] = new int[FluxEntriesPerTrack]; - } - } - } - - public void AttachTracker(bool[] usedTracks) - { - if (_tracks.Length != usedTracks.Length) - { - throw new InvalidOperationException("track and tracker length mismatch! (this should be impossible, please report)"); + _tracks[i] ??= new(); } - - _usedTracks = usedTracks; } /// @@ -146,22 +72,19 @@ public void AttachTracker(bool[] usedTracks) /// deltaUpdateCallback will be called for each track which has been possibly dirtied /// /// callback - public void DeltaUpdate(Action deltaUpdateCallback) + public void DeltaUpdate(Action deltaUpdateCallback) { for (var i = 0; i < _tracks.Length; i++) { if (_usedTracks[i]) { - deltaUpdateCallback(i, _originalMedia[i], _tracks[i]); + deltaUpdateCallback(i, _tracks[i]); } } } - public int[] GetDataForTrack(int halftrack) - { - _usedTracks[halftrack] = true; - return _tracks[halftrack]; - } + public DiskTrack GetTrack(int trackNumber) + => _tracks[trackNumber]; public void SyncState(Serializer ser) { diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskTrack.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskTrack.cs new file mode 100644 index 00000000000..6484f237266 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskTrack.cs @@ -0,0 +1,217 @@ +using System.Buffers; + +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media; + +/// +/// Represents the magnetic flux transitions for one rotation of floppy disk media. Each bit represents +/// the transition of the signal level from 1 to 0, or from 0 to 1. +/// +public sealed class DiskTrack +{ + /// + /// The master clock rate for synchronization. + /// + private const int ClockRateHz = 16000000; + + /// + /// Number of bytes per element in the Bits array. + /// + private const int BytesPerEntry = sizeof(int); + + /// + /// Number of bits contained in a single value of the Bits array. + /// + private const int FluxBitsPerEntry = BytesPerEntry * 8; + + /// + /// The number of flux transition bits stored for each track. + /// + private const int FluxBitsPerTrack = ClockRateHz / 5; + + /// + /// The fixed size of the Bits array. + /// + private const int FluxEntriesPerTrack = FluxBitsPerTrack / FluxBitsPerEntry; + + /// + /// The number of bytes contained in the cached delta, for use with save states. + /// + private const int DeltaBytesPerTrack = FluxEntriesPerTrack * BytesPerEntry + 4; + + private int[] _bits = new int[FluxEntriesPerTrack]; + private int[] _original = new int[FluxEntriesPerTrack]; + private byte[] _delta = new byte[DeltaBytesPerTrack]; + private bool _dirty = true; + private bool _modified = false; + + /// + /// Current state of the disk, which may be changed from the original media. + /// + public ReadOnlySpan Bits => _bits; + + /// + /// Fixed state of the original media, from which deltas will be calculated. + /// + public ReadOnlySpan Original => _original; + + /// + /// The compressed difference between + /// + public byte[] Delta => _delta; + + /// + /// If true, the delta needs to be recalculated. + /// + public bool IsDirty => _dirty; + + /// + /// If true, the track data has been modified. + /// + public bool IsModified => _modified; + + /// + /// Create a clone of the DiskTrack. + /// + /// + /// A new DiskTrack with an identical copy of . + /// + public DiskTrack Clone() + { + var clone = new DiskTrack(); + Bits.CopyTo(clone._bits.AsSpan()); + clone._original = _original; + return clone; + } + + /// + /// Prepare the property. + /// + /// + /// The new value of . + /// + private bool CheckModified() + => _modified = !_original.AsSpan().SequenceEqual(_bits); + + /// + /// Apply a compressed delta over the original media. + /// + /// + /// Compressed delta data. + /// + public void ApplyDelta(ReadOnlySpan delta) + { + DeltaSerializer.ApplyDelta(_original, _bits, delta); + _delta = delta.ToArray(); + _dirty = false; + CheckModified(); + } + + /// + /// Updates the delta for this track. + /// + /// + /// True if the delta has updated, false otherwise. + /// + public bool UpdateDelta() + { + if (!_dirty) return false; + + _delta = DeltaSerializer.GetDelta(_original, _bits).ToArray(); + _dirty = false; + return true; + } + + /// + /// Resets this track to the state of the original media. + /// + public void Reset() + { + _original.CopyTo(_bits.AsSpan()); + _delta = Array.Empty(); + _dirty = false; + } + + /// + /// Synchronize state. + /// + /// + /// Serializer with which to synchronize. + /// + public void SyncState(Serializer ser, string deltaId) + { + ser.Sync(deltaId, ref _delta, useNull: true); + } + + public void Write(int index, int bits) + { + // We only need to update delta if the bits actually changed. + + if (_bits[index] == bits) return; + + _bits[index] = bits; + _dirty = true; + } + + public void ReadFromGCR(int density, ReadOnlySpan bytes, int fluxBitOffset) + { + // There are four levels of track density correlated with the four different clock dividers + // in the 1541 disk drive. Outer tracks have more surface area, so a technique is used to read + // bits at a higher rate. + + var paddedLength = density switch + { + 3 => Math.Max(bytes.Length, 7692), + 2 => Math.Max(bytes.Length, 7142), + 1 => Math.Max(bytes.Length, 6666), + 0 => Math.Max(bytes.Length, 6250), + _ => bytes.Length + }; + + // One extra byte is added at the end to break up tracks so that if the data is perfectly + // aligned in an unfortunate way, loaders don't seize up trying to find data. Some copy protections + // will read the same track repeatedly to account for variations in drive mechanics, and this should get + // the more temperamental ones to load eventually. + + paddedLength++; + + // It is possible that there are more or fewer bits than the specification due to any number + // of reasons (e.g. copy protection, tiny variations in motor speed) so we pad out with the "default" + // bit pattern. + + using var paddedBytesMem = MemoryPool.Shared.Rent(paddedLength); + var paddedBytes = paddedBytesMem.Memory.Span.Slice(0, paddedLength); + bytes.CopyTo(paddedBytes); + paddedBytes.Slice(bytes.Length).Fill(0xAA); + + var lengthBits = paddedLength * 8 - 7; + var remainingBits = lengthBits; + + const long bitsNum = FluxEntriesPerTrack * FluxBitsPerEntry; + long bitsDen = lengthBits; + + for (var i = 0; i < paddedLength; i++) + { + var byteData = paddedBytes[i]; + for (var j = 0; j < 8; j++) + { + var offset = fluxBitOffset + ((i * 8 + j) * bitsNum / bitsDen); + var byteOffset = (int)(offset / FluxBitsPerEntry); + var bitOffset = (int)(offset % FluxBitsPerEntry); + _bits[byteOffset] |= (byteData >> 7) << bitOffset; + byteData <<= 1; + remainingBits--; + if (remainingBits <= 0) + { + break; + } + } + + if (remainingBits <= 0) + { + break; + } + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs index 2e72c0f3e6c..1bf4bc397b7 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs @@ -38,6 +38,9 @@ private void AdvanceRng() private void ExecuteFlux() { + var track = _disk.GetTrack(_trackNumber); + var bits = track.Bits; + // This actually executes the main 16mhz clock while (_clocks > 0) { @@ -56,7 +59,7 @@ private void ExecuteFlux() if (_diskBitsLeft <= 0) { if (_diskWriteEnabled) - _trackImageData[_diskByteOffset] = _diskOutputBits; + track.Write(_diskByteOffset, _diskOutputBits); _diskByteOffset++; @@ -64,7 +67,7 @@ private void ExecuteFlux() _diskByteOffset = 0; if (!_diskWriteEnabled) - _diskBits = _trackImageData[_diskByteOffset]; + _diskBits = bits[_diskByteOffset]; _diskOutputBits = 0; _diskBitsLeft = Disk.FluxBitsPerEntry; @@ -197,6 +200,8 @@ private void ExecuteFlux() _diskDensityCounter++; _diskCycle = (_diskCycle + 1) & 0xF; } + + if (_diskWriteEnabled && track.UpdateDelta()) SaveDelta(_trackNumber, track.Delta); } } } diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs index 6786ffc72c0..2ca4cb2f328 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs @@ -13,20 +13,24 @@ public sealed partial class Drive1541 : ISaveRam // 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 _getCurrentDiskNumber; public void InitSaveRam(int diskCount) { _usedDiskTracks = new bool[diskCount][]; - _diskDeltas = new byte[diskCount, 84][]; + _diskDeltas = new byte[diskCount][][]; for (var i = 0; i < diskCount; i++) { _usedDiskTracks[i] = new bool[84]; + _diskDeltas[i] = new byte[84][]; + for (var j = 0; j < 84; j++) + { + _diskDeltas[i][j] = Array.Empty(); + } } } - public bool SaveRamModified => true; + public bool SaveRamModified { get; private set; } = false; public byte[] CloneSaveRam() { @@ -35,13 +39,13 @@ public byte[] CloneSaveRam() using var ms = new MemoryStream(); using var bw = new BinaryWriter(ms); bw.Write(_usedDiskTracks.Length); - for (var i = 0; i < _usedDiskTracks.Length; i++) + for (var diskNumber = 0; diskNumber < _usedDiskTracks.Length; diskNumber++) { - bw.WriteByteBuffer(_usedDiskTracks[i] + bw.WriteByteBuffer(_usedDiskTracks[diskNumber] .ToUByteBuffer()); - for (var j = 0; j < 84; j++) + for (var trackNumber = 0; trackNumber < 84; trackNumber++) { - bw.WriteByteBuffer(_diskDeltas[i, j]); + bw.WriteByteBuffer(_diskDeltas[diskNumber][trackNumber]); } } @@ -66,38 +70,54 @@ public void StoreSaveRam(byte[] data) _usedDiskTracks[i] = br.ReadByteBuffer(returnNull: false)!.ToBoolBuffer(); for (var j = 0; j < 84; j++) { - _diskDeltas[i, j] = br.ReadByteBuffer(returnNull: true); + _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) => + _disk?.DeltaUpdate((tracknum, track) => { - _diskDeltas[_getCurrentDiskNumber(), tracknum] = DeltaSerializer.GetDelta(original, current) - .ToArray(); + SaveDelta(tracknum, track.Delta); }); } public void LoadDeltas() { - _disk?.DeltaUpdate((tracknum, original, current) => + _disk?.DeltaUpdate((tracknum, track) => { - DeltaSerializer.ApplyDelta(original, current, _diskDeltas[_getCurrentDiskNumber(), tracknum]); + LoadDelta(tracknum, track.Delta); }); } private void ResetDeltas() { - _disk?.DeltaUpdate(static (_, original, current) => + _disk?.DeltaUpdate(static (_, track) => { - original.AsSpan() - .CopyTo(current); + track.Reset(); }); } + + private void SaveDelta(int trackNumber, byte[] delta) + { + SaveRamModified = true; + _diskDeltas[_getCurrentDiskNumber()][trackNumber] = delta; + } + + private void LoadDelta(int trackNumber, byte[] delta) + { + _diskDeltas[_getCurrentDiskNumber()][trackNumber] = delta; + _disk.GetTrack(trackNumber).ApplyDelta(delta); + } + + private void ResetDelta(int trackNumber) + { + SaveRamModified = true; + _diskDeltas[_getCurrentDiskNumber()][trackNumber] = Array.Empty(); + _disk.GetTrack(trackNumber).Reset(); + } } diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs index b9aaa241f38..c78a532163e 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs @@ -7,6 +7,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial { public sealed partial class Drive1541 : SerialPortDevice { + private byte[][][] _diskDeltas; private Disk _disk; private int _bitHistory; private int _bitsRemainingInLatchedByte; @@ -24,7 +25,6 @@ public sealed partial class Drive1541 : SerialPortDevice private int _cpuClockNum; private int _ratioDifference; private int _driveLightOffTime; - private int[] _trackImageData; public Func ReadIec = () => 0xFF; public Action DebuggerStep; public readonly Chip23128 DriveRom; @@ -129,24 +129,19 @@ public override void SyncState(Serializer ser) SaveDeltas(); } - for (var i = 0; i < _usedDiskTracks.Length; i++) + for (var diskNumber = 0; diskNumber < _usedDiskTracks.Length; diskNumber++) { - ser.Sync($"_usedDiskTracks{i}", ref _usedDiskTracks[i], useNull: false); - for (var j = 0; j < 84; j++) + ser.Sync($"_usedDiskTracks{diskNumber}", ref _usedDiskTracks[diskNumber], useNull: false); + for (var trackNumber = 0; trackNumber < 84; trackNumber++) { - ser.Sync($"DiskDeltas{i},{j}", ref _diskDeltas[i, j], useNull: true); + ser.Sync($"DiskDeltas{diskNumber},{trackNumber}", ref _diskDeltas[diskNumber][trackNumber], 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() @@ -230,7 +225,6 @@ public void SoftReset() public void InsertMedia(Disk disk) { _disk = disk; - _disk?.AttachTracker(_usedDiskTracks[_getCurrentDiskNumber()]); UpdateMediaData(); } @@ -238,8 +232,8 @@ private void UpdateMediaData() { if (_disk != null) { - _trackImageData = _disk.GetDataForTrack(_trackNumber); - _diskBits = _trackImageData[_diskByteOffset] >> (Disk.FluxBitsPerEntry - _diskBitsLeft); + var track = _disk.GetTrack(_trackNumber); + _diskBits = track.Bits[_diskByteOffset] >> (Disk.FluxBitsPerEntry - _diskBitsLeft); _diskWriteProtected = _disk.WriteProtected; } else @@ -251,7 +245,6 @@ private void UpdateMediaData() public void RemoveMedia() { _disk = null; - _trackImageData = null; _diskBits = 0; } From 6c9aac67b55e12ebc12773f4b3a2a02675e77aa4 Mon Sep 17 00:00:00 2001 From: saxxonpike Date: Mon, 6 Jan 2025 19:18:24 +1000 Subject: [PATCH 3/3] [C64] Fix up implementation of SaveRam --- .../Computers/Commodore64/Media/Disk.cs | 23 +--- .../Computers/Commodore64/Media/DiskTrack.cs | 93 +++----------- .../Serial/Drive1541.FluxTransitions.cs | 14 +-- .../Commodore64/Serial/Drive1541.SaveRam.cs | 116 ++++++++++++------ .../Computers/Commodore64/Serial/Drive1541.cs | 10 +- 5 files changed, 108 insertions(+), 148 deletions(-) diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs index d148d1971e0..173a22afbf4 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs @@ -10,7 +10,6 @@ public sealed class Disk public const int FluxBitsPerTrack = 16000000 / 5; public const int FluxEntriesPerTrack = FluxBitsPerTrack / FluxBitsPerEntry; private readonly DiskTrack[] _tracks; - private bool[] _usedTracks; public bool Valid; public bool WriteProtected; @@ -21,7 +20,6 @@ public Disk(int trackCount) { WriteProtected = false; _tracks = new DiskTrack[trackCount]; - _usedTracks = new bool[trackCount]; FillMissingTracks(); Valid = true; } @@ -37,7 +35,6 @@ public Disk(IList trackData, IList trackNumbers, IList trackDe { WriteProtected = true; _tracks = new DiskTrack[trackCapacity]; - _usedTracks = new bool[trackCapacity]; for (var i = 0; i < trackData.Count; i++) { var track = new DiskTrack(); @@ -67,24 +64,8 @@ private void FillMissingTracks() } } - /// - /// Generic update of the deltas stored in Drive1541's ISaveRam implementation. - /// deltaUpdateCallback will be called for each track which has been possibly dirtied - /// - /// callback - public void DeltaUpdate(Action deltaUpdateCallback) - { - for (var i = 0; i < _tracks.Length; i++) - { - if (_usedTracks[i]) - { - deltaUpdateCallback(i, _tracks[i]); - } - } - } - - public DiskTrack GetTrack(int trackNumber) - => _tracks[trackNumber]; + public IReadOnlyList Tracks + => _tracks; public void SyncState(Serializer ser) { diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskTrack.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskTrack.cs index 6484f237266..b6a6f11c0b8 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskTrack.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskTrack.cs @@ -1,7 +1,5 @@ using System.Buffers; -using BizHawk.Common; - namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media; /// @@ -23,7 +21,7 @@ public sealed class DiskTrack /// /// Number of bits contained in a single value of the Bits array. /// - private const int FluxBitsPerEntry = BytesPerEntry * 8; + public const int FluxBitsPerEntry = BytesPerEntry * 8; /// /// The number of flux transition bits stored for each track. @@ -35,42 +33,19 @@ public sealed class DiskTrack /// private const int FluxEntriesPerTrack = FluxBitsPerTrack / FluxBitsPerEntry; - /// - /// The number of bytes contained in the cached delta, for use with save states. - /// - private const int DeltaBytesPerTrack = FluxEntriesPerTrack * BytesPerEntry + 4; - private int[] _bits = new int[FluxEntriesPerTrack]; private int[] _original = new int[FluxEntriesPerTrack]; - private byte[] _delta = new byte[DeltaBytesPerTrack]; - private bool _dirty = true; - private bool _modified = false; /// /// Current state of the disk, which may be changed from the original media. /// - public ReadOnlySpan Bits => _bits; + public Span Bits => _bits; /// /// Fixed state of the original media, from which deltas will be calculated. /// public ReadOnlySpan Original => _original; - /// - /// The compressed difference between - /// - public byte[] Delta => _delta; - - /// - /// If true, the delta needs to be recalculated. - /// - public bool IsDirty => _dirty; - - /// - /// If true, the track data has been modified. - /// - public bool IsModified => _modified; - /// /// Create a clone of the DiskTrack. /// @@ -86,42 +61,13 @@ public DiskTrack Clone() } /// - /// Prepare the property. - /// - /// - /// The new value of . - /// - private bool CheckModified() - => _modified = !_original.AsSpan().SequenceEqual(_bits); - - /// - /// Apply a compressed delta over the original media. - /// - /// - /// Compressed delta data. - /// - public void ApplyDelta(ReadOnlySpan delta) - { - DeltaSerializer.ApplyDelta(_original, _bits, delta); - _delta = delta.ToArray(); - _dirty = false; - CheckModified(); - } - - /// - /// Updates the delta for this track. + /// Check to see if the original bits and current bits are equivalent. /// /// - /// True if the delta has updated, false otherwise. + /// True only if the content differs. /// - public bool UpdateDelta() - { - if (!_dirty) return false; - - _delta = DeltaSerializer.GetDelta(_original, _bits).ToArray(); - _dirty = false; - return true; - } + public bool IsModified() + => !_original.AsSpan().SequenceEqual(_bits); /// /// Resets this track to the state of the original media. @@ -129,29 +75,28 @@ public bool UpdateDelta() public void Reset() { _original.CopyTo(_bits.AsSpan()); - _delta = Array.Empty(); - _dirty = false; } /// - /// Synchronize state. + /// Write an entry to . /// - /// - /// Serializer with which to synchronize. + /// + /// Index of the entry to write. /// - public void SyncState(Serializer ser, string deltaId) - { - ser.Sync(deltaId, ref _delta, useNull: true); - } - - public void Write(int index, int bits) + /// + /// The new content of the entry. + /// + /// + /// True only if data in has been altered. + /// + public bool Write(int index, int bits) { // We only need to update delta if the bits actually changed. - if (_bits[index] == bits) return; + if (_bits[index] == bits) return false; _bits[index] = bits; - _dirty = true; + return true; } public void ReadFromGCR(int density, ReadOnlySpan bytes, int fluxBitOffset) @@ -213,5 +158,7 @@ public void ReadFromGCR(int density, ReadOnlySpan bytes, int fluxBitOffset break; } } + + _bits.CopyTo(_original.AsSpan()); } } diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs index 1bf4bc397b7..837d85afcb6 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.FluxTransitions.cs @@ -38,8 +38,8 @@ private void AdvanceRng() private void ExecuteFlux() { - var track = _disk.GetTrack(_trackNumber); - var bits = track.Bits; + var track = _disk?.Tracks[_trackNumber]; + var bits = track == null ? Span.Empty : track.Bits; // This actually executes the main 16mhz clock while (_clocks > 0) @@ -58,8 +58,10 @@ private void ExecuteFlux() { if (_diskBitsLeft <= 0) { - if (_diskWriteEnabled) - track.Write(_diskByteOffset, _diskOutputBits); + if (_diskWriteEnabled && track.Write(_diskByteOffset, _diskOutputBits)) + { + _dirtyDiskTracks[_getCurrentDiskNumber()][_trackNumber] = true; + } _diskByteOffset++; @@ -70,7 +72,7 @@ private void ExecuteFlux() _diskBits = bits[_diskByteOffset]; _diskOutputBits = 0; - _diskBitsLeft = Disk.FluxBitsPerEntry; + _diskBitsLeft = DiskTrack.FluxBitsPerEntry; } } _diskOutputBits >>= 1; @@ -200,8 +202,6 @@ private void ExecuteFlux() _diskDensityCounter++; _diskCycle = (_diskCycle + 1) & 0xF; } - - if (_diskWriteEnabled && track.UpdateDelta()) SaveDelta(_trackNumber, track.Delta); } } } diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs index 2ca4cb2f328..214b9fb0745 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.SaveRam.cs @@ -13,29 +13,34 @@ public sealed partial class Drive1541 : ISaveRam // 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 bool[][] _dirtyDiskTracks; private readonly Func _getCurrentDiskNumber; + private int _diskCount; public void InitSaveRam(int diskCount) { + _diskCount = diskCount; _usedDiskTracks = new bool[diskCount][]; + _dirtyDiskTracks = new bool[diskCount][]; _diskDeltas = new byte[diskCount][][]; - for (var i = 0; i < diskCount; i++) + for (var diskNumber = 0; diskNumber < diskCount; diskNumber++) { - _usedDiskTracks[i] = new bool[84]; - _diskDeltas[i] = new byte[84][]; - for (var j = 0; j < 84; j++) + _usedDiskTracks[diskNumber] = new bool[84]; + _diskDeltas[diskNumber] = new byte[84][]; + _dirtyDiskTracks[diskNumber] = new bool[84]; + for (var trackNumber = 0; trackNumber < 84; trackNumber++) { - _diskDeltas[i][j] = Array.Empty(); + _diskDeltas[diskNumber][trackNumber] = Array.Empty(); } } + + SaveRamModified = false; } public bool SaveRamModified { get; private set; } = false; public byte[] CloneSaveRam() { - SaveDeltas(); // update the current deltas - using var ms = new MemoryStream(); using var bw = new BinaryWriter(ms); bw.Write(_usedDiskTracks.Length); @@ -49,6 +54,7 @@ public byte[] CloneSaveRam() } } + SaveRamModified = false; return ms.ToArray(); } @@ -63,8 +69,6 @@ public void StoreSaveRam(byte[] data) throw new InvalidOperationException("Disk count mismatch!"); } - ResetDeltas(); - for (var i = 0; i < _usedDiskTracks.Length; i++) { _usedDiskTracks[i] = br.ReadByteBuffer(returnNull: false)!.ToBoolBuffer(); @@ -74,50 +78,82 @@ public void StoreSaveRam(byte[] data) } } - LoadDeltas(); // load up new deltas - _usedDiskTracks[_getCurrentDiskNumber()][_trackNumber] = true; // make sure this gets set to true now + LoadDeltas(); + SaveRamModified = false; } - public void SaveDeltas() + /// + /// Clear all cached deltas. + /// + public void ResetDeltas() { - _disk?.DeltaUpdate((tracknum, track) => + for (var i = 0; i < _diskCount; i++) { - SaveDelta(tracknum, track.Delta); - }); + for (var j = 0; j < 84; j++) + { + _diskDeltas[i][j] = Array.Empty(); + _usedDiskTracks[i][j] = false; + } + } } - public void LoadDeltas() + /// + /// Calculate and cache the deltas for each track on the current disk. + /// + public void SaveDeltas() { - _disk?.DeltaUpdate((tracknum, track) => - { - LoadDelta(tracknum, track.Delta); - }); - } + if (_disk == null) return; - private void ResetDeltas() - { - _disk?.DeltaUpdate(static (_, track) => + var diskNumber = _getCurrentDiskNumber(); + var deltas = _diskDeltas[diskNumber]; + + for (var trackNumber = 0; trackNumber < 84; trackNumber++) { - track.Reset(); - }); - } + var track = _disk.Tracks[trackNumber]; + var isModified = track.IsModified(); - private void SaveDelta(int trackNumber, byte[] delta) - { - SaveRamModified = true; - _diskDeltas[_getCurrentDiskNumber()][trackNumber] = delta; - } + _usedDiskTracks[diskNumber][trackNumber] = isModified; - private void LoadDelta(int trackNumber, byte[] delta) - { - _diskDeltas[_getCurrentDiskNumber()][trackNumber] = delta; - _disk.GetTrack(trackNumber).ApplyDelta(delta); + if (_dirtyDiskTracks[diskNumber][trackNumber]) + { + SaveRamModified = true; + + deltas[trackNumber] = isModified + ? DeltaSerializer.GetDelta(track.Original, track.Bits).ToArray() + : Array.Empty(); + + _dirtyDiskTracks[diskNumber][trackNumber] = false; + } + } } - private void ResetDelta(int trackNumber) + /// + /// Apply new deltas for each track on the current disk. + /// + public void LoadDeltas() { - SaveRamModified = true; - _diskDeltas[_getCurrentDiskNumber()][trackNumber] = Array.Empty(); - _disk.GetTrack(trackNumber).Reset(); + if (_disk == null) return; + + var diskNumber = _getCurrentDiskNumber(); + var deltas = _diskDeltas[diskNumber]; + + for (var trackNumber = 0; trackNumber < 84; trackNumber++) + { + var track = _disk.Tracks[trackNumber]; + var delta = deltas[trackNumber]; + + if (delta == null || delta.Length == 0) + { + track.Reset(); + } + else + { + DeltaSerializer.ApplyDelta(track.Original, track.Bits, delta); + SaveRamModified = true; + } + + _usedDiskTracks[diskNumber][trackNumber] = track.IsModified(); + _dirtyDiskTracks[diskNumber][trackNumber] = false; + } } } diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs index c78a532163e..b7b4e085fa3 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs @@ -195,11 +195,7 @@ public override void HardReset() Via0.HardReset(); Via1.HardReset(); _trackNumber = 34; - for (var i = 0; i < _ram.Length; i++) - { - _ram[i] = 0x00; - } - + _ram.AsSpan().Fill(0); _diskDensity = 0; _diskFluxReversalDetected = false; _diskByteOffset = 0; @@ -232,8 +228,8 @@ private void UpdateMediaData() { if (_disk != null) { - var track = _disk.GetTrack(_trackNumber); - _diskBits = track.Bits[_diskByteOffset] >> (Disk.FluxBitsPerEntry - _diskBitsLeft); + var track = _disk.Tracks[_trackNumber]; + _diskBits = track.Bits[_diskByteOffset] >> (DiskTrack.FluxBitsPerEntry - _diskBitsLeft); _diskWriteProtected = _disk.WriteProtected; } else