1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-09-27 18:55:48 +00:00

Factors out the stuff of deferred action interleaving, as I suspect it'll come in handy.

This commit is contained in:
Thomas Harte 2018-08-24 20:04:26 -04:00
parent 61e46399dc
commit 97a89aaf4d
4 changed files with 107 additions and 54 deletions

View File

@ -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 <vector>
/*!
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 <typename TimeUnit> class ClockDeferrer {
public:
/// Constructs a ClockDeferrer that will call target.advance in between deferred actions.
ClockDeferrer(std::function<void(TimeUnit)> &&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<void(void)> &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<void(TimeUnit)> target_;
// The list of deferred actions.
struct DeferredAction {
TimeUnit delay;
std::function<void(void)> action;
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
};
std::vector<DeferredAction> pending_actions_;
};
#endif /* ClockDeferrer_h */

View File

@ -10,9 +10,10 @@
using namespace AppleII::Video;
VideoBase::VideoBase(bool is_iie) :
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&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<void(void)> &action) {
pending_actions_.emplace_back(delay, action);
}

View File

@ -11,6 +11,7 @@
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockDeferrer.hpp"
#include <array>
#include <vector>
@ -33,7 +34,7 @@ class BusHandler {
class VideoBase {
public:
VideoBase(bool is_iie);
VideoBase(bool is_iie, std::function<void(Cycles)> &&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<void(void)> &action);
// The list of deferred actions.
struct DeferredAction {
int delay;
std::function<void(void)> action;
DeferredAction(int delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
};
std::vector<DeferredAction> pending_actions_;
// Maintain a ClockDeferrer for delayed mode switches.
ClockDeferrer<Cycles> deferrer_;
};
template <class BusHandler, bool is_iie> 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 BusHandler, bool is_iie> 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;

View File

@ -1007,6 +1007,7 @@
4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; };
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockDeferrer.hpp; sourceTree = "<group>"; };
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; };
@ -3149,6 +3150,7 @@
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;