Animation Freeze due to PoseControlComponent tick ordering

Hi,

We’re seeing a character’s skeletal mesh animation freeze, sometimes intermittently every few frames, sometimes consistently for hundreds of frames in a row, when the character is stood on a moveable surface. After some investigation this appears to be due to bad tick ordering between the character’s SkeletalMeshComponent and PhysicsControlComponent.

I realise the PhysicsControlComponent is Experimental, but figured you might find it helpful if I documented the issue for you. Also if this is a known issue and you have any recommended workarounds then I’d be grateful to hear them. If the workaround is toggling CVarAnimationDelaysEndGroup then I’d be curious about the expected performance implications of that CVar.

Here’s the detail - a bit dense I’m afraid but I’m happy to clarify if any bits of it aren’t making much sense:

  • We have a character using the PhysicsControlComponent from the PhysicsControl plugin.
  • The PhysicsControlComponent’s PrimaryTickFunction ticks in PrePhysics by default.
  • In the PhysicsControlComponent’s PrimaryTickFunction, FPhysicsControlPoseData::Update() is called which reads from the SkeletalMeshComponent’s editable component space transforms.
  • The PhysicsControlComponent’s PrimaryTickFunction has a pre-requisite on the SkeletalMeshComponent’s PrimaryTickFunction.
  • The SkeletalMeshComponent’s PrimaryTickFunction has a pre-requisite on the CharacterMovementComponent’s PrimaryTickFunction.
  • The SkeletalMeshCompoment also has a EndPhysicsTickFunction.
  • In the SkeletalMeshCompoment’s EndPhysicsTickFunction, USkeletalMeshComponent::BlendInPhysicsInternal() is called which, when using ParallelBlend, calls SwapEvaluationContextBuffers().
  • SwapEvaluationContextBuffers() swaps the SkeletalMeshComponent’s editable component space transforms with non-GameThread buffer.
  • Later, not as part of any tick, USkeletalMeshComponent::CompleteParallelBlendPhysics() swaps those buffers back.

So far, so good. That’s all out-of-the box stuff. Normally FPhysicsControlPoseData::Update() is run in the PrePhysics tick group and runs well before SkeletalMeshComponent::BlendInPhysicsInternal() swaps the buffer it’s reading from so there’s no concern about reading the wrong buffer.

However, now consider that the character moves onto some moveable surface belonging to another “floor” actor:

  • The CharacterMovementComponent searches through all components on the floor actor and adds them as pre-requisites for the CharacterMovementComponent’s PrimaryTickFunction.
  • One of those components might be a SkeletalMeshComponent.
  • The floor’s SkeletalMeshComponent has a tick group of PrePhysics, but its EndTickGroup is set to PostPhysics (search for CVarAnimationDelaysEndGroup in SkeletalMeshComponent.cpp for the why)
  • Now the CharacterMovementComponent won’t tick until PostPhysics, and the PhysicsControlComponent is shifted to PostPhysics too, allowing it to run after the character’s SkeletalMeshComponent’s EndPhysicsTickFunction, after the buffer swap and before USkeletalMeshComponent::CompleteParallelBlendPhysics() swaps those buffers back.
  • In that case, FPhysicsControlPoseData::Update() is reading from whatever was in the AnimEvaluationContext memory, which seems to be cleared to zeros at least, so TMs.Num() is 0, not-equal to BoneDatas.Num().

The visible effect of this is that animation on the character’s skeletal mesh freezes. There’s a timing element to this, so it’s not always every frame, though it can be for a significant number of frames in a row.

Cheers,

Woody

[Attachment Removed]

Quick follow up: I think there might be more to this than I realised. I just put in a customisation to copy rather than swap the component space transforms buffers to see if this resolved the issue. It did fix the TMs.Num() being 0, and resolved a bunch of “missing bone data” log output, but it didn’t actually resolve the pose freeze, suggesting that whilst this is an issue that needs fixing, it isn’t the root cause.

[Attachment Removed]

Hello - thanks a lot for the report and detailed write-up.

PCC really needs three things:

  1. To run before physics, as it needs to set targets on physics constraints etc. It’s “trying” to do that at the moment, but clearly not well enough.
  2. To have access to the pose coming out of the SKM/anim graph
  3. If you’re using world-space controls, then it will need to know the world-space transform of the SKM component.

I’ll investigate to see what we can do to handle the first two better.

However, the third is a big problem, as it will result in your world-space controls driving to the current pose, but calculated using the previous frame’s character location. I can see how this would introduce possibly intermittent frame lag, which could look like a freeze (though I’m not sure how it could explain freezing over multiple frames).

If you want genuinely world-space controls, there’s nothing we can do about that. However, if you want those controls to be parented to the character itself (so they’re “character-space” controls), then it should work to pass in the character’s own capsule component (or whatever you have) to the “WorldComponent” when you create the controls. Alternatively you can change the parents of controls after they’ve been created using the SetControlParent (or SetControlParentsInSet) function. There’s an example of this in ContentExamples - the instance of BP_PhysicsControlDriver on the right hand side of the (long!) corridor in Animation_PhysicsControl does this (in HandleAttachement (sic))

It might be that a combination of this plus a workaround/way of forcing PCC to run after the SKM anim graph, but before physics, will be the way to make it work…

By the way, there was a slightly related issue that came up recently - see here.

[Attachment Removed]

Hello Woody,

Sorry for the big delay following up on this (I have a list of excuses, but will not bore you with them!)

I was able to reproduce the problem, and have a fix. It would be great if you could check. There are two parts (your line numbers will likely differ):

Fix 1: Character.cpp - MovementBaseUtility::AddTickDependency

When a character stands on a moveable surface, the CharacterMOvementCOmponent adds tick prerequisites. The existing filter checked each component’s start tick group, but not the completion one. When there’s a SKM involved, that drags everything into PostPhysics, which is not wanted.

The fix is in two places in Character.cpp:

Overload 1 at line 632 (deprecated, takes UPrimitiveComponent*):

// Current:

if (Component && Component->PrimaryComponentTick.bCanEverTick && Component->PrimaryComponentTick.TickGroup <=

BasedObjectTick.TickGroup)

// Change to:

if (Component && Component->PrimaryComponentTick.bCanEverTick && Component->PrimaryComponentTick.TickGroup <=

BasedObjectTick.TickGroup && Component->PrimaryComponentTick.EndTickGroup <= BasedObjectTick.TickGroup)

Overload 2 at line 847 (new, takes FMovementBaseInterfaceData*):

// Current:

if (Component && Component->PrimaryComponentTick.bCanEverTick && Component->PrimaryComponentTick.TickGroup <=

BasedObjectTick.TickGroup)

// Change to:

if (Component && Component->PrimaryComponentTick.bCanEverTick && Component->PrimaryComponentTick.TickGroup <=

BasedObjectTick.TickGroup && Component->PrimaryComponentTick.EndTickGroup <= BasedObjectTick.TickGroup)

Based on the comments there, I think this actually just makes it behave as originally intended.

Fix 2: PhysicsControlComponentImpl.cpp:183 UpdateCachedSkeletalBoneData

This is more of a guard (in my tests, this fix alone didn’t help with your original problem, but I think it’s still correct!)

if (USkeletalMeshComponent* SkeletalMesh = SkeletalMeshComponent.Get())

{

SkeletalMesh->HandleExistingParallelEvaluationTask(true, true); // <-- add this

CachedSkeletalMeshData.Update(SkeletalMesh, DeltaTime, TeleportDistanceThreshold, TeleportRotationThreshold);

}

CVarAnimationDelaysEndGroup worked as a workaround because it stopped the floor’s SKM from setting EndTickGroup to PostPhysics. With the fix(es) above, you can leave that cvar at its default value and get the parallelism benefit, without the problems (yay!).

Hoping this works for you!

All the best - Danny

[Attachment Removed]

Hi Danny,

Thank you for your response. I’ll admit I’m not sure I follow about us wanting world-space controls vs. character-space controls, would you be able to clarify why our use case implies we’re trying to use world-space? I’ll be honest I’m coming in cold to this code to chase down the animation pose freeze, so I’m not entirely familiar with how our animation programmers have been using the component.

As for the nature and cause of the freeze, it’s a multiple-frame phenomena, often whole seconds long so as you say it’s not easy to explain with phenomena that would cause one frame lags. I’ve narrowed it down to UPhysicsControlComponent::UpdateControls(), specifically the calls to ApplyBodyModifier() - if I comment out that body modifiers loop at the end of UpdateControls() then the freeze no longer occurs. I won’t chase it further though because, as you say, I think the real fix is making sure the component ticks in PrePhysics.

That linked issue you’ve shared does indeed sound very similar, even down to the suggestion of CVarAnimationDelaysEndGroup as a workaround. I think for now I’ll toggle that CVar for our project and keep an eye on our performance benchmarks to make sure it doesn’t impact us.

Still very interested to know if you come up with a better fix though!

Thank you,

Woody

[Attachment Removed]

Hi Danny,

Awesome, thank you for taking the time to find a fix. I’m a bit swamped this week, but I’ll try to find some time to test locally and let you know how it goes!

In the meantime I’d hacked in a fix to make skeletal meshes keep count of any character movement components dependent on them, and then only allow the EndTickGroup to migrate to PostPhysics when that count was zero. This let me leave CVarAnimationDelaysEndGroup as 0 and just have individual meshes opt-out as needed.

Thank you,

Woody

[Attachment Removed]