From c26c8992ae37f89a80891a44a91fc55d1615a441 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Sat, 8 Feb 2020 21:27:04 -0500
Subject: [PATCH] Reintroduces joystick support; eliminates
 CSBestEffortUpdater.

---
 .../Clock Signal.xcodeproj/project.pbxproj    |  14 ---
 .../ClockSignal-Bridging-Header.h             |   1 -
 .../Mac/Clock Signal/Machine/CSMachine.h      |   2 -
 .../Mac/Clock Signal/Machine/CSMachine.mm     | 108 ++++++++++--------
 .../Updater/CSBestEffortUpdater.h             |  27 -----
 .../Updater/CSBestEffortUpdater.mm            |  51 ---------
 6 files changed, 61 insertions(+), 142 deletions(-)
 delete mode 100644 OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h
 delete mode 100644 OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm

diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj
index f4165bef8..751840b21 100644
--- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
+++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
@@ -812,7 +812,6 @@
 		4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
 		4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
 		4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
-		4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; };
 		4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; };
 		4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
 		4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
@@ -1689,8 +1688,6 @@
 		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>"; };
 		4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTargetGLSLFragments.cpp; sourceTree = "<group>"; };
-		4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
-		4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
 		4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Log.hpp; path = ../../Outputs/Log.hpp; sourceTree = "<group>"; };
 		4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
 		4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
@@ -3276,7 +3273,6 @@
 				4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
 				4BE5F85A1C3E1C2500C43F01 /* Resources */,
 				4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
-				4BD5F1961D1352A000631CD1 /* Updater */,
 				4B55CE5A1C3B7D6F0093A61B /* Views */,
 			);
 			path = "Clock Signal";
@@ -3624,15 +3620,6 @@
 			name = 1770;
 			sourceTree = "<group>";
 		};
-		4BD5F1961D1352A000631CD1 /* Updater */ = {
-			isa = PBXGroup;
-			children = (
-				4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */,
-				4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */,
-			);
-			name = Updater;
-			sourceTree = "<group>";
-		};
 		4BD67DC8209BE4D600AB2146 /* DiskII */ = {
 			isa = PBXGroup;
 			children = (
@@ -4493,7 +4480,6 @@
 				4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
 				4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */,
 				4BB0A65B2044FD3000FB3688 /* SN76489.cpp in Sources */,
-				4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */,
 				4B894532201967B4007DE474 /* 6502.cpp in Sources */,
 				4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */,
 				4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h
index c39778ffd..ebbf066d9 100644
--- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h	
+++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h	
@@ -13,7 +13,6 @@
 #import	"CSOpenGLView.h"
 #import "CSROMReceiverView.h"
 
-#import "CSBestEffortUpdater.h"
 #import "CSJoystickManager.h"
 
 #import "NSData+CRC32.h"
diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h
index 18bda6979..844f20243 100644
--- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h	
+++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h	
@@ -57,8 +57,6 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
 */
 - (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
 
-- (NSTimeInterval)runForInterval:(NSTimeInterval)interval untilEvent:(int)events;
-
 - (float)idealSamplingRateFromRange:(NSRange)range;
 - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize;
 
diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm
index 19f8d1292..dad64bee8 100644
--- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm	
+++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm	
@@ -159,6 +159,8 @@ struct ActivityObserver: public Activity::Observer {
 	double _refreshPeriod;
 	BOOL _isSyncLocking;
 
+	NSTimer *_joystickTimer;
+
 	std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> _scanTarget;
 }
 
@@ -211,6 +213,7 @@ struct ActivityObserver: public Activity::Observer {
 		_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
 
 		_joystickMachine = _machine->joystick_machine();
+		[self updateJoystickTimer];
 		_isUpdating.clear();
 	}
 	return self;
@@ -225,6 +228,8 @@ struct ActivityObserver: public Activity::Observer {
 }
 
 - (void)dealloc {
+	[_joystickTimer invalidate];
+
 	// 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
 	// that it's out of data, resulting in an attempt further to run the machine while it is dealloc'ing.
@@ -270,57 +275,64 @@ struct ActivityObserver: public Activity::Observer {
 	}
 }
 
-- (NSTimeInterval)runForInterval:(NSTimeInterval)interval untilEvent:(int)events {
+- (void)updateJoystickTimer {
+	// Joysticks updates are scheduled for a nominal 200 polls/second, using a plain old NSTimer.
+	if(_joystickMachine && _joystickManager) {
+		_joystickTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 200.0 target:self selector:@selector(updateJoysticks) userInfo:nil repeats:YES];
+	} else {
+		[_joystickTimer invalidate];
+		_joystickTimer = nil;
+	}
+}
+
+- (void)updateJoysticks {
+	[_joystickManager update];
+
+	// TODO: configurable mapping from physical joypad inputs to machine inputs.
+	// Until then, apply a default mapping.
+
 	@synchronized(self) {
-		if(_joystickMachine && _joystickManager) {
-			[_joystickManager update];
+		size_t c = 0;
+		auto &machine_joysticks = _joystickMachine->get_joysticks();
+		for(CSJoystick *joystick in _joystickManager.joysticks) {
+			size_t target = c % machine_joysticks.size();
+			++c;
 
-			// TODO: configurable mapping from physical joypad inputs to machine inputs.
-			// Until then, apply a default mapping.
-
-			size_t c = 0;
-			auto &machine_joysticks = _joystickMachine->get_joysticks();
-			for(CSJoystick *joystick in _joystickManager.joysticks) {
-				size_t target = c % machine_joysticks.size();
-				++c;
-
-				// Post the first two analogue axes presented by the controller as horizontal and vertical inputs,
-				// unless the user seems to be using a hat.
-				// SDL will return a value in the range [-32768, 32767], so map from that to [0, 1.0]
-				if(!joystick.hats.count || !joystick.hats[0].direction) {
-					if(joystick.axes.count > 0) {
-						const float x_axis = joystick.axes[0].position;
-						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Horizontal), x_axis);
-					}
-					if(joystick.axes.count > 1) {
-						const float y_axis = joystick.axes[1].position;
-						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis);
-					}
-				} else {
-					// Forward hats as directions; hats always override analogue inputs.
-					for(CSJoystickHat *hat in joystick.hats) {
-						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp));
-						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown));
-						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft));
-						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight));
-					}
+			// Post the first two analogue axes presented by the controller as horizontal and vertical inputs,
+			// unless the user seems to be using a hat.
+			// SDL will return a value in the range [-32768, 32767], so map from that to [0, 1.0]
+			if(!joystick.hats.count || !joystick.hats[0].direction) {
+				if(joystick.axes.count > 0) {
+					const float x_axis = joystick.axes[0].position;
+					machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Horizontal), x_axis);
 				}
+				if(joystick.axes.count > 1) {
+					const float y_axis = joystick.axes[1].position;
+					machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis);
+				}
+			} else {
+				// Forward hats as directions; hats always override analogue inputs.
+				for(CSJoystickHat *hat in joystick.hats) {
+					machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp));
+					machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown));
+					machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft));
+					machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight));
+				}
+			}
 
-				// Forward all fire buttons, mapping as a function of index.
-				if(machine_joysticks[target]->get_number_of_fire_buttons()) {
-					std::vector<bool> button_states((size_t)machine_joysticks[target]->get_number_of_fire_buttons());
-					for(CSJoystickButton *button in joystick.buttons) {
-						if(button.isPressed) button_states[(size_t)(((int)button.index - 1) % machine_joysticks[target]->get_number_of_fire_buttons())] = true;
-					}
-					for(size_t index = 0; index < button_states.size(); ++index) {
-						machine_joysticks[target]->set_input(
-							Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Fire, index),
-							button_states[index]);
-					}
+			// Forward all fire buttons, mapping as a function of index.
+			if(machine_joysticks[target]->get_number_of_fire_buttons()) {
+				std::vector<bool> button_states((size_t)machine_joysticks[target]->get_number_of_fire_buttons());
+				for(CSJoystickButton *button in joystick.buttons) {
+					if(button.isPressed) button_states[(size_t)(((int)button.index - 1) % machine_joysticks[target]->get_number_of_fire_buttons())] = true;
+				}
+				for(size_t index = 0; index < button_states.size(); ++index) {
+					machine_joysticks[target]->set_input(
+						Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Fire, index),
+						button_states[index]);
 				}
 			}
 		}
-		return _machine->crt_machine()->run_until(interval, events);
 	}
 }
 
@@ -387,15 +399,17 @@ struct ActivityObserver: public Activity::Observer {
 }
 
 - (void)setJoystickManager:(CSJoystickManager *)joystickManager {
-	@synchronized(self) {
-		_joystickManager = joystickManager;
-		if(_joystickMachine) {
+	_joystickManager = joystickManager;
+	if(_joystickMachine) {
+		@synchronized(self) {
 			auto &machine_joysticks = _joystickMachine->get_joysticks();
 			for(const auto &joystick: machine_joysticks) {
 				joystick->reset_all_inputs();
 			}
 		}
 	}
+
+	[self updateJoystickTimer];
 }
 
 - (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h
deleted file mode 100644
index 970104107..000000000
--- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h	
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-//  CSBestEffortUpdater.h
-//  Clock Signal
-//
-//  Created by Thomas Harte on 16/06/2016.
-//  Copyright 2016 Thomas Harte. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-#import <CoreVideo/CoreVideo.h>
-
-#import "CSMachine.h"
-
-// The following is coupled to the definitions in CRTMachine.hpp, but exposed here
-// for the benefit of Swift.
-typedef NS_ENUM(NSInteger, CSBestEffortUpdaterEvent) {
-	CSBestEffortUpdaterEventAudioNeeded = 1 << 0
-};
-
-@interface CSBestEffortUpdater : NSObject
-
-- (void)update;
-- (void)updateWithEvent:(CSBestEffortUpdaterEvent)event;
-- (void)flush;
-- (void)setMachine:(CSMachine *)machine;
-
-@end
diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm
deleted file mode 100644
index a0ba3e21f..000000000
--- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm	
+++ /dev/null
@@ -1,51 +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 "BestEffortUpdater.hpp"
-
-struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
-	__weak CSMachine *machine;
-
-	Time::Seconds update(Concurrency::BestEffortUpdater *updater, Time::Seconds seconds, bool did_skip_previous_update, int flags) final {
-		return [machine runForInterval:seconds untilEvent:flags];
-	}
-};
-
-@implementation CSBestEffortUpdater {
-	Concurrency::BestEffortUpdater _updater;
-	UpdaterDelegate _updaterDelegate;
-}
-
-- (instancetype)init {
-	self = [super init];
-	if(self) {
-		_updater.set_delegate(&_updaterDelegate);
-	}
-	return self;
-}
-
-- (void)update {
-	_updater.update();
-}
-
-- (void)updateWithEvent:(CSBestEffortUpdaterEvent)event {
-	_updater.update((int)event);
-}
-
-- (void)flush {
-	_updater.flush();
-}
-
-- (void)setMachine:(CSMachine *)machine {
-	_updater.flush();
-	_updaterDelegate.machine = machine;
-}
-
-@end