From a4e275e1fc257da6ba1ef6f83a88370202959f27 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 17:33:01 -0400 Subject: [PATCH] Provides an implementation of Drive's new interface. Mostly lifted from DiskController. `set_disk_with_track` has been withdrawn in favour of providing a suitable wrapper `Disk` subclass, as being an unnecessary complexity and intermingling of concerns. --- Storage/Disk/Drive.cpp | 178 ++++++++++++++++++++++++++++++++++------- Storage/Disk/Drive.hpp | 42 +++++----- 2 files changed, 172 insertions(+), 48 deletions(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index f920a149a..47f5d1750 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -7,7 +7,11 @@ // #include "Drive.hpp" + +#include "UnformattedTrack.hpp" + #include +#include using namespace Storage::Disk; @@ -18,16 +22,13 @@ Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute): void Drive::set_disk(const std::shared_ptr &disk) { disk_ = disk; - track_ = nullptr; has_disk_ = !!disk_; - update_sleep_observer(); -} -void Drive::set_disk_with_track(const std::shared_ptr &track) { - disk_ = nullptr; - track_ = track; - has_disk_ = !!track_; + invalidate_track(); update_sleep_observer(); + + // TODO: implement ready properly. + is_ready_ = true; } bool Drive::has_disk() { @@ -46,8 +47,15 @@ void Drive::step(int direction) { int old_head_position = head_position_; head_position_ = std::max(head_position_ + direction, 0); - // If the head moved and this drive has a real disk in it, flush the old track. - if(head_position_ != old_head_position && disk_ != nullptr) { + // If the head moved, flush the old track. + if(head_position_ != old_head_position) { + track_ = nullptr; + } +} + +void Drive::set_head(unsigned int head) { + if(head != head_) { + head_ = head; track_ = nullptr; } } @@ -55,34 +63,19 @@ void Drive::step(int direction) { Storage::Time Drive::get_time_into_track() { // `result` will initially be amount of time since the index hole was seen as a // proportion of a second; convert it into proportion of a rotation, simplify and return. - Time result(cycles_since_index_hole_, 8000000); + Time result(cycles_since_index_hole_, (int)get_input_clock_rate()); result /= rotational_multiplier_; result.simplify(); return result; } -void Drive::set_head(unsigned int head) { - head_ = head; -} - bool Drive::get_is_read_only() { if(disk_) return disk_->get_is_read_only(); return true; } bool Drive::get_is_ready() { - // TODO: a real test for this. - return disk_ != nullptr; -} - -std::shared_ptr Drive::get_track() { - if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_); - if(track_) return track_; - return nullptr; -} - -void Drive::set_track(const std::shared_ptr &track) { - if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track); + return is_ready_; } void Drive::set_motor_on(bool motor_is_on) { @@ -90,9 +83,136 @@ void Drive::set_motor_on(bool motor_is_on) { update_sleep_observer(); } -void Drive::process_next_event() { - if(event_delegate_) event_delegate_->process_event(current_event_); +void Drive::run_for(const Cycles cycles) { + Time zero(0); + + if(has_disk_ && motor_is_on_) { + // Grab a new track if not already in possession of one. + if(!track_) setup_track(); + + int number_of_cycles = cycles.as_int(); + while(number_of_cycles) { + int cycles_until_next_event = (int)get_cycles_until_next_event(); + int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles); + if(!is_reading_ && cycles_until_bits_written_ > zero) { + int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int(); + if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++; + cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target); + } + + cycles_since_index_hole_ += (unsigned int)cycles_to_run_for; + + number_of_cycles -= cycles_to_run_for; + if(is_reading_) { + if(event_delegate_) event_delegate_->advance(Cycles(cycles_to_run_for)); + } else { + if(cycles_until_bits_written_ > zero) { + Storage::Time cycles_to_run_for_time(cycles_to_run_for); + if(cycles_until_bits_written_ <= cycles_to_run_for_time) { + if(event_delegate_) event_delegate_->process_write_completed(); + if(cycles_until_bits_written_ <= cycles_to_run_for_time) + cycles_until_bits_written_.set_zero(); + else + cycles_until_bits_written_ -= cycles_to_run_for_time; + } else { + cycles_until_bits_written_ -= cycles_to_run_for_time; + } + } + } + TimedEventLoop::run_for(Cycles(cycles_to_run_for)); + } + } } -void Drive::run_for(const Cycles cycles) { +#pragma mark - Track timed event loop + +void Drive::get_next_event(const Time &duration_already_passed) { + if(track_) { + current_event_ = track_->get_next_event(); + } else { + current_event_.length.length = 1; + current_event_.length.clock_rate = 1; + current_event_.type = Track::Event::IndexHole; + } + + // divide interval, which is in terms of a single rotation of the disk, by rotation speed to + // convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_ + assert(current_event_.length <= Time(1) && current_event_.length >= Time(0)); + Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_; + set_next_event_time_interval(interval); +} + +void Drive::process_next_event() { + // TODO: ready test here. + if(event_delegate_) event_delegate_->process_event(current_event_); + get_next_event(Time(0)); +} + +#pragma mark - Track management + +std::shared_ptr Drive::get_track() { + if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_); + return nullptr; +} + +void Drive::set_track(const std::shared_ptr &track) { + if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track); +} + +void Drive::setup_track() { + track_ = get_track(); + if(!track_) { + track_.reset(new UnformattedTrack); + } + + Time offset; + Time track_time_now = get_time_into_track(); + assert(track_time_now >= Time(0) && current_event_.length <= Time(1)); + + Time time_found = track_->seek_to(track_time_now); + assert(time_found >= Time(0) && time_found <= track_time_now); + offset = track_time_now - time_found; + + get_next_event(offset); +} + +void Drive::invalidate_track() { + track_ = nullptr; +} + +#pragma mark - Writing + +void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) { + is_reading_ = false; + clamp_writing_to_index_hole_ = clamp_to_index_hole; + + write_segment_.length_of_a_bit = bit_length / rotational_multiplier_; + write_segment_.data.clear(); + write_segment_.number_of_bits = 0; + + write_start_time_ = get_time_into_track(); +} + +void Drive::write_bit(bool value) { + bool needs_new_byte = !(write_segment_.number_of_bits&7); + if(needs_new_byte) write_segment_.data.push_back(0); + if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7); + write_segment_.number_of_bits++; + + cycles_until_bits_written_ += cycles_per_bit_; +} + +void Drive::end_writing() { + is_reading_ = true; + + if(!patched_track_) { + // Avoid creating a new patched track if this one is already patched + patched_track_ = std::dynamic_pointer_cast(track_); + if(!patched_track_) { + patched_track_.reset(new PCMPatchedTrack(track_)); + } + } + patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_); + cycles_since_index_hole_ %= get_input_clock_rate(); + invalidate_track(); } diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 234aa87f6..97f12ed49 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -30,11 +30,6 @@ class Drive: public Sleeper, public TimedEventLoop { */ void set_disk(const std::shared_ptr &disk); - /*! - Replaces whatever is in the drive with a disk that contains endless copies of @c track. - */ - void set_disk_with_track(const std::shared_ptr &track); - /*! @returns @c true if a disk is currently inserted; @c false otherwise. */ @@ -61,16 +56,6 @@ class Drive: public Sleeper, public TimedEventLoop { */ bool get_is_read_only(); - /*! - @returns the track underneath the current head at the location now stepped to. - */ - std::shared_ptr get_track(); - - /*! - Attempts to set @c track as the track underneath the current head at the location now stepped to. - */ - void set_track(const std::shared_ptr &track); - /*! @returns @c true if the drive is ready; @c false otherwise. */ @@ -95,7 +80,7 @@ class Drive: public Sleeper, public TimedEventLoop { @param clamp_to_index_hole If @c true then writing will automatically be truncated by the index hole. Writing will continue over the index hole otherwise. */ - void begin_writing(bool clamp_to_index_hole); + void begin_writing(Time bit_length, bool clamp_to_index_hole); /*! Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. @@ -125,6 +110,9 @@ class Drive: public Sleeper, public TimedEventLoop { If the controller provides further bits now then there will be no gap in written data. */ virtual void process_write_completed() {} + + /// Informs the delegate of the passing of @c cycles. + virtual void advance(const Cycles cycles) {} }; /// Sets the current event delegate. @@ -134,7 +122,7 @@ class Drive: public Sleeper, public TimedEventLoop { bool is_sleeping(); private: - // Drives [usually] contain an entire disk; from that a certain track + // Drives contain an entire disk; from that a certain track // will be currently under the head. std::shared_ptr disk_; std::shared_ptr track_; @@ -158,8 +146,8 @@ class Drive: public Sleeper, public TimedEventLoop { // If the drive is not currently reading then it is writing. While writing // it can optionally be told to clamp to the index hole. - bool is_reading_; - bool clamp_writing_to_index_hole_; + bool is_reading_ = false; + bool clamp_writing_to_index_hole_ = false; // If writing is occurring then the drive will be accumulating a write segment, // for addition to a patched track. @@ -167,6 +155,9 @@ class Drive: public Sleeper, public TimedEventLoop { PCMSegment write_segment_; Time write_start_time_; + // Indicates drive ready state. + bool is_ready_ = false; + // Maintains appropriate counting to know when to indicate that writing // is complete. Time cycles_until_bits_written_; @@ -182,6 +173,19 @@ class Drive: public Sleeper, public TimedEventLoop { // The target (if any) for track events. EventDelegate *event_delegate_ = nullptr; + + /*! + @returns the track underneath the current head at the location now stepped to. + */ + std::shared_ptr get_track(); + + /*! + Attempts to set @c track as the track underneath the current head at the location now stepped to. + */ + void set_track(const std::shared_ptr &track); + + void setup_track(); + void invalidate_track(); };