mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Reintroduce sync matching.
This commit is contained in:
parent
d16dc3a5d7
commit
4e9ae65459
@ -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);
|
||||
|
@ -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();
|
||||
// A no-op; retained in case of future changes to the manner of scheduling.
|
||||
}
|
||||
[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)];*/
|
||||
}
|
||||
|
||||
#undef TICKS
|
||||
|
||||
- (void)stop {
|
||||
updater.stop();
|
||||
// [_timer invalidate];
|
||||
// _timer = nil;
|
||||
}
|
||||
|
||||
+ (BOOL)attemptInstallROM:(NSURL *)url {
|
||||
|
@ -170,4 +170,9 @@
|
||||
*/
|
||||
- (void)willChangeScanTargetOwner;
|
||||
|
||||
/*!
|
||||
@returns The period of a frame.
|
||||
*/
|
||||
@property(nonatomic, readonly) double refreshPeriod;
|
||||
|
||||
@end
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user