To anyone using VBlankAttachments to do any kind of animations, read on, as this one’s for you!
VBlank attachments were a huge step up to creating smooth moving components in a JUCE gui. There remains one fatal flaw, however. I propose a solution to fix this and make this mechanism really solid.
The issue:
The VBlankListener ListenerList in the component peer simply calls into each listener in a sequential fashion. It is then up to us, the users, to provide the callback lambda which is called to each listener.
If we want to do any movement, each object which is registered as a listener (usually via the VBlankAttachment class, will need to handle any time based logic from it’s callback. A solid solution is how games do it. You determine the time between this callback and the last, and update your physics based on the elapsed time. Where this falls apart, is each listener is called sequentially. So whoever gets called last now has a later timestamp, and the elapsed time is greater than the first listener to be called.
If you have several listeners, each updating paths, with variable amounts of time being used depending on the work to be done in each callback, this can result in two components of the same type falling out of sync. The one earlier in the listener list does not have enough elapsed time to make a step in the animation, but the later one does.
While this discrepancy evens out on the whole, and one won’t get ahead of the other, it does create little 1 frame glitches, which is noticeable.
If you know what I’m talking about, please back me up in the comments.
I have a quick and dirty fix, which I hope the JUCE team will be able to implement in one way or another:
The Solution
Pass the current timestamp through the function chain from the root of the VBlank itself. I am on windows here, so the relevent code is in juce_Windowing_windows.cpp line 1947, which I have changed to the following:
void onVBlank() override
{
auto time = Time::getMillisecondCounterHiRes();
vBlankListeners.call ([time] (auto& l) { l.onVBlank(time); });
dispatchDeferredRepaints();
if (renderContext != nullptr)
renderContext->onVBlank();
}
Now every single listener is given the same timestamp, and calculations can be made from a common timestamp, without having functions from earlier in the list effecting the timestamp in the ones called later.
It has resolved the issue for me here, and I think this is the correct way for this to work.
