Unreal 对象、属性同步流程

news2025/2/23 7:17:39

文章目录

  • 类型
  • 同步初始化
    • 创建 FObjectReplicator
    • 创建 FRepLayout、Cmd、ShadowOffset
    • 创建 FRepChangedPropertyTracker、FRepState
    • 创建 FReplicationChangelistMgr、FRepChangelistState、ShadowBuffer
  • 属性同步
    • 属性变化检测
      • 查找变化属性,写入ShadowMemory
      • 发送数据
    • 接收数据
  • UObject同步
    • 发送对象和属性变化
    • 接收对象和属性变化
  • UObject指针同步
  • AActor同步
    • 发送新的Actor
    • 接受新的Actor
  • TODO

类型

// 网络驱动,World唯一
UWorld::Driver -> UNetDriver

// 网络连接,OwningActor是PlayerController,服务器会存在多个ClientConnections
UNetDriver::ServerConnection、ClientConnections -> UNetConnection

// 交换、处理Actor数据,无论属性同步还是RPC,都需要经过Actor对应的ActorChannel
UNetConnection::ActorChannels -> UActorChannel

// Actor可能有多个同步的SubObject,每个都会有一个FObjectReplicator(包括Actor)进行同步的处理
UActorChannel::ReplicationMap -> FObjectReplicator

// 包含了一个类的同步信息,主要是各个属性的信息
UNetDriver::RepLayoutMap -> FRepLayout

// 类的每个同步的属性,内部有属性的地址偏移(FProperty* InProperty, int32 InIndex)
UClass::ClassReps -> FRepRecord

// 每个同步属性对应的同步信息,例如在Object内存内的Offset、在ShadowData内的Offset、子Cmd的下标范围、同步和OnRep函数的条件
FRepLayout::Parents -> FRepParentCmd

// UE::Net::Private::FNetPropertyConditionManager::Get()::PropertyTrackerMap
// 		-> TMap<FObjectKey, TSharedPtr<FRepChangedPropertyTracker>>
// 记录具体对象的哪些属性是ActiveForRep的,以及同步的DynamicCondition
FRepChangedPropertyTracker(FCustomPropertyConditionState) 	

// 存了对象的同步状态,例如历史修改、过程中处理的共享数据、ShadowBuffer
UNetDriver::ReplicationChangeListMap -> TMap< UObject*, FReplicationChangelistMgrWrapper >
	::FReplicationChangelistMgr::FRepChangelistState		

// GUID、GUIDCache等信息
UNetConnection::PackageMap -> UPackageMapClient(UPackageMap)

// 内部的GUID信息表
UPackageMapClient::GuidCache -> FNetGUIDCache

同步初始化

创建 FObjectReplicator

创建 UActorChannel:

UNetConnection* Connection = Actor->GetNetConnection();
UActorChannel* Ch = Connection->FindActorChannelRef(Actor);
if (Ch == nullptr)
	Ch = (UActorChannel *)Connection->CreateChannelByName( NAME_Actor, EChannelCreateFlags::OpenedLocally );
	Ch->SetChannelActor(Actor, ESetChannelActorFlags::None);
		ActorReplicator = FindOrCreateReplicator(Actor);

创建 FObjectReplicator:

TSharedRef<FObjectReplicator>& UActorChannel::CreateReplicator(UObject* Obj)
TSharedRef<FObjectReplicator>& UActorChannel::CreateReplicator(UObject* Obj, bool bCheckDormantReplicators)
	NewReplicator = Connection->CreateReplicatorForNewActorChannel(Obj);
		NewReplicator->InitWithObject( Object, this, true );
	// 创建 FReplicationChangelistMgr、FRepChangelistState、ShadowBuffer
	NewRef->StartReplicating(this);
void FObjectReplicator::InitWithObject(UObject* InObject, UNetConnection* InConnection, bool bUseDefaultState)
	// 创建 FRepLayout
	RepLayout = Connection->Driver->GetObjectClassRepLayout(ObjectClass);
	// 创建 FRepChangedPropertyTracker、FRepState
	InitRecentProperties(Source);

创建 FRepLayout、Cmd、ShadowOffset

// Create RepLayout
TSharedPtr<FRepLayout> UNetDriver::GetObjectClassRepLayout( UClass * Class )
	TSharedPtr<FRepLayout>* RepLayoutPtr = RepLayoutMap.Find(Class);
	if (!RepLayoutPtr) 
		RepLayoutPtr = &RepLayoutMap.Add(Class, FRepLayout::CreateFromClass(Class, ServerConnection, Flags));
			RepLayout->InitFromClass(InClass, ServerConnection, CreateFlags);

void FRepLayout::InitFromClass(UClass* InObjectClass, const UNetConnection* ServerConnection, const ECreateRepLayoutFlags CreateFlags)
	// 初始化数组(网络相关属性、函数)
	InObjectClass->SetUpRuntimeReplicationData();
	for (int32 i = 0; i < InObjectClass->ClassReps.Num(); i++)
		// 添加ParentCmd(对应每个UProperty,ArrayDim不为1时一个UProperty会对应多个)
		const int32 ParentHandle = AddParentProperty(Parents, Property, ArrayIdx);
		// 当前属性内部偏移(数组)
		const int32 ParentOffset = Property->ElementSize * ArrayIdx;
		// 击落当前UProperty对应的Cmd数组位置起点
		Parents[ParentHandle].CmdStart = Cmds.Num();
		// 处理对应的Cmd
		RelativeHandle = InitFromProperty_r<ERepBuildType::Class>(SharedParams, StackParams);
		// CmdEnd = 终点+1
		Parents[ParentHandle].CmdEnd = Cmds.Num();
	// 增加一个Return的特殊Cmd
	AddReturnCmd(Cmds);
	TArray<FLifetimeProperty> LifetimeProps;
	UObject* Object = InObjectClass->GetDefaultObject();
	// 调用CDO的GetLifetimeReplicatedProps,收集FLifetimeProperty到LifetimeProps
	Object->GetLifetimeReplicatedProps(LifetimeProps);
	PushModelProperties.Init(false, Parents.Num());
	for (int32 i = 0; i < LifetimeProps.Num(); i++)
		const int32 ParentIndex = LifetimeProps[i].RepIndex;
		Parents[ParentIndex].Condition = LifetimeProps[i].Condition;
		Parents[ParentIndex].RepNotifyCondition = LifetimeProps[i].RepNotifyCondition;
		// 记录RepFunc
		if (UFunction* RepNotifyFunc = InObjectClass->FindFunctionByName(Parents[ParentIndex].Property->RepNotifyFunc))
			Parents[ParentIndex].RepNotifyNumParams = RepNotifyFunc->NumParms;
		if (bIsPushModelEnabled && LifetimeProps[i].bIsPushBased)
			PushModelProperties[ParentIndex] = true;
	BuildHandleToCmdIndexTable_r(0, Cmds.Num() - 1, BaseHandleToCmdIndex);
		for (int32 CmdIndex = CmdStart; CmdIndex < CmdEnd; CmdIndex++)
			const int32 Index = HandleToCmdIndex.Add(FHandleToCmdIndex(CmdIndex));
			if (Cmd.Type == ERepLayoutCmdType::DynamicArray)
				HandleToCmdIndex[Index].HandleToCmdIndex = TUniquePtr<TArray<FHandleToCmdIndex>>(new TArray<FHandleToCmdIndex>());
				BuildHandleToCmdIndexTable_r(CmdIndex + 1, Cmd.EndCmd - 1, ArrayHandleToCmdIndex);
	BuildShadowOffsets<ERepBuildType::Class>(InObjectClass, Parents, Cmds, ShadowDataBufferSize);

处理类内部的同步字段:

// --- Init ClassReps(List of replication records), NetFields(List of network relevant fields (functions))
void UClass::SetUpRuntimeReplicationData()
	// 根据CLASS_ReplicationDataIsSetUp判断是否已经设置
	// NetFields根据名字排序,ClassReps根据地址偏移排序
	// ClassReps内的FRepRecord内存有一个int值,如果一个属性的ArrayDim(c数组,一般不会这样使用所以都是1)大于1,则会记录多次({p,0}{p,1}{p,2})
	if ((Prop->PropertyFlags & CPF_Net)&& Prop->GetOwner<UObject>() == this)
		NetProperties.Add(Prop);
	if ((Func->FunctionFlags&FUNC_Net) && !Func->GetSuperFunction())
		NetFields.Add(Func);
	...
	// UProperty记录在ClassReps数组中的下标
	NetProperties[i]->RepIndex = (uint16)ClassReps.Num();
	for (int32 j = 0; j < NetProperties[i]->ArrayDim; j++)
		new(ClassReps)FRepRecord(NetProperties[i], j);

处理类型信息Cmd:

// --- Init Cmds
template<ERepBuildType BuildType>
static int32 InitFromProperty_r(FInitFromPropertySharedParams& SharedParams, FInitFromPropertyStackParams StackParams)
	if (FArrayProperty* ArrayProp = CastField<FArrayProperty>(StackParams.Property))
		++StackParams.RelativeHandle;
		StackParams.Offset += GetOffsetForProperty<BuildType>(*ArrayProp);
		// 增加一个DynamicArray的特殊Cmd
		const uint32 ArrayChecksum = AddArrayCmd(SharedParams, StackParams);
			FRepLayoutCmd& Cmd = SharedParams.Cmds.AddZeroed_GetRef();
			Cmd.Type = ERepLayoutCmdType::DynamicArray;
		InitFromProperty_r<BuildType>(SharedParams, NewStackParams);
		// 增加一个Return的特殊Cmd
		AddReturnCmd(SharedParams.Cmds);
			Cmds.AddZeroed_GetRef().Type = ERepLayoutCmdType::Return;
		SharedParams.Cmds[CmdStart].EndCmd = CmdEnd;
	else if (FStructProperty* StructProp = CastField<FStructProperty>(StackParams.Property))
		UScriptStruct* Struct = StructProp->Struct;
		StackParams.Offset += GetOffsetForProperty<BuildType>(*StructProp);
		// 如果自己定义了NetSerialize,就不需要知道内部的结构了,所以只增加一个Cmd
		if (EnumHasAnyFlags(Struct->StructFlags, STRUCT_NetSerializeNative))
			SharedParams.bHasNetSerializeProperties = true;
			++StackParams.RelativeHandle;
			AddPropertyCmd(SharedParams, StackParams);
			return StackParams.RelativeHandle;
			
		// 和UClass::SetUpRuntimeReplicationData类似,递归增加Cmd
		return InitFromStructProperty<BuildType>(SharedParams, StackParams, StructProp, Struct);
			TArray<FProperty*> NetProperties;
			NetProperties.Add(*It);
			Sort(NetProperties.GetData(), NetProperties.Num(), FCompareUFieldOffsets());
			const uint32 StructChecksum = GetRepLayoutCmdCompatibleChecksum(SharedParams, StackParams);
			for (int32 i = 0; i < NetProperties.Num(); i++)
				for (int32 j = 0; j < NetProperties[i]->ArrayDim; j++)
					const int32 ArrayElementOffset = j * NetProperties[i]->ElementSize;
					FInitFromPropertyStackParams NewStackParams{
						/*Property=*/NetProperties[i],
						/*Offset=*/StackParams.Offset + ArrayElementOffset,
						/*RelativeHandle=*/StackParams.RelativeHandle,
						/*ParentChecksum=*/StructChecksum,
						/*StaticArrayIndex=*/j,
						/*RecursingNetSerializeStruct=*/StackParams.RecursingNetSerializeStruct
					};
					StackParams.RelativeHandle = InitFromProperty_r<BuildType>(SharedParams, NewStackParams);
			return StackParams.RelativeHandle;
	else
		++StackParams.RelativeHandle;
		StackParams.Offset += GetOffsetForProperty<BuildType>(*StackParams.Property);
		AddPropertyCmd(SharedParams, StackParams);
			Cmd.Property = StackParams.Property;
			Cmd.Type = ERepLayoutCmdType::Property;		// Initially set to generic type
			Cmd.Offset = StackParams.Offset;
			Cmd.ElementSize = Cmd.Property->ElementSize;
			Cmd.RelativeHandle = StackParams.RelativeHandle;
			Cmd.ParentIndex = SharedParams.ParentIndex;
			// 将Property的信息编码成uint32
			Cmd.CompatibleChecksum = GetRepLayoutCmdCompatibleChecksum(SharedParams, StackParams);
			// 记录Cmd.Type(例如ERepLayoutCmdType::PropertyObject)
	return StackParams.RelativeHandle;

DOREPLIFETIME 宏说明:

// --- 收集FLifetimeProperty到LifetimeProps
void UObject::GetLifetimeReplicatedProps( TArray< class FLifetimeProperty > & OutLifetimeProps ) const
#define DOREPLIFETIME(c,v) DOREPLIFETIME_WITH_PARAMS(c,v,FDoRepLifetimeParams())

#define DOREPLIFETIME_WITH_PARAMS(c,v,params) \
	FProperty* ReplicatedProperty = GetReplicatedProperty(StaticClass(), c::StaticClass(),GET_MEMBER_NAME_CHECKED(c,v)); \
	RegisterReplicatedLifetimeProperty(ReplicatedProperty, OutLifetimeProps, params); \

RegisterReplicatedLifetimeProperty(ReplicatedProperty, OutLifetimeProps, params);
	RegisterReplicatedLifetimeProperty(ReplicatedProperty, OutLifetimeProps, Params);
	FRepPropertyDescriptor(const FProperty* Property) : PropertyName(TEXT("")), RepIndex(Property->RepIndex), ArrayDim(Property->ArrayDim)
void RegisterReplicatedLifetimeProperty(const NetworkingPrivate::FRepPropertyDescriptor& PropertyDescriptor, TArray<FLifetimeProperty>& OutLifetimeProps, const FDoRepLifetimeParams& Params)
	for (int32 i = 0; i < PropertyDescriptor.ArrayDim; i++)
		const uint16 RepIndex = PropertyDescriptor.RepIndex + i;
		FLifetimeProperty LifetimeProp(RepIndex, Params.Condition, Params.RepNotifyCondition, Params.bIsPushBased);
		OutLifetimeProps.Add(LifetimeProp);
// --- 构建所有Cmd对应的,在ShadowMemory内部的偏移
template<ERepBuildType ShadowType>
static void BuildShadowOffsets(UStruct* Owner, TArray<FRepParentCmd>& Parents, TArray<FRepLayoutCmd>& Cmds, int32& ShadowOffset)
	if (ShadowType == ERepBuildType::Class && !!GUsePackedShadowBuffers)
		ShadowOffset = 0;
	struct FParentCmdIndexAndAlignment
		// 使用alignof获取最小的对齐方式(int32->4,char->1,则struct {int32, char}->4)
		FParentCmdIndexAndAlignment(int32 ParentIndex, const FRepParentCmd& Parent): Index(ParentIndex), Alignment(Parent.Property->GetMinAlignment())
			// 根据所有属性的alignof进行排序,可以最大程度的减少padding
			bool operator< (const FParentCmdIndexAndAlignment& RHS) const
				return Alignment < RHS.Alignment;
		TArray<FParentCmdIndexAndAlignment> IndexAndAlignmentArray;
			for (int32 i = 0; i < Parents.Num(); ++i)
				IndexAndAlignmentArray.Emplace(i, Parents[i]);
		IndexAndAlignmentArray.StableSort();
		for (int32 i = 0; i < IndexAndAlignmentArray.Num(); ++i)
			for (auto CmdIt = Cmds.CreateIterator() + Parent.CmdStart; CmdIt.GetIndex() < Parent.CmdEnd; ++CmdIt)
				BuildShadowOffsets_r</*bAlreadyAligned=*/false>(CmdIt, ShadowOffset);
					// 特殊处理动态数组(DynamicArray和Return之间的Cmd)
					if (CmdIt->Type == ERepLayoutCmdType::DynamicArray || EnumHasAnyFlags(CmdIt->Flags, ERepLayoutCmdFlags::IsStruct))
					else if (!bAlreadyAligned)
						if (ShadowOffset > 0)
							// bool类型可以通过bitfields占用同一个字节(Offset相同说明地址上是同一个字节)
							if (ERepLayoutCmdType::PropertyBool == CmdIt->Type && CmdIt.GetIndex() > 0)
								const TArray<FRepLayoutCmd>::TIterator PrevCmdIt = CmdIt - 1;
								if (ERepLayoutCmdType::PropertyBool == PrevCmdIt->Type && PrevCmdIt->Offset == CmdIt->Offset)
									ShadowOffset = PrevCmdIt->ShadowOffset;
							// 内存对齐到新的Align
							ShadowOffset = Align(ShadowOffset, CmdIt->Property->GetMinAlignment());
						CmdIt->ShadowOffset = ShadowOffset;
						ShadowOffset += CmdIt->ElementSize;
			Parent.ShadowOffset = Cmds[Parent.CmdStart].ShadowOffset;

创建 FRepChangedPropertyTracker、FRepState

// 创建 FRepChangedPropertyTracker、FRepState
void FObjectReplicator::InitRecentProperties(uint8* Source)
	// 创建 FRepChangedPropertyTracker
	TSharedPtr<FRepChangedPropertyTracker> RepChangedPropertyTracker = bCreateSendingState ? ConnectionDriver->FindOrCreateRepChangedPropertyTracker(MyObject) : nullptr;
	// 创建 FRepState
	RepState = LocalRepLayout.CreateRepState(Source, RepChangedPropertyTracker, Flags);				
// --- 创建 FRepChangedPropertyTracker
TSharedPtr<FRepChangedPropertyTracker> FNetPropertyConditionManager::FindOrCreatePropertyTracker(const FObjectKey ObjectKey)
	TSharedPtr<FRepChangedPropertyTracker> Tracker = FindPropertyTracker(ObjectKey); // PropertyTrackerMap.FindRef(ObjectKey);
	if (!Tracker.IsValid())
		UClass* ObjectClass = Obj->GetClass();
		ObjectClass->SetUpRuntimeReplicationData();
		FCustomPropertyConditionState ActiveState(NumProperties);
			TBitArray<> CurrentState.Init(true, NumProperties);
		// 自定义是否需要同步 DOREPCUSTOMCONDITION_ACTIVE_FAST(ACharacter, RepRootMotion, CharacterMovement->CurrentRootMotion.HasActiveRootMotionSources() || IsPlayingNetworkedRootMotionMontage());
		Obj->GetReplicatedCustomConditionState(ActiveState);
// --- 创建 FRepState
TUniquePtr<FRepState> FRepLayout::CreateRepState(const FConstRepObjectDataBuffer Source, TSharedPtr<FRepChangedPropertyTracker>& InRepChangedPropertyTracker, ECreateRepStateFlags CreateFlags) const
	TUniquePtr<FRepState> RepState(new FRepState());
	RepState->SendingRepState.Reset(new FSendingRepState());
	RepState->SendingRepState->RepChangedPropertyTracker = InRepChangedPropertyTracker;
	RebuildConditionalProperties(RepState->SendingRepState.Get(), FReplicationFlags());
	RepState->SendingRepState->InactiveParents.Init(false, Parents.Num());	
	RepState->ReceivingRepState.Reset(new FReceivingRepState(MoveTemp(StaticBuffer)));

创建 FReplicationChangelistMgr、FRepChangelistState、ShadowBuffer

// 创建 FReplicationChangelistMgr、FRepChangelistState、ShadowBuffer
void FObjectReplicator::StartReplicating(class UActorChannel * InActorChannel)
	ChangelistMgr = WorldNetDriver->GetReplicationChangeListMgr(Object);
		FReplicationChangelistMgrWrapper* ReplicationChangeListMgrPtr = ReplicationChangeListMap.Find(Object);
		if (!ReplicationChangeListMgrPtr)
			FReplicationChangelistMgrWrapper Wrapper(Object, RepLayout->CreateReplicationChangelistMgr(Object, GetCreateReplicationChangelistMgrFlags()));
// --- 创建 FReplicationChangelistMgr
TSharedPtr<FReplicationChangelistMgr> FRepLayout::CreateReplicationChangelistMgr(const UObject* InObject, const ECreateReplicationChangelistMgrFlags CreateFlags) const
	const uint8* ShadowStateSource = (const uint8*)InObject->GetArchetype();
	return MakeShareable(new FReplicationChangelistMgr(AsShared(), ShadowStateSource, InObject, DeltaChangelistState));
		FRepChangelistState::StaticBuffer(InRepLayout->CreateShadowBuffer(InSource))
// --- 创建 ShadowBuffer
FRepStateStaticBuffer FRepLayout::CreateShadowBuffer(const FConstRepObjectDataBuffer Source) const
	FRepStateStaticBuffer ShadowData(AsShared());
	InitRepStateStaticBuffer(ShadowData, Source);
	return ShadowData;
// --- --- 初始化 ShadowBuffer
void FRepLayout::InitRepStateStaticBuffer(FRepStateStaticBuffer& ShadowData, const FConstRepObjectDataBuffer Source) const
	ShadowData.Buffer.SetNumZeroed(ShadowDataBufferSize);
	ConstructProperties(ShadowData);
		for (const FRepParentCmd& Parent : Parents)
			Parent.Property->InitializeValue(ShadowData + Parent); //  return InBuffer + Cmd.ShadowOffset;
				if (PropertyFlags & CPF_ZeroConstructor)
					FMemory::Memzero(Dest,ElementSize * ArrayDim);
				else
					InitializeValueInternal(Dest);
	CopyProperties(ShadowData, Source);
		for (const FRepParentCmd& Parent : Parents)
			// Array内部会处理其它数据
			if (Parent.ArrayIndex == 0)
				Parent.Property->CopyCompleteValue(ShadowData + Parent, Source + Parent);
					if (PropertyFlags & CPF_IsPlainOldData)
						FMemory::Memcpy( Dest, Src, ElementSize * ArrayDim );
					else
						CopyValuesInternal(Dest, Src, ArrayDim);

属性同步

Tick的时候进行检测:

void UNetDriver::TickFlush(float DeltaSeconds)
	Updated = ServerReplicateActors(DeltaSeconds);
		// 走UReplicationGraph
		if (ReplicationDriver)
			return ReplicationDriver->ServerReplicateActors(DeltaSeconds);
				...
				UActorChannel::ReplicateActor()

同步的主要内容:

int64 UActorChannel::ReplicateActor()
	UNetConnection* OwningConnection = Actor->GetNetConnection();
	// 新Actor
	if (RepFlags.bNetInitial && OpenedLocally)
		Connection->PackageMap->SerializeNewActor(Bunch, this, static_cast<AActor*&>(Actor));
		Actor->OnSerializeNewActor(Bunch);
	bWroteSomethingImportant |= ActorReplicator->ReplicateProperties(Bunch, RepFlags);
		const bool bHasRepLayout = RepLayout->ReplicateProperties(SendingRepState, ChangelistMgr->GetRepChangelistState(), (uint8*)Object, ObjectClass, OwningChannel, Writer, RepFlags);
	bWroteSomethingImportant |= DoSubObjectReplication(Bunch, RepFlags);
	bWroteSomethingImportant |= UpdateDeletedSubObjects(Bunch);
	if (bWroteSomethingImportant)
		// SendBunch之后再聊
		FPacketIdRange PacketRange = SendBunch( &Bunch, 1 );
		for (auto RepComp = ReplicationMap.CreateIterator(); RepComp; ++RepComp)
			RepComp.Value()->PostSendBunch(PacketRange, Bunch.bReliable);

属性变化检测

查找变化属性,写入ShadowMemory

拿之前记录的的ShadowData内的数据进行比较

// FObjectReplicator::ReplicateProperties
bool FObjectReplicator::ReplicateProperties_r( FOutBunch & Bunch, FReplicationFlags RepFlags, FNetBitWriter& Writer)
	FNetSerializeCB::UpdateChangelistMgr(*RepLayout, SendingRepState, *ChangelistMgr, Object, Connection->Driver->ReplicationFrame, RepFlags, OwningChannel->bForceCompareProperties || bUseCheckpointRepState);
		RepLayout.UpdateChangelistMgr(RepState, InChangelistMgr, InObject, ReplicationFrame, RepFlags, bForceCompare);
			Result = CompareProperties(RepState, &InChangelistMgr.RepChangelistState, (const uint8*)InObject, RepFlags);
				static void CompareParentProperties(const FComparePropertiesSharedParams& SharedParams, FComparePropertiesStackParams& StackParams)
					CompareProperties_r(SharedParams, StackParams, Parent.CmdStart, Parent.CmdEnd, Cmd.RelativeHandle - 1);
						if(!PropertiesAreIdentical(Cmd, ShadowData.Data, Data.Data, SharedParams.NetSerializeLayouts))
							// PropertiesAreIdenticalNative(Cmd, A, B, NetSerializeLayouts)
							// 将变化后的属性复制到Shadow内存
							StoreProperty(Cmd, ShadowData.Data, Data.Data);
							StackParams.Changed.Add(Handle);

如果是UStruct,默认的比较为:

bool FStructProperty::Identical( const void* A, const void* B, uint32 PortFlags ) const
	return Struct->CompareScriptStruct(A, B, PortFlags);

bool UScriptStruct::CompareScriptStruct(const void* A, const void* B, uint32 PortFlags) const
	// 遍历所有UProperty递归比较
	for( TFieldIterator<FProperty> It(this); It; ++It )
		for( int32 i=0; i<It->ArrayDim; i++ )
			if( !It->Identical_InContainer(A,B,i,PortFlags) )
				return false;

可以使用以下方式定制比较,以减少比较时间

bool Identical(const FXXX* Other, uint32 PortFlags) const;

template<>
struct TStructOpsTypeTraits<FXXX> : public TStructOpsTypeTraitsBase2<FXXX>
{
	enum
	{
		WithIdentical = true,
	};
};

在这里插入图片描述

发送数据

bool FObjectReplicator::ReplicateProperties_r( FOutBunch & Bunch, FReplicationFlags RepFlags, FNetBitWriter& Writer)
	// 下面的Data是(uint8*)Object
	const bool bHasRepLayout = RepLayout->ReplicateProperties(SendingRepState, ChangelistMgr->GetRepChangelistState(), (uint8*)Object, ObjectClass, OwningChannel, Writer, RepFlags);
		// 同步条件发生变化的处理
		if (RepState->RepFlags.Value != RepFlags.Value)
			...
		// 维护ChangeHistory
		...
		RepState->HistoryEnd++;
		UpdateChangelistHistory(RepState, ObjectClass, Data, OwningChannel->Connection, &Changed);
		BuildSharedSerialization(Data, Changed, true, RepChangelistState->SharedSerialization);
			BuildSharedSerialization_r(HandleIterator, Data, bWriteHandle, bDoChecksum, 0, SharedInfo);
				while (HandleIterator.NextHandle())
					// 写入到:ChangelistMgr->GetRepChangelistState()->SharedSerialization
					SharedInfo.WriteSharedProperty(Cmd, PropertyKey, HandleIterator.CmdIndex, HandleIterator.Handle, Data.Data, bWriteHandle, bDoChecksum);
						// 写Handle,用于找Cmd也就是发生修改的属性的信息		
						WritePropertyHandle(*SerializedProperties, Handle, bDoChecksum);
						Cmd.Property->NetSerializeItem(*SerializedProperties, nullptr, const_cast<uint8*>(Data.Data));

	// 筛选掉非激活的
	FilterChangeList(UnfilteredChanged, RepState->InactiveParents, NewlyInactiveChangelist, Changed);
	// 发送
	SendProperties(RepState, ChangeTracker, Data, ObjectClass, Writer, Changed, RepChangelistState->SharedSerialization, RepFlags.bSerializePropertyNames ? ESerializePropertyType::Name : ESerializePropertyType::Handle);
		SendProperties_r(RepState, Writer, bDoChecksum, HandleIterator, Data, 0, &SharedInfo, SerializePropertyType);
			while (HandleIterator.NextHandle())
				// 发送Handle和属性值
				WritePropertyHandle(Writer, HandleIterator.Handle, bDoChecksum);
				Cmd.Property->NetSerializeItem(Writer, Writer.PackageMap, const_cast<uint8*>(Data.Data));
	
	if ( RemoteFunctions != nullptr && RemoteFunctions->GetNumBits() > 0 )
		Writer.SerializeBits( RemoteFunctions->GetData(), RemoteFunctions->GetNumBits() );
		
	const bool WroteImportantData = Writer.GetNumBits() != 0;
	if ( WroteImportantData )
		OwningChannel->WriteContentBlockPayload( Object, Bunch, bHasRepLayout, Writer );

接收数据

在接收到一个包可以组成一个完整的Bunch后,处理这个Bunch的数据
在这里插入图片描述

void UActorChannel::ProcessBunch( FInBunch & Bunch )
	Replicator->ReceivedBunch( Reader, RepFlags, bHasRepLayout, bHasUnmapped )
		UObject* Object = GetObject();
		const FRepLayout& LocalRepLayout = *RepLayout;
		FReceivingRepState* ReceivingRepState = RepState->GetReceivingRepState();
	if (bHasRepLayout)
		bool bLocalHasUnmapped = false;
		LocalRepLayout.ReceiveProperties(OwningChannel, ObjectClass, RepState->GetReceivingRepState(), Object, Bunch, bLocalHasUnmapped, bGuidsChanged, ReceivePropFlags)
			if (ReceiveProperties_r(Params, StackParams))
				static bool ReceivePropertyHelper(...)
					const FRepLayoutCmd& Cmd = Cmds[CmdIndex];
					// 客户端ShadowMemory,用于保存接收之前的值
					StoreProperty(Cmd, ShadowData + Cmd, Data + SwappedCmd);
					// 反序列化
					Cmd.Property->NetSerializeItem(Bunch, Bunch.PackageMap, Data + SwappedCmd);
					// 判断是否需要调用RepNotify
					if (Parent.RepNotifyCondition == REPNOTIFY_Always || !PropertiesAreIdentical(Cmd, ShadowData + Cmd, Data + SwappedCmd, NetSerializeLayouts))
						RepNotifies->AddUnique(Parent.Property);
		bOutHasUnmapped |= bLocalHasUnmapped;

UObject同步

UObject都是通过OwnedActor进行同步

int64 UActorChannel::ReplicateActor()
	bWroteSomethingImportant |= DoSubObjectReplication(Bunch, RepFlags);
		// 两种同步的情况,参考AActor::bReplicateUsingRegisteredSubObjectList,大部分都是false,也就是如果有需要,要重载ReplicateSubobjects
		if (Actor->IsUsingRegisteredSubObjectList())
			bWroteSomethingImportant |= ReplicateRegisteredSubObjects(Bunch, OutRepFlags);
		else
			bWroteSomethingImportant |= Actor->ReplicateSubobjects(this, &Bunch, &OutRepFlags);
			
bool AActor::ReplicateSubobjects(UActorChannel *Channel, FOutBunch *Bunch, FReplicationFlags *RepFlags)
	for (UActorComponent* ActorComp : ReplicatedComponents)
		UActorChannel::SetCurrentSubObjectOwner(ActorComp);
		UActorChannel::SetCurrentSubObjectOwner(this);
		WroteSomething |= Channel->ReplicateSubobject(ActorComp, *Bunch, *RepFlags);
			// 同上,参考UActorComponent::bReplicateUsingRegisteredSubObjectList
			if (ReplicatedComponent->IsUsingRegisteredSubObjectList() && !DataChannelInternal::bTestingLegacyMethodForComparison)
				const TStaticBitArray<COND_Max> ConditionMap = UE::Net::BuildConditionMapFromRepFlags(RepFlags);
				bWroteSomethingImportant |= WriteComponentSubObjects(ReplicatedComponent, Bunch, RepFlags, ConditionMap);
			else
				return ReplicateSubobject(StaticCast<UObject*>(ReplicatedComponent), Bunch, RepFlags);

通过重写ReplicateSubobjects函数,支持增加额外的同步SubObjects,函数内调用的是UActorChannel::ReplicateSubobject

处理单个Object:Object同步一共是三部分,第一是Object本身(Load),第二是变化的属性,第三是指针的处理。

发送对象和属性变化

写入对象和属性变化如下:

bool UActorChannel::ReplicateSubobject(UObject* SubObj, FOutBunch& Bunch, FReplicationFlags RepFlags)
	bWroteSomethingImportant = WriteSubObjectInBunch(SubObj, Bunch, RepFlags);
		TSharedRef<FObjectReplicator>* FoundReplicator = FindReplicator(Obj);
		TSharedRef<FObjectReplicator>& ObjectReplicator = !bFoundReplicator ? CreateReplicator(Obj) : *FoundReplicator;
		// 从ObjectReplicator判定是否是新的Object
		const bool bIsNewSubObject = (ObjectReplicator->bSentSubObjectCreation == false) || bNewToReplay;
		if (bIsNewSubObject)
			ObjRepFlags.bNetInitial = true;
		// 同步属性,同上
		bool bWroteSomething = ObjectReplicator.Get().ReplicateProperties(Bunch, ObjRepFlags);
		// 新的对象且没有属性修改,直接调用WriteContentBlockHeader,写入整个Object
		if (bIsNewSubObject && !bWroteSomething)
			FNetBitWriter EmptyPayload;
			WriteContentBlockPayload( Obj, Bunch, false, EmptyPayload );
				// WriteContentBlockHeader( Obj, Bunch, bHasRepLayout );
				Bunch.WriteBit( bHasRepLayout ? 1 : 0 );
				Bunch.WriteBit( IsActor ? 1 : 0 );
				// 序列化对象
				Bunch << Obj;
				if ( Connection->Driver->IsServer() )
					if ( Obj->IsNameStableForNetworking() )
						Bunch.WriteBit( 1 );
					else
						Bunch.WriteBit( 0 );
						Bunch.WriteBit( 0 );
						Bunch << ObjClass;
						Bunch.WriteBit(bActorIsOuter ? 1 : 0)
						if (!bActorIsOuter)
							Bunch << ObjOuter;
			bWroteSomething = true;

Object指针在服务器往客户端传递的时候,会生成一个NetGUID,存在UNetConnection::PackageMap(UPackageMapClient)::GuidCache内的Map内。所以同步的指针实际上只是GUID而已。

bool FObjectPropertyBase::NetSerializeItem( FArchive& Ar, UPackageMap* Map, void* Data, TArray<uint8> * MetaData ) const
	UObject* Object = GetObjectPropertyValue(Data);
	// GUID相关信息
	UPackageMapClient::SerializeObject
		// Ar.IsSaving()
		FNetworkGUID NetGUID = GuidCache->GetOrAssignNetGUID( Object );
		InternalWriteObject( Ar, NetGUID, Object, TEXT( "" ), NULL );
			Ar << NetGUID;
			// 存在路径的对象,通过Path本地StaticLoad出来后再关联GUID
			if (ExportFlags.bHasPath)
				FNetworkGUID OuterNetGUID = GuidCache->GetOrAssignNetGUID(ObjectOuter);
				InternalWriteObject(Ar, OuterNetGUID, ObjectOuter, TEXT( "" ), nullptr);
				Ar << ObjectPathName;
	// 同步置空操作或是合法对象,才会进行设置
	if (!Object || IsValidChecked(Object))
		SetObjectPropertyValue(Data, Object);

服务器创建新的NetGUID的过程如下:

FNetworkGUID FNetGUIDCache::GetOrAssignNetGUID(UObject* Object, const TWeakObjectPtr<UObject>* WeakObjectPtr)
	if (!Object || !SupportsObject(Object, &WeakObject))
		IsNameStableForNetworking // RF_WasLoaded | RF_DefaultSubObject | RF_ClassDefaultObject、IsNative、IsDefaultSubobject
		IsSupportedForNetworking // Actor都是Supported,也就是说UObject需要考虑这个,UActorComponent用的是bReplicates
		return FNetworkGUID();
	// 缓存的Object->GUID
	FNetworkGUID NetGUID = NetGUIDLookup.FindRef(WeakObject);
	if (NetGUID.IsValid())
		// bReadOnly的处理
		return NetGUID;
	// 只在服务器允许生成GUID
	if(!bIsNetGUIDAuthority)
		return FNetworkGUID::GetDefault();
	return AssignNewNetGUID_Server(Object);
		// 是不是静态的
		const int32 IsStatic = IsDynamicObject( Object ) ? 0 : 1;
		// 下标静态和动态分开
		const FNetworkGUID NewNetGuid = FNetworkGUID::CreateFromIndex(++NetworkGuidIndex[IsStatic], IsStatic != 0);
			// 最后一位用于标记是否静态
			NewGuid.ObjectId = NetIndex << 1 | (bIsStatic ? 1 : 0);
		RegisterNetGUID_Server( NewNetGuid, Object );
			// 创建FNetGuidCacheObject
			FNetGuidCacheObject CacheObject;
			// 初始化 Object、OuterGUID、PathName、NetworkChecksum、bNoLoad
			// 加到ObjectLookup、NetGUIDLookup
			RegisterNetGUID_Internal( NetGUID, CacheObject );

接收对象和属性变化

对于新的Object,在收到包的时候,直接读取GUID和加载对象,并进行关联

void UChannel::ReceivedRawBunch( FInBunch & Bunch, bool & bOutSkipAck )
	if ( Bunch.bHasPackageMapExports && !Connection->IsInternalAck() )
		Cast<UPackageMapClient>( Connection->PackageMap )->ReceiveNetGUIDBunch( Bunch );
			int32 NumGUIDsInBunch = 0;
			InBunch << NumGUIDsInBunch;
			while( NumGUIDsRead < NumGUIDsInBunch )
				UObject* Obj = NULL;
				const FNetworkGUID LoadedGUID = InternalLoadObject( InBunch, Obj, 0 );
				NumGUIDsRead++;
  • 尝试本地加载Object
FNetworkGUID UPackageMapClient::InternalLoadObject( FArchive & Ar, UObject *& Object, const int32 InternalLoadObjectRecursionCount )
	FNetworkGUID NetGUID;
	Ar << NetGUID;
	if ( NetGUID.IsValid() && !NetGUID.IsDefault() )
		Object = GetObjectFromNetGUID( NetGUID, GuidCache->IsExportingNetGUIDBunch );
			return GuidCache->GetObjectFromNetGUID( NetGUID, bIgnoreMustBeMapped );
				FNetGuidCacheObject * CacheObjectPtr = ObjectLookup.Find( NetGUID );
					if (Object == nullptr && bIsPackage)
						Object = LoadPackage(nullptr, Path, LOAD_None);
	// 存在路径的对象,通过Path本地StaticLoad出来后再关联GUID
	if ( ExportFlags.bHasPath )
		// 对于Path的Object,Outer应该是个Package,先加载
		UObject* ObjOuter = NULL;
		FNetworkGUID OuterGUID = InternalLoadObject( Ar, ObjOuter, InternalLoadObjectRecursionCount + 1 );
		Ar << ObjectName;
		// 如果Outer是空,说明本身已经是Package了
		const bool bIsPackage = NetGUID.IsStatic() && !OuterGUID.IsValid();

		// DefaultObject直接处理
		if (NetGUID.IsDefault())
			Object = StaticFindObject(UObject::StaticClass(), ObjOuter, *ObjectName, false);
			NetGUID = GuidCache->GetOrAssignNetGUID( Object );
			if (Object == nullptr && bIsPackage)
				FPackagePath Path = FPackagePath::FromPackageNameChecked(ObjectName);
				Object = LoadPackage(nullptr, Path, LOAD_None);
			return NetGUID;
			
		// 注册GUID,包含Path信息
		GuidCache->RegisterNetGUIDFromPath_Client( NetGUID, ObjectName, OuterGUID, NetworkChecksum, ExportFlags.bNoLoad, bIgnoreWhenMissing );
			const FNetGuidCacheObject* ExistingCacheObjectPtr = ObjectLookup.Find( NetGUID );
			if ( ExistingCacheObjectPtr != NULL )
				return;
			FNetGuidCacheObject CacheObject;
			CacheObject.PathName			= FName( *PathName );
			RegisterNetGUID_Internal( NetGUID, CacheObject );
			
		// 从GUID-Path读取对象
		Object = GuidCache->GetObjectFromNetGUID( NetGUID, GuidCache->IsExportingNetGUIDBunch );
			FNetGuidCacheObject * CacheObjectPtr = ObjectLookup.Find( NetGUID );
			// 找到OuterPackage
			if ( CacheObjectPtr->OuterGUID.IsValid() )
				FNetGuidCacheObject * OuterCacheObject = ObjectLookup.Find( CacheObjectPtr->OuterGUID );
				ObjOuter = GetObjectFromNetGUID( CacheObjectPtr->OuterGUID, bIgnoreMustBeMapped );

			// 通过Package和PathName找到、加载Object
			Object = FindObjectFast<UObject>(ObjOuter, CacheObjectPtr->PathName);
			if ( Object == NULL && !CacheObjectPtr->bNoLoad )
				Object = StaticLoadObject( UObject::StaticClass(), ObjOuter, *CacheObjectPtr->PathName.ToString(), NULL, LOAD_NoWarn );
			CacheObjectPtr->Object = Object;	
			NetGUIDLookup.Add( Object, NetGUID );
			// 更新QueuedBunchObjectReferences内GUID索引的对象
			UpdateQueuedBunchObjectReference(NetGUID, Object);
				if (TWeakPtr<FQueuedBunchObjectReference>* WeakObjectReference = QueuedBunchObjectReferences.Find(NetGUID))
					ObjectReference->Object = NewObject;

如果是运行时生成的Object,在以下位置处理本地生成:

void UActorChannel::ProcessBunch( FInBunch & Bunch )
	// Actor处理...
	while ( !Bunch.AtEnd() && Connection != NULL && Connection->GetConnectionState() != USOCK_Closed )
		FNetBitReader Reader( Bunch.PackageMap, 0 );
		UObject* RepObj = ReadContentBlockPayload( Bunch, Reader, bHasRepLayout );
			UObject* RepObj = ReadContentBlockHeader( Bunch, bObjectDeleted, bOutHasRepLayout );
				// 尝试加载
				Connection->PackageMap->SerializeObject(Bunch, UObject::StaticClass(), SubObj, &NetGUID);
				// Class
				UObject* SubObjClassObj = nullptr;
				if (bSerializeClass)
					Connection->PackageMap->SerializeObject(Bunch, UObject::StaticClass(), SubObjClassObj, &ClassNetGUID);
					UClass* SubObjClass = Cast< UClass >(SubObjClassObj);
				// Outer
				UObject*ObjOuter = Actor;
				if (!bActorIsOuter)
					Bunch << ObjOuter;
				// 本地创建Object,并关联GUID
				if (!SubObj)
					SubObj = NewObject< UObject >(ObjOuter, SubObjClass);
					Actor->OnSubobjectCreatedFromReplication( SubObj );
					Connection->Driver->GuidCache->RegisterNetGUID_Client( NetGUID, SubObj );
					Connection->Driver->GuidCache->ImportedNetGuids.Add( NetGUID );

		TSharedRef< FObjectReplicator > & Replicator = FindOrCreateReplicator( RepObj );
		bool bHasUnmapped = false;
		// 属性同步
		Replicator->ReceivedBunch( Reader, RepFlags, bHasRepLayout, bHasUnmapped )
		// 引用了其它的还没有对应GUID的对象,加到将自己加到UnmappedReplicators
		if ( bHasUnmapped )
			Connection->Driver->UnmappedReplicators.Add( &Replicator.Get() );
		// 移除对象的处理
		TArray<TWeakObjectPtr<UObject>, TInlineAllocator<16>> ReferencesToRemove;
		Connection->Driver->GetNetworkObjectList().RemoveMultipleSubObjectChannelReference(Actor, ReferencesToRemove, this);

客户端同步指针:

bool FObjectPropertyBase::NetSerializeItem( FArchive& Ar, UPackageMap* Map, void* Data, TArray<uint8> * MetaData ) const
	bool UPackageMapClient::SerializeObject( FArchive& Ar, UClass* Class, UObject*& Object, FNetworkGUID *OutNetGUID)
		// Ar.IsLoading()
		// 同上
		NetGUID = InternalLoadObject(Ar, Object, 0);
			
		// TODO 没找到的情况
		if ( NetGUID.IsValid() && bShouldTrackUnmappedGuids && !GuidCache->IsGUIDBroken( NetGUID, false ) )
			if ( Object == nullptr )
				TrackedUnmappedNetGuids.Add( NetGUID );
			else if ( NetGUID.IsDynamic() )
				TrackedMappedDynamicNetGuids.Add( NetGUID );

UObject指针同步

上面有提到,如果传递下来的指针,要么GUID在本地找到对象,要么是Static的对象可以本地加载,另外一种情况就是,当前指针对应的UObject还没有在本地创建。接下来就讲一下这种情况。

指针下来UObject没创建的情况,加入到TrackedUnmappedNetGuids内:

bool FObjectPropertyBase::NetSerializeItem( FArchive& Ar, UPackageMap* Map, void* Data, TArray<uint8> * MetaData ) const
	bool UPackageMapClient::SerializeObject( FArchive& Ar, UClass* Class, UObject*& Object, FNetworkGUID *OutNetGUID)
		if ( NetGUID.IsValid() && bShouldTrackUnmappedGuids && !GuidCache->IsGUIDBroken( NetGUID, false ) )
			if ( Object == nullptr )
				TrackedUnmappedNetGuids.Add( NetGUID );

Object内部存在这种情况的指针成员时,将ObjectReplicator加入到UnmappedReplicators内:

void UActorChannel::ProcessBunch( FInBunch & Bunch )
	...
	while ( !Bunch.AtEnd() && Connection != NULL && Connection->GetConnectionState() != USOCK_Closed )
		...
		TSharedRef< FObjectReplicator > & Replicator = FindOrCreateReplicator( RepObj );
		bool bHasUnmapped = false;
		// 属性同步
		Replicator->ReceivedBunch( Reader, RepFlags, bHasRepLayout, bHasUnmapped )
			bool bLocalHasUnmapped = false;
			LocalRepLayout.ReceiveProperties(OwningChannel, ObjectClass, RepState->GetReceivingRepState(), Object, Bunch, bLocalHasUnmapped, bGuidsChanged, ReceivePropFlags)
				const TSet<FNetworkGUID>& TrackedUnmappedGuids = Bunch.PackageMap->GetTrackedUnmappedGuids();
				const bool bHasUnmapped = TrackedUnmappedGuids.Num()> 0;
			bOutHasUnmapped |= bLocalHasUnmapped;
		// 引用了其它的还没有对应GUID的对象,加到将自己加到UnmappedReplicators
		if ( bHasUnmapped )
			Connection->Driver->UnmappedReplicators.Add( &Replicator.Get() );

本地新生成的Object的GUID会加到ImportedNetGuids内:

bool UPackageMapClient::SerializeNewActor(FArchive& Ar, class UActorChannel *Channel, class AActor*& Actor)
	if ( GuidCache.IsValid() )
		GuidCache->ImportedNetGuids.Add(NetGUID);
		
UObject* UActorChannel::ReadContentBlockHeader(FInBunch& Bunch, bool& bObjectDeleted, bool& bOutHasRepLayout)
	Connection->Driver->GuidCache->ImportedNetGuids.Add( NetGUID );

然后在Tick时,去更新UnmappedReplicators内引用的指针:

void UNetDriver::TickFlush(float DeltaSeconds)
	if (!IsUsingIrisReplication())
		UpdateUnmappedObjects();
		
void UNetDriver::UpdateUnmappedObjects()
	TSet<FObjectReplicator*> ForceUpdateReplicators;
	for (FObjectReplicator* Replicator : UnmappedReplicators)
		if (Replicator->bForceUpdateUnmapped)
			Replicator->bForceUpdateUnmapped = false;
			ForceUpdateReplicators.Add(Replicator);
	if (ImportedNetGuidsRef.Num() || ForceUpdateReplicators.Num())
		for (auto It = ImportedNetGuidsRef.CreateIterator(); It; ++It)
			// 本地已经创建Object了
			if (GuidCache->GetObjectFromNetGUID(NetworkGuid, false) != nullptr)
				NewlyMappedGuids.Add(NetworkGuid);
			It.RemoveCurrent();
			// 没找到的情况,Outer加到PendingOuterNetGuidsRef内(Outer : TSet<Sub GUID>)
			if (!bMappedOrBroken)
				const FNetworkGUID OuterGUID = GuidCache->GetOuterNetGUID(NetworkGuid);
				TSet<FNetworkGUID>& PendingGuidsRef = PendingOuterNetGuidsRef.FindOrAdd(OuterGUID);
				PendingGuidsRef.Add(NetworkGuid);
			if (UnmappedGuids.Num())
				ImportedNetGuidsRef.Append(UnmappedGuids);
			for (const FNetworkGUID& NetGuid : NewlyMappedGuids)
				...			
			for (FObjectReplicator* Replicator : ReplicatorsToUpdate)
				if (UnmappedReplicators.Contains(Replicator))
					Replicator->UpdateUnmappedObjects(bHasMoreUnmapped);
					// 所有都解决了,可以移除了
					if (!bHasMoreUnmapped)
						UnmappedReplicators.Remove(Replicator);


void FObjectReplicator::UpdateUnmappedObjects(bool & bOutHasMoreUnmapped)
	LocalRepLayout.UpdateUnmappedObjects(ReceivingRepState, Connection->PackageMap, Object, Parms, bCalledPreNetReceive, bSomeObjectsWereMapped, bOutHasMoreUnmapped);
		UpdateUnmappedObjects_r(...)
			// 遍历所有的引用的对象GUID
			for (auto It = GuidReferencesMap->CreateIterator(); It; ++It)
				FGuidReferences& GuidReferences = It.Value();
				// 指针成员信息
				const FRepLayoutCmd& Cmd = Cmds[GuidReferences.CmdIndex];
				const int32 ShadowOffset = (AbsOffset - Cmd.Offset) + Cmd.ShadowOffset;
				bool bMappedSomeGUIDs = GuidReferences.UpdateUnmappedGUIDs(Connection->PackageMap, OriginalObject, Cmd.Property, AbsOffset);
					for (auto UnmappedIt = UnmappedGUIDs.CreateIterator(); UnmappedIt; ++UnmappedIt)
						UObject* Object = InPackageMap->GetObjectFromNetGUID(GUID, false);
						// 成功找到对象
						if (Object != nullptr)
							InPackageMap->RemoveUnmappedNetGUIDReference(GUID);
							UnmappedIt.RemoveCurrent();
							bMappedSomeGUIDs = true;
				if (bMappedSomeGUIDs)
					bOutSomeObjectsWereMapped = true;				
					StoreProperty(Cmd, ShadowData + ShadowOffset, Data + AbsOffset);
					// 设置指针
					Cmd.Property->NetSerializeItem(Reader, Connection->PackageMap, Data + AbsOffset);
					// 添加到同步通知数组,同上方属性同步
					if (Parent.RepNotifyCondition == REPNOTIFY_Always || !PropertiesAreIdentical(Cmd, ShadowData + ShadowOffset, Data + AbsOffset, NetSerializeLayouts))
						RepState->RepNotifies.AddUnique(Parent.Property);
	// 调用RepNotify
	CallRepNotifies(false);

AActor同步

AActor大部分都是和UObject走的一样的,就是多了AActor相关的处理,例如SpawnActor、ActorChannel、Velocity

发送新的Actor

int64 UActorChannel::ReplicateActor()
	// ObjectReplicator设置的Flag
	if (RepFlags.bNetInitial && OpenedLocally)
		Connection->PackageMap->SerializeNewActor(Bunch, this, static_cast<AActor*&>(Actor));
		Actor->OnSerializeNewActor(Bunch);

接受新的Actor

如果是刚刚同步下的Actor,先存下GUID

void UActorChannel::ReceivedBunch( FInBunch & Bunch )
	if (Actor == NULL && Bunch.bOpen)
		Bunch << ActorNetGUID;
	// 超时的话,需要加到Queue内之后处理(UNetDriver::ClientIncomingBunchFrameTimeLimitMS)
	if (PendingGuidResolves.Num() > 0 || QueuedBunches.Num() > 0 || Connection->KeepProcessingActorChannelBunchesMap.Contains(ActorNetGUID) ||
		Connection->Driver->ShouldQueueBunchesForActorGUID(ActorNetGUID) ||
		Connection->Driver->HasExceededIncomingBunchFrameProcessingTime())
	ProcessBunch(Bunch);
void UActorChannel::ProcessBunch( FInBunch & Bunch )
	// 同步下新的Actor
	if( Actor == NULL )
		AActor* NewChannelActor = NULL;
		bSpawnedNewActor = Connection->PackageMap->SerializeNewActor(Bunch, this, NewChannelActor);

反序列化,本地SpawnActor,并设置一些基础信息,关联GUID(中间会附带自身为UObject的处理)

bool UPackageMapClient::SerializeNewActor(FArchive& Ar, class UActorChannel *Channel, class AActor*& Actor)
	FNetworkGUID NetGUID;
	UObject *NewObj = Actor;
	// 处理好UObject的流程,这个同上
	SerializeObject(Ar, AActor::StaticClass(), NewObj, &NetGUID);	
	Channel->ActorNetGUID = NetGUID;
	// 运行时创建的Actor(静态的以及处理好了)
	if ( NetGUID.IsDynamic() )
		if (Ar.IsSaving())
			// 同步 Level、Transform、Velocity等
		if ( Ar.IsLoading() )
			Actor = World->SpawnActorAbsolute(Archetype->GetClass(), FTransform(Rotation, SpawnLocation), SpawnInfo);
			// 速度、旋转等
			GuidCache->RegisterNetGUID_Client(NetGUID, Actor);
				CacheObject.Object = MakeWeakObjectPtr(const_cast<UObject*>(Object));
				// 加到ObjectLookup、NetGUIDLookup
				RegisterNetGUID_Internal( NetGUID, CacheObject );

设置ActorChannel信息

void UActorChannel::SetChannelActor(AActor* InActor, ESetChannelActorFlags Flags)
	Actor = InActor;
	bActorIsPendingKill = false;
	if (Actor)
		Connection->AddActorChannel(Actor, this);
		// 创建本地ActorReplicator
		ActorReplicator = FindOrCreateReplicator(Actor);
		// 加到激活列表
		Connection->Driver->GetNetworkObjectList().MarkActive(Actor, Connection, Connection->Driver);
			TSharedPtr<FNetworkObjectInfo>* NetworkObjectInfoPtr = FindOrAdd(Actor, NetDriver);
			return MarkActiveInternal(*NetworkObjectInfoPtr, Connection, NetDriver);
				ActiveNetworkObjects.Add(ObjectInfo);
		Connection->Driver->GetNetworkObjectList().ClearRecentlyDormantConnection(Actor, Connection, Connection->Driver);

TODO

同步底层保序
客户端连接过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2180437.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在Code::Blocks中集成EasyX库?

EasyX库是一个轻量级的C图形库&#xff0c;专为Windows平台设计&#xff0c;适用于图形编程初学者&#xff0c;特别是少儿编程或编程启蒙阶段。使用EasyX库&#xff0c;用户可以在C环境下快速绘制简单的图形、动画等视觉效果。本文将详细介绍如何在Code::Blocks中集成EasyX库&a…

Python兼职接单,30天收益近16.5K,分享详细方法!

写在开篇 随着大数据和人工智能的兴起。各行业对爬虫类和数分类服务的需求量呈指数级的暴增。传统行业受经济下行的影响&#xff0c;近段时间失业找不到工作的朋友&#xff0c;后台咨询技术变现、兼职接单问题也越来越多。 当然也有网友向我发来喜报&#xff0c;告诉我他上半年…

JVM 垃圾回收算法细节

目录 前言 GC Root 可达性分析 根节点枚举 安全点 安全区域 记忆集与卡表 写屏障 并行的可达性分析 前言 学习了几种垃圾收集算法之后&#xff0c; 我们再来看看它们在具体实现上有什么细节之处&#xff0c;我们所能看到的理论很简单&#xff0c;但是实现起来那…

聊一下数据脱敏

背景 随着信息社会高速发展&#xff0c;大家对隐私数据的安全越来越重视&#xff0c;现在市面上各种搜集个人信息的网站&#xff0c;app层出不穷&#xff0c;你也不知道你的信息提交上去后&#xff0c;提供服务的那边&#xff0c;是不是会妥善保管好你的个人数据&#xff0c;包…

Python和MATLAB库尔巴克–莱布勒散度信息论统计学生物学和算法模型

&#x1f3af;要点 高斯混合模型聚类和t分布随机邻域嵌入底层分析信息论测量复合彩票统计学计算结果离散分布速率最优估计器样本统计相似性快速闭环散度和交叉熵计算催乳素诱导模型贝叶斯快速推理模型视觉皮层活动神经数据分布 Python散度 在数理统计中&#xff0c;库尔巴克…

悬浮提词器免费版,5款便捷软件分享推荐

在这个信息爆炸、内容为王的时代&#xff0c;无论是直播带货、视频创作还是公开演讲&#xff0c;流畅自然的表达都是吸引观众的关键。然而&#xff0c;面对镜头时忘词卡顿却成了不少人的“心头痛”。今天&#xff0c;就给大家揭秘五款完全免费的悬浮提词器软件&#xff0c;它们…

初学51单片机之I2C总线与E2PROM二

总结下上篇博文的结论&#xff1a; 1&#xff1a;ACK信号在SCL为高电平期间会一直保持。 2&#xff1a;在字节数据传输过程中如果发送电平跳变&#xff0c;那么电平信号就会变成重复起始或者结束的信号。&#xff08;上篇博文的测试方法还是不能够明确证明这个结论&#xff0…

GWAS分析中显著位点如何注释基因:excel???

大家好&#xff0c;我是邓飞。 今天星球的小伙伴问了一个问题&#xff1a; 我现在在做GWAS分析&#xff0c;现在已经找到性状关联的SNP位点&#xff0c;下一步我如何根据position 找到基因呢&#xff1f; 关于基因注释&#xff0c;之前写过一些博客&#xff0c;可以用到的软件…

mono.cecil实现动态插入IL代码

准备 namespace ConsoleApp1 {internal class Program{static void Main(string[] args){Console.WriteLine("Hello, World!");Console.ReadKey();}} }编译出来如下内容 使用ilspy打开 代码 安装Mono.Cecil包 代码如下 using Mono.Cecil; using Mono.Cecil.Ci…

云计算Openstack Neutron

OpenStack Neutron是OpenStack云计算平台中的网络服务组件&#xff0c;它为OpenStack提供了强大的网络连接功能。 一、基本概念 Neutron是一个网络服务项目&#xff0c;旨在为OpenStack提供网络连接。它允许用户创建和管理虚拟网络&#xff0c;包括子网、路由、安全组等&…

Arweave的出块原理

一、Arweave 关键技术 1.1数据结构&#xff1a;Blockweave 区块纺 区块坊中包括区块哈希列表和钱包列表。拥有区块哈希列表使旧区块可以被请求/验证&#xff0c;拥有钱包列表可以验证新交易&#xff0c;而无需处理钱包上一次交易所在的区块。区块哈希列表和钱包列表由矿工保持…

【最新华为OD机试E卷-支持在线评测】字符串分割转换(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

基于大数据的电脑硬件推荐及数据分析系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

基于卷积神经网络的体育运动项目分类识别系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 随着计算机视觉和深度学习技术的快速发展&#xff0c;利用先进的图像处理技术对体育运动进行智能分类与识别已成为研究热点。传统的运动分析方法通常依赖于人工观察和记录&#xff0c;耗时耗力且容…

fatfs API使用手册

配置 /*---------------------------------------------------------------------------/ / Configurations of FatFs Module /---------------------------------------------------------------------------*/#define FFCONF_DEF 80286 /* Revision ID *//*---------------…

JWT(JSON Web Token)的介绍

JSON Web Token&#xff08;缩写 JWT&#xff09;是目前最流行的跨域认证解决方案&#xff0c;本文介绍它的原理和用法。 一、跨域认证的问题 互联网服务离不开用户认证。一般流程是下面这样。 1、用户向服务器发送用户名和密码。 2、服务器验证通过后&#xff0c;在当前对话&…

如何组织鼠标的默认的事件

如何组织鼠标的默认的事件 我原先的代码是 dblclick"checkNode(data)"设置了一个双击的事件&#xff0c;我如果双击的话就会导致这个内容被选中。 选中内容的同时会触发浏览器默认的操作&#xff0c;导致出现复制的框这些东西。 解决的方法。加一句。 mousedown.pr…

尝鲜使用 YOLO V11 Fine-Tuning 训练自定义的目标检测模型

一、YOLO V11 2024年9月30日&#xff0c;Ultralytics官方团队宣布YOLOv11正式发布&#xff0c;标志着YOLO系列实时目标检测器的又一次重大升级。这一新版本不仅在准确性和检测速度上再创新高&#xff0c;还通过架构和训练方法的革新&#xff0c;极大地提升了目标检测的综合性能…

算法打卡:第十一章 图论part11

今日收获&#xff1a;Floyd 算法&#xff0c;A * 算法&#xff0c;最短路算法总结 1. Floyd 算法 题目链接&#xff1a;97. 小明逛公园 思路&#xff1a;Floyd用于解决多源最短路问题&#xff0c;对边的正负权值没有要求。核心是动态规划 &#xff08;1&#xff09;dp数组的…

Stable Diffusion的Lora使用和训练 如何使用和训练LoRA模型?你想要的都在这!--人人都可以当炼金术士!

随着人工智能技术的不断发展&#xff0c;图像生成与反推技术已经成为了AI领域的一大热点。今天&#xff0c;我们就来为大家详细介绍Stable Diffusion的Lora使用和训练方法&#xff0c;让每个人都能成为炼金术士&#xff0c;创造出属于自己的图像生成魔法&#xff01; 在我们使…