Unable to serialize subobject's outer

We have a player pawn which owns a replicated components which owns a set of replicated subobjects. Sometimes those subobjects’ outers fail to resolve correctly. Rather than the replicated component, their outer is set to the owning actor (character pawn).

The code that leads to this behavior lives in UActorChannel::ReadContentBlockHeader. Snippet follows:

// When replicating subobjects, the engine will by default replicate lower subobjects before their outers, using the owning actor as the outer on the client.

// To have a chain of subobjects maintain their correct outers on the client, the subobjects should be replicated top-down, so the outers are received before any subobjects they own.

UE_LOG(LogNetTraffic, Log, TEXT(“UActorChannel::ReadContentBlockHeader: Unable to serialize subobject’s outer, using owning actor as outer instead. Class: %s, Actor: %s”), *GetNameSafe(SubObjClass), *GetNameSafe(Actor));

ObjOuter = Actor;

I’m not sure how to interpret this comment. To me it reads as if the default replication behavior for subobjects is bottom-up, but to maintain a correct outer chain should be top-down. Can you comment on what’s being said here? If the behavior should be top-down, does the engine provide support for this and if not, is there a way for us to coerce the correct behavior out of the replication system? In our case all subobjects are using replicated subobject lists rather than the old-style ReplicateSubobjects mechanism.

The log also seems like it should be at Warning or Error rather than Log verbosity, since objects which expect a particular outer chain will be making invalid assumptions when we get into this path.

Steps to Reproduce
To reproduce I’d probably need to share our character setup with you.

Hi,

By default, the engine does just replicate all subobjects with the owning actor as their outer, regardless of whether they have a different outer on the server. A while ago, that handling in Read/WriteContentBlockHeader was added to try and create replicated subobjects on the client with their correct outer.

However, your reading of that comment is correct. The default implementation of AActor::ReplicateSubobjects replicates the lowest subobjects first, before their outers. This means that when processing the bunch on the client, the lower subobjects are created first before their outer has been read in and created. Because the outer doesn’t exist on the client yet, the engine falls back to the default behavior of assigning the actor as the outer.

We didn’t change the behavior of ReplicateSubobjects, as certain projects/features may have been relying on subobjects being replicated in this order (the log message was also not made an error or warning because of this). Instead, the project itself would need to override ReplicateSubobjects on their actors in order to replicate higher level components/subobjects before lower ones.

That being said, there’s unfortunately no way to enforce any specific ordering when using the registered subobjects list. We do generally recommend using the registered subobjects list now, and if you’re using Iris, it is required to use this (although in Iris, subobjects should have the expected outer on the client anyway).

We do have an open task to rework the replicated subobject outer handling with the registered subobjects list, although I can’t provide any estimate as to when that may happen.

In the meanwhile, I’ve heard of projects working around the issue by having a replicated reference property on the object that points to the subobjects “true” outer. This way, you can use this property to access the “true” outer, even if the actor is the object’s outer on the client.

Thanks,

Alex