Hello! We’re implementing an inventory system where items are replicated UObjects (subobjects) and can be moved between different owning actors/inventories at runtime (e.g. container → player inventory → dropped body → another player). Replication itself works after transfer (properties update, references are valid), but client-side lifetime of the UObject seems tied to the actor channel that originally created it. When that original actor is destroyed or streamed out, the client destroys/GCs the item UObject even if it is currently being replicated by a different actor.
This causes frequent item invalidation on clients in streaming/destroy scenarios.
What we found in engine behavior :
On the client, the initial replication path adds created subobjects to UActorChannel::CreateSubObjects. When the actor channel is torn down (destroy/stream-out), it iterates that list and calls PreDestroyFromReplication() and then MarkAsGarbage() on those subobjects.
So even if the same UObject is now being replicated from another actor/channel, it still gets destroyed when the original channel closes. This looks like an engine assumption that subobject lifetime is bound to the channel that first created it.
Our current workaround (client-side migration)
When an item is transferred, we manually remove the UObject from any old channels’ CreateSubObjects and add it to the new channel, in FastArraySerializer post-rep callbacks.
UWorld* World = ParentContainer->ParentManager->GetWorld();
if (!World) return;
UNetDriver* NetDriver = World->GetNetDriver();
if (!NetDriver || !NetDriver->ServerConnection) return;
AActor* NewOwner = ParentContainer->ParentManager->GetOwner();
UActorChannel* NewChannel = NetDriver->ServerConnection->FindActorChannelRef(NewOwner);
// Remove from all other channels
for (UChannel* Channel : NetDriver->ServerConnection->Channels)
{
UActorChannel* ActorChannel = Cast<UActorChannel>(Channel);
if (ActorChannel && ActorChannel != NewChannel)
{
ActorChannel->CreateSubObjects.Remove(ItemSlot.Item);
}
}
// Add to new owner's channel
if (NewChannel)
{
NewChannel->CreateSubObjects.AddUnique(ItemSlot.Item);
}
However, CreateSubObjects is deprecated and flagged to become private (“Use GetCreatedSubObjects() instead”), and in 5.4+ the replacement is not usable for mutation (and/or private/const depending on version). So this workaround is not future-proof.
Our questions :
Is this behavior intended?
Is the engine design that replicated UObject subobjects are expected to live and die with the actor channel that originally created them, making “transferring the same subobject instance between different replicating actors” unsupported?
If transfer is supported, what is the correct API/pattern to migrate ownership without touching CreateSubObjects?
Is there an engine-supported way to:
detach a subobject from the old channel’s created-subobject lifetime tracking, and/or re-associate it with the new actor/channel so it won’t be destroyed when the old channel closes?
If transfer is not supported, what is the recommended approach for inventory items that must persist across actor destruction/streaming?
Any clarification on the intended lifetime model and the correct way to implement “moving items between inventories/owners” (especially with streaming) would be appreciated.
Thanks in advance.
[Attachment Removed]