Coherent GT allows clients to control the time in the UISystem
. Currently time control can only be applied across all views and not individual ones.
Enabling the feature has 3 steps:
The feature requires the client to return a monotonically increasing time point in seconds (could be fractional) whenever that's needed. Time cannot go back, it can only be slowed down or sped up. In case the client provides a time point in the past, the SDK will disregard it and use the last known time.
User-controlled time affects almost all subsystems with a few exceptions, such as timestamps for:
Most other subsystems are affected. For example, JavaScript garbage collection is affected, too. Care must be taken especially in the case where manual GC is used as the sweep duration is affected by the user time scale.
Another example is video playback. The rendered picture can be sped up or slowed down using the time control and that happens automatically. The audio playback rate must be adjusted in the client code, though, as Coherent GT provides the raw PCM bytes and is oblivious of the playback engine.
We have added a module for clients that want to allow user-generated UI content to be ran inside their projects, which helps mitigate the effects of Spectre and Meltdown vulnerabilities. It works by reducing the precision of timers and adding jitter to them. It is located in Modules\TimeClamper\ThirdParty\chromium\TimeClamper.h. Use the TimeClamper::ClampTimeResolution method to modify the time you provide to Coherent::UIGT::UISystemListener::GetMonotonicallyIncreasingTime
There are some specific details for different animation methods.
CSS animations are basically unaffected by the framerate and will work as expected regardless of the application FPS.
The two functions are very similar so we will only explore setInterval
, which has a slightly more complex behaviour. Internally, setInterval
works roughly as follows:
clearInterval
hasn't been called, start again with the first step for computing the next fire interval and repeat.This leads to somewhat correct behaviour when slowing down time, but speeding it up has no effect if the application has its framerate locked.
Here's an example to illustrate this better:
The setup is an app locked at 60 FPS and a function that should be called every 20ms using setInterval
. Time is slowed down to half speed. Here's the timeline for this case:
For the sample period of 128ms the callback was fired 3 times instead of the 7 that would have happened if using a 1x time scale. The half speed time scale produced roughly half of the expected ticks. The fire time differences have a slightly different pattern depending on the time scale, but in general when the fire interval is larger than the app delta time the result will be as expected.
Here's the case where time is sped up 10 times:
The timer ticked only 4 times in a period where you'd expect it to tick 640/20 = 32 times. That's because the timer cannot tick faster than the app framerate.
Lower frame deltas have a positive effect on the execution and expected results, but that is not always possible/desirable. Extra care must be taken when using setInterval
considering the notes above.
requestAnimationFrame
is invoked every frame (unless Coherent::UIGT::ViewInfo::AnimationFrameDefer defers it). It isn't directly affected by the time control in the sense that if you have a fixed animation (e.g. moving a <div>
10 pixels to the right) it will always be executed every frame regardless of the monotonically increasing time passed. The requestAnimationFrame
callback provides a timepoint as its first argument, which could be used for delta-time animation - e.g. move a <div>
10*dt pixels to the right. You only need to save the previous timepoint to be able to compute the difference.
A new function that provides the "application time" is introduced. You can get that time using new Date().appTime()
, which corresponds to the new Date()
call. It returns a Date
object constructed from the number of milliseconds since the epoch in application time, not 'real' time.
The raw number of ticks from the epoch corrected by the application time is available via __coherentTicks()
. Sometimes it might be useful to have Date()
return the application time as well - for instance if you do not want to change already existing JavaScript code but you still want to be able to control time. This can be trivially achieved: just pass Date = Date.prototype.appTime
as JavaScript code you want to be executed as soon as a view is created. This is done via the Coherent::UIGT::UISystem::CreateView overload which has a const char* scriptToExecute
parameter. The string passed in that parameter is JavaScript code that will be executed when the view is created. Now even the default 'Date' object will work only in application time.