From f40e1fd840fcf05b4c8cf87dc1da734b171e99ef Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
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 <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;
+	});
+}
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 <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 */
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 = "<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 */,
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<CSBestEffortUpdaterDelegate> delegate;
+@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> 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 <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
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<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