mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 01:31:42 +00:00
Merge pull request #254 from TomHarte/C++BestEffortUpdater
Commutes the best-effort updater into C++11.
This commit is contained in:
commit
2511fc8401
67
Concurrency/BestEffortUpdater.cpp
Normal file
67
Concurrency/BestEffortUpdater.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// 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() {
|
||||
// Perform an update only if one is not currently ongoing.
|
||||
if(!update_is_ongoing_.test_and_set()) {
|
||||
async_task_queue_.enqueue([this]() {
|
||||
// Get time now using the highest-resolution clock provided by the implementation, and determine
|
||||
// the duration since the last time this section was entered.
|
||||
const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
|
||||
const auto elapsed = now - previous_time_point_;
|
||||
previous_time_point_ = now;
|
||||
|
||||
if(has_previous_time_point_) {
|
||||
// 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);
|
||||
|
||||
if(delegate_) {
|
||||
delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
|
||||
}
|
||||
has_skipped_ = false;
|
||||
}
|
||||
} else {
|
||||
has_previous_time_point_ = true;
|
||||
}
|
||||
|
||||
// Allow furthers updates to occur.
|
||||
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 *const delegate) {
|
||||
async_task_queue_.enqueue([this, delegate]() {
|
||||
delegate_ = delegate;
|
||||
});
|
||||
}
|
||||
|
||||
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
|
||||
async_task_queue_.enqueue([this, clock_rate]() {
|
||||
this->clock_rate_ = clock_rate;
|
||||
});
|
||||
}
|
63
Concurrency/BestEffortUpdater.hpp
Normal file
63
Concurrency/BestEffortUpdater.hpp
Normal file
@ -0,0 +1,63 @@
|
||||
//
|
||||
// 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.
|
||||
|
||||
No guarantees about the thread that the delegate will be called on are made.
|
||||
*/
|
||||
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 */
|
@ -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 */,
|
||||
|
@ -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;
|
||||
|
@ -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
|
73
OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm
Normal file
73
OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm
Normal 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
|
Loading…
Reference in New Issue
Block a user