Hello,
we were using blocking evaluation to force all animated widget properties to their initial or final animation state by temporarily enabling the blocking evaluation flag at runtime and then playing the animation with a very high speed. The synchronous runner used to evaluate one animation step immediately during the initial play which was a very useful property.
It seems that this functionality has been removed in this commit, where the SynchronousRunner initialization has been removed in UMGSequencePlayer.cpp Line 240:
https://github.com/EpicGames/UnrealEngine/commit/4aa3f9f33bb1c77b9fad1402a901a6bc8ff8ea10\#diff\-243796978c18e4b49d6de716f6b2a456e25fd918a28f51ef6ff52391b159feb2L240
This change makes the existence of the SynchronousRunner property obsolete because it is never initialized.
Is this intended behavior now? If yes, how can we make a UMG animation scrub to the final frame? - similar to how you can jump to a frame in movie scenes while evaluating all keyframes and triggers along the way.
Hi! Yes, sorry for bad communication on this -- blocking evaluation are being deprecated, and in fact in UE 5.6 the whole UMGSequencePlayer class is deprecated. This is all part of a longer-term effort to address the performance issues of many animated widgets.
The blocking evaluation flag and the synchronous runners may have indirectly helped with this, but that was never their original intent, so it might have worked due to unintended side effects.
A while ago (and again, as part of a performance push) we started evaluating all ongoing Sequencer animations together for a given “domain”. So all UMG animations would be evaluated together, all Level Sequences would be evaluated together, and so on. The idea there was that if we were running, say, 50 widget animations, each with a dozen X/Y/Color/etc. keyframed curves, then that’s potentially 600+ curves to evaluate. Instead of evaluating them a dozen at a time “synchronously”, we can now evaluate all 600+ curves together in parallel across multiple CPUs.
So we had the blocking evaluation flag and synchronous runners as a temporary safety measure in case the new parallel evaluation broke something, and we needed to run a few of them synchronously to unblock a critical bug. Since the new parallel evaluation has been running for a long time now without any big problem, we are removing the old synchronous way altogether.
As a corollary, the PlayAnimation does indeed only queue the new animation, to be played later along with all the other ones queued this frame. Again, this is for performance, and that’s why we are reluctant to add any BP method that does a synchronous update, since that has a history of causing problems in the long run.
What is your actual use-case? Is the goal to reset all widgets to their initial state and/or set them all to their finished state? Do you actually need to run operations like these in a synchronous way? Or can you just queue multiple operations and let the update phase run through them later?
Hi, sorry for the delays in replying to this.
We’re currently finalizing the 5.6 release so I’ll try and make sure Blocking Evaluation works again in there.
In the meantime, for 5.5, I think it might actually be a fairly simple fix if you can patch the engine code. When Blocking Evaluation is enabled on the animation, `UUMGSequencePlayer::ConstructEntitySystemLinker()` makes a standalone/private linker for this animation. There’s no need for the `SynchronousRunner` anymore because the linker (and it associated runner) can be considered and used as “synchronous”. So I think that inside `UUMGSequencePlayer::PlayInternal`, instead of checking `Runner == SynchronousRunner`, you can check `EnumHasAnyFlags(Animation->GetFlags(), EMovieSceneSequenceFlags::BlockingEvaluation)`. This would make it flush the queued animation updates immediately.
Hi Ludovic,
thanks for the thorough answer. I understand that batching all sequence types helps with performance.
Our use case for the synchronous runner has been to set up the widget to its initial animation state during construction. We “scrubbed” the animation to its first or last frame depending on, say for example, an “enabled” state on construction and then later when the user clicked on a different entry played the animation normally. This was to avoid having to add blueprint code for setting the initial or final state in blueprint logic which could get out of sync with the animation if someone edited it. A fade in animation could also be realized nicely with this approach because the animation could be set to the first frame in construct which would hide the widget before the first draw.
While this is still somewhat possible without the synchronous runner, this approach fails if the animations are not evaluated before painting the widget the next time which would cause the widget to show up in a wrong state for one frame, which is not really acceptable.
A combination of the “scrub to” (either from back or from front) behavior that is available in the level sequence player and a guarantee that animations are evaluated before the widget is drawn the next time would probably fulfill our requirements.