From cedb809c21868db51d99bcad455287ea50a65a53 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Aug 2017 10:53:25 -0400 Subject: [PATCH 1/5] =?UTF-8?q?Sketched=20out=20a=20protocol=20designed=20?= =?UTF-8?q?to=20save=20processing=20time=20on=20anything=20that=20may=20sl?= =?UTF-8?q?eep=20=E2=80=94=20probably=20just=20disk=20controllers=20for=20?= =?UTF-8?q?now=20but=20one=20can=20easily=20imagine=20it=20being=20applica?= =?UTF-8?q?ble=20to=20printers,=20and=20possibly=20sound=20chips=20with=20?= =?UTF-8?q?suitable=20changes=20in=20guarantee=20for=20sound=20packet=20re?= =?UTF-8?q?ceivers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ClockReceiver/Sleeper.h | 46 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 2 + 2 files changed, 48 insertions(+) create mode 100644 ClockReceiver/Sleeper.h diff --git a/ClockReceiver/Sleeper.h b/ClockReceiver/Sleeper.h new file mode 100644 index 000000000..caa837cf8 --- /dev/null +++ b/ClockReceiver/Sleeper.h @@ -0,0 +1,46 @@ +// +// Sleeper.h +// Clock Signal +// +// Created by Thomas Harte on 20/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Sleeper_h +#define Sleeper_h + +/*! + A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing + any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example. + + A sleeper will signal sleeps and wakes to an observer. + + This is intended to allow for performance improvements to machines with components that can sleep. The observer + callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions + into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that + the saved ::run_fors add up to a substantial amount. + + By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint, + not a command. +*/ +class Sleeper { + public: + Sleeper() : sleep_observer_(nullptr) {} + + class SleepObserver { + public: + /// Called to inform an observer that the component @c component has either gone to sleep or become awake. + void set_component_is_sleeping(void *component, bool is_sleeping) = 0; + }; + + /// Registers @c observer as the new sleep observer; + void set_sleep_observer(SleepObserver *observer) { + sleep_observer_ = delegate; + } + + protected: + /// Provided for subclasses; send sleep announcements to the sleep_observer_. + SleepObserver *sleep_observer_; +}; + +#endif /* Sleeper_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 106529857..7ed659817 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -678,6 +678,7 @@ 4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UnformattedTrack.cpp; sourceTree = ""; }; 4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = UnformattedTrack.hpp; sourceTree = ""; }; 4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = ""; }; + 4BB146C61F49D7D700253439 /* Sleeper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Sleeper.h; sourceTree = ""; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = ""; }; 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = ""; }; 4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = ""; }; @@ -2280,6 +2281,7 @@ children = ( 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, 4BB06B211F316A3F00600C7A /* ForceInline.h */, + 4BB146C61F49D7D700253439 /* Sleeper.h */, ); name = ClockReceiver; path = ../../ClockReceiver; From 49285e9caaba558363a52a76e878681f9f63fdf9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Aug 2017 11:54:54 -0400 Subject: [PATCH 2/5] Attempted to implement Sleeper in Drive and therefore in DiskController. Also corrected a couple of nonconformant file names. --- .../{ForceInline.h => ForceInline.hpp} | 4 ++-- ClockReceiver/{Sleeper.h => Sleeper.hpp} | 22 +++++++++++++++---- .../Clock Signal.xcodeproj/project.pbxproj | 8 +++---- Storage/Disk/DiskController.cpp | 11 ++++++++++ Storage/Disk/DiskController.hpp | 8 ++++++- Storage/Disk/Drive.cpp | 6 +++++ Storage/Disk/Drive.hpp | 7 +++++- 7 files changed, 54 insertions(+), 12 deletions(-) rename ClockReceiver/{ForceInline.h => ForceInline.hpp} (86%) rename ClockReceiver/{Sleeper.h => Sleeper.hpp} (69%) diff --git a/ClockReceiver/ForceInline.h b/ClockReceiver/ForceInline.hpp similarity index 86% rename from ClockReceiver/ForceInline.h rename to ClockReceiver/ForceInline.hpp index 786e41c2d..2b6cf5589 100644 --- a/ClockReceiver/ForceInline.h +++ b/ClockReceiver/ForceInline.hpp @@ -6,8 +6,8 @@ // Copyright © 2017 Thomas Harte. All rights reserved. // -#ifndef ForceInline_h -#define ForceInline_h +#ifndef ForceInline_hpp +#define ForceInline_hpp #ifdef __GNUC__ #define forceinline __attribute__((always_inline)) inline diff --git a/ClockReceiver/Sleeper.h b/ClockReceiver/Sleeper.hpp similarity index 69% rename from ClockReceiver/Sleeper.h rename to ClockReceiver/Sleeper.hpp index caa837cf8..2a6e00e08 100644 --- a/ClockReceiver/Sleeper.h +++ b/ClockReceiver/Sleeper.hpp @@ -6,8 +6,8 @@ // Copyright © 2017 Thomas Harte. All rights reserved. // -#ifndef Sleeper_h -#define Sleeper_h +#ifndef Sleeper_hpp +#define Sleeper_hpp /*! A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing @@ -30,17 +30,31 @@ class Sleeper { class SleepObserver { public: /// Called to inform an observer that the component @c component has either gone to sleep or become awake. - void set_component_is_sleeping(void *component, bool is_sleeping) = 0; + virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0; }; /// Registers @c observer as the new sleep observer; void set_sleep_observer(SleepObserver *observer) { - sleep_observer_ = delegate; + sleep_observer_ = observer; } + /// @returns @c true if the component is currently sleeping; @c false otherwise. + virtual bool is_sleeping() = 0; + protected: /// Provided for subclasses; send sleep announcements to the sleep_observer_. SleepObserver *sleep_observer_; + + /*! + Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified, + if one exists. + + @c is_sleeping will be called only if there is an observer. + */ + void update_sleep_observer() { + if(!sleep_observer_) return; + sleep_observer_->set_component_is_sleeping(this, is_sleeping()); + } }; #endif /* Sleeper_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 7ed659817..e77b72e90 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -677,8 +677,8 @@ 4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = AmstradCPC/CharacterMapper.hpp; sourceTree = ""; }; 4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UnformattedTrack.cpp; sourceTree = ""; }; 4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = UnformattedTrack.hpp; sourceTree = ""; }; - 4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = ""; }; - 4BB146C61F49D7D700253439 /* Sleeper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Sleeper.h; sourceTree = ""; }; + 4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = ""; }; + 4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = ""; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = ""; }; 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = ""; }; 4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = ""; }; @@ -2280,8 +2280,8 @@ isa = PBXGroup; children = ( 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, - 4BB06B211F316A3F00600C7A /* ForceInline.h */, - 4BB146C61F49D7D700253439 /* Sleeper.h */, + 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, + 4BB146C61F49D7D700253439 /* Sleeper.hpp */, ); name = ClockReceiver; path = ../../ClockReceiver; diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 6a7ae1f85..772750e88 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -46,6 +46,14 @@ void Controller::setup_track() { get_next_event(offset); } +void Controller::set_component_is_sleeping(void *component, bool is_sleeping) { + update_sleep_observer(); +} + +bool Controller::is_sleeping() { + return !(drive_ && drive_->has_disk() && motor_is_on_); +} + void Controller::run_for(const Cycles cycles) { Time zero(0); @@ -208,6 +216,7 @@ void Controller::step(int direction) { void Controller::set_motor_on(bool motor_on) { motor_is_on_ = motor_on; + update_sleep_observer(); } bool Controller::get_motor_on() { @@ -218,6 +227,8 @@ void Controller::set_drive(std::shared_ptr drive) { if(drive_ != drive) { invalidate_track(); drive_ = drive; + drive->set_sleep_observer(this); + update_sleep_observer(); } } diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index 399a92d84..4ab4e009c 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -14,7 +14,9 @@ #include "PCMSegment.hpp" #include "PCMPatchedTrack.hpp" #include "../TimedEventLoop.hpp" + #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../ClockReceiver/Sleeper.hpp" namespace Storage { namespace Disk { @@ -28,7 +30,7 @@ namespace Disk { TODO: communication of head size and permissible stepping extents, appropriate simulation of gain. */ -class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { +class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop, public Sleeper, public Sleeper::SleepObserver { protected: /*! Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier, @@ -116,6 +118,8 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop virtual bool get_drive_is_ready(); bool get_drive_is_read_only(); + bool is_sleeping(); + private: Time bit_length_; int clock_rate_; @@ -142,6 +146,8 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop void setup_track(); Time get_time_into_track(); + + void set_component_is_sleeping(void *component, bool is_sleeping); }; } diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 99e8e6d11..817bdd994 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -18,18 +18,24 @@ 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_; + update_sleep_observer(); } bool Drive::has_disk() { return has_disk_; } +bool Drive::is_sleeping() { + return !has_disk_; +} + bool Drive::get_is_track_zero() { return head_position_ == 0; } diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 009b15dc9..2ed27ea71 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -10,12 +10,14 @@ #define Drive_hpp #include + #include "Disk.hpp" +#include "../../ClockReceiver/Sleeper.hpp" namespace Storage { namespace Disk { -class Drive { +class Drive: public Sleeper { public: Drive(); @@ -70,6 +72,9 @@ class Drive { */ bool get_is_ready(); + // As per Sleeper. + bool is_sleeping(); + private: std::shared_ptr track_; std::shared_ptr disk_; From e88a51e75e4fe5ecaa0b71c6a5f039c6008da201 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Aug 2017 12:05:00 -0400 Subject: [PATCH 3/5] Worked logic all the way down to the CPC. If the 8272 announces that it is asleep, it is now no longer clocked. Also very slightly cut down on IRQ line chatter to the Z80. --- Components/8272/i8272.cpp | 9 ++++++++- Components/8272/i8272.hpp | 2 ++ Machines/AmstradCPC/AmstradCPC.cpp | 24 ++++++++++++++++++++---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 926879f1d..664bf54b8 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -88,6 +88,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multipli posit_event((int)Event8272::CommandByte); } +bool i8272::is_sleeping() { + return is_sleeping_ && Storage::Disk::MFMController::is_sleeping(); +} + void i8272::run_for(Cycles cycles) { Storage::Disk::MFMController::run_for(cycles); @@ -154,6 +158,7 @@ void i8272::run_for(Cycles cycles) { } is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; + if(is_sleeping_) update_sleep_observer(); } void i8272::set_register(int address, uint8_t value) { @@ -198,7 +203,7 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { #define MS_TO_CYCLES(x) x * 8000 #define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__: -#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; case __LINE__: if(delay_time_) return; +#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return; #define PASTE(x, y) x##y #define CONCAT(x, y) PASTE(x, y) @@ -257,6 +262,7 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \ head_timers_running_++; \ is_sleeping_ = false; \ + update_sleep_observer(); \ } \ drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ } @@ -711,6 +717,7 @@ void i8272::posit_event(int event_type) { if(drives_[drive].phase != Drive::Seeking) { drives_seeking_++; is_sleeping_ = false; + update_sleep_observer(); } // Set currently seeking, with a step to occur right now (yes, it sounds like jamming these diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 9c216aa7c..6c31a9517 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -41,6 +41,8 @@ class i8272: public Storage::Disk::MFMController { void set_disk(std::shared_ptr disk, int drive); + bool is_sleeping(); + private: // The bus handler, for interrupt and DMA-driven usage. BusHandler &bus_handler_; diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 3fdded550..593393946 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -76,7 +76,12 @@ class InterruptTimer { /// @returns @c true if an interrupt is currently requested; @c false otherwise. inline bool get_request() { - return interrupt_request_; + return last_interrupt_request_ = interrupt_request_; + } + + /// Asks whether the interrupt status has changed. + inline bool request_has_changed() { + return last_interrupt_request_ != interrupt_request_; } /// Resets the timer. @@ -88,6 +93,7 @@ class InterruptTimer { private: int reset_counter_; bool interrupt_request_; + bool last_interrupt_request_; int timer_; }; @@ -659,6 +665,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { class ConcreteMachine: public Utility::TypeRecipient, public CPU::Z80::BusHandler, + public Sleeper::SleepObserver, public Machine { public: ConcreteMachine() : @@ -674,6 +681,10 @@ class ConcreteMachine: // ensure memory starts in a random state Memory::Fuzz(ram_, sizeof(ram_)); + + // register this class as the sleep observer for the FDC + fdc_.set_sleep_observer(this); + fdc_is_sleeping_ = fdc_.is_sleeping(); } /// The entry point for performing a partial Z80 machine cycle. @@ -689,7 +700,7 @@ class ConcreteMachine: crtc_counter_ += cycle.length; Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4)); if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles); - z80_.set_interrupt_line(interrupt_timer_.get_request()); + if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request()); // TODO (in the player, not here): adapt it to accept an input clock rate and // run_for as HalfCycles @@ -699,7 +710,7 @@ class ConcreteMachine: ay_.run_for(cycle.length); // Clock the FDC, if connected, using a lazy scale by two - if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int())); + if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int())); // Update typing activity if(typer_) typer_->run_for(cycle.length); @@ -902,6 +913,11 @@ class ConcreteMachine: roms_[(int)type] = data; } + void set_component_is_sleeping(void *component, bool is_sleeping) { + // The FDC is the only thing this registers with as a sleep observer. + fdc_is_sleeping_ = is_sleeping; + } + #pragma mark - Keyboard void set_typer_for_string(const char *string) { @@ -995,7 +1011,7 @@ class ConcreteMachine: std::vector roms_[7]; int rom_model_; - bool has_fdc_; + bool has_fdc_, fdc_is_sleeping_; bool has_128k_; bool upper_rom_is_paged_; int upper_rom_; From 8fdc5012e4bb47dd7e8bb893db31948ce61f97d2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Aug 2017 12:18:36 -0400 Subject: [PATCH 4/5] Updated `TapePlayer` and `BinaryTapePlayer` to be sleepers. --- Storage/Tape/Tape.cpp | 14 +++++++++++++- Storage/Tape/Tape.hpp | 8 +++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index 280b02100..c71f43779 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -65,10 +65,15 @@ void Tape::set_offset(uint64_t offset) { #pragma mark - Player +bool TapePlayer::is_sleeping() { + return !tape_ || tape_->is_at_end(); +} + void TapePlayer::set_tape(std::shared_ptr tape) { tape_ = tape; reset_timer(); get_next_pulse(); + update_sleep_observer(); } std::shared_ptr TapePlayer::get_tape() { @@ -81,8 +86,10 @@ bool TapePlayer::has_tape() { void TapePlayer::get_next_pulse() { // get the new pulse - if(tape_) + if(tape_) { current_pulse_ = tape_->get_next_pulse(); + if(tape_->is_at_end()) update_sleep_observer(); + } else { current_pulse_.length.length = 1; current_pulse_.length.clock_rate = 1; @@ -113,8 +120,13 @@ BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) : TapePlayer(input_clock_rate), motor_is_running_(false), input_level_(false), delegate_(nullptr) {} +bool BinaryTapePlayer::is_sleeping() { + return !motor_is_running_ || TapePlayer::is_sleeping(); +} + void BinaryTapePlayer::set_motor_control(bool enabled) { motor_is_running_ = enabled; + update_sleep_observer(); } void BinaryTapePlayer::set_tape_output(bool set) { diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index e76f6470e..25e53ed81 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -12,6 +12,8 @@ #include #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../ClockReceiver/Sleeper.hpp" + #include "../TimedEventLoop.hpp" namespace Storage { @@ -93,7 +95,7 @@ class Tape { Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type. */ -class TapePlayer: public TimedEventLoop { +class TapePlayer: public TimedEventLoop, public Sleeper { public: TapePlayer(unsigned int input_clock_rate); @@ -105,6 +107,8 @@ class TapePlayer: public TimedEventLoop { void run_for_input_pulse(); + bool is_sleeping(); + protected: virtual void process_next_event(); virtual void process_input_pulse(const Tape::Pulse &pulse) = 0; @@ -139,6 +143,8 @@ class BinaryTapePlayer: public TapePlayer { }; void set_delegate(Delegate *delegate); + bool is_sleeping(); + protected: Delegate *delegate_; virtual void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse); From 8f5ae4a326627227eaf5a743f3984cc44b576d46 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Aug 2017 12:21:02 -0400 Subject: [PATCH 5/5] The CPC now responds to tape-originating sleeper observations. --- Machines/AmstradCPC/AmstradCPC.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 593393946..b23c67014 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -682,9 +682,12 @@ class ConcreteMachine: // ensure memory starts in a random state Memory::Fuzz(ram_, sizeof(ram_)); - // register this class as the sleep observer for the FDC + // register this class as the sleep observer for the FDC and tape fdc_.set_sleep_observer(this); fdc_is_sleeping_ = fdc_.is_sleeping(); + + tape_player_.set_sleep_observer(this); + tape_player_is_sleeping_ = tape_player_.is_sleeping(); } /// The entry point for performing a partial Z80 machine cycle. @@ -704,7 +707,7 @@ class ConcreteMachine: // TODO (in the player, not here): adapt it to accept an input clock rate and // run_for as HalfCycles - tape_player_.run_for(cycle.length.as_int()); + if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int()); // Pump the AY ay_.run_for(cycle.length); @@ -914,8 +917,8 @@ class ConcreteMachine: } void set_component_is_sleeping(void *component, bool is_sleeping) { - // The FDC is the only thing this registers with as a sleep observer. - fdc_is_sleeping_ = is_sleeping; + if(component == &fdc_) fdc_is_sleeping_ = is_sleeping; + else tape_player_is_sleeping_ = is_sleeping; } #pragma mark - Keyboard @@ -1012,6 +1015,7 @@ class ConcreteMachine: std::vector roms_[7]; int rom_model_; bool has_fdc_, fdc_is_sleeping_; + bool tape_player_is_sleeping_; bool has_128k_; bool upper_rom_is_paged_; int upper_rom_;