Explorar el Código

feat: 添加属性同步的一些解释

NiceTry12138 hace 4 meses
padre
commit
546991813e

BIN
UE5/UClass/Image/004.png


BIN
UE5/UClass/Image/005.png


BIN
UE5/UClass/Image/006.png


BIN
UE5/UClass/Image/007.png


+ 47 - 0
UE5/UClass/README.md

@@ -384,4 +384,51 @@ public:
 
 在不使用 `bCopyTransientsFromClassDefaults` 的情况下,只有 `UPROPERTY` 中配置了 `config` 的属性,才会在初始化的时候从 CDO 拷贝属性值,其他属性不会从 CDO 中拷贝属性值
 
+## FField 和 FProperty
+
+```cpp
+class FField
+{
+	UE_NONCOPYABLE(FField);
+	FFieldClass* ClassPrivate;  // 指向该字段的类型描述符
+
+public:
+	FFieldVariant Owner;    // 标识字段的所有者
+	FField* Next;           // 单链表指针,连接同属一个作用域的字段
+	FName NamePrivate;      // 字段的名称标识符
+	EObjectFlags FlagsPrivate;  // 字段的行为标志位
+}
+```
+
+- FField 运行时
+
+![](Image/004.png) 
+
+- FFieldClass 运行时
+
+![](Image/005.png) 
+
+```cpp
+class FProperty : public FField
+{
+	DECLARE_FIELD_API(FProperty, FField, CASTCLASS_FProperty, COREUOBJECT_API)
+
+	int32			ArrayDim;       // 定义属性数组维度,比如 int32 Value[10] 那么 ArrayDim 值为 10
+	int32			ElementSize;    // 单个元素的字节大小
+	EPropertyFlags	PropertyFlags;  // 属性的核心行为特征
+	uint16			RepIndex;       // 网络复制时的唯一标识符
+	TEnumAsByte<ELifetimeCondition> BlueprintReplicationCondition;  // 蓝图属性的网络复制条件
+	int32		Offset_Internal;        // 属性在所属对象内存块中的偏移量
+	FProperty*	PropertyLinkNext;       // 按声明顺序链接属性(从派生类到基类)
+	FProperty*  NextRef;                // 链接所有含 UObject 引用的属性
+	FProperty*	DestructorLinkNext;     // 链接需要显式析构的非平凡类型
+	FProperty*	PostConstructLinkNext;  // 链接需要后置构造处理的属性
+	FName		RepNotifyFunc;          // 存储复制通知函数名称
+}
+```
+
+![](Image/006.png)
+
+![](Image/007.png)
+
 

+ 172 - 0
UE5/网络/属性同步/README.md

@@ -344,3 +344,175 @@ PriorityActors[k]->ActorInfo->bPendingNetUpdate = true;
 ![](Image/008.png)
 
 ![](Image/009.png)
+
+## FObjectReplicator
+
+每一个同步的 UObject,都对应着一个 FObjectReplicator 对象,这个对象负责了属性的对比,同步属性的提取,RepState的维护,丢包的处理,属性同步可靠的处理等等
+
+在创建 `UActorChannel` 并通过 `SetChannelActor` 设置 `Actor` 的时候,就会初始化 `ActorReplicator` 对象
+
+### FRepLayout
+
+`FRepLayout` 对象,该对象存储了对象中所有需要网络复制的属性(标记为CPF_Net)的布局信息,包括属性类型、内存偏移、复制条件等
+
+通过 `FRepLayout::CreateFromClass` 创建 `FRepLayout`,并通过 `FRepLayout::InitFromClass` 初始化 `FRepLayout`
+
+在 `FObjectReplicator` 中就包含着 `FRepLayout` 的成员属性
+
+通过 `InObjectClass->SetUpRuntimeReplicationData()` 初始化 `UClass` 中所有网络相关的属性,并保存在 `UClass::ClassReps` 数组中
+
+通过遍历 `UClass::ClassReps` 数组,初始化 `FRepLayout::Parents` 数组
+
+随后调用 CDO 的 `GetLifetimeReplicatedProps` 函数构建 `LifetimeProps` 数组
+
+```cpp
+TArray<FLifetimeProperty> LifetimeProps;
+LifetimeProps.Reserve(Parents.Num());
+
+UObject* Object = InObjectClass->GetDefaultObject();
+
+Object->GetLifetimeReplicatedProps(LifetimeProps);
+```
+
+`GetLifetimeReplicatedProps` 需要子类重写,该函数主要用于定义需要复制的属性、复制属性的条件等
+
+最后通过遍历 `LifetimeProps` 数组,将手动设置的属性的 `Condition` 复制到 `Parents` 数组中
+
+```cpp
+Parents[ParentIndex].Condition = LifetimeProps[i].Condition;
+Parents[ParentIndex].RepNotifyCondition = LifetimeProps[i].RepNotifyCondition;
+```
+
+> 通过这段代码,可以知道 `GetLifetimeReplicatedProps` 是在构建 `FRepLayout` 时执行的
+
+由于每个类型的 UClass 是固定的,所以每个 UClass 只需要创建一个对应的 FRepLayout 即可
+
+```cpp
+TSharedPtr<FRepLayout>* RepLayoutPtr = RepLayoutMap.Find(Class);
+```
+
+> `RepLayoutMap` 是 `UNetDriver` 的属性,全局唯一
+
+### InitWithObject
+
+`FObjectReplicator::InitWithObject` 初始化 `FObjectReplicator`
+
+```cpp
+ObjectClass = InObject->GetClass();
+Connection = InConnection;
+RemoteFunctions = nullptr;
+RepLayout = Connection->Driver->GetObjectClassRepLayout(ObjectClass);
+// 其他属性初始化
+
+// 缓存当前属性状态,为后面比较数值是否变化
+InitRecentProperties(Source);
+```
+
+### ReplicateProperties_r
+
+1. 更新 `ChangelistMgr` 
+
+```cpp
+const ERepLayoutResult UpdateResult = FNetSerializeCB::UpdateChangelistMgr(*RepLayout, SendingRepState, *ChangelistMgr, Object, Connection->Driver->ReplicationFrame, RepFlags, OwningChannel->bForceCompareProperties || bUseCheckpointRepState);
+```
+
+2. 属性对比 并写入 `Writer`
+
+```cpp
+const bool bHasRepLayout = RepLayout->ReplicateProperties(SendingRepState, ChangelistMgr->GetRepChangelistState(), (uint8*)Object, ObjectClass, OwningChannel, Writer, RepFlags);
+```
+
+3. 写入自定义数据数据到 `Writer` 中
+
+```cpp
+ReplicateCustomDeltaProperties(Writer, RepFlags, bSkippedPropertyCondition);
+```
+
+4. 写入 Unreliable 的 RPC 数据
+
+```cpp
+Writer.SerializeBits( RemoteFunctions->GetData(), RemoteFunctions->GetNumBits() );
+```
+
+5. 写入 Bunch
+
+```cpp
+OwningChannel->WriteContentBlockPayload( Object, Bunch, bHasRepLayout, Writer );
+```
+
+## 客户端接收
+
+在 `UActorChannel::ReceivedBunch` 中判断 `Bunch` 当前能否处理
+
+在 `UActorChannel::ReceivedBunch` 中真正处理 `Bunch`
+
+```cpp
+// Bunch 没有到末尾 Connection 有效且没有被关闭
+while ( !Bunch.AtEnd() && Connection != NULL && Connection->GetConnectionState() != USOCK_Closed )
+{
+	// 有效性判断
+	
+	// 处理 Bunch
+	if ( !Replicator->ReceivedBunch( Reader, RepFlags, bHasRepLayout, bHasUnmapped ) )
+	{
+		// 是否跳过
+	}
+}
+
+// 通知 Actor 初次网络同步完成,只有第一次调用
+if (Actor && bSpawnedNewActor)
+{
+	SCOPE_CYCLE_COUNTER(Stat_PostNetInit);
+	Actor->PostNetInit();
+}
+```
+
+通过 `FObjectReplicator::ReceivedBunch` 来处理 `Bunch` 中的数据
+
+从 `Bunch` 中得到
+
+- `FieldCache` 元数据
+- `Reader` 字段数据
+
+```cpp
+// 处理 非自定义增量的属性/结构体 
+if (!LocalRepLayout.ReceiveProperties(OwningChannel, ObjectClass, RepState->GetReceivingRepState(), Object, Bunch, bLocalHasUnmapped, bGuidsChanged, ReceivePropFlags))
+{
+	UE_LOG(LogRep, Error, TEXT( "RepLayout->ReceiveProperties FAILED: %s" ), *Object->GetFullName());
+	return false;
+}
+
+// 读取 Bunch 中的数据
+if (!OwningChannel->ReadFieldHeaderAndPayload(Object, ClassCache, NetFieldExportGroup, Bunch, &FieldCache, Reader))
+{
+	break;
+}
+
+// Handle property
+if (FStructProperty* ReplicatedProp = CastField<FStructProperty>(FieldCache->Field.ToField()))
+{
+	// 处理自定义结构体的属性复制
+}
+// Handle function call
+else if ( Cast< UFunction >( FieldCache->Field.ToUObject() ) )
+{
+	// 处理 Unreliable 的 RPC 调用
+}
+```
+
+当处理完毕之后,统一触发属性修改的回调函数
+
+```cpp
+FObjectReplicator::PostReceivedBunch()
+```
+
+在 `RepState` 中存储着需要被 Notify 的属性,遍历触发即可
+
+```cpp
+for (FProperty* RepProperty : RepState->RepNotifies)
+{
+	// ... do something
+	UFunction* RepNotifyFunc = Object->FindFunction(RepProperty->RepNotifyFunc);
+	Object->ProcessEvent(RepNotifyFunc, nullptr);
+}
+```