导言
需要验证的情况大致分成两类,
开启/未开启net.AllowAsyncLoading
,
每类又包含如下细分情况:
SpawnActor
, BeginPlay
,
InitOnly
RPC
MulticastRPC(Reliable+Unreliable)
的详细流程
针对Actor
本身, InitOnly
RPC
MulticastRPC(Reliable+Unreliable)
BeginPlay
的执行顺序
针对非指针类型的变量, 例如int32
,
float
等.
针对指针类型Replicate
变量:
- 指针对象已经存在.
- 指针对象不存在.
- 特殊的, 要详细说明以下类型:
- 针对
Default Sub ActorComponent
, InitOnly
RPC
MulticastRPC(Reliable+Unreliable)
BeginPlay
的执行顺序
- 针对按需创建的
SubActorComponent
, InitOnly
RPC
MulticastRPC(Reliable+Unreliable)
BeginPlay
的执行顺序
前置知识
想要了解清楚上述各种情况下的执行顺序,
必须先搞清楚BeginPlay
, InitOnly
,
RPC
,
MulticastRPC(Reliable+Unreliable)
自身的执行顺序,
然在再将情况变得复杂, 即控制变量法
:先了解清楚简单情况,
再加入各种变量, 逐步扩延展到各种复杂情况.
BeginPlay
执行顺序
官方给出的Actor
生命周期中BeginPlay
所处的时机(Actor
生命周期).
UE中有三种SpawnActor的方法, 针对这三种创建Actor的方式,
依次查看其BeginPlay的时机.
- 主动创建:UWorld.SpawnActor
- 网络同步过来需要创建的Actor: SpawnActorAbsolute
- UWorld.SpawnActorDeferred+AActor.FinishSpawning
SpawnActor
DS端, 主动使用UWorld.SpawnActor
创建Actor
,
Actor
和DefaultSubActorComponent
的BeginPlay
执行顺序.
1 2 3
| ALSV: ATestRepFlowActor::ATestRepFlowActor FrameIndex[1113] RoleType[ROLE_Authority] ALSV: UTestRepFlowDefaultCmp::BeginPlay FrameIndex[1113] RoleType[ROLE_Authority] Owner[BPTestRepFlowSpawnedActor_C_0] ALSV: ATestRepFlowActor::BeginPlay FrameIndex[1113] RoleType[ROLE_Authority]
|
从日志上可以看出,
在DS
端执行顺序为:UTestRepFlowDefaultCmp::BeginPlay
->ATestRepFlowActor::BeginPlay
.
详细堆栈为:
创建Actor的BeginPlay堆栈+DefaultSubComp BeginPlay的堆栈
--UWorld.SpawnActor |--UWorld.SpawnActor |
|--AActor.PostSpawnInitialize.if | | |--AActor.FinishSpawning //
这个函数可以独立出来执行 | | |
|--AActor.PostActorConstruction | | | | |--AActor.DispatchBeginPlay
| | | | | |--ATestRepFlowSpawnedActor.BeginPlay | | | | | |
|--AActor.BeginPlay | | | | | | | |--// for all components
BeginPlay | | | | | | | |--UTestRepFlowDefaultCmp.BeginPlay <br
|
SpawnActorAbsolute
当网络数据中需要创建Actor, 则会调用该函数进行创建. 详细堆栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| --UActorChannel.ProcessBunch.if |--UPackageMapClient.SerializeNewActor | |--UWorld.SpawnActorAbsolute | | |--UWorld.SpawnActor | | | |--AActor.PostSpawnInitialize.if | | | | |--AActor.FinishSpawning.if.{ | | | | | |--AActor.PostActorConstruction | | | | | | |-- | | | | | | |--const bool bDeferBeginPlayAndUpdateOverlaps = (bExchangedRoles && RemoteRole == ROLE_Authority) && !GIsReinstancing; | | | | | | |--bool bRunBeginPlay = !bDeferBeginPlayAndUpdateOverlaps && (BeginPlayCallDepth > 0 || World->HasBegunPlay()); |-- |-- |--if (Actor && bSpawnedNewActor) |--{ |-- SCOPE_CYCLE_COUNTER(Stat_PostNetInit); |-- Actor->PostNetInit(); | |-- | |--AActor.DispatchBeginPlay | | |--AActor.BeginPlay | | | |--ATestRepFlowSpawnedActor.BeginPlay | | |--AActor.UpdateInitialOverlaps |--}
|
UE针对依据Bunch创建的Actor, 特意将BeginPlay延迟. 但是可以看出,
即使延迟执行BeginPlay, 但是Actor和Components也立即进行了Initialize.
即调用了AActor.PreInitializeComponents
和AActor.InitializeComponents
.
延迟原因
最早的版本就这样, 已经无法再查了. 再查就要往UE3查了.
SpawnActorDeferred+FinishSpawning
从上图可以清晰看到,
SpawnActorDeferred
不会调用FinishSpawning
,
也就不会初始化Actor Components, 以及调用BeginPlay了.
FinishSpawning
需要手动调用, 即逻辑分开,
UE提供了另一种方式供大家使用, 以便满足千变万化的需求.
InitOnly
及其OnRep
执行顺序
OnRep
为何物
OnRep为何物, 依据什么填充的, 又是怎么触发回调的, 以及怎么清除的?
构建OnRep信息
在Replayout中构建需要同步属性的Onrep信息,
RepNotifyCondition
+RepNotifyNumParams
1 2 3 4 5 6 7 8
| --FRepLayout.InitFromClass |-- |--Parents[ParentIndex].RepNotifyCondition = LifetimeProps[i].RepNotifyCondition; |--if (UFunction* RepNotifyFunc = InObjectClass->FindFunctionByName(Parents[ParentIndex].Property->RepNotifyFunc)) |--{ |-- Parents[ParentIndex].RepNotifyNumParams = RepNotifyFunc->NumParms; |--}
|
OnRep设置,回调和清理
在客户端收到DS发来的Bunch解析后, 设置属性内容, 并记录其OnRep信息.
如果需要OnRep, 则放入FReceivingRepState.RepNotifies
中.
佐证代码:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| --UNetDriver.TickFlush.if |--UIpConnection.Tick | |--UNetConnection.Tick.else.if.for | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.while | | | | |--FObjectReplicator.ReceivedBunch.if | | | | | |--FRepLayout.ReceiveProperties | | | | | | |--ReceiveProperties_r.for.else.else | | | | | | | |--ReceivePropertyHelper.if | | | | | | | | |-- | | | | | | | | |--Cmd.Property->NetSerializeItem(Bunch, Bunch.PackageMap, Data + SwappedCmd); | | | | | | | | |-- | | | | | | | | |--RepNotifies->AddUnique(Parent.Property); | | |--UActorChannel.ProcessQueuedBunches.if.if | | | |--FObjectReplicator.CallRepNotifies | | | | |--FRepLayout.CallRepNotifies.for.switch.{ | | | | | |--for (FProperty* RepProperty : RepState->RepNotifies) { | | | | | |-- | | | | | |-- | | | | | |-- UFunction* RepNotifyFunc = Object->FindFunction(RepProperty->RepNotifyFunc); | | | | | |-- | | | | | |-- | | | | | |-- Object->ProcessEvent(RepNotifyFunc, nullptr); | | | | | |-- | | | | | |-- | | | | | |-- FRepShadowDataBuffer PropertyData = ShadowData + Parent; | | | | | |-- | | | | | |-- if (EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsCustomDelta)) | | | | | |-- { | | | | | |-- Object->ProcessEvent(RepNotifyFunc, PropertyData); | | | | | |-- } | | | | | |-- else | | | | | |-- { | | | | | |-- | | | | | |-- const FBoolProperty* BoolProperty = CastField<const FBoolProperty>(Parent.Property); | | | | | |-- if (BoolProperty && !BoolProperty->IsNativeBool()) | | | | | |-- { | | | | | |-- bool BoolPropertyValue = BoolProperty->GetPropertyValue(PropertyData); | | | | | |-- Object->ProcessEvent(RepNotifyFunc, &BoolPropertyValue); | | | | | |-- } | | | | | |-- else | | | | | |-- { | | | | | |-- Object->ProcessEvent(RepNotifyFunc, PropertyData); | | | | | |-- } | | | | | |-- | | | | | |-- | | | | | |-- if (!EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsNetSerialize)) | | | | | |-- { | | | | | |-- RepProperty->CopyCompleteValue(ShadowData + Parent, ObjectData + Parent); | | | | | |-- } | | | | | |-- } | | | | | |-- | | | | | |-- | | | | | |-- check(EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsCustomDelta)); | | | | | |-- | | | | | |-- | | | | | |-- | | | | | |-- | | | | | |-- | | | | | |-- | | | | | |-- | | | | | |-- | | | | | |-- | | | | | |-- | | | | | |-- FMemMark Mark(FMemStack::Get()); | | | | | |-- uint8* Parms = new(FMemStack::Get(), MEM_Zeroed, RepNotifyFunc->ParmsSize)uint8; | | | | | |-- | | | | | |-- TFieldIterator<FProperty> Itr(RepNotifyFunc); | | | | | |-- check(Itr); | | | | | |-- | | | | | |-- FRepShadowDataBuffer PropertyData = ShadowData + Parent; | | | | | |-- | | | | | |-- Itr->CopyCompleteValue(Itr->ContainerPtrToValuePtr<void>(Parms), PropertyData); | | | | | |-- ++Itr; | | | | | |-- check(Itr); | | | | | |-- | | | | | |-- TArray<uint8> *NotifyMetaData = RepState->RepNotifyMetaData.Find(RepProperty); | | | | | |-- check(NotifyMetaData); | | | | | |-- Itr->CopyCompleteValue(Itr->ContainerPtrToValuePtr<void>(Parms), NotifyMetaData); | | | | | |-- | | | | | |-- Object->ProcessEvent(RepNotifyFunc, Parms); | | | | | |-- | | | | | |-- Mark.Pop(); | | | | | |-- break; | | | | | |--} | | | | | |-- | | | | | |--RepState->RepNotifies.Empty(); | | | | | |--RepState->RepNotifyMetaData.Empty();
|
通过如上堆栈可以清晰看出,
在for循环遍历所有FReceivingRepState.RepNotifies
处理完回调之后,
调用RepState->RepNotifies.Empty();
清理本次的OnRep数据.
这样OnRep的设置和回调是可以分开进行的.
InitOnly
为何物
InitOnly
对应的枚举类型为COND_InitialOnly
,
只随创建Actor
的Bunch
一起发送,
错过了就永远不会发送(Dormancy重新唤醒RepFlags.bNetInitial
会为true,
会触发COND_InitialOnly
的OnRep
). 佐证代码:
CompareParentProperty
返回true
,
证明变量没有发生改变, 所以不会进行属性同步, 也就不会触发OnRep.
并且也可以反推出,
创建Actor的bunch一定包含InitOnly
的所有数据.
InitOnly
的OnRep
流程
InitOnly
的OnRep
流程和其他属性的OnRep流程完全一样,
UE是不会区分其是否为InitOnly
的.
只不会InitOnly
数据会和SerializeActor
的数据放在同一个Bunch中.
这里再贴一下Bunch的结构, 参考: :UE网络-ReplicationActor及其相关文章.
这里要特意指出一下,
AActor.PostNetInit
是特意为网络初始化时没有进行BeginPlay
而添加的补充阶段,
即在网路层, 根据远端创建的Actor, 在这个bunch处理的末尾, 调用BeginPlay,
但是这时候, OnRep
可能还没有进行调用.
开启net.AllowAsyncLoading
开启net.AllowAsyncLoading
后,
SpawnActor+SerializeProperty+OnRep+BeginPlay详细堆栈
--UNetDriver.TickFlush.if |--UIpConnection.Tick |
|--UNetConnection.Tick.else.if.for | |
|--UActorChannel.ProcessQueuedBunches | | |
|--UActorChannel.ProcessBunch.if | | | |
|--// 创建Actor | | | | |--UPackageMapClient.SerializeNewActor
| | | | | |--UWorld.SpawnActorAbsolute | | | |
|--FObjectReplicator.ReceivedBunch.if | |
| | | |--// 根据远端数据序列化本地变量 | | | | |
|--FRepLayout.ReceiveProperties | | | | | |
|--ReceiveProperties_r.for.else.else | | | | | | |
|--ReceivePropertyHelper.if | | | | | | | |
|--FProperty.NetSerializeItem | |
|--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--// 调用OnRep | | |
|--FObjectReplicator.CallRepNotifies | | | | |-- //这里特殊说明一下,
如果为QueuedBunches(即开启了net.AllowAsyncLoading),
则会在QueuedBunches为空的时候, 才执行. | | | | |--if
(bSkipIfChannelHasQueuedBunches && (OwningChannel &&
OwningChannel->QueuedBunches.Num() > 0)) | | | | |--{ |
| | | |-- return; | | | | |--} | | | | |--// 调用 OnRep |
| | | |--FRepLayout.CallRepNotifies
| | |--UActorChannel.ProcessQueuedBunches.if.if.for | | |
|--UActorChannel.ProcessBunch.if | | | |
|--// 调用BeginPlay | | | | |--AActor.PostNetInit.if.if
| | | | | |--AActor.DispatchBeginPlay.if
|
这里要特意指出一下:
当开启net.AllowAsyncLoading
后,
在FObjectReplicator.CallRepNotifies
中,
会判断QueuedBunches
大小, 如果为0, 才会进行OnRep回调.
佐证代码:
那OnRep
什么时候回调呢?
答案: ActorChannel
内,
QueuedBunches
为0的时候, 即当前最后一个.
未开启net.AllowAsyncLoading
未开启net.AllowAsyncLoading
后,
SpawnActor+SerializeProperty+OnRep+BeginPlay详细堆栈
// 未开启net.AllowAsyncLoading后,
SpawnActor+SerializeProperty+OnRep+BeginPlay详细堆栈
--UIpNetDriver.TickDispatch
|--UNetConnection.ReceivedRawPacket.if.if.if |
|--UNetConnection.ReceivedPacket.while.{ | |
|--UChannel.ReceivedRawBunch.else | | |
|--UChannel.ReceivedNextBunch.if | | | |
|--UChannel.ReceivedSequencedBunch | | | | |
|--UActorChannel.ReceivedBunch | | | | | |
|--UActorChannel.ProcessBunch.if | | | |
| | | |--// 创建Actor | | | | | | |
|--UPackageMapClient.SerializeNewActor.if.if.if.if.if | | | | | | |
| |--UWorld.SpawnActorAbsolute | | | | | | | | |
|--UWorld.SpawnActor | | | | | |
|--UActorChannel.ProcessBunch.if | | | | | | |
|--FObjectReplicator.ReceivedBunch.if | |
| | | | | | |--// 根据远端数据序列化本地变量 | | | | | | | |
|--FRepLayout.ReceiveProperties | | | | | | | | |
|--ReceiveProperties_r.for.else.else | | | | | | | | | |
|--ReceivePropertyHelper.if | | | | | | | | | | |
|--FProperty.NetSerializeItem | | | | | |
|--UActorChannel.ProcessBunch.if | | | |
| | | |--// 调用OnRep | | | | | | |
|--FObjectReplicator.CallRepNotifies | | | | | | | |
|--FRepLayout.CallRepNotifies.for.switch.{ | | | | | |
|--UActorChannel.ProcessBunch.if | | | |
| | | |--// 调用BeginPlay | | | | | | | |--AActor.PostNetInit.if.if
| | | | | | | | |--AActor.DispatchBeginPlay.if | | | | | | | |
| |--ATestRepFlowSpawnedActor.BeginPlay
|
总结
可以看出无论是否开启net.AllowAsyncLoading
,
调用堆栈最后都是用UActorChannel.ProcessBunch
处理bunch.
其中有所区别的地方为:开启net.AllowAsyncLoading
可能会导致QueuedBunches
中有数据,
造成OnRep
延迟回调,
最终导致OnRep回调可能晚于BeginPlay
.
针对QueuedBunches还有数据, 就不能执行OnRep,
这段代码作用是什么呢? 通过查询提交记录得知, 是为了修复Replay
scrubbing后, Actor传送问题.
1
| Github上搜索这个唯一编号:db9ddd66282065b33e9b71ffb7c58ed5f042b146
|
所以, 对于正常不需要Replay的情景,
可以直接干掉, 对于需要Replay的, 加上Replay判定.
指针类型和非指针类型的InitOnly
如果一个UObject指针也设置成了InitOnly
类型,
那么它一定会传输吗?
当UObject在Actor创建时候就已经设置对应的值, 那么可能会被传输.
测试代码(这里还引申出ActorComponent网络归属权问题,
详见:Component Replication归属权):
1 2 3 4 5 6 7 8 9 10 11 12
| 开启net.AllowAsyncLoading后, 执行流程的一种情况为: ATestRepFlowSpawnedActor::ATestRepFlowSpawnedActor ->ATestRepFlowSpawnedActor.mTestNumber Init ->UTestRepFlowSpawnedCmp::UTestRepFlowSpawnedCmp ->UTestRepFlowSpawnedCmp.TestNumber Init ->AActor.PostNetInit ->ATestRepFlowSpawnedActor.BeginPlay ->Super::BeginPlay(); ->UTestRepFlowSpawnedCmp.BeginPlay ->ATestRepFlowSpawnedActor.OnRep_TestNumber ->UTestRepFlowSpawnedCmp.OnRep_TestNumber ->ATestRepFlowSpawnedActor.OnRep_TestRepFlowSpawnedCmp
|
首先把DS端ReplicateActor时序列化各种属性的顺序贴出来;
反序列化Actor
首先ATestRepFlowSpawnedActor::ATestRepFlowSpawnedActor
执行堆栈:
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 27 28
| // 未触发异步加载, `ATestRepFlowSpawnedActor::ATestRepFlowSpawnedActor`执行堆栈: --UIpNetDriver.TickDispatch |--UNetConnection.ReceivedRawPacket.if.if.if | |--UNetConnection.ReceivedPacket.while.{ | | |--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
// 触发异步加载, 创建`ATestRepFlowSpawnedActor`堆栈 --UNetDriver.TickFlush.if |--UIpConnection.Tick | |--UNetConnection.Tick.else.if.for | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.if | | | | |--UPackageMapClient.SerializeNewActor.if.if.if.if | | | | | |--UWorld.SpawnActorAbsolute | | | | | | |--UWorld.SpawnActor | | | | | | | |--NewObject | | | | | | | | |--StaticConstructObject_Internal.if | | | | | | | | | |--ATestRepFlowSpawnedActor.ATestRepFlowSpawnedActor
|
核心关键代码: 在处理bunch时, 由于ActorChannel的Actor为空,
需要根据Bunch内容重新创建.
这也对应了图ReplicateActor内填充Bunch的顺序(第一个填充的就是SerializeActor的信息).
初始化Actor内属性
初始化ATestRepFlowSpawnedActor.mTestNumber
.
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
| // 未触发异步加载, 初始化`ATestRepFlowSpawnedActor.mTestNumber`. --UIpNetDriver.TickDispatch |--UNetConnection.ReceivedRawPacket.if.if.if | |--UNetConnection.ReceivedPacket.while.{ | | |--UChannel.ReceivedRawBunch.else | | | |--UChannel.ReceivedNextBunch.if | | | | |--UChannel.ReceivedSequencedBunch | | | | | |--UActorChannel.ReceivedBunch | | | | | | |--UActorChannel.ProcessBunch.if | | | | | | | |--FObjectReplicator.ReceivedBunch.if | | | | | | | | |--FRepLayout.ReceiveProperties | | | | | | | | | |--ReceiveProperties_r.for.else.else | | | | | | | | | | |--ReceivePropertyHelper.if | | | | | | | | | | | |--FProperty.NetSerializeItem
// 触发异步加载, 初始化`ATestRepFlowSpawnedActor.mTestNumber`. --UNetDriver.TickFlush.if |--UIpConnection.Tick | |--UNetConnection.Tick.else.if.for | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.while | | | | |--FObjectReplicator.ReceivedBunch.if | | | | | |--FRepLayout.ReceiveProperties | | | | | | |--ReceiveProperties_r.for.else.else | | | | | | | |--ReceivePropertyHelper.if | | | | | | | | |--FProperty.NetSerializeItem
|
此时处理ActorProperties对应的ContentBlock.
这也和代码能对应上: 循环处理ContentBlock.
ActorComponent序列化
创建UTestRepFlowSpawnedCmp
:
UTestRepFlowSpawnedCmp::UTestRepFlowSpawnedCmp
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
| // 未触发异步加载, 创建`UTestRepFlowSpawnedCmp`堆栈 --UIpNetDriver.TickDispatch |--UNetConnection.ReceivedRawPacket.if.if.if | |--UNetConnection.ReceivedPacket.while.{ | | |--UChannel.ReceivedRawBunch.else | | | |--UChannel.ReceivedNextBunch.if | | | | |--UChannel.ReceivedSequencedBunch | | | | | |--UActorChannel.ReceivedBunch | | | | | | |--UActorChannel.ProcessBunch.if | | | | | | | |--UActorChannel.ReadContentBlockPayload | | | | | | | | |--UActorChannel.ReadContentBlockHeader.if | | | | | | | | | |--NewObject | | | | | | | | | | |--StaticConstructObject_Internal.if | | | | | | | | | | | |--UTestRepFlowSpawnedCmp.UTestRepFlowSpawnedCmp
// 触发异步加载, 创建`UTestRepFlowSpawnedCmp`堆栈 --UNetDriver.TickFlush.if |--UIpConnection.Tick | |--UNetConnection.Tick.else.if.for | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.while | | | | |--UActorChannel.ReadContentBlockPayload | | | | | |--UActorChannel.ReadContentBlockHeader.if | | | | | | |--NewObject | | | | | | | |--StaticConstructObject_Internal.if | | | | | | | | |--UTestRepFlowSpawnedCmp.UTestRepFlowSpawnedCmp
|
初始化ActorComponent属性
UTestRepFlowSpawnedCmp.TestNumber Init
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
| // 未触发异步加载, UTestRepFlowSpawnedCmp.TestNumber init堆栈 --UIpNetDriver.TickDispatch |--UNetConnection.ReceivedRawPacket.if.if.if | |--UNetConnection.ReceivedPacket.while.{ | | |--UChannel.ReceivedRawBunch.else | | | |--UChannel.ReceivedNextBunch.if | | | | |--UChannel.ReceivedSequencedBunch | | | | | |--UActorChannel.ReceivedBunch | | | | | | |--UActorChannel.ProcessBunch.if | | | | | | | |--FObjectReplicator.ReceivedBunch.if | | | | | | | | |--FRepLayout.ReceiveProperties | | | | | | | | | |--ReceiveProperties_r.for.else.else | | | | | | | | | | |--ReceivePropertyHelper.if | | | | | | | | | | | |--FProperty.NetSerializeItem
// 触发异步加载, UTestRepFlowSpawnedCmp.TestNumber init堆栈 --UNetDriver.TickFlush.if |--UIpConnection.Tick | |--UNetConnection.Tick.else.if.for | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.while | | | | |--FObjectReplicator.ReceivedBunch.if | | | | | |--FRepLayout.ReceiveProperties | | | | | | |--ReceiveProperties_r.for.else.else | | | | | | | |--ReceivePropertyHelper.if | | | | | | | | |--FProperty.NetSerializeItem
|
AActor.PostNetInit
由于开启了net.AllowAsyncLoading
, 一般而言,
此时还会有Bunch未处理, 所以, 跳过OnRep阶段, 执行AActor.PostNetInit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| --UNetDriver.TickFlush.if |--UIpConnection.Tick | |--UNetConnection.Tick.else.if.for | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.if | | | | |--AActor.PostNetInit.if.if | | | | | |--AActor.DispatchBeginPlay.if | | | | | | |--ATestRepFlowSpawnedActor.BeginPlay
--UIpNetDriver.TickDispatch |--UNetConnection.ReceivedRawPacket.if.if.if | |--UNetConnection.ReceivedPacket.while.{ | | |--UChannel.ReceivedRawBunch.else | | | |--UChannel.ReceivedNextBunch.if | | | | |--UChannel.ReceivedSequencedBunch | | | | | |--UActorChannel.ReceivedBunch | | | | | | |--UActorChannel.ProcessBunch.if | | | | | | | |--AActor.PostNetInit.if.if | | | | | | | | |--AActor.DispatchBeginPlay.if | | | | | | | | | |--ATestRepFlowSpawnedActor.BeginPlay
|
总结
开启net.AllowAsyncLoading
后, 执行顺序一定吗? 不一定,
因为开启它之后, 可能会因为异步加载导致在QueueBunch中处理,
也可能没有触发异步加载, 还在原来流程中处理. 所以各种情况都会存在.
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| // 触发异步加载后, 执行堆栈的一种情况 --UNetDriver.TickFlush.if |--UIpConnection.Tick | |--UNetConnection.Tick.else.if.for | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.if | | | | |--// 触发异步加载, 创建`ATestRepFlowSpawnedActor`堆栈 | | | | |--UPackageMapClient.SerializeNewActor.if.if.if.if | | | | | |--UWorld.SpawnActorAbsolute | | | | | | |--UWorld.SpawnActor | | | | | | | |--NewObject | | | | | | | | |--StaticConstructObject_Internal.if | | | | | | | | | |--ATestRepFlowSpawnedActor.ATestRepFlowSpawnedActor | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.while | | | | |--FObjectReplicator.ReceivedBunch.if | | | | | |--// 触发异步加载, 初始化`ATestRepFlowSpawnedActor.mTestNumber`. | | | | | |--FRepLayout.ReceiveProperties | | | | | | |--ReceiveProperties_r.for.else.else | | | | | | | |--ReceivePropertyHelper.if | | | | | | | | |--FProperty.NetSerializeItem | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.while | | | | |--UActorChannel.ReadContentBlockPayload | | | | | |--// 触发异步加载, 创建`UTestRepFlowSpawnedCmp`堆栈 | | | | | |--UActorChannel.ReadContentBlockHeader.if | | | | | | |--NewObject | | | | | | | |--StaticConstructObject_Internal.if | | | | | | | | |--UTestRepFlowSpawnedCmp.UTestRepFlowSpawnedCmp | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.while | | | | |--FObjectReplicator.ReceivedBunch.if | | | | | |--// 触发异步加载, UTestRepFlowSpawnedCmp.TestNumber init堆栈 | | | | | |--FRepLayout.ReceiveProperties | | | | | | |--ReceiveProperties_r.for.else.else | | | | | | | |--ReceivePropertyHelper.if | | | | | | | | |--FProperty.NetSerializeItem | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--UActorChannel.ProcessBunch.if | | | | |--AActor.PostNetInit.if.if | | | | | |--// 触发异步加载后,ATestRepFlowSpawnedActor.BeginPlay堆栈 | | | | | |--AActor.DispatchBeginPlay.if | | | | | | |--ATestRepFlowSpawnedActor.BeginPlay | | | | | | | |--AActor.BeginPlay.for.if | | | | | | | | |--// 触发异步加载后,UTestRepFlowSpawnedCmp.BeginPlay堆栈 | | | | | | | | |--UTestRepFlowSpawnedCmp.BeginPlay | | |--UActorChannel.ProcessQueuedBunches.if.if.for | | | |--FObjectReplicator.CallRepNotifies | | | | |--FRepLayout.CallRepNotifies.for.switch.{ | | | | | |--AActor.ProcessEvent.if.if | | | | | | |--UObject.ProcessEvent.{ | | | | | | | |--UFunction.Invoke | | | | | | | | |--// 触发异步加载后,ATestRepFlowSpawnedActor::OnRep_TestNumber堆栈 | | | | | | | | |--ATestRepFlowSpawnedActor.OnRep_TestNumber | | | |--FObjectReplicator.CallRepNotifies | | | | |--FRepLayout.CallRepNotifies.for.switch.{ | | | | | |--UObject.ProcessEvent.{ | | | | | | |--UFunction.Invoke | | | | | | | |--// 触发异步加载后, UTestRepFlowSpawnedCmp::OnRep_TestNumber堆栈 | | | | | | | |--UTestRepFlowSpawnedCmp.OnRep_TestNumber --UNetDriver.TickFlush |--// 处理UnmappedObject |--FObjectReplicator.UpdateUnmappedObjects | |--FRepLayout.ReceiveProperties_BackwardsCompatible_r.while.FRepLayout.UpdateUnmappedObjects.if | | |--FRepLayout.UpdateUnmappedObjects_r.for.if | | | |--FObjectPropertyBase.NetSerializeItem | | | | |--FObjectProperty.SetObjectPropertyValue |--FObjectReplicator.UpdateUnmappedObjects | |--FObjectReplicator.CallRepNotifies | | |--FRepLayout.CallRepNotifies
|
RPC
执行顺序
TODO
MulticastRPC Reliable
执行顺序
TODO
MulticastRPC Unreliable
执行顺序
TODO
Actor
情形
TODO
Default Sub ActorComponent
情形
TODO
按需创建的SubActorComponent
情形
如果SpawnSubActorComponent
中有COND_InitialOnly
类型的属性,
那么这类属性如果在SerializeActorBunch之前还没有设置对应的值,
那么该属性以后都无法进行同步(除非Dormancy后重新唤醒). 示例:
原因: COND_InitialOnly
的意思是,
只有网络上第一次(Dormancy
之后再次重新使用, 也算第一次,
因为Dormancy
是将其从网络中移除.)同步某一个Actor
时候,
才会将这次同步的Bunch
标记为bNetInitial
,
并且将其设置为Reliable
.
具体堆栈:
1 2 3 4 5 6 7 8 9 10 11
| --UActorChannel.ReplicateActor.if |--AActor.ReplicateSubobjects.for.if | |--UActorChannel.ReplicateSubobject | | |--FObjectReplicator.ReplicateProperties | | | |--FNetSerializeCB.UpdateChangelistMgr | | | | |--FRepLayout.UpdateChangelistMgr | | | | | |--FRepLayout.CompareProperties.else | | | | | | |--CompareParentProperties.if.else | | | | | | | |--UE4_RepLayout_Private.CompareParentPropertyHelper | | | | | | | | |--CompareParentProperty.if | | | | | | | | | |--const bool bShouldSkip = !bIsLifetime || !bIsActive || (Parent.Condition == COND_InitialOnly && !SharedParams.bIsInitial);
|
从上图也可以验证, 这个ObjectReplicator
为新创建的,
即通过上文NewObject
创建的.
那么怎么做才能针对首次动态创建的SubActorComponent
,
也可以OnRep
COND_InitialOnly
属性呢?
答: 如果是新创建的ActorComponent
,
将RepFlags.bNetInitial
改成true.
总结
延迟创建(和SpawnActor不在同一帧, 并且创建Actor的Bunch已经同步过了),
该Component永远不会进行属性同步, 即客户端永远不会有OnRep的调用了.
总结
TODO
附言
net.AllowAsyncLoading
可以参考我之前的文章:网络同步中, Package异步加载