Mutable Grooms trigger ensure when used on a COI in a sequence

Since we’ve begun integrating Mutable as a replacement for a previous modular mesh system, we’ve encountered some workflow issues, including unexpected ensures like the one this question details in its repro steps. Currently, we’re not sure how best to resolve this RegisterComponent ensure, other than perhaps patching the RegisterComponent to pre-check if the actor has a world before trying to register the groom component? I’m not very clued in on how movie scenes and level sequences function to understand quite what’s going wrong here, so advice on how to fix this would be most appreciated.

This problem extends beyond the naive, minimal, repro steps since our project has just converted a large quantity of level sequences from our previous modular mesh system to using a new system built around Mutable appearances. We’ve replaced the previous actors in these level sequences with actors which follow the general structure of ACustomizableSkeletalMeshActor, with some additional supporting components and functionality which combine the skeletal mesh components managed by a COI and Usages, with additional Posing/Driver skeletal mesh components as part of our CopyPose/RetargetFromMesh-based animation system. Now we’re hitting this ensure whenever people open a level with a level sequence in it with an actor using the new Mutable appearances.

We have concerns we have gone about something incorrectly with setting up this new Mutable-based system. It’s hard to judge whether we’re pushing Mutable into areas it was not designed for, or if we’re just uncovering gaps that haven’t been tested yet. This game’s cinematics rely on allowing the player and companion NPCs to appear in cinematics with the same equipment they have during play, with both the Player and NPCs using these new Mutable appearances, combining a variety of curated character COIs and runtime parameter adjustments to apply inventories of items on top.

We quickly learned that COIs should be cloned during gameplay to avoid parameters being shared between actors in the world and those runtime parameters leaking back into the editor and potentially being saved by accident.

We’ve also had great fun finding a reliable flow to ensure a CO is compiled when going into game in PIE. While not an issue in a packaged game, COs don’t automatically compile unless they are found to be in use by a component, but we don’t know how many components a CO can output until it is compiled. Catch 22. To work around this, we currently have Editor-Only code in our appearance component (which manages the creation of components for a COI) that calls SetIsBeingUsedByComponentInPlay(true) on the COI, similar to how a Usage does in UpdateDistFromComponentToPlayer, triggering the auto-compile behaviour via TickUpdateCloseCustomizableObjects. The above is working fine, generally, for spawnable characters.

The slimmed-down actors we use for cinematics present another challenge, since they more closely resemble CustomizableSkeletalMeshActors which when placed with a compiled CO, include Usage objects which are enough to trigger the auto-compile when the actor is loaded. This is great, except as our CustomizableObjects grow in complexity and referenced assets, compilation time has grown too. These stalls on loading into levels with level sequences have impacted developer iteration time significantly. We suspect we could reduce this compilation time if we were able to better leverage Tables and Parameterised inputs, but since our assets were developed for another system with different constraints they are not easily converted to table rows due to a variance in materials per part and complex modularity. Another factor here has been our design and layout of CustomizableObjects. Since we are not able to create reusable, modular COs we opted for a more generic structure of sub-COs which encapsulates all NPC variations, split only on gender (i.e. CO_Male and CO_Female), with more bespoke COs for hero characters. This avoids duplication, but leads to long stalls when a CO is first encountered, compiles, and streams in all the associated meshes. Often, when first opening a map, you’ll stall for multiple minutes as it processes multiple COs. We’ve also noticed COs do not appear to cache to the DDC in editor, so these stalls repeat every time you boot. UE-222775, from the looks of it?

[Attachment Removed]

Steps to Reproduce
Using MutableSample and 5.6 checked out from GitHub:

  • Create a new Open World level
  • Open COI_Cyborg to ensure its CO is compiled
  • Drag COI_Cyborg into the level
  • Add a new Level Sequence to the level
  • Add a new Actor Track to the Level Sequence, bind this track to the COI_Cyborg placed in the level
  • Right-click the new Actor Track, navigate to Binding Properties, Select Convert Binding(s) to drop-down, Select Replaceable

The GroomComponent RegisterComponent call originating in UHairStrandsMutableExtension::OnCustomizableObjectInstanceUsageUpdated should trigger the ensure(MyOwnerWorld) ensure in UActorComponent, because the actor resides in a UMovieScene, and has no owning world.

[Attachment Removed]

Hi Mark. I will try to answer all questions but I may miss some. Let me know if I miss something.

Auto Compilation:

CO only auto-compile on PostLoad if something changed (if this does not work there is a bug). PIE does not auto-compile by design.

If you are manually calling for compilation, we recommend using UCustomizableObject::Compile since it has a flag that allows you to skip the compilation if nothing has changed.

Compilation times:

Tables will not help reducing the compilation times, but parameters yes. Also Passthrough Textures and SKM will help.

DDC:

As you mentioned, to amortize compilation times between users we strongly recommend using the DDC. Unfortunately this will require UE-222775, implemented only on 5.7. Porting this to 5.6 is not feasible.

Groom ensure:

We are having issues trying to reproduce the ensure. Could you post the call stack?

[Attachment Removed]

Hey there, I’ve taken this case over for Gerard.

This doesn’t repro in mainline, and I’m just doing some research to see which CL it’s in and whether it’s something you can cherry-pick into your engine.

Dustin

[Attachment Removed]

No unfortuantely I haven’t been able to track it down yet. The issue with registration ensures that any change to the base instantiation code could affect it. It may not be related to MovieScene at all. Apologies for the delay, and thank you for your patience.

[Attachment Removed]

I manage to get an answer from the LevelSequence team. The only way to know if it is a template is to check for UMovieScene as the

outer. Or either all loops should check the outer or simply do not register if there is no world.

[Attachment Removed]

Hi Gerard, thank you for your reply.

Regarding the Groom Ensure, here’s the callstack,

>	[Inline Frame] UnrealEditor-Engine.dll!UActorComponent::RegisterComponent::__l3::<lambda_1>::operator()() Line 1965	C++
 	UnrealEditor-Engine.dll!UActorComponent::RegisterComponent() Line 1965	C++
 	UnrealEditor-HairStrandsMutable.dll!UHairStrandsMutableExtension::OnCustomizableObjectInstanceUsageUpdated(UCustomizableObjectInstanceUsage & InstanceUsage) Line 147	C++
 	UnrealEditor-CustomizableObject.dll!UCustomizableObjectInstanceUsagePrivate::Callbacks() Line 27	C++
 	UnrealEditor-CustomizableObject.dll!FinishUpdateGlobal(const TSharedRef<FUpdateContextPrivate,1> & Context) Line 1232	C++
 	UnrealEditor-CustomizableObject.dll!impl::Task_Game_Callbacks(const TSharedRef<FUpdateContextPrivate,1> & OperationData) Line 3247	C++
 	[Inline Frame] UnrealEditor-CustomizableObject.dll!UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::TFunctionStorage<1>,void __cdecl(void)>::operator()() Line 471	C++
 	UnrealEditor-CustomizableObject.dll!FMutableTaskGraph::TryLaunchGameThreadTask() Line 324	C++
 	[Inline Frame] UnrealEditor-CustomizableObject.dll!FMutableTaskGraph::Tick() Line 335	C++
 	UnrealEditor-CustomizableObject.dll!UCustomizableObjectSystem::TickInternal(bool bBlocking) Line 4231	C++
 	UnrealEditor-Engine.dll!FStreamingManagerCollection::UpdateResourceStreaming(float DeltaTime, bool bProcessEverything) Line 908	C++
 	UnrealEditor-Engine.dll!IStreamingManager::Tick(float DeltaTime, bool bProcessEverything) Line 741	C++
 	UnrealEditor-Engine.dll!FStreamingManagerCollection::Tick(float DeltaTime, bool bProcessEverything) Line 880	C++
 	UnrealEditor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 2430	C++
 	UnrealEditor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 533	C++
 	UnrealEditor.exe!FEngineLoop::Tick() Line 5625	C++
 	[Inline Frame] UnrealEditor.exe!EngineTick() Line 60	C++
 	UnrealEditor.exe!GuardedMain(const wchar_t * CmdLine) Line 187	C++
 	UnrealEditor.exe!LaunchWindowsStartup(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * __formal, int nCmdShow, const wchar_t * CmdLine) Line 271	C++
 	UnrealEditor.exe!WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * pCmdLine, int nCmdShow) Line 339	C++
 	[Inline Frame] UnrealEditor.exe!invoke_main() Line 102	C++
 	UnrealEditor.exe!__scrt_common_main_seh() Line 288	C++
 	kernel32.dll!00007ffaa542259d()	Unknown
 	ntdll.dll!00007ffaa6d0af78()	Unknown

I’ve also attached a short screen recording demonstrating myself going step by step to achieve the ensure.

I’ll follow up on the auto compilation and compilation times in another reply when I have a longer minute to expand on what we’re doing and why we ended up there.

[Attachment Removed]

Hey Dustin, thanks for looking into this. Have you had any luck tracking down which changes contributed to fixing this ensure? Has something changed with the nature of actors inside MovieScenes?

[Attachment Removed]

I was looking at this more yesterday and trying to solve it from the other direction, if it’s unlikely for us to track down what has changed to avoid this ensure in later versions. If I understand the problem, Mutable/CustomizableObjectSystem is picking up the actor inside the MovieScene as a real, functioning actor, when technically that “actor” is the Actor Template the MovieScene/LevelSequence will use to spawn the real version when it plays. It does this because it’s scanning for CustomizableObjectInstanceUsage objects across the whole process, irrespective of where they reside, inside a world or not. This works great in principle since it picks up Usages in various editor preview scenes and worlds, as well as during play, but also, apparently, inside loaded UMovieScene objects.

I may have to resort to checking the outer chain for the presence of a UMovieScene to skip trying to register the component, or at a higher level, filter out CustomizableObjectInstanceUsage objects which reside inside actors in a UMovieScene from being considered by the CustomizableObjectSystem, since it currently uses a TObjectIterator which applies to the whole engine and editor and not a specific world. The latter may be a necessary change in general, to avoid Mutable trying to generate meshes for those Template actors and eating up memory.

[Attachment Removed]

Thanks for the insight. You are completely right, we should skip these MovieScene/LevelSequence Actor Template in all cases. Although not sure which condition we should use to skip these, I will have to investigate it.

[Attachment Removed]

Thanks for the confirmation! For the moment we’ve wrapped the registration in the hair strands extension with a check for the world to minimise changes to the mutable plugin. We’ve been keeping an eye on the 5.7 and 5.8 changes and noticed the replacement of TObjectIterator with a Usage registration system which looks like something we can cherrypick even if we don’t take the rest of the surrounding CL. From that we expect we can then reliably filter out these template actors by checking the outer chain once on attempt to register, rather than inside every iterator loop.

[Attachment Removed]