前言
当指针类型进行OnRep时候, 可能会触发两次. 触发条件为
- 该指针成员指向的对象还没有创建, 但是其GUID已经随Bunch发送而来.
- 该成员被设置成REPNOTIFY_Always类型.
触发流程
构造复现环境
创建两个Actor, 然后指针互相指向对方.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| FVector Loc = FVector::ZeroVector; FRotator Rot = FRotator::ZeroRotator; FActorSpawnParameters Par; Par.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; UClass* pClass = LoadObject<UClass>(this, TEXT("Blueprint'/Game/Test/TestRepFlow/BPTestRepFlowSpawnedActor.BPTestRepFlowSpawnedActor_C'")); TestRepFlowSpawnedActor = GetWorld()->SpawnActor<AActor>(pClass, Loc, Rot, Par); TestRepFlowSpawnedActor->SetAutonomousProxy(true); TestRepFlowSpawnedActor->SetOwner(this);
TestRepFlowSpawnedActor12 = GetWorld()->SpawnActor<AActor>(pClass, Loc, Rot, Par); TestRepFlowSpawnedActor12->SetAutonomousProxy(true); TestRepFlowSpawnedActor12->SetOwner(this);
if (ATestRepFlowSpawnedActor *pTestRepFlowSpawnedActor = Cast<ATestRepFlowSpawnedActor>(TestRepFlowSpawnedActor)) { pTestRepFlowSpawnedActor->TestRepFlowSpawnedActor = TestRepFlowSpawnedActor12; ATestRepFlowSpawnedActor* pTestRepFlowSpawnedActor12 = Cast<ATestRepFlowSpawnedActor>(TestRepFlowSpawnedActor12); pTestRepFlowSpawnedActor12->TestRepFlowSpawnedActor = pTestRepFlowSpawnedActor; }
void ATestRepFlowSpawnedActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME_CONDITION_NOTIFY(ATestRepFlowSpawnedActor, TestRepFlowSpawnedActor, COND_None, REPNOTIFY_Always); }
|
序列化Actor
在收到SerializeNewActor Bunch时候, 创建Actor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| --UIpNetDriver.TickDispatch.for.else.if |--UNetConnection.ReceivedRawPacket.if.if.if | |--UNetConnection.ReceivedRawPacket.if.if.if | | |--UChannel.ReceivedRawBunch.else | | | |--UChannel.ReceivedNextBunch.if | | | | |--UChannel.ReceivedSequencedBunch | | | | | |--UActorChannel.ReceivedBunch | | | | | | |--UActorChannel.ProcessBunch.if | | | | | | | |--UPackageMapClient.SerializeNewActor.if.if.if.if.if | | | | | | | | |--UWorld.SpawnActorAbsolute | | | | | | | | | |--UWorld.SpawnActor | | | | | | | | | | |--NewObject | | | | | | | | | | | |--StaticConstructObject_Internal.if | | | | | | | | | | | | |--ATestRepFlowSpawnedActor.ATestRepFlowSpawnedActor
|
将Property放入RepNotifies中
在解析Bunch时候, 由于GUID指向的对象还没有创建, 所以序列化属性失败,
但是Property的RepNotifyCondition类型为REPNOTIFY_Always,
所以无论无论序列化成功与否, 都会将其塞入RepNotifies中, 等待OnRep.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| --UIpNetDriver.TickDispatch.for.else.if |--UNetConnection.ReceivedRawPacket.if.if.if | |--UNetConnection.ReceivedPacket.while.{ | | |--UChannel.ReceivedRawBunch.else | | | |--UChannel.ReceivedNextBunch.if | | | | |--UChannel.ReceivedSequencedBunch | | | | | |--UActorChannel.ReceivedBunch | | | | | | |--UActorChannel.ProcessBunch.while | | | | | | | |--FObjectReplicator.ReceivedBunch.if | | | | | | | | |--FRepLayout.ReceiveProperties | | | | | | | | | |--ReceiveProperties_r | | | | | | | | | | |--ReceivePropertyHelper | | | | | | | | | | | |-- | | | | | | | | | | | |--Cmd.Property->NetSerializeItem(Bunch, Bunch.PackageMap, Data + SwappedCmd); | | | | | | | | | | | |-- | | | | | | | | | | | |--RepNotifies->AddUnique(Parent.Property);
|
触发第一次OnRep
触发第一次OnRep时, 此时Actor属性还没有赋值, 即为空.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| --UIpNetDriver.TickDispatch.for.else.if |--UNetConnection.ReceivedRawPacket.if.if.if | |--UNetConnection.ReceivedPacket.while.{ | | |--UChannel.ReceivedRawBunch.else | | | |--UChannel.ReceivedNextBunch.if | | | | |--UChannel.ReceivedSequencedBunch | | | | | |--UActorChannel.ReceivedBunch | | | | | | |--UActorChannel.ProcessBunch | | | | | | | |--FObjectReplicator.CallRepNotifies | | | | | | | | |--FRepLayout.CallRepNotifies.for.switch.{ | | | | | | | | | |--AActor.ProcessEvent.if.if | | | | | | | | | | |--UObject.ProcessEvent.{ | | | | | | | | | | | |--UFunction.Invoke | | | | | | | | | | | | |--ATestRepFlowSpawnedActor.OnRep_SpawnedActor
|
关键调用位置:
触发原因:RepNotifies中存了对应属性, 所以一定会触发. 而且触发完后,
还会清理RepNotifies
设置值
通过每帧Tick, 来查询GUID对应的对象是否存在, 如果存在, 则进行赋值.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| --UNetDriver.TickFlush.if.if.if.for.if |--FObjectReplicator.UpdateUnmappedObjects | |--FRepLayout.UpdateUnmappedObjects.if | | |--FRepLayout.UpdateUnmappedObjects_r.for.if | | | |-- | | | |--FObjectPropertyBase.NetSerializeItem | | | | |--FObjectProperty.SetObjectPropertyValue | | | |-- | | | |--RepState->RepNotifies.AddUnique(Parent.Property); | |-- | |--FObjectReplicator.CallRepNotifies | | |--FRepLayout.CallRepNotifies.for.switch.{ | | | |--AActor.ProcessEvent.if.if | | | | |--UObject.ProcessEvent.{ | | | | | |--UFunction.Invoke | | | | | | |--ATestRepFlowSpawnedActor.OnRep_SpawnedActor
|
在赋值之后, 还要将其塞入RepNotifies中, 等待OnRep.
第二次OnRep
当属性设置完成之后, 会在同一帧调用OnRep.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| --UNetDriver.TickFlush.if.if.if.for.if |--FObjectReplicator.UpdateUnmappedObjects | |--FRepLayout.UpdateUnmappedObjects.if | | |--FRepLayout.UpdateUnmappedObjects_r.for.if | | | |-- | | | |--FObjectPropertyBase.NetSerializeItem | | | | |--FObjectProperty.SetObjectPropertyValue | | | |-- | | | |--RepState->RepNotifies.AddUnique(Parent.Property); | |-- | |--FObjectReplicator.CallRepNotifies | | |--FRepLayout.CallRepNotifies.for.switch.{ | | | |--AActor.ProcessEvent.if.if | | | | |--UObject.ProcessEvent.{ | | | | | |--UFunction.Invoke | | | | | | |--ATestRepFlowSpawnedActor.OnRep_SpawnedActor
|
思考
带有指针的结构体怎么OnRep呢
从如下代码可以看出, 只要结构体成员中有一个从DS传输过来,
就会直接OnRep, 它不会区分其他成员是否到齐.
优化
通过以上分析, 可以清晰看出, 其为什么调用了两次OnRep,
并且也知道了调用两次OnRep条件什么.
那么我们能不能干掉一次无用的OnRep呢?
针对单个指针类型
当反序列化一个Object指针类型时候,
会进入Map->SerializeObject
(UPackageMapClient::SerializeObject
).
UPackageMapClient::SerializeObject
的返回值即为其是否解析出来了.
所以, 直接用其返回值即可:
其他类型属性也能直接使用返回值判断吗? 可以的. 这里是Cmd,
一定是子属性, 不可能是结构体等复杂属性. 如果是结构体,
那么一定自己重写了NetSerialize
等函数,
这个就需要考虑具体情况了.
针对带有指针类型的结构体等
需要添加记录数组结构, 等到都到来的时候才能调用OnRep,
还要考虑多个相关Bunch到来后, 指针类型依旧没有映射的情况.