From 4e9ae654592efe34138e86f4c03098c4ca87c119 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Jul 2022 09:56:13 -0400 Subject: [PATCH] Reintroduce sync matching. --- Machines/TimedMachine.hpp | 4 + .../Mac/Clock Signal/Machine/CSMachine.mm | 137 ++---------------- .../Mac/Clock Signal/Views/CSScanTargetView.h | 5 + .../Mac/Clock Signal/Views/CSScanTargetView.m | 6 +- 4 files changed, 26 insertions(+), 126 deletions(-) diff --git a/Machines/TimedMachine.hpp b/Machines/TimedMachine.hpp index f97a5ac66..fef5015b1 100644 --- a/Machines/TimedMachine.hpp +++ b/Machines/TimedMachine.hpp @@ -39,6 +39,10 @@ class TimedMachine { fiction: it will apply across the system, including to the CRT. */ virtual void set_speed_multiplier(double multiplier) { + if(speed_multiplier_ == multiplier) { + return; + } + speed_multiplier_ = multiplier; auto audio_producer = dynamic_cast(this); diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index b9b312b79..a178049db 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -117,18 +117,11 @@ struct ActivityObserver: public Activity::Observer { CSStaticAnalyser *_analyser; std::unique_ptr _machine; MachineTypes::JoystickMachine *_joystickMachine; - Concurrency::AsyncUpdater updater; CSJoystickManager *_joystickManager; NSMutableArray *_leds; - CSHighPrecisionTimer *_timer; - std::atomic_flag _isUpdating; - Time::Nanos _syncTime; - Time::Nanos _timeDiff; - double _refreshPeriod; - BOOL _isSyncLocking; - + Concurrency::AsyncUpdater updater; Time::ScanSynchroniser _scanSynchroniser; NSTimer *_joystickTimer; @@ -177,7 +170,6 @@ struct ActivityObserver: public Activity::Observer { _joystickMachine = _machine->joystick_machine(); [self updateJoystickTimer]; - _isUpdating.clear(); } return self; } @@ -687,135 +679,30 @@ struct ActivityObserver: public Activity::Observer { outputs |= MachineTypes::TimedMachine::Output::Audio; } updater.performer.machine->flush_output(outputs); + + const auto scanStatus = self->_machine->scan_producer()->get_scan_status(); + const bool canSynchronise = self->_scanSynchroniser.can_synchronise(scanStatus, self.view.refreshPeriod); + if(canSynchronise) { + const double multiplier = self->_scanSynchroniser.next_speed_multiplier(self->_machine->scan_producer()->get_scan_status()); + self->_machine->timed_machine()->set_speed_multiplier(multiplier); + } else { + self->_machine->timed_machine()->set_speed_multiplier(1.0); + } + [self.view updateBacking]; dispatch_async(dispatch_get_main_queue(), ^{ [self.view draw]; }); }); - - // TODO: restory sync locking; see below. - - // First order of business: grab a timestamp. -/* const auto timeNow = Time::nanos_now(); - - BOOL isSyncLocking; - @synchronized(self) { - // Store a means to map from CVTimeStamp.hostTime to Time::Nanos; - // there is an extremely dodgy assumption here that the former is in ns. - // If you can find a well-defined way to get the CVTimeStamp.hostTime units, - // whether at runtime or via preprocessor define, I'd love to know about it. - if(!_timeDiff) { - _timeDiff = int64_t(timeNow) - int64_t(now->hostTime); - } - - // Store the next end-of-frame time. TODO: and start of next and implied visible duration, if raster racing? - _syncTime = int64_t(now->hostTime) + _timeDiff; - - // Set the current refresh period. - _refreshPeriod = double(now->videoRefreshPeriod) / double(now->videoTimeScale); - - // Determine where responsibility lies for drawing. - isSyncLocking = _isSyncLocking; - } - - // 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 120 - - (void)start { -// _timer = [[CSHighPrecisionTimer alloc] initWithTask:^{ -// updater.update([] {}); -// } interval:uint64_t(1000000000) / uint64_t(TICKS)]; - -// 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 - // (subject to a cap to avoid potential perpetual regression). - const auto timeNow = Time::nanos_now(); - lastTime = std::max(timeNow - Time::Nanos(10'000'000'000 / TICKS), lastTime); - const auto duration = timeNow - lastTime; - - BOOL splitAndSync = NO; - @synchronized(self) { - // Post on input events. - @synchronized(self->_inputEvents) { - for(dispatch_block_t action: self->_inputEvents) { - action(); - } - [self->_inputEvents removeAllObjects]; - } - - // If this tick includes vsync then inspect the machine. - // - // _syncTime = 0 is used here as a sentinel to mark that a sync time is known; - // this with the >= test ensures that no syncs are missed even if some sort of - // performance problem is afoot (e.g. I'm debugging). - if(self->_syncTime && timeNow >= self->_syncTime) { - splitAndSync = self->_isSyncLocking = self->_scanSynchroniser.can_synchronise(self->_machine->scan_producer()->get_scan_status(), self->_refreshPeriod); - - // If the time window is being split, run up to the split, then check out machine speed, possibly - // adjusting multiplier, then run after the split. Include a sanity check against an out-of-bounds - // _syncTime; that can happen when debugging (possibly inter alia?). - if(splitAndSync) { - if(self->_syncTime >= lastTime) { - self->_machine->timed_machine()->run_for((double)(self->_syncTime - lastTime) / 1e9); - self->_machine->timed_machine()->set_speed_multiplier( - self->_scanSynchroniser.next_speed_multiplier(self->_machine->scan_producer()->get_scan_status()) - ); - self->_machine->timed_machine()->run_for((double)(timeNow - self->_syncTime) / 1e9); - } else { - self->_machine->timed_machine()->run_for((double)(timeNow - lastTime) / 1e9); - } - } - - self->_syncTime = 0; - } - - // If the time window is being split, run up to the split, then check out machine speed, possibly - // adjusting multiplier, then run after the split. - if(!splitAndSync) { - self->_machine->timed_machine()->run_for((double)duration / 1e9); - } - } - - // If this was not a split-and-sync then dispatch the update request asynchronously, unless - // there is an earlier one not yet finished, in which case don't worry about it for now. - // - // If it was a split-and-sync then spin until it is safe to dispatch, and dispatch with - // a concluding draw. Implicit assumption here: whatever is left to be done in the final window - // can be done within the retrace period. - auto wasUpdating = self->_isUpdating.test_and_set(); - if(wasUpdating && splitAndSync) { - while(self->_isUpdating.test_and_set()); - wasUpdating = false; - } - if(!wasUpdating) { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ - [self.view updateBacking]; - if(splitAndSync) { - [self.view draw]; - } - self->_isUpdating.clear(); - }); - } - - lastTime = timeNow; - } interval:uint64_t(1000000000) / uint64_t(TICKS)];*/ + // A no-op; retained in case of future changes to the manner of scheduling. } -#undef TICKS - - (void)stop { updater.stop(); -// [_timer invalidate]; -// _timer = nil; } + (BOOL)attemptInstallROM:(NSURL *)url { diff --git a/OSBindings/Mac/Clock Signal/Views/CSScanTargetView.h b/OSBindings/Mac/Clock Signal/Views/CSScanTargetView.h index 5373e2be2..e53d2318e 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSScanTargetView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSScanTargetView.h @@ -170,4 +170,9 @@ */ - (void)willChangeScanTargetOwner; +/*! + @returns The period of a frame. +*/ +@property(nonatomic, readonly) double refreshPeriod; + @end diff --git a/OSBindings/Mac/Clock Signal/Views/CSScanTargetView.m b/OSBindings/Mac/Clock Signal/Views/CSScanTargetView.m index 3b7af0af5..4d54dceb8 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSScanTargetView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSScanTargetView.m @@ -103,8 +103,12 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const [self stopDisplayLink]; } +- (double)refreshPeriod { + return CVDisplayLinkGetActualOutputVideoRefreshPeriod(_displayLink); +} + - (void)stopDisplayLink { - const double duration = CVDisplayLinkGetActualOutputVideoRefreshPeriod(_displayLink); + const double duration = [self refreshPeriod]; CVDisplayLinkStop(_displayLink); // This is a workaround; CVDisplayLinkStop does not wait for any existing call to the