FFastTArraySerializer replicated UObject is nullptr until next tick - Lyra related

Hello

I’m using Lyra’s Equipment Manager architecture in my game.

This system uses a FFastTArraySerializer to trigger PostReplicateAdd events on the client when a new equipment entry is added to the FFastTArraySerializerArray.

I have pretty much copied and pasted the system inside my project (i’m not using lyra’s project).

This is what the FFastTArraySerializer and the FFastArraySerializerItem looks like (exact same of lyra, except by the names):

USTRUCT(BlueprintType)
struct FSpawnedScorestreakEntry : public FFastArraySerializerItem
{
	GENERATED_BODY()

	FSpawnedScorestreakEntry()
	{}

public:
	FString GetDebugString() const
	{
		return FString::Printf(TEXT("%s of %s"), *Instance.GetName(), *GetNameSafe(ScorestreakDefinition.Get()));
	}

private:
	friend APRT_ScorestreakManager;
	friend struct FScorestreakInstanceList;
	
	UPROPERTY()
	TSubclassOf<UPRT_ScorestreakDefinition> ScorestreakDefinition;

	UPROPERTY(BlueprintReadOnly, meta=(AllowPrivateAccess=true))
	TObjectPtr<UPRT_ScorestreakInstance> Instance = nullptr;
};

/* ACTUAL LIST*/
USTRUCT(BlueprintType)
struct FScorestreakInstanceList : public FFastArraySerializer
{
	GENERATED_BODY()

	FScorestreakInstanceList()
		: OwnerComponent(nullptr)
	{
	}

	FScorestreakInstanceList(AActor* InOwnerComponent)
		: OwnerComponent(InOwnerComponent)
	{
	}

public:
	//~FFastArraySerializer contract
	void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
	void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
	void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
	//~End of FFastArraySerializer contract

	bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
	{
		return FFastArraySerializer::FastArrayDeltaSerialize<FSpawnedScorestreakEntry, FScorestreakInstanceList>(Entries, DeltaParms, *this);
	}

	UPRT_ScorestreakInstance* AddEntry(TSubclassOf<UPRT_ScorestreakDefinition> scorestreakDef);
	void RemoveEntry(UPRT_ScorestreakInstance* Instance);

private:
	friend APRT_ScorestreakManager;
	
	// Replicated list of equipment entries
	UPROPERTY(BlueprintReadOnly, meta=(AllowPrivateAccess=true))
	TArray<FSpawnedScorestreakEntry> Entries;

	UPROPERTY(NotReplicated)
	TObjectPtr<AActor> OwnerComponent;
};

Everything is working exactly the same as Lyra’s project. Except that when the PostReplicateAdd event triggers on the client, the instance uobject pointer is nullptr.

my project (nullptr on post replicate add):

lyra (valid on post replicate add):

The next frame however, the pointer will be valid and no longer nullptr.

I’m using an actor data channel to replicate the UObject (just like lyra does), it doesn’t matter if I’m using the replicated subobject list or the ReplicateSubobjects function. The problem persists.
In Lyra, when the postreplicateadd event triggers, the uobject pointer is already valid.

Is there any config regarding replication that I might be missing? thanks!

Hello, I encountered the same problem. May I ask if you have solved it now

i could be wrong but i think FFastArraySerializer expects your array to be called Items (not Entries)

also make sure you have your template


template<>
struct TStructOpsTypeTraits<MyList> : public TStructOpsTypeTraitsBase2<MyList>
{
    enum
    {
        WithNetDeltaSerializer = true,
    };
};

我已经解决了这个问题,这是由于FastArray与SubObject的同步顺序不一样导致的,只需要在ActorComponent中重载ReplicateSubobjects函数

bool UInvSys_InventoryComponent::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch,
	FReplicationFlags* RepFlags)
{
	bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
	for (UInvSys_BaseInventoryObject* InventoryObject : InventoryObjectList)
	{
		if (InventoryObject == nullptr || IsValid(InventoryObject) == false)
		{
			continue;
		}
		
		// 让库存子对象复制自己的属性之前,复制子对象。
		UActorChannel::SetCurrentSubObjectOwner(this);
		WroteSomething |= InventoryObject->ReplicateSubobjects(Channel, Bunch, RepFlags);

		UActorChannel::SetCurrentSubObjectOwner(GetOwner());
		WroteSomething |= Channel->ReplicateSubobject(InventoryObject, *Bunch, *RepFlags);
	}
	// UE_LOG(LogInventorySystem, Log, TEXT("[WroteSomething = %s]"), WroteSomething ? TEXT("True") : TEXT("False"));
	return WroteSomething;
}

我这里使用了两层子对象,即组件拥有的子对象还有一组子对象,所以对这个子对象添加了ReplicateSubObject函数。
这里需要注意的是UActorChannel::SetCurrentSubObjectOwner(this);必须设置正确,祝好运。
具体代码也可以参考Lyra的库存组件中的内容,过程是一致的。另外就是不要使用bReplicateUsingRegisteredSubObjectList :partying_face:

:partying_face:I have solved this problem. Thank you

1 Like

UActorChannel::SetCurrentSubObjectOwner(this);这个不调用,好像也是没有问题的,刚刚测试了一下,没出现失序的问题。