1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-22 12:33:29 +00:00

Reintroduce sync matching.

This commit is contained in:
Thomas Harte 2022-07-12 09:56:13 -04:00
parent d16dc3a5d7
commit 4e9ae65459
4 changed files with 26 additions and 126 deletions

View File

@ -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<AudioProducer *>(this);

View File

@ -117,18 +117,11 @@ struct ActivityObserver: public Activity::Observer {
CSStaticAnalyser *_analyser;
std::unique_ptr<Machine::DynamicMachine> _machine;
MachineTypes::JoystickMachine *_joystickMachine;
Concurrency::AsyncUpdater<MachineUpdater> updater;
CSJoystickManager *_joystickManager;
NSMutableArray<CSMachineLED *> *_leds;
CSHighPrecisionTimer *_timer;
std::atomic_flag _isUpdating;
Time::Nanos _syncTime;
Time::Nanos _timeDiff;
double _refreshPeriod;
BOOL _isSyncLocking;
Concurrency::AsyncUpdater<MachineUpdater> 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 {

View File

@ -170,4 +170,9 @@
*/
- (void)willChangeScanTargetOwner;
/*!
@returns The period of a frame.
*/
@property(nonatomic, readonly) double refreshPeriod;
@end

View File

@ -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