From f40e1fd840fcf05b4c8cf87dc1da734b171e99ef Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2017 18:09:58 -0400 Subject: [PATCH] Commutes the best-effort updater into C++11. --- Concurrency/BestEffortUpdater.cpp | 58 +++++++++++++ Concurrency/BestEffortUpdater.hpp | 61 ++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 14 +++- .../Updater/CSBestEffortUpdater.h | 2 +- .../Updater/CSBestEffortUpdater.m | 84 ------------------- .../Updater/CSBestEffortUpdater.mm | 73 ++++++++++++++++ 6 files changed, 203 insertions(+), 89 deletions(-) create mode 100644 Concurrency/BestEffortUpdater.cpp create mode 100644 Concurrency/BestEffortUpdater.hpp delete mode 100644 OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m create mode 100644 OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm diff --git a/Concurrency/BestEffortUpdater.cpp b/Concurrency/BestEffortUpdater.cpp new file mode 100644 index 000000000..716303ca5 --- /dev/null +++ b/Concurrency/BestEffortUpdater.cpp @@ -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 + +using namespace Concurrency; + +void BestEffortUpdater::update() { + if(!update_is_ongoing_.test_and_set()) { + async_task_queue_.enqueue([this]() { + std::chrono::time_point 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(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; + }); +} diff --git a/Concurrency/BestEffortUpdater.hpp b/Concurrency/BestEffortUpdater.hpp new file mode 100644 index 000000000..33b267bbb --- /dev/null +++ b/Concurrency/BestEffortUpdater.hpp @@ -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 +#include + +#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 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 */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f15c35951..0cdf62929 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = ""; }; 4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = ""; }; + 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BestEffortUpdater.cpp; path = ../../Concurrency/BestEffortUpdater.cpp; sourceTree = ""; }; + 4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BestEffortUpdater.hpp; path = ../../Concurrency/BestEffortUpdater.hpp; sourceTree = ""; }; 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PartialMachineCycle.cpp; sourceTree = ""; }; 4B8334831F5DA0360097E338 /* Z80Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Z80Storage.cpp; sourceTree = ""; }; 4B8334851F5DA3780097E338 /* 6502Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Storage.cpp; sourceTree = ""; }; @@ -1073,7 +1076,7 @@ 4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = ""; }; 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = ""; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = ""; }; - 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = ""; }; + 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = ""; }; 4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = ""; }; 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; @@ -1390,6 +1393,8 @@ children = ( 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */, 4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */, + 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */, + 4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */, ); name = Concurrency; sourceTree = ""; @@ -2362,7 +2367,7 @@ isa = PBXGroup; children = ( 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */, - 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */, + 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */, ); name = Updater; sourceTree = ""; @@ -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 */, diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h index 5bf76a152..7130f3636 100644 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h @@ -22,7 +22,7 @@ @property (nonatomic, assign) double clockRate; @property (nonatomic, assign) BOOL runAsUnlimited; -@property (atomic, weak) id delegate; +@property (nonatomic, weak) id delegate; - (void)update; - (void)flush; diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m deleted file mode 100644 index 67b44d18c..000000000 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m +++ /dev/null @@ -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 - -@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 _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)delegate { - dispatch_sync(_serialDispatchQueue, ^{ - _delegate = delegate; - }); -} - -- (id)delegate { - __block id delegate; - dispatch_sync(_serialDispatchQueue, ^{ - delegate = _delegate; - }); - return delegate; -} - -@end diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm new file mode 100644 index 000000000..fdcb9dc9f --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm @@ -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 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)delegate { + [_delegateLock lock]; + _updaterDelegate.delegate = delegate; + [_delegateLock unlock]; +} + +- (id)delegate { + id delegate; + + [_delegateLock lock]; + delegate = _updaterDelegate.delegate; + [_delegateLock unlock]; + + return delegate; +} + +@end