InitOnly RPC MulticastRPC(Reliable+Unreliable) BeginPlay的执行顺序

image-20231119110314263

导言

需要验证的情况大致分成两类, 开启/未开启net.AllowAsyncLoading, 每类又包含如下细分情况:

  1. SpawnActor, BeginPlay, InitOnly RPC MulticastRPC(Reliable+Unreliable)的详细流程

  2. 针对Actor本身, InitOnly RPC MulticastRPC(Reliable+Unreliable) BeginPlay的执行顺序

    • 动态创建的Actor

    • 场景中摆放的Actor

  3. 针对非指针类型的变量, 例如int32, float等.

  4. 针对指针类型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 生命周期).

ActorLifeCycle.png

UE中有三种SpawnActor的方法, 针对这三种创建Actor的方式, 依次查看其BeginPlay的时机.

  1. 主动创建:UWorld.SpawnActor
  2. 网络同步过来需要创建的Actor: SpawnActorAbsolute
  3. UWorld.SpawnActorDeferred+AActor.FinishSpawning

SpawnActor

DS端, 主动使用UWorld.SpawnActor创建Actor, ActorDefaultSubActorComponentBeginPlay执行顺序.

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
// 远端, 根据Bunch被动创建, 不会立即执行BeginPlay, 而是等待处理完这个Bunch的所有消息才执行BeginPlay
--UActorChannel.ProcessBunch.if
|--UPackageMapClient.SerializeNewActor
| |--UWorld.SpawnActorAbsolute
| | |--UWorld.SpawnActor
| | | |--AActor.PostSpawnInitialize.if
| | | | |--AActor.FinishSpawning.if.{
| | | | | |--AActor.PostActorConstruction
| | | | | | |--// 对于根据Bunch创建的Actor, 不会立即执行BeginPlay
| | | | | | |--const bool bDeferBeginPlayAndUpdateOverlaps = (bExchangedRoles && RemoteRole == ROLE_Authority) && !GIsReinstancing;
| | | | | | |--bool bRunBeginPlay = !bDeferBeginPlayAndUpdateOverlaps && (BeginPlayCallDepth > 0 || World->HasBegunPlay());
|--// 处理Bunch中ContentBlock内容
|--// After all properties have been initialized, call PostNetInit. This should call BeginPlay() so initialization can be done with proper starting values.
|--if (Actor && bSpawnedNewActor)
|--{
|-- SCOPE_CYCLE_COUNTER(Stat_PostNetInit);
|-- Actor->PostNetInit();
| |--// 如果Actor没有执行BeginPlay, 就会执行BeginPlay
| |--AActor.DispatchBeginPlay
| | |--AActor.BeginPlay
| | | |--ATestRepFlowSpawnedActor.BeginPlay
| | |--AActor.UpdateInitialOverlaps
|--}

UE针对依据Bunch创建的Actor, 特意将BeginPlay延迟. 但是可以看出, 即使延迟执行BeginPlay, 但是Actor和Components也立即进行了Initialize. 即调用了AActor.PreInitializeComponentsAActor.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
// Replayout中OnRep相关设置初始化流程
--FRepLayout.InitFromClass
|--// 遍历所有LifetimeProps, 设置其RepNotifyCondition和RepNotifyNumParams
|--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中. 佐证代码:

image-20231118125441054
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
// OnRep设置以及回调堆栈
--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
| | | | | | | | |--// Read the property
| | | | | | | | |--Cmd.Property->NetSerializeItem(Bunch, Bunch.PackageMap, Data + SwappedCmd);
| | | | | | | | |--// 如果需要, 则将Property放入RepNotify中
| | | | | | | | |--RepNotifies->AddUnique(Parent.Property);
| | |--UActorChannel.ProcessQueuedBunches.if.if
| | | |--FObjectReplicator.CallRepNotifies
| | | | |--FRepLayout.CallRepNotifies.for.switch.{
| | | | | |--for (FProperty* RepProperty : RepState->RepNotifies) {
| | | | | |-- // 遍历所有FReceivingRepState.RepNotifies, 执行OnRep
| | | | | |-- // 找到OnRep对应的UFunction
| | | | | |-- UFunction* RepNotifyFunc = Object->FindFunction(RepProperty->RepNotifyFunc);
| | | | | |--
| | | | | |-- // 如果0个参数, 直接调用OnRep
| | | | | |-- Object->ProcessEvent(RepNotifyFunc, nullptr);
| | | | | |--
| | | | | |-- // 如果一个参数, 则该参数为旧历史数据
| | | | | |-- FRepShadowDataBuffer PropertyData = ShadowData + Parent;
| | | | | |--
| | | | | |-- if (EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsCustomDelta))
| | | | | |-- {
| | | | | |-- Object->ProcessEvent(RepNotifyFunc, PropertyData);
| | | | | |-- }
| | | | | |-- else
| | | | | |-- {
| | | | | |-- // Handle bitfields.
| | | | | |-- 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);
| | | | | |-- }
| | | | | |--
| | | | | |-- // now store the complete value in the shadow buffer
| | | | | |-- if (!EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsNetSerialize))
| | | | | |-- {
| | | | | |-- RepProperty->CopyCompleteValue(ShadowData + Parent, ObjectData + Parent);
| | | | | |-- }
| | | | | |-- }
| | | | | |--
| | | | | |-- // 如果为两个参数, 则
| | | | | |-- check(EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsCustomDelta));
| | | | | |--
| | | | | |-- // Fixme: this isn't as safe as it could be. Right now we have two types of parameters: MetaData (a TArray<uint8>)
| | | | | |-- // and the last local value (pointer into the Recent[] array).
| | | | | |-- //
| | | | | |-- // Arrays always expect MetaData. Everything else, including structs, expect last value.
| | | | | |-- // This is enforced with UHT only. If a ::NetSerialize function ever starts producing a MetaData array thats not in FArrayProperty,
| | | | | |-- // we have no static way of catching this and the replication system could pass the wrong thing into ProcessEvent here.
| | | | | |-- //
| | | | | |-- // But this is all sort of an edge case feature anyways, so its not worth tearing things up too much over.
| | | | | |--
| | | | | |-- 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;
| | | | | |--}
| | | | | |--// for循环遍历之后, 清理OnRep
| | | | | |--RepState->RepNotifies.Empty();
| | | | | |--RepState->RepNotifyMetaData.Empty();

通过如上堆栈可以清晰看出, 在for循环遍历所有FReceivingRepState.RepNotifies处理完回调之后, 调用RepState->RepNotifies.Empty();清理本次的OnRep数据. 这样OnRep的设置和回调是可以分开进行的.

InitOnly为何物

InitOnly对应的枚举类型为COND_InitialOnly, 只随创建ActorBunch一起发送, 错过了就永远不会发送(Dormancy重新唤醒RepFlags.bNetInitial会为true, 会触发COND_InitialOnlyOnRep). 佐证代码:

CompareParentProperty返回true, 证明变量没有发生改变, 所以不会进行属性同步, 也就不会触发OnRep. 并且也可以反推出, 创建Actor的bunch一定包含InitOnly的所有数据.

InitOnlyOnRep流程

InitOnlyOnRep流程和其他属性的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(); // Actor::BeginPlay()
->UTestRepFlowSpawnedCmp.BeginPlay
->ATestRepFlowSpawnedActor.OnRep_TestNumber
->UTestRepFlowSpawnedCmp.OnRep_TestNumber
->ATestRepFlowSpawnedActor.OnRep_TestRepFlowSpawnedCmp

首先把DS端ReplicateActor时序列化各种属性的顺序贴出来;

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.

image-20231118180041065

这也和代码能对应上: 循环处理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
// 触发异步加载后,ATestRepFlowSpawnedActor.BeginPlay堆栈
--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

// 未触发异步加载后,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

附言

  1. net.AllowAsyncLoading可以参考我之前的文章:网络同步中, Package异步加载