From b5406b90cdefda098c45d41f1b46e63fee21a6f3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 22 Sep 2017 20:28:11 -0400 Subject: [PATCH] Introduces a new class hierarchy for disk images. Increasing independence of format-specific stuff and generic caching without mangling them into a common namespace, and allowing in some cases for a decrease in read/write blocking. --- .../Clock Signal.xcodeproj/project.pbxproj | 10 +- StaticAnalyser/StaticAnalyser.cpp | 44 +++--- Storage/Disk/Disk.cpp | 49 ------ Storage/Disk/Disk.hpp | 55 +------ Storage/Disk/DiskImage.cpp | 16 ++ Storage/Disk/DiskImage.hpp | 147 ++++++++++++++++++ Storage/Disk/Encodings/MFM.cpp | 2 +- Storage/Disk/Formats/AcornADF.cpp | 39 ++--- Storage/Disk/Formats/AcornADF.hpp | 11 +- Storage/Disk/Formats/CPCDSK.cpp | 2 +- Storage/Disk/Formats/CPCDSK.hpp | 6 +- Storage/Disk/Formats/D64.cpp | 2 +- Storage/Disk/Formats/D64.hpp | 6 +- Storage/Disk/Formats/G64.cpp | 2 +- Storage/Disk/Formats/G64.hpp | 6 +- Storage/Disk/Formats/HFE.cpp | 7 +- Storage/Disk/Formats/HFE.hpp | 7 +- Storage/Disk/Formats/OricMFMDSK.cpp | 100 ++++++------ Storage/Disk/Formats/OricMFMDSK.hpp | 15 +- Storage/Disk/Formats/SSD.cpp | 51 +++--- Storage/Disk/Formats/SSD.hpp | 10 +- Storage/Disk/SingleTrackDisk.cpp | 6 +- Storage/Disk/SingleTrackDisk.hpp | 11 +- Storage/FileHolder.hpp | 2 + 24 files changed, 335 insertions(+), 271 deletions(-) delete mode 100644 Storage/Disk/Disk.cpp create mode 100644 Storage/Disk/DiskImage.cpp create mode 100644 Storage/Disk/DiskImage.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a48c72781..556a8a1bd 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -100,6 +100,7 @@ 4B8378E21F336920005CA9E4 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8378E01F336920005CA9E4 /* CharacterMapper.cpp */; }; 4B8378E51F3378C4005CA9E4 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8378E31F3378C4005CA9E4 /* CharacterMapper.cpp */; }; 4B838F1F1F35FDCD0016B5E6 /* CPCDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B838F1D1F35FDCD0016B5E6 /* CPCDSK.cpp */; }; + 4B84E24A1F7498120054AB7D /* DiskImage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B84E2481F7498120054AB7D /* DiskImage.cpp */; }; 4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; }; 4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; }; 4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; }; @@ -123,7 +124,6 @@ 4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA22B051D8817CE0008C640 /* Disk.cpp */; }; 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */; }; - 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; }; 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; }; 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */; }; 4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */; }; @@ -649,6 +649,8 @@ 4B8378E41F3378C4005CA9E4 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CharacterMapper.hpp; sourceTree = ""; }; 4B838F1D1F35FDCD0016B5E6 /* CPCDSK.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPCDSK.cpp; sourceTree = ""; }; 4B838F1E1F35FDCD0016B5E6 /* CPCDSK.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPCDSK.hpp; sourceTree = ""; }; + 4B84E2481F7498120054AB7D /* DiskImage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskImage.cpp; sourceTree = ""; }; + 4B84E2491F7498120054AB7D /* DiskImage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImage.hpp; sourceTree = ""; }; 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = ""; }; 4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = ""; }; 4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = ""; }; @@ -686,7 +688,6 @@ 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.cpp; sourceTree = ""; }; 4BA799941D8B656E0045123D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.hpp; sourceTree = ""; }; 4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = ""; }; - 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Disk.cpp; sourceTree = ""; }; 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = ""; }; 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = ""; }; 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = G64.cpp; sourceTree = ""; }; @@ -1637,8 +1638,8 @@ isa = PBXGroup; children = ( 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */, - 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */, + 4B84E2481F7498120054AB7D /* DiskImage.cpp */, 4B30512B1D989E2200B4FED8 /* Drive.cpp */, 4BBC95201F36B16C008F4C34 /* MFMDiskController.cpp */, 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */, @@ -1649,6 +1650,7 @@ 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */, + 4B84E2491F7498120054AB7D /* DiskImage.hpp */, 4B30512C1D989E2200B4FED8 /* Drive.hpp */, 4BBC95211F36B16C008F4C34 /* MFMDiskController.hpp */, 4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */, @@ -2800,7 +2802,6 @@ buildActionMask = 2147483647; files = ( 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, - 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, @@ -2820,6 +2821,7 @@ 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */, 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, + 4B84E24A1F7498120054AB7D /* DiskImage.cpp in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, 4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */, 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 339d4fa96..3793a1df2 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -77,22 +77,22 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType } if(lowercase_extension) { - Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80 - Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81 - Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 - Format("adf", result.disks, Disk::AcornADF, TargetPlatform::Acorn) // ADF - Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN - Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT - Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW - Format("d64", result.disks, Disk::D64, TargetPlatform::Commodore) // D64 - Format("dsd", result.disks, Disk::SSD, TargetPlatform::Acorn) // DSD - Format("dsk", result.disks, Disk::CPCDSK, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC) - Format("dsk", result.disks, Disk::OricMFMDSK, TargetPlatform::Oric) // DSK (Oric) - Format("g64", result.disks, Disk::G64, TargetPlatform::Commodore) // G64 - Format("hfe", result.disks, Disk::HFE, TargetPlatform::AmstradCPC) // HFE (TODO: plus other target platforms) - Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O - Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P - Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81 + Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80 + Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81 + Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 + Format("adf", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // ADF + Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN + Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT + Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW + Format("d64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // D64 + Format("dsd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // DSD + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC) + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Oric) // DSK (Oric) + Format("g64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // G64 + Format("hfe", result.disks, Disk::DiskImageHolder, TargetPlatform::AmstradCPC) // HFE (TODO: plus other target platforms) + Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O + Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P + Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81 // PRG if(!strcmp(lowercase_extension, "prg")) { @@ -106,12 +106,12 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType } } - Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM - Format("ssd", result.disks, Disk::SSD, TargetPlatform::Acorn) // SSD - Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) - Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) - Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX - Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) + Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM + Format("ssd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // SSD + Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) + Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) + Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX + Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) #undef Format #undef Insert diff --git a/Storage/Disk/Disk.cpp b/Storage/Disk/Disk.cpp deleted file mode 100644 index ffd714385..000000000 --- a/Storage/Disk/Disk.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// -// Disk.cpp -// Clock Signal -// -// Created by Thomas Harte on 10/07/2016. -// Copyright © 2016 Thomas Harte. All rights reserved. -// - -#include "Disk.hpp" - -using namespace Storage::Disk; - -int Disk::get_id_for_track_at_position(unsigned int head, unsigned int position) { - return (int)(position * get_head_count() + head); -} - -void Disk::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track) { - if(get_is_read_only()) return; - - int address = get_id_for_track_at_position(head, position); - cached_tracks_[address] = track; - - if(!update_queue_) update_queue_.reset(new Concurrency::AsyncTaskQueue); - std::shared_ptr track_copy(track->clone()); - update_queue_->enqueue([this, head, position, track_copy] { - store_updated_track_at_position(head, position, track_copy, file_access_mutex_); - }); -} - -std::shared_ptr Disk::get_track_at_position(unsigned int head, unsigned int position) { - if(head >= get_head_count()) return nullptr; - if(position >= get_head_position_count()) return nullptr; - - int address = get_id_for_track_at_position(head, position); - std::map>::iterator cached_track = cached_tracks_.find(address); - if(cached_track != cached_tracks_.end()) return cached_track->second; - - std::lock_guard lock_guard(file_access_mutex_); - std::shared_ptr track = get_uncached_track_at_position(head, position); - if(!track) return nullptr; - cached_tracks_[address] = track; - return track; -} - -void Disk::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex) {} - -void Disk::flush_updates() { - if(update_queue_) update_queue_->flush(); -} diff --git a/Storage/Disk/Disk.hpp b/Storage/Disk/Disk.hpp index c1d97d115..606322cd4 100644 --- a/Storage/Disk/Disk.hpp +++ b/Storage/Disk/Disk.hpp @@ -60,18 +60,6 @@ class Track { virtual Track *clone() = 0; }; -/*! - Models a disk as a collection of tracks, providing a range of possible track positions and allowing - a point sampling of the track beneath any of those positions (if any). - - The intention is not that tracks necessarily be evenly spaced; a head_position_count of 3 wih track - A appearing in positions 0 and 1, and track B appearing in position 2 is an appropriate use of this API - if it matches the media. - - The track returned is point sampled only; if a particular disk drive has a sufficiently large head to - pick up multiple tracks at once then the drive responsible for asking for multiple tracks and for - merging the results. -*/ class Disk { public: virtual ~Disk() {} @@ -87,60 +75,25 @@ class Disk { /*! @returns the number of heads (and, therefore, impliedly surfaces) available on this disk. */ - virtual unsigned int get_head_count() { return 1; } + virtual unsigned int get_head_count() = 0; /*! @returns the @c Track at @c position underneath @c head if there are any detectable events there; returns @c nullptr otherwise. */ - std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); + virtual std::shared_ptr get_track_at_position(unsigned int head, unsigned int position) = 0; /*! Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only. Subclasses that are not read-only should use the protected methods @c get_is_modified and, optionally, @c get_modified_track_at_position to query for changes when closing. */ - void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track); + virtual void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track) = 0; /*! @returns whether the disk image is read only. Defaults to @c true if not overridden. */ - virtual bool get_is_read_only() { return true; } - - protected: - /*! - Subclasses should implement this to return the @c Track at @c position underneath @c head. Returned tracks - are cached internally so subclasses shouldn't attempt to build their own caches or worry about preparing - for track accesses at file load time. Appropriate behaviour is to create them lazily, on demand. - */ - virtual std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position) = 0; - - /*! - Subclasses that support writing should implement @c store_updated_track_at_position to determine which bytes - have to be written from @c track, then obtain @c file_access_mutex and write new data to their file to represent - the track underneath @c head at @c position. - - The base class will ensure that calls are made to @c get_uncached_track_at_position only while it holds @c file_access_mutex; - that mutex therefore provides serialisation of file access. - - This method will be called asynchronously. Subclasses are responsible for any synchronisation other than that - provided automatically via @c file_access_mutex. - */ - virtual void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex); - - /*! - Subclasses that support writing should call @c flush_updates during their destructor if there is anything they - do in @c store_updated_track_at_position that would not be valid after their destructor has completed but prior - to Disk's constructor running. - */ - void flush_updates(); - - private: - int get_id_for_track_at_position(unsigned int head, unsigned int position); - std::map> cached_tracks_; - - std::mutex file_access_mutex_; - std::unique_ptr update_queue_; + virtual bool get_is_read_only() = 0; }; } diff --git a/Storage/Disk/DiskImage.cpp b/Storage/Disk/DiskImage.cpp new file mode 100644 index 000000000..76586c814 --- /dev/null +++ b/Storage/Disk/DiskImage.cpp @@ -0,0 +1,16 @@ +// +// DiskImage.cpp +// Clock Signal +// +// Created by Thomas Harte on 21/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "DiskImage.hpp" + +using namespace Storage::Disk; + +int DiskImageHolderBase::get_id_for_track_at_position(unsigned int head, unsigned int position) { + return (int)(position * get_head_count() + head); +} + diff --git a/Storage/Disk/DiskImage.hpp b/Storage/Disk/DiskImage.hpp new file mode 100644 index 000000000..8b4bb6ca7 --- /dev/null +++ b/Storage/Disk/DiskImage.hpp @@ -0,0 +1,147 @@ +// +// DiskImage.hpp +// Clock Signal +// +// Created by Thomas Harte on 21/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef DiskImage_hpp +#define DiskImage_hpp + +#include +#include +#include +#include "Disk.hpp" + +namespace Storage { +namespace Disk { + +/*! + Models a disk as a collection of tracks, providing a range of possible track positions and allowing + a point sampling of the track beneath any of those positions (if any). + + The intention is not that tracks necessarily be evenly spaced; a head_position_count of 3 wih track + A appearing in positions 0 and 1, and track B appearing in position 2 is an appropriate use of this API + if it matches the media. + + The track returned is point sampled only; if a particular disk drive has a sufficiently large head to + pick up multiple tracks at once then the drive responsible for asking for multiple tracks and for + merging the results. +*/ +class DiskImage { + public: + virtual ~DiskImage() {} + + /*! + @returns the number of discrete positions that this disk uses to model its complete surface area. + + This is not necessarily a track count. There is no implicit guarantee that every position will + return a distinct track, or — e.g. if the media is holeless — will return any track at all. + */ + virtual unsigned int get_head_position_count() = 0; + + /*! + @returns the number of heads (and, therefore, impliedly surfaces) available on this disk. + */ + virtual unsigned int get_head_count() { return 1; } + + /*! + @returns the @c Track at @c position underneath @c head if there are any detectable events there; + returns @c nullptr otherwise. + */ + virtual std::shared_ptr get_track_at_position(unsigned int head, unsigned int position) = 0; + + /*! + Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only. + Subclasses that are not read-only should use the protected methods @c get_is_modified and, optionally, + @c get_modified_track_at_position to query for changes when closing. + */ + virtual void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track) {} + + /*! + @returns whether the disk image is read only. Defaults to @c true if not overridden. + */ + virtual bool get_is_read_only() { return true; } +}; + +class PatchingDiskImage { + public: + struct TrackUpdate { + long file_offset; + std::vector data; + }; +}; + +class DiskImageHolderBase: public Disk { + protected: + int get_id_for_track_at_position(unsigned int head, unsigned int position); + std::map> cached_tracks_; + + std::unique_ptr update_queue_; +}; + +template class DiskImageHolder: public DiskImageHolderBase { + public: + template DiskImageHolder(Ts&&... args) : + disk_image_(args...) {} + ~DiskImageHolder(); + + unsigned int get_head_position_count(); + unsigned int get_head_count(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); + void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track); + bool get_is_read_only(); + + private: + T disk_image_; +}; + +template unsigned int DiskImageHolder::get_head_position_count() { + return disk_image_.get_head_position_count(); +} + +template unsigned int DiskImageHolder::get_head_count() { + return disk_image_.get_head_count(); +} + +template bool DiskImageHolder::get_is_read_only() { + return disk_image_.get_is_read_only(); +} + +template void DiskImageHolder::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track) { + if(disk_image_.get_is_read_only()) return; + + int address = get_id_for_track_at_position(head, position); + cached_tracks_[address] = track; + + if(!update_queue_) update_queue_.reset(new Concurrency::AsyncTaskQueue); + std::shared_ptr track_copy(track->clone()); + update_queue_->enqueue([this, head, position, track_copy] { + disk_image_.set_track_at_position(head, position, track_copy); + }); +} + +template std::shared_ptr DiskImageHolder::get_track_at_position(unsigned int head, unsigned int position) { + if(head >= get_head_count()) return nullptr; + if(position >= get_head_position_count()) return nullptr; + + int address = get_id_for_track_at_position(head, position); + std::map>::iterator cached_track = cached_tracks_.find(address); + if(cached_track != cached_tracks_.end()) return cached_track->second; + + std::shared_ptr track = disk_image_.get_track_at_position(head, position); + if(!track) return nullptr; + cached_tracks_[address] = track; + return track; +} + +template DiskImageHolder::~DiskImageHolder() { + if(update_queue_) update_queue_->flush(); +} + + +} +} + +#endif /* DiskImage_hpp */ diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index ec85a8665..cb31b297e 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -256,7 +256,7 @@ Parser::Parser(bool is_mfm, const std::shared_ptr &disk) : Parser::Parser(bool is_mfm, const std::shared_ptr &track) : Parser(is_mfm) { - drive_->set_disk(std::make_shared(track)); + drive_->set_disk(std::make_shared>(track)); } void Parser::seek_to_track(uint8_t track) { diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp index 91f4846d2..c6491536f 100644 --- a/Storage/Disk/Formats/AcornADF.cpp +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -37,10 +37,6 @@ AcornADF::AcornADF(const char *file_name) : if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; } -AcornADF::~AcornADF() { - flush_updates(); -} - unsigned int AcornADF::get_head_position_count() { return 80; } @@ -57,27 +53,31 @@ long AcornADF::get_file_offset_for_position(unsigned int head, unsigned int posi return (position * 1 + head) * bytes_per_sector * sectors_per_track; } -std::shared_ptr AcornADF::get_uncached_track_at_position(unsigned int head, unsigned int position) { +std::shared_ptr AcornADF::get_track_at_position(unsigned int head, unsigned int position) { std::shared_ptr track; if(head >= 2) return track; long file_offset = get_file_offset_for_position(head, position); - fseek(file_, file_offset, SEEK_SET); std::vector sectors; - for(unsigned int sector = 0; sector < sectors_per_track; sector++) { - Storage::Encodings::MFM::Sector new_sector; - new_sector.track = (uint8_t)position; - new_sector.side = (uint8_t)head; - new_sector.sector = (uint8_t)sector; - new_sector.size = sector_size; + { + std::lock_guard lock_guard(file_access_mutex_); + fseek(file_, file_offset, SEEK_SET); - new_sector.data.resize(bytes_per_sector); - fread(&new_sector.data[0], 1, bytes_per_sector, file_); - if(feof(file_)) - break; + for(unsigned int sector = 0; sector < sectors_per_track; sector++) { + Storage::Encodings::MFM::Sector new_sector; + new_sector.track = (uint8_t)position; + new_sector.side = (uint8_t)head; + new_sector.sector = (uint8_t)sector; + new_sector.size = sector_size; - sectors.push_back(std::move(new_sector)); + new_sector.data.resize(bytes_per_sector); + fread(&new_sector.data[0], 1, bytes_per_sector, file_); + if(feof(file_)) + break; + + sectors.push_back(std::move(new_sector)); + } } if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors); @@ -85,7 +85,7 @@ std::shared_ptr AcornADF::get_uncached_track_at_position(unsigned int hea return track; } -void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex) { +void AcornADF::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track) { std::vector parsed_track; Storage::Encodings::MFM::Parser parser(true, track); for(unsigned int c = 0; c < sectors_per_track; c++) { @@ -99,8 +99,9 @@ void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int p } } - std::lock_guard lock_guard(file_access_mutex); long file_offset = get_file_offset_for_position(head, position); + + std::lock_guard lock_guard(file_access_mutex_); ensure_file_is_at_least_length(file_offset); fseek(file_, file_offset, SEEK_SET); fwrite(parsed_track.data(), 1, parsed_track.size(), file_); diff --git a/Storage/Disk/Formats/AcornADF.hpp b/Storage/Disk/Formats/AcornADF.hpp index 0100a4d2c..ca4c831c3 100644 --- a/Storage/Disk/Formats/AcornADF.hpp +++ b/Storage/Disk/Formats/AcornADF.hpp @@ -9,7 +9,7 @@ #ifndef AcornADF_hpp #define AcornADF_hpp -#include "../Disk.hpp" +#include "../DiskImage.hpp" #include "../../FileHolder.hpp" namespace Storage { @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing an ADF disk image — a decoded sector dump of an Acorn ADFS disk. */ -class AcornADF: public Disk, public Storage::FileHolder { +class AcornADF: public DiskImage, public Storage::FileHolder { public: /*! Construct an @c AcornADF containing content from the file with name @c file_name. @@ -27,20 +27,19 @@ class AcornADF: public Disk, public Storage::FileHolder { @throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image. */ AcornADF(const char *file_name); - ~AcornADF(); enum { ErrorNotAcornADF, }; - // implemented to satisfy @c Disk unsigned int get_head_position_count(); unsigned int get_head_count(); bool get_is_read_only(); + void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: - void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex); - std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); + std::mutex file_access_mutex_; long get_file_offset_for_position(unsigned int head, unsigned int position); }; diff --git a/Storage/Disk/Formats/CPCDSK.cpp b/Storage/Disk/Formats/CPCDSK.cpp index b67d5a007..6bf9dfe30 100644 --- a/Storage/Disk/Formats/CPCDSK.cpp +++ b/Storage/Disk/Formats/CPCDSK.cpp @@ -50,7 +50,7 @@ bool CPCDSK::get_is_read_only() { return false; } -std::shared_ptr CPCDSK::get_uncached_track_at_position(unsigned int head, unsigned int position) { +std::shared_ptr CPCDSK::get_track_at_position(unsigned int head, unsigned int position) { // Given that thesea are interleaved images, determine which track, chronologically, is being requested. unsigned int chronological_track = (position * head_count_) + head; diff --git a/Storage/Disk/Formats/CPCDSK.hpp b/Storage/Disk/Formats/CPCDSK.hpp index 0867dbb84..5f96f3820 100644 --- a/Storage/Disk/Formats/CPCDSK.hpp +++ b/Storage/Disk/Formats/CPCDSK.hpp @@ -9,7 +9,7 @@ #ifndef CPCDSK_hpp #define CPCDSK_hpp -#include "../Disk.hpp" +#include "../DiskImage.hpp" #include "../../FileHolder.hpp" #include @@ -20,7 +20,7 @@ namespace Disk { /*! Provies a @c Disk containing an Amstrad CPC-stype disk image — some arrangement of sectors with status bits. */ -class CPCDSK: public Disk, public Storage::FileHolder { +class CPCDSK: public DiskImage, public Storage::FileHolder { public: /*! Construct an @c AcornADF containing content from the file with name @c file_name. @@ -38,9 +38,9 @@ class CPCDSK: public Disk, public Storage::FileHolder { unsigned int get_head_position_count(); unsigned int get_head_count(); bool get_is_read_only(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: - std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); unsigned int head_count_; unsigned int head_position_count_; diff --git a/Storage/Disk/Formats/D64.cpp b/Storage/Disk/Formats/D64.cpp index 72da167e7..9de8da842 100644 --- a/Storage/Disk/Formats/D64.cpp +++ b/Storage/Disk/Formats/D64.cpp @@ -38,7 +38,7 @@ unsigned int D64::get_head_position_count() { return number_of_tracks_*2; } -std::shared_ptr D64::get_uncached_track_at_position(unsigned int head, unsigned int position) { +std::shared_ptr D64::get_track_at_position(unsigned int head, unsigned int position) { // every other track is missing, as is any head above 0 if(position&1 || head) return std::shared_ptr(); diff --git a/Storage/Disk/Formats/D64.hpp b/Storage/Disk/Formats/D64.hpp index 6280331d9..518516c0f 100644 --- a/Storage/Disk/Formats/D64.hpp +++ b/Storage/Disk/Formats/D64.hpp @@ -9,7 +9,7 @@ #ifndef D64_hpp #define D64_hpp -#include "../Disk.hpp" +#include "../DiskImage.hpp" #include "../../FileHolder.hpp" namespace Storage { @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing a D64 disk image — a decoded sector dump of a C1540-format disk. */ -class D64: public Disk, public Storage::FileHolder { +class D64: public DiskImage, public Storage::FileHolder { public: /*! Construct a @c D64 containing content from the file with name @c file_name. @@ -34,9 +34,9 @@ class D64: public Disk, public Storage::FileHolder { // implemented to satisfy @c Disk unsigned int get_head_position_count(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: - std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); unsigned int number_of_tracks_; uint16_t disk_id_; }; diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 908644b02..661729fb0 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -34,7 +34,7 @@ unsigned int G64::get_head_position_count() { return number_of_tracks_ > 84 ? number_of_tracks_ : 84; } -std::shared_ptr G64::get_uncached_track_at_position(unsigned int head, unsigned int position) { +std::shared_ptr G64::get_track_at_position(unsigned int head, unsigned int position) { std::shared_ptr resulting_track; // if there's definitely no track here, return the empty track diff --git a/Storage/Disk/Formats/G64.hpp b/Storage/Disk/Formats/G64.hpp index 825851f19..0e6c10615 100644 --- a/Storage/Disk/Formats/G64.hpp +++ b/Storage/Disk/Formats/G64.hpp @@ -9,7 +9,7 @@ #ifndef G64_hpp #define G64_hpp -#include "../Disk.hpp" +#include "../DiskImage.hpp" #include "../../FileHolder.hpp" namespace Storage { @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing a G64 disk image — a raw but perfectly-clocked GCR stream. */ -class G64: public Disk, public Storage::FileHolder { +class G64: public DiskImage, public Storage::FileHolder { public: /*! Construct a @c G64 containing content from the file with name @c file_name. @@ -37,9 +37,9 @@ class G64: public Disk, public Storage::FileHolder { // implemented to satisfy @c Disk unsigned int get_head_position_count(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: - std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); uint8_t number_of_tracks_; uint16_t maximum_track_size_; }; diff --git a/Storage/Disk/Formats/HFE.cpp b/Storage/Disk/Formats/HFE.cpp index b850c04da..80275c83b 100644 --- a/Storage/Disk/Formats/HFE.cpp +++ b/Storage/Disk/Formats/HFE.cpp @@ -35,11 +35,7 @@ unsigned int HFE::get_head_count() { return head_count_; } -bool HFE::get_is_read_only() { - return true; -} - -std::shared_ptr HFE::get_uncached_track_at_position(unsigned int head, unsigned int position) { +std::shared_ptr HFE::get_track_at_position(unsigned int head, unsigned int position) { // Get track position and length from the lookup table; data is then always interleaved // based on an assumption of two heads. fseek(file_, track_list_offset_ + position * 4, SEEK_SET); @@ -84,3 +80,4 @@ std::shared_ptr HFE::get_uncached_track_at_position(unsigned int head, un std::shared_ptr track(new PCMTrack(segment)); return track; } + diff --git a/Storage/Disk/Formats/HFE.hpp b/Storage/Disk/Formats/HFE.hpp index 504140d9b..370f1a7e5 100644 --- a/Storage/Disk/Formats/HFE.hpp +++ b/Storage/Disk/Formats/HFE.hpp @@ -9,7 +9,7 @@ #ifndef HFE_hpp #define HFE_hpp -#include "../Disk.hpp" +#include "../DiskImage.hpp" #include "../../FileHolder.hpp" namespace Storage { @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing an HFE disk image — a bit stream representation of a floppy. */ -class HFE: public Disk, public Storage::FileHolder { +class HFE: public DiskImage, public Storage::FileHolder { public: /*! Construct an @c SSD containing content from the file with name @c file_name. @@ -36,10 +36,9 @@ class HFE: public Disk, public Storage::FileHolder { // implemented to satisfy @c Disk unsigned int get_head_position_count(); unsigned int get_head_count(); - bool get_is_read_only(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: - std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); unsigned int head_count_; unsigned int track_count_; diff --git a/Storage/Disk/Formats/OricMFMDSK.cpp b/Storage/Disk/Formats/OricMFMDSK.cpp index b3bdaf4bc..fe3f37f1a 100644 --- a/Storage/Disk/Formats/OricMFMDSK.cpp +++ b/Storage/Disk/Formats/OricMFMDSK.cpp @@ -25,10 +25,6 @@ OricMFMDSK::OricMFMDSK(const char *file_name) : throw ErrorNotOricMFMDSK; } -OricMFMDSK::~OricMFMDSK() { - flush_updates(); -} - unsigned int OricMFMDSK::get_head_position_count() { return track_count_; } @@ -54,59 +50,61 @@ long OricMFMDSK::get_file_offset_for_position(unsigned int head, unsigned int po return (seek_offset * 6400) + 256; } -std::shared_ptr OricMFMDSK::get_uncached_track_at_position(unsigned int head, unsigned int position) { - fseek(file_, get_file_offset_for_position(head, position), SEEK_SET); - +std::shared_ptr OricMFMDSK::get_track_at_position(unsigned int head, unsigned int position) { PCMSegment segment; + { + std::lock_guard lock_guard(file_access_mutex_); + fseek(file_, get_file_offset_for_position(head, position), SEEK_SET); - // The file format omits clock bits. So it's not a genuine MFM capture. - // A consumer must contextually guess when an FB, FC, etc is meant to be a control mark. - size_t track_offset = 0; - uint8_t last_header[6] = {0, 0, 0, 0, 0, 0}; - std::unique_ptr encoder = Encodings::MFM::GetMFMEncoder(segment.data); - bool did_sync = false; - while(track_offset < 6250) { - uint8_t next_byte = (uint8_t)fgetc(file_); - track_offset++; + // The file format omits clock bits. So it's not a genuine MFM capture. + // A consumer must contextually guess when an FB, FC, etc is meant to be a control mark. + size_t track_offset = 0; + uint8_t last_header[6] = {0, 0, 0, 0, 0, 0}; + std::unique_ptr encoder = Encodings::MFM::GetMFMEncoder(segment.data); + bool did_sync = false; + while(track_offset < 6250) { + uint8_t next_byte = (uint8_t)fgetc(file_); + track_offset++; - switch(next_byte) { - default: { - encoder->add_byte(next_byte); - if(did_sync) { - switch(next_byte) { - default: break; + switch(next_byte) { + default: { + encoder->add_byte(next_byte); + if(did_sync) { + switch(next_byte) { + default: break; - case 0xfe: - for(int byte = 0; byte < 6; byte++) { - last_header[byte] = (uint8_t)fgetc(file_); - encoder->add_byte(last_header[byte]); - track_offset++; - if(track_offset == 6250) break; - } - break; + case 0xfe: + for(int byte = 0; byte < 6; byte++) { + last_header[byte] = (uint8_t)fgetc(file_); + encoder->add_byte(last_header[byte]); + track_offset++; + if(track_offset == 6250) break; + } + break; - case 0xfb: - for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) { - encoder->add_byte((uint8_t)fgetc(file_)); - track_offset++; - if(track_offset == 6250) break; - } - break; + case 0xfb: + for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) { + encoder->add_byte((uint8_t)fgetc(file_)); + track_offset++; + if(track_offset == 6250) break; + } + break; + } } + + did_sync = false; } + break; - did_sync = false; + case 0xa1: // a synchronisation mark that implies a sector or header coming + encoder->output_short(Storage::Encodings::MFM::MFMSync); + did_sync = true; + break; + + case 0xc2: // an 'ordinary' synchronisation mark + encoder->output_short(Storage::Encodings::MFM::MFMIndexSync); + break; } - break; - - case 0xa1: // a synchronisation mark that implies a sector or header coming - encoder->output_short(Storage::Encodings::MFM::MFMSync); - did_sync = true; - break; - - case 0xc2: // an 'ordinary' synchronisation mark - encoder->output_short(Storage::Encodings::MFM::MFMIndexSync); - break; } } @@ -116,13 +114,13 @@ std::shared_ptr OricMFMDSK::get_uncached_track_at_position(unsigned int h return track; } -void OricMFMDSK::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex) { +void OricMFMDSK::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track) { Storage::Encodings::MFM::Parser parser(true, track); std::vector parsed_track = parser.get_track(0); long file_offset = get_file_offset_for_position(head, position); - std::lock_guard lock_guard(file_access_mutex); - fseek(file_, file_offset, SEEK_SET); + std::lock_guard lock_guard(file_access_mutex_); +fseek(file_, file_offset, SEEK_SET); size_t track_size = std::min((size_t)6400, parsed_track.size()); fwrite(parsed_track.data(), 1, track_size, file_); } diff --git a/Storage/Disk/Formats/OricMFMDSK.hpp b/Storage/Disk/Formats/OricMFMDSK.hpp index 53dac1904..da9e7cb1b 100644 --- a/Storage/Disk/Formats/OricMFMDSK.hpp +++ b/Storage/Disk/Formats/OricMFMDSK.hpp @@ -9,7 +9,7 @@ #ifndef OricMFMDSK_hpp #define OricMFMDSK_hpp -#include "../Disk.hpp" +#include "../DiskImage.hpp" #include "../../FileHolder.hpp" namespace Storage { @@ -18,16 +18,14 @@ namespace Disk { /*! Provies a @c Disk containing an Oric MFM-stype disk image — a stream of the MFM data bits with clocks omitted. */ -class OricMFMDSK: public Disk, public Storage::FileHolder { +class OricMFMDSK: public DiskImage, public Storage::FileHolder { public: /*! - Construct an @c AcornADF containing content from the file with name @c file_name. + Construct an @c OricMFMDSK containing content from the file with name @c file_name. - @throws ErrorCantOpen if this file can't be opened. - @throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image. + @throws ErrorNotOricMFMDSK if the file doesn't appear to contain an Oric MFM format image. */ OricMFMDSK(const char *file_name); - ~OricMFMDSK(); enum { ErrorNotOricMFMDSK, @@ -37,10 +35,11 @@ class OricMFMDSK: public Disk, public Storage::FileHolder { unsigned int get_head_position_count(); unsigned int get_head_count(); bool get_is_read_only(); + void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: - void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex); - std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); + std::mutex file_access_mutex_; long get_file_offset_for_position(unsigned int head, unsigned int position); uint32_t head_count_; diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index 8bea6b936..9cb8e5be9 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -28,10 +28,6 @@ SSD::SSD(const char *file_name) : else if(track_count_ < 80) track_count_ = 80; } -SSD::~SSD() { - flush_updates(); -} - unsigned int SSD::get_head_position_count() { return track_count_; } @@ -48,29 +44,33 @@ long SSD::get_file_offset_for_position(unsigned int head, unsigned int position) return (position * head_count_ + head) * 256 * 10; } -std::shared_ptr SSD::get_uncached_track_at_position(unsigned int head, unsigned int position) { +std::shared_ptr SSD::get_track_at_position(unsigned int head, unsigned int position) { std::shared_ptr track; if(head >= head_count_) return track; - fseek(file_, get_file_offset_for_position(head, position), SEEK_SET); std::vector sectors; - for(int sector = 0; sector < 10; sector++) { - Storage::Encodings::MFM::Sector new_sector; - new_sector.track = (uint8_t)position; - new_sector.side = 0; - new_sector.sector = (uint8_t)sector; - new_sector.size = 1; + { + std::lock_guard lock_guard(file_access_mutex_); + fseek(file_, get_file_offset_for_position(head, position), SEEK_SET); - new_sector.data.resize(256); - fread(new_sector.data.data(), 1, 256, file_); + for(int sector = 0; sector < 10; sector++) { + Storage::Encodings::MFM::Sector new_sector; + new_sector.track = (uint8_t)position; + new_sector.side = 0; + new_sector.sector = (uint8_t)sector; + new_sector.size = 1; - // zero out if this wasn't present in the disk image; it's still appropriate to put a sector - // on disk because one will have been placed during formatting, but there's no reason to leak - // information from outside the emulated machine's world - if(feof(file_)) memset(new_sector.data.data(), 0, 256); + new_sector.data.resize(256); + fread(new_sector.data.data(), 1, 256, file_); - sectors.push_back(std::move(new_sector)); + // zero out if this wasn't present in the disk image; it's still appropriate to put a sector + // on disk because one will have been placed during formatting, but there's no reason to leak + // information from outside the emulated machine's world + if(feof(file_)) memset(new_sector.data.data(), 0, 256); + + sectors.push_back(std::move(new_sector)); + } } if(sectors.size()) return Storage::Encodings::MFM::GetFMTrackWithSectors(sectors); @@ -78,23 +78,24 @@ std::shared_ptr SSD::get_uncached_track_at_position(unsigned int head, un return track; } -void SSD::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex) { - std::vector parsed_track; +void SSD::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track) { + std::vector data; Storage::Encodings::MFM::Parser parser(false, track); for(unsigned int c = 0; c < 10; c++) { std::shared_ptr sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c); if(sector) { - parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); + data.insert(data.end(), sector->data.begin(), sector->data.end()); } else { // TODO: what's correct here? Warn the user that whatever has been written to the disk, // it can no longer be stored as an SSD? If so, warn them by what route? - parsed_track.resize(parsed_track.size() + 256); + data.resize(data.size() + 256); } } - std::lock_guard lock_guard(file_access_mutex); long file_offset = get_file_offset_for_position(head, position); + + std::lock_guard lock_guard(file_access_mutex_); ensure_file_is_at_least_length(file_offset); fseek(file_, file_offset, SEEK_SET); - fwrite(parsed_track.data(), 1, parsed_track.size(), file_); + fwrite(data.data(), 1, data.size(), file_); } diff --git a/Storage/Disk/Formats/SSD.hpp b/Storage/Disk/Formats/SSD.hpp index a26432c10..b8d334847 100644 --- a/Storage/Disk/Formats/SSD.hpp +++ b/Storage/Disk/Formats/SSD.hpp @@ -9,7 +9,7 @@ #ifndef SSD_hpp #define SSD_hpp -#include "../Disk.hpp" +#include "../DiskImage.hpp" #include "../../FileHolder.hpp" namespace Storage { @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing a DSD or SSD disk image — a decoded sector dump of an Acorn DFS disk. */ -class SSD: public Disk, public Storage::FileHolder { +class SSD: public DiskImage, public Storage::FileHolder { public: /*! Construct an @c SSD containing content from the file with name @c file_name. @@ -27,7 +27,6 @@ class SSD: public Disk, public Storage::FileHolder { @throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image. */ SSD(const char *file_name); - ~SSD(); enum { ErrorNotSSD, @@ -37,10 +36,11 @@ class SSD: public Disk, public Storage::FileHolder { unsigned int get_head_position_count(); unsigned int get_head_count(); bool get_is_read_only(); + void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: - void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex); - std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); + std::mutex file_access_mutex_; long get_file_offset_for_position(unsigned int head, unsigned int position); unsigned int head_count_; diff --git a/Storage/Disk/SingleTrackDisk.cpp b/Storage/Disk/SingleTrackDisk.cpp index c40775c91..648a2549e 100644 --- a/Storage/Disk/SingleTrackDisk.cpp +++ b/Storage/Disk/SingleTrackDisk.cpp @@ -10,13 +10,13 @@ using namespace Storage::Disk; -SingleTrackDisk::SingleTrackDisk(const std::shared_ptr &track) : +SingleTrackDiskImage::SingleTrackDiskImage(const std::shared_ptr &track) : track_(track) {} -unsigned int SingleTrackDisk::get_head_position_count() { +unsigned int SingleTrackDiskImage::get_head_position_count() { return 1; } -std::shared_ptr SingleTrackDisk::get_uncached_track_at_position(unsigned int head, unsigned int position) { +std::shared_ptr SingleTrackDiskImage::get_track_at_position(unsigned int head, unsigned int position) { return track_; } diff --git a/Storage/Disk/SingleTrackDisk.hpp b/Storage/Disk/SingleTrackDisk.hpp index 139a8f165..31261568d 100644 --- a/Storage/Disk/SingleTrackDisk.hpp +++ b/Storage/Disk/SingleTrackDisk.hpp @@ -9,7 +9,7 @@ #ifndef SingleTrackDisk_hpp #define SingleTrackDisk_hpp -#include "Disk.hpp" +#include "DiskImage.hpp" namespace Storage { namespace Disk { @@ -17,16 +17,15 @@ namespace Disk { /*! Provides a disk that has houses a single track. */ -class SingleTrackDisk: public Disk { +class SingleTrackDiskImage: public DiskImage { public: /// Constructs a single-track disk with the track @c track. - SingleTrackDisk(const std::shared_ptr &track); + SingleTrackDiskImage(const std::shared_ptr &track); + unsigned int get_head_position_count(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: std::shared_ptr track_; - - unsigned int get_head_position_count(); - std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); }; } diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index 217e14721..d68ca2544 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Storage { @@ -124,6 +125,7 @@ class FileHolder { FILE *file_; struct stat file_stats_; bool is_read_only_; + std::mutex file_access_mutex_; const std::string name_; };