Defined order of actor component initialization

Our actor components sometimes have some connections between them. For this reason we need them to initialize in a predefined order. The issue is that the order of components seems to be arbitrary. Is there any good systematic way to achieve this?

Note that we need this initialization happen even in editor without turning on the game mode.

My custom solution would probably be something like:

1] Derive a custom class from Component. Add a virtual “PostInit” method and a “GetPriority” method.

2] Derive a custom class from Actor (or ACharacter or whatever). Override the PostInitializeComponents method to gather all the components into array, sorts them by priority obtained from GetPriority method and then call PostInit method on them in the right order.

3] Derive all the Component and Actor classes from that.

Pros:

  • It works without engine modification

Cons:

  • As soon as someone assigns that component to a regular actor that is not deriving from my custom class, the PostInit will not be called and things will break. Easy mistake to make.
  • If I want to use this on an AActor and ACharacter, I would have to do it for both separately.

Any better ideas?

Thanks!

[Attachment Removed]

Component registration

“Note that we need this initialization happen even in editor without turning on the game mode.”

Keep in mind that initializing components is a runtime task, so AActor::InitializeComponents(), AActor::PostInitializeComponents, and UActorComponent::InitializeComponent() don’t get called until you start the game. They do run before the actor’s BeginPlay.

If you want to run logic also in the editor without starting play, consider looking at component registration instead. AActor::IncrementalRegisterComponents takes care of component registration. It’s called incremental, because in some contexts, like level streaming, the workload is spread over multiple ticks. Though in other contexts like in the editor, it will force all components to register immediately.

Engine modifications

Unless you’re absolutely avoiding any engine modifications, I would recommend simply modifying the engine to impose an order on how components are registered, instead of bending over backwards. We have been reluctant to make more AActor functions virtual, because of the vtable lookup cost, but it has resulted in rigidity in some places where it makes sense that studios want to customize behavior.

Sorting components before register / initialize

In AActor::IncrementalRegisterComponents, the following lines grab the components from a TSet into a TArray:

TInlineComponentArray<UActorComponent*> Components;
GetComponents(Components);

If you modify the function to sort the array here, it would be a straightforward way to enforce ordering. You may want to do the same sorting in other places like AActor::InitializeComponents(), to enforce ordering when initializing too. FHierarchicalLODBuilder::FindMST() is an example in engine code where HeapSort() is used to sort an array via a custom comparison function. That’s where you could check your custom component class’s priority value.

To minimize performance overhead, you might also want to take some extra steps:

  • Make the sorting opt-in per actor class, like a bool setting. Only enable the setting for your classes that need it.
    • Write an editor validator to catch devs forgetting to turn the setting on for an actor class, when detecting your custom component that has priority
  • Normal components that don’t have a priority, don’t sort those. Sorting costs will become expensive if you do it for all actors in the world, so avoid that cost when you can
    • Either remove your custom components from the array and place them sorted at the end
    • Or better yet, swap the component references in-place. If custom components A, B, C have to be registered in that order, but X, Y, Z don’t. Then ideally you modify IncrementalRegisterComponents so that input array C, B, X, A, Y, Z gets sorted in-place to A, B, X, C, Y, Z. Hopefully, that example makes sense.

That would be my recommendation.

[Attachment Removed]

Hello! Just checking in. Where any of those suggestions of use to you?

[Attachment Removed]

Hello!

Thanks for your answer. I think it was very useful.

I was looking into how could I effectively determine which actors need to do the sort, to avoid the performance overhead. With minimal engine changes to minimize merging issues in the future. Also I am not sure if the impact of this is really measurable. And then I got distracted by other parts of the task I need this for :D. I will get back to it very soon.

Anyway, you guys are not interested in a feature like this?

[Attachment Removed]

We’re not going to add explicit or customizable component ordering! Since there are so many virtuals already, for example for actor component there are:

ENGINE_API virtual void PostInitProperties() override;
ENGINE_API virtual void PostLoad() override;
 
ENGINE_API virtual void OnPreRegister();
ENGINE_API virtual void OnPreRegistered();
ENGINE_API virtual void OnRegister();
 
ENGINE_API virtual void InitializeComponent();
ENGINE_API virtual void Activate(bool bReset=false);
ENGINE_API virtual void BeginPlay();
 
ENGINE_API virtual void Deactivate();
ENGINE_API virtual void UninitializeComponent();
ENGINE_API virtual void EndPlay(const EEndPlayReason::Type EndPlayReason);
 
ENGINE_API virtual void OnPreUnregister();
ENGINE_API virtual void OnPreUnregistered();
ENGINE_API virtual void OnUnregister();

And we expect that most inter-component dependencies can be solved by utilizing these functions. We consider a team needing explicit ordering to be more rare, and in those situations we expect people to modify engine code.

[Attachment Removed]