diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp new file mode 100644 index 000000000..8f650a973 --- /dev/null +++ b/ClockReceiver/ClockReceiver.hpp @@ -0,0 +1,171 @@ +// +// ClockReceiver.hpp +// Clock Signal +// +// Created by Thomas Harte on 22/07/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef ClockReceiver_hpp +#define ClockReceiver_hpp + +/*! + Provides a class that wraps a plain int, providing most of the basic arithmetic and + Boolean operators, but forcing callers and receivers to be explicit as to usage. +*/ +template class WrappedInt { + public: + inline WrappedInt(int l) : length_(l) {} + inline WrappedInt() : length_(0) {} + + inline T &operator =(const T &rhs) { + length_ = rhs.length_; + return *this; + } + + inline T &operator +=(const T &rhs) { + length_ += rhs.length_; + return *static_cast(this); + } + + inline T &operator -=(const T &rhs) { + length_ -= rhs.length_; + return *static_cast(this); + } + + inline T &operator ++() { + ++ length_; + return *static_cast(this); + } + + inline T &operator ++(int) { + length_ ++; + return *static_cast(this); + } + + inline T &operator --() { + -- length_; + return *static_cast(this); + } + + inline T &operator --(int) { + length_ --; + return *static_cast(this); + } + + inline T &operator %=(const T &rhs) { + length_ %= rhs.length_; + return *static_cast(this); + } + + inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); } + inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); } + + inline bool operator <(const T &rhs) const { return length_ < rhs.length_; } + inline bool operator >(const T &rhs) const { return length_ > rhs.length_; } + inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; } + inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; } + inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; } + inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; } + + inline bool operator !() const { return !length_; } + inline operator bool() const { return !!length_; } + + inline int as_int() const { return length_; } + + /*! + Severs from @c this the effect of dividing by @c divisor — @c this will end up with + the value of @c this modulo @c divisor and @c divided by @c divisor is returned. + */ + inline T divide(const T &divisor) { + T result(length_ / divisor.length_); + length_ %= divisor.length_; + return result; + } + + /*! + Flushes the value in @c this. The current value is returned, and the internal value + is reset to zero. + */ + inline T flush() { + T result(length_); + length_ = 0; + return result; + } + + // operator int() is deliberately not provided, to avoid accidental subtitution of + // classes that use this template. + + protected: + int length_; +}; + +/// Describes an integer number of whole cycles — pairs of clock signal transitions. +class Cycles: public WrappedInt { + public: + inline Cycles(int l) : WrappedInt(l) {} + inline Cycles() : WrappedInt() {} + inline Cycles(const Cycles &cycles) : WrappedInt(cycles.length_) {} +}; + +/// Describes an integer number of half cycles — single clock signal transitions. +class HalfCycles: public WrappedInt { + public: + inline HalfCycles(int l) : WrappedInt(l) {} + inline HalfCycles() : WrappedInt() {} + + inline HalfCycles(const Cycles &cycles) : WrappedInt(cycles.as_int() << 1) {} + inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt(half_cycles.length_) {} +}; + +/*! + ClockReceiver is a template for components that receove a clock, measured either + in cycles or in half cycles. They are expected to implement either of the run_for + methods and to declare that they are `using` the other; buying into the template + means that the other run_for will automatically map appropriately to the implemented + one, so callers may use either. + + Alignment rule: + + run_for(Cycles) may be called only at the start of a cycle. E.g. the following + sequence will have undefined results: + + run_for(HalfCycles(1)) + run_for(Cycles(1)) + + An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and + run_for(HalfCycles) to use. + + Reasoning: + + Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles) + where there is a need to implement at half-cycle precision but a faster execution + path can be offered for full-cycle precision. Those users are permitted to assume + phase in run_for(Cycles) and should do so to be compatible with callers that use + only run_for(Cycles). + + Corollary: + + Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half + of a full cycle. The second will do the second half. Etc. + +*/ +template class ClockReceiver { + public: + ClockReceiver() : half_cycle_carry_(0) {} + + inline void run_for(const Cycles &cycles) { + static_cast(this)->run_for(HalfCycles(cycles)); + } + + inline void run_for(const HalfCycles &half_cycles) { + int cycles = half_cycles.as_int() + half_cycle_carry_; + half_cycle_carry_ = cycles & 1; + static_cast(this)->run_for(Cycles(cycles >> 1)); + } + + private: + int half_cycle_carry_; +}; + +#endif /* ClockReceiver_hpp */ diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 6e2011f45..168dfb817 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -124,10 +124,11 @@ uint8_t WD1770::get_register(int address) { } } -void WD1770::run_for_cycles(unsigned int number_of_cycles) { - Storage::Disk::Controller::run_for_cycles((int)number_of_cycles); +void WD1770::run_for(const Cycles &cycles) { + Storage::Disk::Controller::run_for(cycles); if(delay_time_) { + unsigned int number_of_cycles = (unsigned int)cycles.as_int(); if(delay_time_ <= number_of_cycles) { delay_time_ = 0; posit_event(Event::Timer); diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 285552be7..e5d59dc8d 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -14,21 +14,37 @@ namespace WD { +/*! + Provides an emulation of various Western Digital drive controllers, including the + WD1770, WD1772, FDC1773 and FDC1793. +*/ class WD1770: public Storage::Disk::Controller { public: enum Personality { - P1770, // implies automatic motor-on management with Type 2 commands offering a spin-up disable + P1770, // implies automatic motor-on management, with Type 2 commands offering a spin-up disable P1772, // as per the 1770, with different stepping rates P1773, // implements the side number-testing logic of the 1793; omits spin-up/loading logic P1793 // implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT }; + + /*! + Constructs an instance of the drive controller that behaves according to personality @c p. + @param p The type of controller to emulate. + */ WD1770(Personality p); + /// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data. void set_is_double_density(bool is_double_density); + + /// Writes @c value to the register at @c address. Only the low two bits of the address are decoded. void set_register(int address, uint8_t value); + + /// Fetches the value of the register @c address. Only the low two bits of the address are decoded. uint8_t get_register(int address); - void run_for_cycles(unsigned int number_of_cycles); + /// Runs the controller for @c number_of_cycles cycles. + void run_for(const Cycles &cycles); + using Storage::Disk::Controller::run_for; enum Flag: uint8_t { NotReady = 0x80, @@ -47,8 +63,12 @@ class WD1770: public Storage::Disk::Controller { Busy = 0x01 }; + /// @returns The current value of the IRQ line output. inline bool get_interrupt_request_line() { return status_.interrupt_request; } + + /// @returns The current value of the DRQ line output. inline bool get_data_request_line() { return status_.data_request; } + class Delegate { public: virtual void wd1770_did_change_output(WD1770 *wd1770) = 0; diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index 472a6ac78..7eee8df14 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -13,6 +13,8 @@ #include #include +#include "../../ClockReceiver/ClockReceiver.hpp" + namespace MOS { /*! @@ -26,7 +28,7 @@ namespace MOS { Consumers should derive their own curiously-recurring-template-pattern subclass, implementing bus communications as required. */ -template class MOS6522 { +template class MOS6522: public ClockReceiver> { private: enum InterruptFlag: uint8_t { CA2ActiveEdge = 1 << 0, @@ -250,32 +252,22 @@ template class MOS6522 { timer_is_running_[0] = false;\ } - /*! - Runs for a specified number of half cycles. + /*! Runs for a specified number of half cycles. */ + inline void run_for(const HalfCycles &half_cycles) { + int number_of_half_cycles = half_cycles.as_int(); - Although the original chip accepts only a phase-2 input, timer reloads are specified as occuring - 1.5 cycles after the timer hits zero. It therefore may be necessary to emulate at half-cycle precision. - - The first emulated half-cycle will be the period between the trailing edge of a phase-2 input and the - next rising edge. So it should align with a full system's phase-1. The next emulated half-cycle will be - that which occurs during phase-2. - - Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not - intermingle usage. - */ - inline void run_for_half_cycles(unsigned int number_of_cycles) { if(is_phase2_) { phase2(); - number_of_cycles--; + number_of_half_cycles--; } - while(number_of_cycles >= 2) { + while(number_of_half_cycles >= 2) { phase1(); phase2(); - number_of_cycles -= 2; + number_of_half_cycles -= 2; } - if(number_of_cycles) { + if(number_of_half_cycles) { phase1(); is_phase2_ = true; } else { @@ -283,13 +275,9 @@ template class MOS6522 { } } - /*! - Runs for a specified number of cycles. - - Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not - intermingle usage. - */ - inline void run_for_cycles(unsigned int number_of_cycles) { + /*! Runs for a specified number of cycles. */ + inline void run_for(const Cycles &cycles) { + int number_of_cycles = cycles.as_int(); while(number_of_cycles--) { phase1(); phase2(); diff --git a/Components/6532/6532.hpp b/Components/6532/6532.hpp index 69780a2cd..42ff5dc6f 100644 --- a/Components/6532/6532.hpp +++ b/Components/6532/6532.hpp @@ -12,6 +12,8 @@ #include #include +#include "../../ClockReceiver/ClockReceiver.hpp" + namespace MOS { /*! @@ -25,7 +27,7 @@ namespace MOS { Consumers should derive their own curiously-recurring-template-pattern subclass, implementing bus communications as required. */ -template class MOS6532 { +template class MOS6532: public ClockReceiver> { public: inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; } inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; } @@ -104,7 +106,10 @@ template class MOS6532 { return 0xff; } - inline void run_for_cycles(unsigned int number_of_cycles) { + using ClockReceiver>::run_for; + inline void run_for(const Cycles &cycles) { + unsigned int number_of_cycles = (unsigned int)cycles.as_int(); + // permit counting _to_ zero; counting _through_ zero initiates the other behaviour if(timer_.value >= number_of_cycles) { timer_.value -= number_of_cycles; diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 829df3e7a..44c5c943a 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -11,6 +11,7 @@ #include "../../Outputs/CRT/CRT.hpp" #include "../../Outputs/Speaker.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" namespace MOS { @@ -40,7 +41,7 @@ class Speaker: public ::Outputs::Filter { @c set_register and @c get_register provide register access. */ -template class MOS6560 { +template class MOS6560: public ClockReceiver> { public: MOS6560() : crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)), @@ -146,13 +147,15 @@ template class MOS6560 { } } + using ClockReceiver>::run_for; /*! Runs for cycles. Derr. */ - inline void run_for_cycles(unsigned int number_of_cycles) { + inline void run_for(const Cycles &cycles) { // keep track of the amount of time since the speaker was updated; lazy updates are applied - cycles_since_speaker_update_ += number_of_cycles; + cycles_since_speaker_update_ += cycles; + int number_of_cycles = cycles.as_int(); while(number_of_cycles--) { // keep an old copy of the vertical count because that test is a cycle later than the actual changes int previous_vertical_counter = vertical_counter_; @@ -406,10 +409,9 @@ template class MOS6560 { std::shared_ptr crt_; std::shared_ptr speaker_; - unsigned int cycles_since_speaker_update_; + Cycles cycles_since_speaker_update_; void update_audio() { - speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2); - cycles_since_speaker_update_ &= 3; + speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4)))); } // register state diff --git a/Concurrency/AsyncTaskQueue.hpp b/Concurrency/AsyncTaskQueue.hpp index f695cd2a1..34c54bb61 100644 --- a/Concurrency/AsyncTaskQueue.hpp +++ b/Concurrency/AsyncTaskQueue.hpp @@ -26,7 +26,6 @@ namespace Concurrency { causing it to block until all previously-enqueued functions are complete. */ class AsyncTaskQueue { - public: AsyncTaskQueue(); ~AsyncTaskQueue(); diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index a682001bb..3ba31e972 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -44,7 +44,7 @@ class Machine: virtual void close_output(); virtual std::shared_ptr get_crt() { return bus_->tia_->get_crt(); } virtual std::shared_ptr get_speaker() { return bus_->speaker_; } - virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); } + virtual void run_for(const Cycles &cycles) { bus_->run_for(cycles); } // to satisfy Outputs::CRT::Delegate virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs); diff --git a/Machines/Atari2600/Bus.hpp b/Machines/Atari2600/Bus.hpp index 8d5d731a9..e881e8bc7 100644 --- a/Machines/Atari2600/Bus.hpp +++ b/Machines/Atari2600/Bus.hpp @@ -14,17 +14,17 @@ #include "Speaker.hpp" #include "TIA.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" + namespace Atari2600 { class Bus { public: Bus() : tia_input_value_{0xff, 0xff}, - cycles_since_speaker_update_(0), - cycles_since_video_update_(0), - cycles_since_6532_update_(0) {} + cycles_since_speaker_update_(0) {} - virtual void run_for_cycles(int number_of_cycles) = 0; + virtual void run_for(const Cycles &cycles) = 0; virtual void set_reset_line(bool state) = 0; // the RIOT, TIA and speaker @@ -37,25 +37,21 @@ class Bus { protected: // speaker backlog accumlation counter - unsigned int cycles_since_speaker_update_; + Cycles cycles_since_speaker_update_; inline void update_audio() { - unsigned int audio_cycles = cycles_since_speaker_update_ / (CPUTicksPerAudioTick * 3); - cycles_since_speaker_update_ %= (CPUTicksPerAudioTick * 3); - speaker_->run_for_cycles(audio_cycles); + speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3))); } // video backlog accumulation counter - unsigned int cycles_since_video_update_; + Cycles cycles_since_video_update_; inline void update_video() { - tia_->run_for_cycles((int)cycles_since_video_update_); - cycles_since_video_update_ = 0; + tia_->run_for(cycles_since_video_update_.flush()); } // RIOT backlog accumulation counter - unsigned int cycles_since_6532_update_; + Cycles cycles_since_6532_update_; inline void update_6532() { - mos6532_.run_for_cycles(cycles_since_6532_update_); - cycles_since_6532_update_ = 0; + mos6532_.run_for(cycles_since_6532_update_.flush()); } }; diff --git a/Machines/Atari2600/Cartridges/Cartridge.hpp b/Machines/Atari2600/Cartridges/Cartridge.hpp index fa445fcd1..eaec87913 100644 --- a/Machines/Atari2600/Cartridges/Cartridge.hpp +++ b/Machines/Atari2600/Cartridges/Cartridge.hpp @@ -22,25 +22,25 @@ template class Cartridge: Cartridge(const std::vector &rom) : rom_(rom) {} - void run_for_cycles(int number_of_cycles) { CPU::MOS6502::Processor>::run_for_cycles(number_of_cycles); } + void run_for(const Cycles &cycles) { CPU::MOS6502::Processor>::run_for(cycles); } void set_reset_line(bool state) { CPU::MOS6502::Processor>::set_reset_line(state); } - void advance_cycles(unsigned int cycles) {} + void advance_cycles(int cycles) {} // to satisfy CPU::MOS6502::Processor - unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { + Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { uint8_t returnValue = 0xff; - unsigned int cycles_run_for = 3; + int cycles_run_for = 3; // this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for // leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take // effect until the next read; therefore it isn't safe to assume that signalling ready immediately // skips to the end of the line. if(operation == CPU::MOS6502::BusOperation::Ready) - cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); + cycles_run_for = tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); - cycles_since_speaker_update_ += cycles_run_for; - cycles_since_video_update_ += cycles_run_for; - cycles_since_6532_update_ += (cycles_run_for / 3); + cycles_since_speaker_update_ += Cycles(cycles_run_for); + cycles_since_video_update_ += Cycles(cycles_run_for); + cycles_since_6532_update_ += Cycles(cycles_run_for / 3); static_cast(this)->advance_cycles(cycles_run_for / 3); if(operation != CPU::MOS6502::BusOperation::Ready) { @@ -158,7 +158,7 @@ template class Cartridge: if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU::MOS6502::Processor>::set_ready_line(false); - return cycles_run_for / 3; + return Cycles(cycles_run_for / 3); } void flush() { diff --git a/Machines/Atari2600/Cartridges/CartridgePitfall2.hpp b/Machines/Atari2600/Cartridges/CartridgePitfall2.hpp index dfd8c73ea..7921a918a 100644 --- a/Machines/Atari2600/Cartridges/CartridgePitfall2.hpp +++ b/Machines/Atari2600/Cartridges/CartridgePitfall2.hpp @@ -22,7 +22,7 @@ class CartridgePitfall2: public Cartridge { rom_ptr_ = rom_.data(); } - void advance_cycles(unsigned int cycles) { + void advance_cycles(int cycles) { cycles_since_audio_update_ += cycles; } @@ -105,8 +105,7 @@ class CartridgePitfall2: public Cartridge { inline uint8_t update_audio() { const unsigned int clock_divisor = 57; - unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor; - cycles_since_audio_update_ %= clock_divisor; + int cycles_to_run_for = cycles_since_audio_update_.divide(clock_divisor).as_int(); int table_position = 0; for(int c = 0; c < 3; c++) { @@ -126,7 +125,7 @@ class CartridgePitfall2: public Cartridge { uint8_t random_number_generator_; uint8_t *rom_ptr_; uint8_t audio_channel_[3]; - unsigned int cycles_since_audio_update_; + Cycles cycles_since_audio_update_; }; } diff --git a/Machines/Atari2600/TIA.cpp b/Machines/Atari2600/TIA.cpp index 0e5630e9b..270a495f6 100644 --- a/Machines/Atari2600/TIA.cpp +++ b/Machines/Atari2600/TIA.cpp @@ -165,13 +165,14 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) { /* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/ } -void TIA::run_for_cycles(int number_of_cycles) -{ +void TIA::run_for(const Cycles &cycles) { + int number_of_cycles = cycles.as_int(); + // if part way through a line, definitely perform a partial, at most up to the end of the line if(horizontal_counter_) { - int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_); - output_for_cycles(cycles); - number_of_cycles -= cycles; + int output_cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_); + output_for_cycles(output_cycles); + number_of_cycles -= output_cycles; } // output full lines for as long as possible @@ -197,8 +198,8 @@ void TIA::set_blank(bool blank) { void TIA::reset_horizontal_counter() { } -int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) { - return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line; +int TIA::get_cycles_until_horizontal_blank(const Cycles &from_offset) { + return (cycles_per_line - (horizontal_counter_ + from_offset.as_int()) % cycles_per_line) % cycles_per_line; } void TIA::set_background_colour(uint8_t colour) { diff --git a/Machines/Atari2600/TIA.hpp b/Machines/Atari2600/TIA.hpp index 73438c9a5..8e7f50826 100644 --- a/Machines/Atari2600/TIA.hpp +++ b/Machines/Atari2600/TIA.hpp @@ -10,11 +10,13 @@ #define TIA_hpp #include + #include "../CRTMachine.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" namespace Atari2600 { -class TIA { +class TIA: public ClockReceiver { public: TIA(); // The supplied hook is for unit testing only; if instantiated with a line_end_function then it will @@ -27,10 +29,10 @@ class TIA { }; /*! - Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the - first cycle performed. + Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed. */ - void run_for_cycles(int number_of_cycles); + void run_for(const Cycles &cycles); + using ClockReceiver::run_for; void set_output_mode(OutputMode output_mode); void set_sync(bool sync); @@ -41,7 +43,7 @@ class TIA { @returns the number of cycles between (current TIA time) + from_offset to the current or next horizontal blanking period. Returns numbers in the range [0, 227]. */ - int get_cycles_until_horizontal_blank(unsigned int from_offset); + int get_cycles_until_horizontal_blank(const Cycles &from_offset); void set_background_colour(uint8_t colour); diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp index 9cc2f869f..545221ca3 100644 --- a/Machines/CRTMachine.hpp +++ b/Machines/CRTMachine.hpp @@ -11,6 +11,7 @@ #include "../Outputs/CRT/CRT.hpp" #include "../Outputs/Speaker.hpp" +#include "../ClockReceiver/ClockReceiver.hpp" namespace CRTMachine { @@ -23,13 +24,26 @@ class Machine { public: Machine() : clock_is_unlimited_(false) {} + /*! + Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees + that an OpenGL context is bound. + */ virtual void setup_output(float aspect_ratio) = 0; + + /*! + Gives the machine a chance to release all owned resources. The caller guarantees that the + OpenGL context is bound. + */ virtual void close_output() = 0; + /// @returns The CRT this machine is drawing to. Should not be @c nullptr. virtual std::shared_ptr get_crt() = 0; + + /// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. virtual std::shared_ptr get_speaker() = 0; - virtual void run_for_cycles(int number_of_cycles) = 0; + /// Runs the machine for @c cycles. + virtual void run_for(const Cycles &cycles) = 0; // TODO: sever the clock-rate stuff. double get_clock_rate() { diff --git a/Machines/Commodore/1540/C1540.cpp b/Machines/Commodore/1540/C1540.cpp index ca92c51c2..ddaa65eab 100644 --- a/Machines/Commodore/1540/C1540.cpp +++ b/Machines/Commodore/1540/C1540.cpp @@ -34,7 +34,7 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); } -unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { +Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { /* Memory map (given that I'm unsure yet on any potential mirroring): @@ -63,10 +63,10 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation drive_VIA_.set_register(address, *value); } - serial_port_VIA_->run_for_cycles(1); - drive_VIA_.run_for_cycles(1); + serial_port_VIA_->run_for(Cycles(1)); + drive_VIA_.run_for(Cycles(1)); - return 1; + return Cycles(1); } void Machine::set_rom(const std::vector &rom) { @@ -79,11 +79,11 @@ void Machine::set_disk(std::shared_ptr disk) { set_drive(drive); } -void Machine::run_for_cycles(int number_of_cycles) { - CPU::MOS6502::Processor::run_for_cycles(number_of_cycles); +void Machine::run_for(const Cycles &cycles) { + CPU::MOS6502::Processor::run_for(cycles); set_motor_on(drive_VIA_.get_motor_enabled()); if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down - Storage::Disk::Controller::run_for_cycles(number_of_cycles); + Storage::Disk::Controller::run_for(cycles); } #pragma mark - 6522 delegate diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 5fdcf4294..2cb2cbe7e 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -138,11 +138,11 @@ class Machine: */ void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); - void run_for_cycles(int number_of_cycles); + void run_for(const Cycles &cycles); void set_disk(std::shared_ptr disk); // to satisfy CPU::MOS6502::Processor - unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); + Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); // to satisfy MOS::MOS6522::Delegate virtual void mos6522_did_change_interrupt_status(void *mos6522); diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 67513e38a..1babf5273 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -97,9 +97,9 @@ Machine::~Machine() { delete[] rom_; } -unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { +Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { // run the phase-1 part of this cycle, in which the VIC accesses memory - if(!is_running_at_zero_cost_) mos6560_->run_for_cycles(1); + if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1)); // run the phase-2 part of the cycle, which is whatever the 6502 said it should be if(isReadOperation(operation)) { @@ -179,18 +179,18 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation } } - user_port_via_->run_for_cycles(1); - keyboard_via_->run_for_cycles(1); + user_port_via_->run_for(Cycles(1)); + keyboard_via_->run_for(Cycles(1)); if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) { if(!typer_->type_next_character()) { clear_all_keys(); typer_.reset(); } } - tape_->run_for_cycles(1); - if(c1540_) c1540_->run_for_cycles(1); + tape_->run_for(Cycles(1)); + if(c1540_) c1540_->run_for(Cycles(1)); - return 1; + return Cycles(1); } #pragma mark - 6522 delegate @@ -315,7 +315,7 @@ void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) { void Machine::install_disk_rom() { if(!drive_rom_.empty() && c1540_) { c1540_->set_rom(drive_rom_); - c1540_->run_for_cycles(2000000); + c1540_->run_for(Cycles(2000000)); drive_rom_.clear(); } } diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index e70c845cd..c6ffa22d9 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -166,7 +166,7 @@ class Machine: inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; } // to satisfy CPU::MOS6502::Processor - unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); + Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); void flush() { mos6560_->flush(); } // to satisfy CRTMachine::Machine @@ -174,7 +174,7 @@ class Machine: virtual void close_output(); virtual std::shared_ptr get_crt() { return mos6560_->get_crt(); } virtual std::shared_ptr get_speaker() { return mos6560_->get_speaker(); } - virtual void run_for_cycles(int number_of_cycles) { CPU::MOS6502::Processor::run_for_cycles(number_of_cycles); } + virtual void run_for(const Cycles &cycles) { CPU::MOS6502::Processor::run_for(cycles); } // to satisfy MOS::MOS6522::Delegate virtual void mos6522_did_change_interrupt_status(void *mos6522); diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 7441c7f90..77531b576 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -15,7 +15,6 @@ using namespace Electron; Machine::Machine() : interrupt_control_(0), interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80), - cycles_since_display_update_(0), cycles_since_audio_update_(0), use_fast_tape_hack_(false), cycles_until_display_interrupt_(0) { @@ -123,7 +122,7 @@ void Machine::set_rom(ROMSlot slot, std::vector data, bool is_writeable #pragma mark - The bus -unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { +Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { unsigned int cycles = 1; if(address < 0x8000) { @@ -136,7 +135,7 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation // for the entire frame, RAM is accessible only on odd cycles; in modes below 4 // it's also accessible only outside of the pixel regions - cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1)); + cycles += video_output_->get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1); } else { switch(address & 0xff0f) { case 0xfe00: @@ -316,10 +315,10 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation } } - cycles_since_display_update_ += cycles; - cycles_since_audio_update_ += cycles; - if(cycles_since_audio_update_ > 16384) update_audio(); - tape_.run_for_cycles(cycles); + cycles_since_display_update_ += Cycles((int)cycles); + cycles_since_audio_update_ += Cycles((int)cycles); + if(cycles_since_audio_update_ > Cycles(16384)) update_audio(); + tape_.run_for(Cycles((int)cycles)); cycles_until_display_interrupt_ -= cycles; if(cycles_until_display_interrupt_ < 0) { @@ -329,7 +328,7 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation } if(typer_) typer_->update((int)cycles); - if(plus3_) plus3_->run_for_cycles(4*cycles); + if(plus3_) plus3_->run_for(Cycles(4*(int)cycles)); if(shift_restart_counter_) { shift_restart_counter_ -= cycles; if(shift_restart_counter_ <= 0) { @@ -340,7 +339,7 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation } } - return cycles; + return Cycles((int)cycles); } void Machine::flush() { @@ -353,8 +352,7 @@ void Machine::flush() { inline void Machine::update_display() { if(cycles_since_display_update_) { - video_output_->run_for_cycles((int)cycles_since_display_update_); - cycles_since_display_update_ = 0; + video_output_->run_for(cycles_since_display_update_.flush()); } } @@ -366,9 +364,7 @@ inline void Machine::queue_next_display_interrupt() { inline void Machine::update_audio() { if(cycles_since_audio_update_) { - unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider; - cycles_since_audio_update_ %= Speaker::clock_rate_divider; - speaker_->run_for_cycles(difference); + speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider))); } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index f24462a51..7badbb228 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -86,7 +86,7 @@ class Machine: void configure_as_target(const StaticAnalyser::Target &target); // to satisfy CPU::MOS6502::Processor - unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); + Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); void flush(); // to satisfy CRTMachine::Machine @@ -94,7 +94,7 @@ class Machine: virtual void close_output(); virtual std::shared_ptr get_crt(); virtual std::shared_ptr get_speaker(); - virtual void run_for_cycles(int number_of_cycles) { CPU::MOS6502::Processor::run_for_cycles(number_of_cycles); } + virtual void run_for(const Cycles &cycles) { CPU::MOS6502::Processor::run_for(cycles); } // to satisfy Tape::Delegate virtual void tape_did_change_interrupt_status(Tape *tape); @@ -128,8 +128,8 @@ class Machine: uint8_t key_states_[14]; // Counters related to simultaneous subsystems - unsigned int cycles_since_display_update_; - unsigned int cycles_since_audio_update_; + Cycles cycles_since_display_update_; + Cycles cycles_since_audio_update_; int cycles_until_display_interrupt_; Interrupt next_display_interrupt_; VideoOutput::Range video_access_range_; diff --git a/Machines/Electron/Tape.cpp b/Machines/Electron/Tape.cpp index 4d0d37959..2aac4b172 100644 --- a/Machines/Electron/Tape.cpp +++ b/Machines/Electron/Tape.cpp @@ -80,14 +80,14 @@ void Tape::acorn_shifter_output_bit(int value) { push_tape_bit((uint16_t)value); } -void Tape::run_for_cycles(unsigned int number_of_cycles) { +void Tape::run_for(const Cycles &cycles) { if(is_enabled_) { if(is_in_input_mode_) { if(is_running_) { - TapePlayer::run_for_cycles((int)number_of_cycles); + TapePlayer::run_for(cycles); } } else { - output_.cycles_into_pulse += number_of_cycles; + output_.cycles_into_pulse += (unsigned int)cycles.as_int(); while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off. push_tape_bit(1); diff --git a/Machines/Electron/Tape.hpp b/Machines/Electron/Tape.hpp index 2d499e5e7..cfd230eb6 100644 --- a/Machines/Electron/Tape.hpp +++ b/Machines/Electron/Tape.hpp @@ -9,12 +9,13 @@ #ifndef Electron_Tape_h #define Electron_Tape_h +#include + +#include "../../ClockReceiver/ClockReceiver.hpp" #include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Parsers/Acorn.hpp" #include "Interrupts.hpp" -#include - namespace Electron { class Tape: @@ -23,7 +24,8 @@ class Tape: public: Tape(); - void run_for_cycles(unsigned int number_of_cycles); + void run_for(const Cycles &cycles); + using Storage::Tape::TapePlayer::run_for; uint8_t get_data_register(); void set_data_register(uint8_t value); diff --git a/Machines/Electron/Video.cpp b/Machines/Electron/Video.cpp index 08eaf4550..ceedb1c95 100644 --- a/Machines/Electron/Video.cpp +++ b/Machines/Electron/Video.cpp @@ -223,7 +223,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { } } -void VideoOutput::run_for_cycles(int number_of_cycles) { +void VideoOutput::run_for(const Cycles &cycles) { + int number_of_cycles = cycles.as_int(); output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame; while(number_of_cycles) { int draw_action_length = screen_map_[screen_map_pointer_].length; diff --git a/Machines/Electron/Video.hpp b/Machines/Electron/Video.hpp index ec0b36395..65ad5bc2d 100644 --- a/Machines/Electron/Video.hpp +++ b/Machines/Electron/Video.hpp @@ -10,6 +10,7 @@ #define Machines_Electron_Video_hpp #include "../../Outputs/CRT/CRT.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" #include "Interrupts.hpp" namespace Electron { @@ -21,7 +22,7 @@ namespace Electron { running either at 40 or 80 columns. Memory is shared between video and CPU; when the video is accessing it the CPU may not. */ -class VideoOutput { +class VideoOutput: public ClockReceiver { public: /*! Instantiates a VideoOutput that will read its pixels from @c memory. The pointer supplied @@ -32,8 +33,9 @@ class VideoOutput { /// @returns the CRT to which output is being painted. std::shared_ptr get_crt(); - /// Produces the next @c number_of_cycles cycles of video output. - void run_for_cycles(int number_of_cycles); + /// Produces the next @c cycles of video output. + void run_for(const Cycles &cycles); + using ClockReceiver::run_for; /*! Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt, @@ -53,14 +55,14 @@ class VideoOutput { /*! @returns the next interrupt that should be generated as a result of the video hardware. The time until signalling returned is the number of cycles after the final one triggered - by the most recent call to @c run_for_cycles. + by the most recent call to @c run_for. This result may be mutated by calls to @c set_register. */ Interrupt get_next_interrupt(); /*! - @returns the number of cycles after (final cycle of last run_for_cycles batch + @c from_time) + @returns the number of cycles after (final cycle of last run_for batch + @c from_time) before the video circuits will allow the CPU to access RAM. */ unsigned int get_cycles_until_next_ram_availability(int from_time); diff --git a/Machines/KeyboardMachine.hpp b/Machines/KeyboardMachine.hpp index a5aa972b7..6399d6f58 100644 --- a/Machines/KeyboardMachine.hpp +++ b/Machines/KeyboardMachine.hpp @@ -13,7 +13,15 @@ namespace KeyboardMachine { class Machine { public: + /*! + Indicates that the key @c key has been either pressed or released, according to + the state of @c isPressed. + */ virtual void set_key_state(uint16_t key, bool isPressed) = 0; + + /*! + Instructs that all keys should now be treated as released. + */ virtual void clear_all_keys() = 0; }; diff --git a/Machines/MemoryFuzzer.hpp b/Machines/MemoryFuzzer.hpp index 3de6bdcbd..614692d54 100644 --- a/Machines/MemoryFuzzer.hpp +++ b/Machines/MemoryFuzzer.hpp @@ -15,7 +15,10 @@ namespace Memory { +/// Stores @c size random bytes from @c buffer onwards. void Fuzz(uint8_t *buffer, size_t size); + +// Replaces all existing vector contents with random bytes. void Fuzz(std::vector &buffer); } diff --git a/Machines/Oric/Microdisc.cpp b/Machines/Oric/Microdisc.cpp index f9bed5a0e..344da6006 100644 --- a/Machines/Oric/Microdisc.cpp +++ b/Machines/Oric/Microdisc.cpp @@ -104,12 +104,12 @@ void Microdisc::set_head_load_request(bool head_load) { } } -void Microdisc::run_for_cycles(unsigned int number_of_cycles) { +void Microdisc::run_for(const Cycles &cycles) { if(head_load_request_counter_ < head_load_request_counter_target) { - head_load_request_counter_ += number_of_cycles; + head_load_request_counter_ += cycles.as_int(); if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true); } - WD::WD1770::run_for_cycles(number_of_cycles); + WD::WD1770::run_for(cycles); } bool Microdisc::get_drive_is_ready() { diff --git a/Machines/Oric/Microdisc.hpp b/Machines/Oric/Microdisc.hpp index d320c7721..4bb756411 100644 --- a/Machines/Oric/Microdisc.hpp +++ b/Machines/Oric/Microdisc.hpp @@ -24,7 +24,8 @@ class Microdisc: public WD::WD1770 { bool get_interrupt_request_line(); - void run_for_cycles(unsigned int number_of_cycles); + void run_for(const Cycles &cycles); + using WD::WD1770::run_for; enum PagingFlags { BASICDisable = (1 << 0), diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 3310cc319..614f18e29 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -12,7 +12,6 @@ using namespace Oric; Machine::Machine() : - cycles_since_video_update_(0), use_fast_tape_hack_(false), typer_delay_(2500000), keyboard_read_count_(0), @@ -78,7 +77,7 @@ void Machine::set_rom(ROM rom, const std::vector &data) { } } -unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { +Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { if(address > ram_top_) { if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1]; @@ -130,10 +129,10 @@ unsigned int Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation } } - via_.run_for_cycles(1); - if(microdisc_is_enabled_) microdisc_.run_for_cycles(8); + via_.run_for(Cycles(1)); + if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8)); cycles_since_video_update_++; - return 1; + return Cycles(1); } void Machine::flush() { @@ -142,8 +141,7 @@ void Machine::flush() { } void Machine::update_video() { - video_output_->run_for_cycles(cycles_since_video_update_); - cycles_since_video_update_ = 0; + video_output_->run_for(cycles_since_video_update_.flush()); } void Machine::setup_output(float aspect_ratio) { @@ -198,15 +196,14 @@ std::shared_ptr Machine::get_speaker() { return via_.ay8910; } -void Machine::run_for_cycles(int number_of_cycles) { - CPU::MOS6502::Processor::run_for_cycles(number_of_cycles); +void Machine::run_for(const Cycles &cycles) { + CPU::MOS6502::Processor::run_for(cycles); } #pragma mark - The 6522 Machine::VIA::VIA() : MOS::MOS6522(), - cycles_since_ay_update_(0), tape(new TapePlayer) {} void Machine::VIA::set_control_line_output(Port port, Line line, bool value) { @@ -235,20 +232,18 @@ uint8_t Machine::VIA::get_port_input(Port port) { } void Machine::VIA::flush() { - ay8910->run_for_cycles(cycles_since_ay_update_); + ay8910->run_for(cycles_since_ay_update_.flush()); ay8910->flush(); - cycles_since_ay_update_ = 0; } -void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) { - cycles_since_ay_update_ += number_of_cycles; - MOS::MOS6522::run_for_cycles(number_of_cycles); - tape->run_for_cycles((int)number_of_cycles); +void Machine::VIA::run_for(const Cycles &cycles) { + cycles_since_ay_update_ += cycles; + MOS::MOS6522::run_for(cycles); + tape->run_for(cycles); } void Machine::VIA::update_ay() { - ay8910->run_for_cycles(cycles_since_ay_update_); - cycles_since_ay_update_ = 0; + ay8910->run_for(cycles_since_ay_update_.flush()); ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); } diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 43766d710..f1b1ff37e 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -77,7 +77,7 @@ class Machine: void configure_as_target(const StaticAnalyser::Target &target); // to satisfy CPU::MOS6502::Processor - unsigned int perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); + Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); void flush(); // to satisfy CRTMachine::Machine @@ -85,7 +85,7 @@ class Machine: virtual void close_output(); virtual std::shared_ptr get_crt(); virtual std::shared_ptr get_speaker(); - virtual void run_for_cycles(int number_of_cycles); + virtual void run_for(const Cycles &cyclesß); // to satisfy MOS::MOS6522IRQDelegate::Delegate void mos6522_did_change_interrupt_status(void *mos6522); @@ -104,7 +104,7 @@ class Machine: // RAM and ROM std::vector basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_; uint8_t ram_[65536], rom_[16384]; - int cycles_since_video_update_; + Cycles cycles_since_video_update_; inline void update_video(); // ROM bookkeeping @@ -143,7 +143,7 @@ class Machine: void set_control_line_output(Port port, Line line, bool value); void set_port_output(Port port, uint8_t value, uint8_t direction_mask); uint8_t get_port_input(Port port); - inline void run_for_cycles(unsigned int number_of_cycles); + inline void run_for(const Cycles &cycles); std::shared_ptr ay8910; std::unique_ptr tape; @@ -154,7 +154,7 @@ class Machine: private: void update_ay(); bool ay_bdir_, ay_bc1_; - unsigned int cycles_since_ay_update_; + Cycles cycles_since_ay_update_; }; VIA via_; std::shared_ptr keyboard_; diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index a4107101a..2e455d873 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -74,13 +74,14 @@ std::shared_ptr VideoOutput::get_crt() { return crt_; } -void VideoOutput::run_for_cycles(int number_of_cycles) { +void VideoOutput::run_for(const Cycles &cycles) { // Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst // Horizontal: 0–223: pixels; otherwise blank; 256–259 sync #define clamp(action) \ if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles; + int number_of_cycles = cycles.as_int(); while(number_of_cycles) { int h_counter = counter_ & 63; int cycles_run_for = 0; diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index 992d73470..6ceb076fb 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -10,14 +10,16 @@ #define Machines_Oric_Video_hpp #include "../../Outputs/CRT/CRT.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" namespace Oric { -class VideoOutput { +class VideoOutput: public ClockReceiver { public: VideoOutput(uint8_t *memory); std::shared_ptr get_crt(); - void run_for_cycles(int number_of_cycles); + void run_for(const Cycles &cycles); + using ClockReceiver::run_for; void set_colour_rom(const std::vector &rom); void set_output_device(Outputs::CRT::OutputDevice output_device); diff --git a/Machines/ZX8081/Video.cpp b/Machines/ZX8081/Video.cpp index 5531e7dee..a5443f7e2 100644 --- a/Machines/ZX8081/Video.cpp +++ b/Machines/ZX8081/Video.cpp @@ -29,9 +29,9 @@ Video::Video() : crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); } -void Video::run_for_cycles(int number_of_cycles) { +void Video::run_for(const HalfCycles &half_cycles) { // Just keep a running total of the amount of time that remains owed to the CRT. - cycles_since_update_ += (unsigned int)number_of_cycles << 1; + cycles_since_update_ += (unsigned int)half_cycles.as_int(); } void Video::flush() { diff --git a/Machines/ZX8081/Video.hpp b/Machines/ZX8081/Video.hpp index 9c63021ba..5c128d424 100644 --- a/Machines/ZX8081/Video.hpp +++ b/Machines/ZX8081/Video.hpp @@ -10,6 +10,7 @@ #define Machines_ZX8081_Video_hpp #include "../../Outputs/CRT/CRT.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" namespace ZX8081 { @@ -23,15 +24,16 @@ namespace ZX8081 { a 1-bit graphic and output over the next 4 cycles, picking between the white level and the black level. */ -class Video { +class Video: public ClockReceiver