From 97a89aaf4d9ba187bba308fe7aa48b6de32b9f87 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 Aug 2018 20:04:26 -0400 Subject: [PATCH] Factors out the stuff of deferred action interleaving, as I suspect it'll come in handy. --- ClockReceiver/ClockDeferrer.hpp | 84 +++++++++++++++++++ Machines/AppleII/Video.cpp | 21 ++--- Machines/AppleII/Video.hpp | 54 +++--------- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 4 files changed, 107 insertions(+), 54 deletions(-) create mode 100644 ClockReceiver/ClockDeferrer.hpp diff --git a/ClockReceiver/ClockDeferrer.hpp b/ClockReceiver/ClockDeferrer.hpp new file mode 100644 index 000000000..1b9d2e01e --- /dev/null +++ b/ClockReceiver/ClockDeferrer.hpp @@ -0,0 +1,84 @@ +// +// ClockDeferrer.hpp +// Clock Signal +// +// Created by Thomas Harte on 23/08/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef ClockDeferrer_h +#define ClockDeferrer_h + +#include + +/*! + A ClockDeferrer maintains a list of ordered actions and the times at which + they should happen, and divides a total execution period up into the portions + that occur between those actions, triggering each action when it is reached. + + @c Class should be a class that implements @c advance(TimeUnit), to advance + that amount of time. +*/ +template class ClockDeferrer { + public: + /// Constructs a ClockDeferrer that will call target.advance in between deferred actions. + ClockDeferrer(std::function &&target) : target_(std::move(target)) {} + + /*! + Schedules @c action to occur in @c delay units of time. + + Actions must be scheduled in the order they will occur. It is undefined behaviour + to schedule them out of order. + */ + void defer(TimeUnit delay, const std::function &action) { + pending_actions_.emplace_back(delay, action); + } + + /*! + Runs for @c length units of time. + + The target's @c advance will be called with one or more periods that add up to @c length; + any scheduled actions will be called between periods. + */ + void run_for(TimeUnit length) { + // If there are no pending actions, just run for the entire length. + // This should be the normal branch. + if(pending_actions_.empty()) { + target_(length); + return; + } + + // Divide the time to run according to the pending actions. + while(length > TimeUnit(0)) { + TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay); + target_(next_period); + length -= next_period; + + off_t performances = 0; + for(auto &action: pending_actions_) { + action.delay -= next_period; + if(!action.delay) { + action.action(); + ++performances; + } + } + if(performances) { + pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances); + } + } + } + + private: + std::function target_; + + // The list of deferred actions. + struct DeferredAction { + TimeUnit delay; + std::function action; + + DeferredAction(TimeUnit delay, const std::function &action) : delay(delay), action(std::move(action)) {} + }; + std::vector pending_actions_; +}; + +#endif /* ClockDeferrer_h */ diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index 03e1afcef..63e87cc29 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -10,9 +10,10 @@ using namespace AppleII::Video; -VideoBase::VideoBase(bool is_iie) : +VideoBase::VideoBase(bool is_iie, std::function &&target) : crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)), - is_iie_(is_iie) { + is_iie_(is_iie), + deferrer_(std::move(target)) { // Set a composite sampling function that assumes one byte per pixel input, and // accepts any non-zero value as being fully on, zero being fully off. @@ -37,7 +38,7 @@ Outputs::CRT::CRT *VideoBase::get_crt() { */ void VideoBase::set_alternative_character_set(bool alternative_character_set) { set_alternative_character_set_ = alternative_character_set; - defer(2, [=] { + deferrer_.defer(Cycles(2), [=] { alternative_character_set_ = alternative_character_set; }); } @@ -48,7 +49,7 @@ bool VideoBase::get_alternative_character_set() { void VideoBase::set_80_columns(bool columns_80) { set_columns_80_ = columns_80; - defer(2, [=] { + deferrer_.defer(Cycles(2), [=] { columns_80_ = columns_80; }); } @@ -75,7 +76,7 @@ bool VideoBase::get_page2() { void VideoBase::set_text(bool text) { set_text_ = text; - defer(2, [=] { + deferrer_.defer(Cycles(2), [=] { text_ = text; }); } @@ -86,7 +87,7 @@ bool VideoBase::get_text() { void VideoBase::set_mixed(bool mixed) { set_mixed_ = mixed; - defer(2, [=] { + deferrer_.defer(Cycles(2), [=] { mixed_ = mixed; }); } @@ -97,7 +98,7 @@ bool VideoBase::get_mixed() { void VideoBase::set_high_resolution(bool high_resolution) { set_high_resolution_ = high_resolution; - defer(2, [=] { + deferrer_.defer(Cycles(2), [=] { high_resolution_ = high_resolution; }); } @@ -108,7 +109,7 @@ bool VideoBase::get_high_resolution() { void VideoBase::set_double_high_resolution(bool double_high_resolution) { set_double_high_resolution_ = double_high_resolution; - defer(2, [=] { + deferrer_.defer(Cycles(2), [=] { double_high_resolution_ = double_high_resolution; }); } @@ -301,7 +302,3 @@ void VideoBase::output_double_high_resolution(uint8_t *target, uint8_t *source, target += 14; } } - -void VideoBase::defer(int delay, const std::function &action) { - pending_actions_.emplace_back(delay, action); -} diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index ffd99fca3..c9da50555 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -11,6 +11,7 @@ #include "../../Outputs/CRT/CRT.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../ClockReceiver/ClockDeferrer.hpp" #include #include @@ -33,7 +34,7 @@ class BusHandler { class VideoBase { public: - VideoBase(bool is_iie); + VideoBase(bool is_iie, std::function &&target); /// @returns The CRT this video feed is feeding. Outputs::CRT::CRT *get_crt(); @@ -224,58 +225,22 @@ class VideoBase { */ void output_double_high_resolution(uint8_t *target, uint8_t *source, uint8_t *auxiliary_source, size_t length) const; - /// Schedule @c action to occur in @c delay cycles. - void defer(int delay, const std::function &action); - - // The list of deferred actions. - struct DeferredAction { - int delay; - std::function action; - - DeferredAction(int delay, const std::function &action) : delay(delay), action(std::move(action)) {} - }; - std::vector pending_actions_; + // Maintain a ClockDeferrer for delayed mode switches. + ClockDeferrer deferrer_; }; template class Video: public VideoBase { public: /// Constructs an instance of the video feed; a CRT is also created. Video(BusHandler &bus_handler) : - VideoBase(is_iie), + VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }), bus_handler_(bus_handler) {} /*! - Advances time by @c cycles; expects to be fed by the CPU clock. - Implicitly adds an extra half a colour clock at the end of every - line. + Runs video for @c cycles. */ void run_for(Cycles cycles) { - // If there are no pending actions, just run for the entire length. - // This should be the normal branch. - if(pending_actions_.empty()) { - advance(cycles.as_int()); - return; - } - - // Divide the time to run according to the pending actions. - int cycles_remaining = cycles.as_int(); - while(cycles_remaining) { - int next_period = pending_actions_.empty() ? cycles_remaining : std::min(cycles_remaining, pending_actions_[0].delay); - advance(next_period); - cycles_remaining -= next_period; - - off_t performances = 0; - for(auto &action: pending_actions_) { - action.delay -= next_period; - if(!action.delay) { - action.action(); - ++performances; - } - } - if(performances) { - pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances); - } - } + deferrer_.run_for(cycles); } /*! @@ -336,6 +301,11 @@ template class Video: public VideoBase { } private: + /*! + Advances time by @c cycles; expects to be fed by the CPU clock. + Implicitly adds an extra half a colour clock at the end of + line. + */ void advance(Cycles cycles) { /* Addressing scheme used throughout is that column 0 is the first column with pixels in it; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5a1ab50bb..b52f8a493 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1007,6 +1007,7 @@ 4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = ""; }; + 4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockDeferrer.hpp; sourceTree = ""; }; 4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = ""; }; 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = ""; }; 4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = ""; }; @@ -3149,6 +3150,7 @@ 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, 4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */, 4B449C942063389900A095C8 /* TimeTypes.hpp */, + 4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */, ); name = ClockReceiver; path = ../../ClockReceiver;