1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-01 06:41:36 +00:00

Devolves time to cycle conversion to machines.

Thereby avoids a whole bunch of complicated machinations that would otherwise have been required of the multimachine.
This commit is contained in:
Thomas Harte 2018-03-21 22:18:13 -04:00
parent ece3a05504
commit da3d65c18f
12 changed files with 56 additions and 161 deletions

View File

@ -75,41 +75,16 @@ Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_;
}
void MultiCRTMachine::run_for(const Cycles cycles) {
void MultiCRTMachine::run_for(Time::Seconds duration) {
perform_parallel([=](::CRTMachine::Machine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(cycles);
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
if(delegate_) delegate_->multi_crt_did_run_machines();
}
double MultiCRTMachine::get_clock_rate() {
// TODO: something smarter than this? Not all clock rates will necessarily be the same.
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_clock_rate() : 0.0;
}
bool MultiCRTMachine::get_clock_is_unlimited() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_clock_is_unlimited() : false;
}
void MultiCRTMachine::did_change_machine_order() {
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}
}
void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) {
// TODO:
}
void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) {
// TODO: consider passing along.
}
void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) {
// TODO: consider passing along.
}

View File

@ -29,7 +29,7 @@ namespace Dynamic {
acquiring a supplied mutex. The owner should also call did_change_machine_order()
if the order of machines changes.
*/
class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate {
class MultiCRTMachine: public CRTMachine::Machine {
public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
@ -57,16 +57,10 @@ class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::D
void close_output() override;
Outputs::CRT::CRT *get_crt() override;
Outputs::Speaker::Speaker *get_speaker() override;
void run_for(const Cycles cycles) override;
double get_clock_rate() override;
bool get_clock_is_unlimited() override;
void set_delegate(::CRTMachine::Machine::Delegate *delegate) override;
void run_for(Time::Seconds duration) override;
private:
// CRTMachine::Machine::Delegate
void machine_did_change_clock_rate(Machine *machine) override;
void machine_did_change_clock_is_unlimited(Machine *machine) override;
void run_for(const Cycles cycles) override {}
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;

View File

@ -0,0 +1,18 @@
//
// TimeTypes.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef TimeTypes_h
#define TimeTypes_h
namespace Time {
typedef double Seconds;
}
#endif /* TimeTypes_h */

View File

@ -36,13 +36,11 @@ void BestEffortUpdater::update() {
// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if(duration > 0) {
double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
error_ = fmod(cycles, 1.0);
const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if(integer_duration > 0) {
if(delegate_) {
delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
const double duration = static_cast<double>(integer_duration) / 1e9;
delegate_->update(this, duration, has_skipped_);
}
has_skipped_ = false;
}
@ -70,8 +68,3 @@ void BestEffortUpdater::set_delegate(Delegate *const delegate) {
});
}
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
async_task_queue_.enqueue([this, clock_rate]() {
this->clock_rate_ = clock_rate;
});
}

View File

@ -13,6 +13,7 @@
#include <chrono>
#include "AsyncTaskQueue.hpp"
#include "TimeTypes.hpp"
namespace Concurrency {
@ -30,15 +31,12 @@ class BestEffortUpdater {
/// A delegate receives timing cues.
struct Delegate {
virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0;
virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0;
};
/// Sets the current delegate.
void set_delegate(Delegate *);
/// Sets the clock rate of the delegate.
void set_clock_rate(double clock_rate);
/*!
If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
The call is asynchronous; this method will return immediately.
@ -54,11 +52,9 @@ class BestEffortUpdater {
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
bool has_previous_time_point_ = false;
double error_ = 0.0;
bool has_skipped_ = false;
Delegate *delegate_ = nullptr;
double clock_rate_ = 1.0;
};
}

View File

@ -12,8 +12,11 @@
#include "../Outputs/CRT/CRT.hpp"
#include "../Outputs/Speaker/Speaker.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
#include "../ClockReceiver/TimeTypes.hpp"
#include "ROMMachine.hpp"
#include <cmath>
namespace CRTMachine {
/*!
@ -41,45 +44,31 @@ class Machine: public ROMMachine::Machine {
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
/// @returns The confidence that this machine is running content it understands.
virtual float get_confidence() { return 0.5f; }
virtual void print_type() {}
// TODO: sever the clock-rate stuff.
virtual double get_clock_rate() {
/// Runs the machine for @c duration seconds.
virtual void run_for(Time::Seconds duration) {
const double cycles = (duration * clock_rate_) + clock_conversion_error_;
clock_conversion_error_ = std::fmod(cycles, 1.0);
run_for(Cycles(static_cast<int>(cycles)));
}
double get_clock_rate() {
return clock_rate_;
}
virtual bool get_clock_is_unlimited() {
return clock_is_unlimited_;
}
class Delegate {
public:
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
};
virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; }
protected:
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
void set_clock_rate(double clock_rate) {
if(clock_rate_ != clock_rate) {
clock_rate_ = clock_rate;
if(delegate_) delegate_->machine_did_change_clock_rate(this);
}
}
void set_clock_is_unlimited(bool clock_is_unlimited) {
if(clock_is_unlimited != clock_is_unlimited_) {
clock_is_unlimited_ = clock_is_unlimited;
if(delegate_) delegate_->machine_did_change_clock_is_unlimited(this);
}
clock_rate_ = clock_rate;
}
private:
Delegate *delegate_ = nullptr;
double clock_rate_ = 1.0;
bool clock_is_unlimited_ = false;
double clock_conversion_error_ = 0.0;
};
}

View File

@ -780,6 +780,7 @@
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = "<group>"; };
4B449C942063389900A095C8 /* TimeTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimeTypes.hpp; sourceTree = "<group>"; };
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
@ -2972,6 +2973,7 @@
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;

View File

@ -12,7 +12,6 @@ import AudioToolbox
class MachineDocument:
NSDocument,
NSWindowDelegate,
CSMachineDelegate,
CSOpenGLViewDelegate,
CSOpenGLViewResponderDelegate,
CSBestEffortUpdaterDelegate,
@ -56,7 +55,6 @@ class MachineDocument:
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
})
self.machine.delegate = self
self.bestEffortUpdater = CSBestEffortUpdater()
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
@ -74,16 +72,6 @@ class MachineDocument:
self.bestEffortUpdater!.delegate = self
}
func machineDidChangeClockRate(_ machine: CSMachine!) {
setupClockRate()
}
func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) {
bestEffortLock.lock()
self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited
bestEffortLock.unlock()
}
fileprivate func setupClockRate() {
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
@ -94,10 +82,6 @@ class MachineDocument:
self.machine.audioQueue = self.audioQueue
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
}
bestEffortLock.lock()
self.bestEffortUpdater?.clockRate = self.machine.clockRate
bestEffortLock.unlock()
}
override func close() {
@ -152,21 +136,10 @@ class MachineDocument:
}
// MARK: CSBestEffortUpdaterDelegate
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) {
runForNumberOfCycles(Int32(cycles))
}
func runForNumberOfCycles(_ numberOfCycles: Int32) {
bestEffortLock.lock()
if let bestEffortUpdater = bestEffortUpdater {
bestEffortLock.unlock()
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
if actionLock.try() {
self.machine.runForNumber(ofCycles: cyclesToRunFor)
actionLock.unlock()
}
} else {
bestEffortLock.unlock()
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
if actionLock.try() {
self.machine.run(forInterval: duration)
actionLock.unlock()
}
}

View File

@ -14,10 +14,6 @@
#import "CSStaticAnalyser.h"
@class CSMachine;
@protocol CSMachineDelegate
- (void)machineDidChangeClockRate:(CSMachine *)machine;
- (void)machineDidChangeClockIsUnlimited:(CSMachine *)machine;
@end
// Deliberately low; to ensure CSMachine has been declared as an @class already.
#import "CSAtari2600.h"
@ -33,7 +29,7 @@
*/
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
- (void)runForNumberOfCycles:(int)numberOfCycles;
- (void)runForInterval:(NSTimeInterval)interval;
- (float)idealSamplingRateFromRange:(NSRange)range;
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize;
@ -46,7 +42,6 @@
@property (nonatomic, strong) CSAudioQueue *audioQueue;
@property (nonatomic, readonly) CSOpenGLView *view;
@property (nonatomic, weak) id<CSMachineDelegate> delegate;
@property (nonatomic, readonly) double clockRate;
@property (nonatomic, readonly) BOOL clockIsUnlimited;

View File

@ -25,8 +25,6 @@
@interface CSMachine() <CSFastLoading>
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
- (void)machineDidChangeClockRate;
- (void)machineDidChangeClockIsUnlimited;
@end
struct LockProtectedDelegate {
@ -44,22 +42,8 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
}
};
struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDelegate {
void machine_did_change_clock_rate(CRTMachine::Machine *sender) {
[machineAccessLock lock];
[machine machineDidChangeClockRate];
[machineAccessLock unlock];
}
void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) {
[machineAccessLock lock];
[machine machineDidChangeClockIsUnlimited];
[machineAccessLock unlock];
}
};
@implementation CSMachine {
SpeakerDelegate _speakerDelegate;
MachineDelegate _machineDelegate;
NSLock *_delegateMachineAccessLock;
CSStaticAnalyser *_analyser;
@ -77,12 +61,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
_delegateMachineAccessLock = [[NSLock alloc] init];
_machineDelegate.machine = self;
_speakerDelegate.machine = self;
_machineDelegate.machineAccessLock = _delegateMachineAccessLock;
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
_machine->crt_machine()->set_delegate(&_machineDelegate);
}
return self;
}
@ -91,14 +71,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length];
}
- (void)machineDidChangeClockRate {
[self.delegate machineDidChangeClockRate:self];
}
- (void)machineDidChangeClockIsUnlimited {
[self.delegate machineDidChangeClockIsUnlimited:self];
}
- (void)dealloc {
// The two delegate's references to this machine are nilled out here because close_output may result
// in a data flush, which might cause an audio callback, which could cause the audio queue to decide
@ -107,7 +79,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
// They are nilled inside an explicit lock because that allows the delegates to protect their entire
// call into the machine, not just the pointer access.
[_delegateMachineAccessLock lock];
_machineDelegate.machine = nil;
_speakerDelegate.machine = nil;
[_delegateMachineAccessLock unlock];
@ -146,9 +117,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
}
}
- (void)runForNumberOfCycles:(int)numberOfCycles {
- (void)runForInterval:(NSTimeInterval)interval {
@synchronized(self) {
_machine->crt_machine()->run_for(Cycles(numberOfCycles));
_machine->crt_machine()->run_for(interval);
}
}
@ -175,10 +146,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
return _machine->crt_machine()->get_clock_rate();
}
- (BOOL)clockIsUnlimited {
return _machine->crt_machine()->get_clock_is_unlimited() ? YES : NO;
}
- (void)paste:(NSString *)paste {
KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine();
if(keyboardMachine)

View File

@ -13,15 +13,13 @@
@protocol CSBestEffortUpdaterDelegate <NSObject>
- (void)bestEffortUpdater:(CSBestEffortUpdater *)bestEffortUpdater runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate;
- (void)bestEffortUpdater:(CSBestEffortUpdater *)bestEffortUpdater runForInterval:(NSTimeInterval)interval didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate;
@end
@interface CSBestEffortUpdater : NSObject
@property (nonatomic, assign) double clockRate;
@property (nonatomic, assign) BOOL runAsUnlimited;
@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
- (void)update;

View File

@ -14,12 +14,12 @@ struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
__weak id<CSBestEffortUpdaterDelegate> delegate;
NSLock *delegateLock;
void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) {
void update(Concurrency::BestEffortUpdater *updater, Time::Seconds cycles, bool did_skip_previous_update) {
[delegateLock lock];
__weak id<CSBestEffortUpdaterDelegate> delegateCopy = delegate;
[delegateLock unlock];
[delegateCopy bestEffortUpdater:nil runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:did_skip_previous_update];
[delegateCopy bestEffortUpdater:nil runForInterval:(NSTimeInterval)cycles didSkipPreviousUpdate:did_skip_previous_update];
}
};
@ -51,11 +51,6 @@ struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
_updater.flush();
}
- (void)setClockRate:(double)clockRate {
_clockRate = clockRate;
_updater.set_clock_rate(clockRate);
}
- (void)setDelegate:(id<CSBestEffortUpdaterDelegate>)delegate {
[_delegateLock lock];
_updaterDelegate.delegate = delegate;