diff --git a/ClockReceiver/TimeTypes.hpp b/ClockReceiver/TimeTypes.hpp index c8ef2fe1c..c697b274b 100644 --- a/ClockReceiver/TimeTypes.hpp +++ b/ClockReceiver/TimeTypes.hpp @@ -20,6 +20,11 @@ inline Nanos nanos_now() { return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); } +inline Seconds seconds(Nanos nanos) { + return double(nanos) / 1e9; +} + } #endif /* TimeTypes_h */ + diff --git a/Concurrency/AsyncUpdater.hpp b/Concurrency/AsyncUpdater.hpp index 700bd7951..74e1fd334 100644 --- a/Concurrency/AsyncUpdater.hpp +++ b/Concurrency/AsyncUpdater.hpp @@ -20,7 +20,7 @@ namespace Concurrency { template class AsyncUpdater { public: template AsyncUpdater(Args&&... args) : - performer_(std::forward(args)...), + performer(std::forward(args)...), actions_(std::make_unique()), performer_thread_{ [this] { @@ -30,7 +30,7 @@ template class AsyncUpdater { while(!should_quit) { // Wait for new actions to be signalled, and grab them. std::unique_lock lock(condition_mutex_); - while(!actions_) { + while(actions_->empty()) { condition_.wait(lock); } std::swap(actions, actions_); @@ -38,7 +38,7 @@ template class AsyncUpdater { // Update to now. auto time_now = Time::nanos_now(); - performer_.perform(time_now - last_fired); + performer.perform(time_now - last_fired); last_fired = time_now; // Perform the actions. @@ -68,10 +68,10 @@ template class AsyncUpdater { performer_thread_.join(); } - private: // The object that will actually receive time advances. - Performer performer_; + Performer performer; + private: // The list of actions waiting be performed. These will be elided, // increasing their latency, if the emulation thread falls behind. using ActionVector = std::vector>; diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index fd9b5810b..f4d3deccc 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -15,7 +15,6 @@ class MachineDocument: NSWindowDelegate, CSMachineDelegate, CSScanTargetViewResponderDelegate, - CSAudioQueueDelegate, CSROMReciverViewDelegate { // MARK: - Mutual Exclusion. @@ -274,17 +273,12 @@ class MachineDocument: if self.audioQueue == nil || self.audioQueue.samplingRate != selectedSamplingRate || self.audioQueue != self.machine.audioQueue { self.machine.audioQueue = nil self.audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate), isStereo:isStereo) - self.audioQueue.delegate = self self.machine.audioQueue = self.audioQueue self.machine.setAudioSamplingRate(Float(selectedSamplingRate), bufferSize:audioQueue.preferredBufferSize, stereo:isStereo) } } } - /// Responds to the CSAudioQueueDelegate dry-queue warning message by requesting a machine update. - final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) { - } - // MARK: - Pasteboard Forwarding. /// Forwards any text currently on the pasteboard into the active machine. diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 214aa280c..f1e11b04b 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -37,9 +37,16 @@ namespace { -struct Updater {}; +struct MachineUpdater { + void perform(Time::Nanos duration) { + // Top out at 0.1 seconds; this is a safeguard against a negative + // feedback loop if emulation starts running slowly. + const auto seconds = std::min(Time::seconds(duration), 0.1); + machine->run_for(seconds); + } -Concurrency::AsyncUpdater updater(); + MachineTypes::TimedMachine *machine = nullptr; +}; } @@ -49,6 +56,10 @@ Concurrency::AsyncUpdater updater(); - (void)addLED:(NSString *)led isPersistent:(BOOL)isPersistent; @end +@interface CSMachine() +- (void)audioQueueIsRunningDry:(nonnull CSAudioQueue *)audioQueue; +@end + struct LockProtectedDelegate { // Contractual promise is: machine, the pointer **and** the object **, may be accessed only // in sections protected by the machineAccessLock; @@ -106,6 +117,7 @@ struct ActivityObserver: public Activity::Observer { CSStaticAnalyser *_analyser; std::unique_ptr _machine; MachineTypes::JoystickMachine *_joystickMachine; + Concurrency::AsyncUpdater updater; CSJoystickManager *_joystickManager; NSMutableArray *_leds; @@ -142,6 +154,7 @@ struct ActivityObserver: public Activity::Observer { [missingROMs appendString:[NSString stringWithUTF8String:wstring_converter.to_bytes(description).c_str()]]; return nil; } + updater.performer.machine = _machine->timed_machine(); // Use the keyboard as a joystick if the machine has no keyboard, or if it has a 'non-exclusive' keyboard. _inputMode = @@ -219,6 +232,11 @@ struct ActivityObserver: public Activity::Observer { } } +- (void)setAudioQueue:(CSAudioQueue *)audioQueue { + _audioQueue = audioQueue; + audioQueue.delegate = self; +} + - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize stereo:(BOOL)stereo { @synchronized(self) { [self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate bufferSize:bufferSize stereo:stereo]; @@ -442,9 +460,12 @@ struct ActivityObserver: public Activity::Observer { } - (void)applyInputEvent:(dispatch_block_t)event { - @synchronized(_inputEvents) { - [_inputEvents addObject:event]; - } + updater.update([event] { + event(); + }); +// @synchronized(_inputEvents) { +// [_inputEvents addObject:event]; +// } } - (void)clearAllKeys { @@ -655,9 +676,21 @@ struct ActivityObserver: public Activity::Observer { #pragma mark - Timer +- (void)audioQueueIsRunningDry:(nonnull CSAudioQueue *)audioQueue { + // TODO: Make audio flushes overt, and do one here. + updater.update([] {}); +} + - (void)scanTargetViewDisplayLinkDidFire:(CSScanTargetView *)view now:(const CVTimeStamp *)now outputTime:(const CVTimeStamp *)outputTime { + updater.update([self] { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.view updateBacking]; + [self.view draw]; + }); + }); + // First order of business: grab a timestamp. - const auto timeNow = Time::nanos_now(); +/* const auto timeNow = Time::nanos_now(); BOOL isSyncLocking; @synchronized(self) { @@ -682,13 +715,14 @@ struct ActivityObserver: public Activity::Observer { // Draw the current output. (TODO: do this within the timer if either raster racing or, at least, sync matching). if(!isSyncLocking) { [self.view draw]; - } + }*/ } -#define TICKS 1000 +#define TICKS 120 - (void)start { - __block auto lastTime = Time::nanos_now(); +// updater.performer.machine = _machine->timed_machine(); +/* __block auto lastTime = Time::nanos_now(); _timer = [[CSHighPrecisionTimer alloc] initWithTask:^{ // Grab the time now and, therefore, the amount of time since the timer last fired @@ -762,7 +796,7 @@ struct ActivityObserver: public Activity::Observer { } lastTime = timeNow; - } interval:uint64_t(1000000000) / uint64_t(TICKS)]; + } interval:uint64_t(1000000000) / uint64_t(TICKS)];*/ } #undef TICKS