1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 01:31:42 +00:00

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.
This commit is contained in:
Thomas Harte 2017-09-10 17:33:01 -04:00
parent 6075064400
commit a4e275e1fc
2 changed files with 172 additions and 48 deletions

View File

@ -7,7 +7,11 @@
// //
#include "Drive.hpp" #include "Drive.hpp"
#include "UnformattedTrack.hpp"
#include <algorithm> #include <algorithm>
#include <cassert>
using namespace Storage::Disk; 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) { void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
disk_ = disk; disk_ = disk;
track_ = nullptr;
has_disk_ = !!disk_; has_disk_ = !!disk_;
update_sleep_observer();
}
void Drive::set_disk_with_track(const std::shared_ptr<Track> &track) { invalidate_track();
disk_ = nullptr;
track_ = track;
has_disk_ = !!track_;
update_sleep_observer(); update_sleep_observer();
// TODO: implement ready properly.
is_ready_ = true;
} }
bool Drive::has_disk() { bool Drive::has_disk() {
@ -46,8 +47,15 @@ void Drive::step(int direction) {
int old_head_position = head_position_; int old_head_position = head_position_;
head_position_ = std::max(head_position_ + direction, 0); 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 the head moved, flush the old track.
if(head_position_ != old_head_position && disk_ != nullptr) { if(head_position_ != old_head_position) {
track_ = nullptr;
}
}
void Drive::set_head(unsigned int head) {
if(head != head_) {
head_ = head;
track_ = nullptr; track_ = nullptr;
} }
} }
@ -55,34 +63,19 @@ void Drive::step(int direction) {
Storage::Time Drive::get_time_into_track() { Storage::Time Drive::get_time_into_track() {
// `result` will initially be amount of time since the index hole was seen as a // `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. // 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 /= rotational_multiplier_;
result.simplify(); result.simplify();
return result; return result;
} }
void Drive::set_head(unsigned int head) {
head_ = head;
}
bool Drive::get_is_read_only() { bool Drive::get_is_read_only() {
if(disk_) return disk_->get_is_read_only(); if(disk_) return disk_->get_is_read_only();
return true; return true;
} }
bool Drive::get_is_ready() { bool Drive::get_is_ready() {
// TODO: a real test for this. return is_ready_;
return disk_ != nullptr;
}
std::shared_ptr<Track> 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> &track) {
if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track);
} }
void Drive::set_motor_on(bool motor_is_on) { 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(); update_sleep_observer();
} }
void Drive::process_next_event() { void Drive::run_for(const Cycles cycles) {
if(event_delegate_) event_delegate_->process_event(current_event_); 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);
} }
void Drive::run_for(const Cycles cycles) { 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));
}
}
}
#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<Track> 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> &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<PCMPatchedTrack>(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();
} }

View File

@ -30,11 +30,6 @@ class Drive: public Sleeper, public TimedEventLoop {
*/ */
void set_disk(const std::shared_ptr<Disk> &disk); void set_disk(const std::shared_ptr<Disk> &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> &track);
/*! /*!
@returns @c true if a disk is currently inserted; @c false otherwise. @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(); bool get_is_read_only();
/*!
@returns the track underneath the current head at the location now stepped to.
*/
std::shared_ptr<Track> 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> &track);
/*! /*!
@returns @c true if the drive is ready; @c false otherwise. @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 @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. 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. 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. If the controller provides further bits now then there will be no gap in written data.
*/ */
virtual void process_write_completed() {} 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. /// Sets the current event delegate.
@ -134,7 +122,7 @@ class Drive: public Sleeper, public TimedEventLoop {
bool is_sleeping(); bool is_sleeping();
private: 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. // will be currently under the head.
std::shared_ptr<Disk> disk_; std::shared_ptr<Disk> disk_;
std::shared_ptr<Track> track_; std::shared_ptr<Track> track_;
@ -158,8 +146,8 @@ class Drive: public Sleeper, public TimedEventLoop {
// If the drive is not currently reading then it is writing. While writing // If the drive is not currently reading then it is writing. While writing
// it can optionally be told to clamp to the index hole. // it can optionally be told to clamp to the index hole.
bool is_reading_; bool is_reading_ = false;
bool clamp_writing_to_index_hole_; bool clamp_writing_to_index_hole_ = false;
// If writing is occurring then the drive will be accumulating a write segment, // If writing is occurring then the drive will be accumulating a write segment,
// for addition to a patched track. // for addition to a patched track.
@ -167,6 +155,9 @@ class Drive: public Sleeper, public TimedEventLoop {
PCMSegment write_segment_; PCMSegment write_segment_;
Time write_start_time_; Time write_start_time_;
// Indicates drive ready state.
bool is_ready_ = false;
// Maintains appropriate counting to know when to indicate that writing // Maintains appropriate counting to know when to indicate that writing
// is complete. // is complete.
Time cycles_until_bits_written_; Time cycles_until_bits_written_;
@ -182,6 +173,19 @@ class Drive: public Sleeper, public TimedEventLoop {
// The target (if any) for track events. // The target (if any) for track events.
EventDelegate *event_delegate_ = nullptr; EventDelegate *event_delegate_ = nullptr;
/*!
@returns the track underneath the current head at the location now stepped to.
*/
std::shared_ptr<Track> 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> &track);
void setup_track();
void invalidate_track();
}; };