Binding to a Post Process AnimBP in Sequencer Causes MovieScene to Run Every Phase Every Frame

Hello!

I bumped into a bug where binding to a Post Process AnimBP in Sequencer causes MovieScene to run the Spawn and Instantiation phase every frame. I dug into it a bit, and it seems the culprit is in ULevelSequence::GatherExpiredObjects.

The code here explicitly looks at the AnimInstance on the Skeletal Mesh Component to see if its the same as the one that we bound to. The issue is that we should also check the PostProcess. The simplest version would be to simply do that here, but then we’re duplicating the resolve code that exists in FAnimInstanceLocatorFragment. Below is my version of this, though I don’t think it’s fully bullet-proof since technically the LevelSequenceActor could have binding overrides on the AnimBP.

void ULevelSequence::GatherExpiredObjects(const FMovieSceneObjectCache& InObjectCache, TArray<FGuid>& OutInvalidIDs) const
{
    using namespace UE::UniversalObjectLocator;

    TArrayView<const FMovieSceneBindingReference> References = BindingReferences.GetAllReferences();
    
    for (int32 Index = 0; Index < References.Num(); ++Index)
    {
       const FMovieSceneBindingReference& Reference = References[Index];
       
       if (Reference.Locator.GetLastFragmentTypeHandle() == FAnimInstanceLocatorFragment::FragmentType)
       {
          for (TWeakObjectPtr<> WeakObject : InObjectCache.IterateBoundObjects(Reference.ID))
          {
             UAnimInstance* AnimInstance = Cast<UAnimInstance>(WeakObject.Get());
             
             if (!AnimInstance || !AnimInstance->GetOwningComponent())
             {
                OutInvalidIDs.Add(Reference.ID);
                continue;
             }
             
             // Resolve the objects using a more typical MovieScene pattern. This really only works because AnimInstances are always Possessables and typically don't have Dynamic Binding
             TArray<UObject*, TInlineAllocator<1>> NewlyResolvedObjects;
             LocateBoundObjects(Reference.ID, FResolveParams(AnimInstance->GetOwningComponent()), nullptr, NewlyResolvedObjects);
             
             if (NewlyResolvedObjects.IsEmpty() || !NewlyResolvedObjects.Contains(AnimInstance))
             {
                OutInvalidIDs.Add(Reference.ID);
             }
          }

          // Skip over subsequent matched IDs
          while (Index < References.Num()-1 && References[Index+1].ID == Reference.ID)
          {
             ++Index;
          }
       }
    }
}

Do you think this is a good approach? Any guidance is appreciated.

Thanks!

-Nathaniel

[Attachment Removed]

Hey there,

Thanks for raising this, I’ve logged an issue here that you can follow too: https://issues.unrealengine.com/issue/UE-370818

I’m checking with the team now on whether there are any issues with this approach. If there are, we’ll probably have an alternative for you soon.

Dustin

[Attachment Removed]

Hey there, we have a bit more straightforward approach (similar to what is in there vs. the broader approach you’re taking here) that we’re working through. I’ll let you know if and when that gets submitted. But if this approach works better for you, then those make sense.

Dustin

[Attachment Removed]

Awesome, thank you Dustin!

[Attachment Removed]

Hi Dustin,

Did the team think this solution was a good stopgap?

[Attachment Removed]

Sounds good! I’ll keep the broad approach and then once that hits 5.8, we’ll take your change. Thanks Dustin!

[Attachment Removed]