1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-16 15:29:34 +00:00

Commutes the best-effort updater into C++11.

This commit is contained in:
Thomas Harte 2017-10-05 18:09:58 -04:00
parent e194a2a015
commit f40e1fd840
6 changed files with 203 additions and 89 deletions

View File

@ -0,0 +1,58 @@
//
// BestEffortUpdater.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "BestEffortUpdater.hpp"
#include <cmath>
using namespace Concurrency;
void BestEffortUpdater::update() {
if(!update_is_ongoing_.test_and_set()) {
async_task_queue_.enqueue([this]() {
std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
auto elapsed = now - previous_time_point_;
previous_time_point_ = now;
if(has_previous_time_point_) {
int64_t timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
double cycles = ((timestamp * clock_rate_) / 1e9) + error_;
error_ = fmod(cycles, 1.0);
if(delegate_) {
delegate_->update(this, (int)cycles, has_skipped_);
}
has_skipped_ = false;
} else {
has_previous_time_point_ = true;
}
update_is_ongoing_.clear();
});
} else {
async_task_queue_.enqueue([this]() {
has_skipped_ = true;
});
}
}
void BestEffortUpdater::flush() {
async_task_queue_.flush();
}
void BestEffortUpdater::set_delegate(Delegate *delegate) {
async_task_queue_.enqueue([this, delegate]() {
delegate_ = delegate;
});
}
void BestEffortUpdater::set_clock_rate(double clock_rate) {
async_task_queue_.enqueue([this, clock_rate]() {
this->clock_rate_ = clock_rate;
});
}

View File

@ -0,0 +1,61 @@
//
// BestEffortUpdater.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef BestEffortUpdater_hpp
#define BestEffortUpdater_hpp
#include <atomic>
#include <chrono>
#include "AsyncTaskQueue.hpp"
namespace Concurrency {
/*!
Accepts timing cues from multiple threads and ensures that a delegate receives calls to total
a certain number of cycles per second, that those calls are strictly serialised, and that no
backlog of calls accrues.
*/
class BestEffortUpdater {
public:
/// A delegate receives timing cues.
struct Delegate {
virtual void update(BestEffortUpdater *updater, int cycles, 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.
*/
void update();
/// Blocks until any ongoing update is complete.
void flush();
private:
std::atomic_flag update_is_ongoing_;
AsyncTaskQueue async_task_queue_;
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;
};
}
#endif /* BestEffortUpdater_hpp */

View File

@ -106,6 +106,7 @@
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; };
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
4B7BC7F61F58F7D200D1B1B4 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */; };
4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */; };
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; };
4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334851F5DA3780097E338 /* 6502Storage.cpp */; };
@ -442,7 +443,7 @@
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3A3091EE755C800B5B501 /* Video.cpp */; };
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; };
4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; };
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
@ -674,6 +675,8 @@
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BestEffortUpdater.cpp; path = ../../Concurrency/BestEffortUpdater.cpp; sourceTree = "<group>"; };
4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BestEffortUpdater.hpp; path = ../../Concurrency/BestEffortUpdater.hpp; sourceTree = "<group>"; };
4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PartialMachineCycle.cpp; sourceTree = "<group>"; };
4B8334831F5DA0360097E338 /* Z80Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Z80Storage.cpp; sourceTree = "<group>"; };
4B8334851F5DA3780097E338 /* 6502Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Storage.cpp; sourceTree = "<group>"; };
@ -1073,7 +1076,7 @@
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
@ -1390,6 +1393,8 @@
children = (
4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */,
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */,
4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */,
4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */,
);
name = Concurrency;
sourceTree = "<group>";
@ -2362,7 +2367,7 @@
isa = PBXGroup;
children = (
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */,
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */,
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */,
);
name = Updater;
sourceTree = "<group>";
@ -2939,7 +2944,7 @@
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */,
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */,
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */,
4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */,
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */,
@ -2982,6 +2987,7 @@
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */,
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */,
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,

View File

@ -22,7 +22,7 @@
@property (nonatomic, assign) double clockRate;
@property (nonatomic, assign) BOOL runAsUnlimited;
@property (atomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
- (void)update;
- (void)flush;

View File

@ -1,84 +0,0 @@
//
// CSBestEffortUpdater.m
// Clock Signal
//
// Created by Thomas Harte on 16/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "CSBestEffortUpdater.h"
#include <stdatomic.h>
@implementation CSBestEffortUpdater
{
// these are inherently handled only by thread-safe constructions
atomic_flag _updateIsOngoing;
dispatch_queue_t _serialDispatchQueue;
// these are permitted for modification on _serialDispatchQueue only
NSTimeInterval _previousTimeInterval;
NSTimeInterval _cyclesError;
BOOL _hasSkipped;
id<CSBestEffortUpdaterDelegate> _delegate;
}
- (instancetype)init
{
if(self = [super init])
{
_serialDispatchQueue = dispatch_queue_create("Best Effort Updater", DISPATCH_QUEUE_SERIAL);
// This is a workaround for assigning the correct initial value within Objective-C's form.
atomic_flag initialFlagValue = ATOMIC_FLAG_INIT;
_updateIsOngoing = initialFlagValue;
}
return self;
}
- (void)update {
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
if(!atomic_flag_test_and_set(&_updateIsOngoing)) {
dispatch_async(_serialDispatchQueue, ^{
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval) {
NSTimeInterval timeToRunFor = timeInterval - _previousTimeInterval;
double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError;
_cyclesError = fmod(cyclesToRunFor, 1.0);
NSUInteger integerCyclesToRunFor = (NSUInteger)MIN(cyclesToRunFor, self.clockRate * 0.5);
// treat 'unlimited' as running at a factor of 10
if(self.runAsUnlimited) integerCyclesToRunFor *= 10;
[_delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
}
_previousTimeInterval = timeInterval;
_hasSkipped = NO;
atomic_flag_clear(&_updateIsOngoing);
});
} else {
dispatch_async(_serialDispatchQueue, ^{
_hasSkipped = YES;
});
}
}
- (void)flush {
dispatch_sync(_serialDispatchQueue, ^{});
}
- (void)setDelegate:(id<CSBestEffortUpdaterDelegate>)delegate {
dispatch_sync(_serialDispatchQueue, ^{
_delegate = delegate;
});
}
- (id<CSBestEffortUpdaterDelegate>)delegate {
__block id<CSBestEffortUpdaterDelegate> delegate;
dispatch_sync(_serialDispatchQueue, ^{
delegate = _delegate;
});
return delegate;
}
@end

View File

@ -0,0 +1,73 @@
//
// CSBestEffortUpdater.m
// Clock Signal
//
// Created by Thomas Harte on 16/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "CSBestEffortUpdater.h"
#include "BestEffortUpdater.hpp"
struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
__weak id<CSBestEffortUpdaterDelegate> delegate;
NSLock *delegateLock;
void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) {
[delegateLock lock];
[delegate bestEffortUpdater:nil runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:did_skip_previous_update];
[delegateLock unlock];
}
};
@implementation CSBestEffortUpdater {
Concurrency::BestEffortUpdater _updater;
UpdaterDelegate _updaterDelegate;
NSLock *_delegateLock;
}
- (instancetype)init {
self = [super init];
if(self) {
_delegateLock = [[NSLock alloc] init];
_updaterDelegate.delegateLock = _delegateLock;
_updater.set_delegate(&_updaterDelegate);
}
return self;
}
- (void)dealloc {
_updater.flush();
}
- (void)update {
_updater.update();
}
- (void)flush {
_updater.flush();
}
- (void)setClockRate:(double)clockRate {
_clockRate = clockRate;
_updater.set_clock_rate(clockRate);
}
- (void)setDelegate:(id<CSBestEffortUpdaterDelegate>)delegate {
[_delegateLock lock];
_updaterDelegate.delegate = delegate;
[_delegateLock unlock];
}
- (id<CSBestEffortUpdaterDelegate>)delegate {
id<CSBestEffortUpdaterDelegate> delegate;
[_delegateLock lock];
delegate = _updaterDelegate.delegate;
[_delegateLock unlock];
return delegate;
}
@end