Unreal 对象、属性同步流程

文章目录

  • 类型
  • 同步初始化
    • 创建 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、ShadowBufferNewRef->StartReplicating(this);
void FObjectReplicator::InitWithObject(UObject* InObject, UNetConnection* InConnection, bool bUseDefaultState)// 创建 FRepLayoutRepLayout = Connection->Driver->GetObjectClassRepLayout(ObjectClass);// 创建 FRepChangedPropertyTracker、FRepStateInitRecentProperties(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();// 处理对应的CmdRelativeHandle = InitFromProperty_r<ERepBuildType::Class>(SharedParams, StackParams);// CmdEnd = 终点+1Parents[ParentHandle].CmdEnd = Cmds.Num();// 增加一个Return的特殊CmdAddReturnCmd(Cmds);TArray<FLifetimeProperty> LifetimeProps;UObject* Object = InObjectClass->GetDefaultObject();// 调用CDO的GetLifetimeReplicatedProps,收集FLifetimeProperty到LifetimePropsObject->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;// 记录RepFuncif (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的特殊Cmdconst uint32 ArrayChecksum = AddArrayCmd(SharedParams, StackParams);FRepLayoutCmd& Cmd = SharedParams.Cmds.AddZeroed_GetRef();Cmd.Type = ERepLayoutCmdType::DynamicArray;InitFromProperty_r<BuildType>(SharedParams, NewStackParams);// 增加一个Return的特殊CmdAddReturnCmd(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,就不需要知道内部的结构了,所以只增加一个Cmdif (EnumHasAnyFlags(Struct->StructFlags, STRUCT_NetSerializeNative))SharedParams.bHasNetSerializeProperties = true;++StackParams.RelativeHandle;AddPropertyCmd(SharedParams, StackParams);return StackParams.RelativeHandle;// 和UClass::SetUpRuntimeReplicationData类似,递归增加Cmdreturn 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 typeCmd.Offset = StackParams.Offset;Cmd.ElementSize = Cmd.Property->ElementSize;Cmd.RelativeHandle = StackParams.RelativeHandle;Cmd.ParentIndex = SharedParams.ParentIndex;// 将Property的信息编码成uint32Cmd.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进行排序,可以最大程度的减少paddingbool operator< (const FParentCmdIndexAndAlignment& RHS) constreturn 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;// 内存对齐到新的AlignShadowOffset = 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)// 创建 FRepChangedPropertyTrackerTSharedPtr<FRepChangedPropertyTracker> RepChangedPropertyTracker = bCreateSendingState ? ConnectionDriver->FindOrCreateRepChangedPropertyTracker(MyObject) : nullptr;// 创建 FRepStateRepState = 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) constTUniquePtr<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) constconst 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) constFRepStateStaticBuffer ShadowData(AsShared());InitRepStateStaticBuffer(ShadowData, Source);return ShadowData;
// --- --- 初始化 ShadowBuffer
void FRepLayout::InitRepStateStaticBuffer(FRepStateStaticBuffer& ShadowData, const FConstRepObjectDataBuffer Source) constShadowData.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);elseInitializeValueInternal(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 );elseCopyValuesInternal(Dest, Src, ArrayDim);

属性同步

Tick的时候进行检测:

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

同步的主要内容:

int64 UActorChannel::ReplicateActor()UNetConnection* OwningConnection = Actor->GetNetConnection();// 新Actorif (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 ) constreturn 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*)Objectconst 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()->SharedSerializationSharedInfo.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);// 判断是否需要调用RepNotifyif (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,也就是如果有需要,要重载ReplicateSubobjectsif (Actor->IsUsingRegisteredSubObjectList())bWroteSomethingImportant |= ReplicateRegisteredSubObjects(Bunch, OutRepFlags);elsebWroteSomethingImportant |= 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::bReplicateUsingRegisteredSubObjectListif (ReplicatedComponent->IsUsingRegisteredSubObjectList() && !DataChannelInternal::bTestingLegacyMethodForComparison)const TStaticBitArray<COND_Max> ConditionMap = UE::Net::BuildConditionMapFromRepFlags(RepFlags);bWroteSomethingImportant |= WriteComponentSubObjects(ReplicatedComponent, Bunch, RepFlags, ConditionMap);elsereturn 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判定是否是新的Objectconst bool bIsNewSubObject = (ObjectReplicator->bSentSubObjectCreation == false) || bNewToReplay;if (bIsNewSubObject)ObjRepFlags.bNetInitial = true;// 同步属性,同上bool bWroteSomething = ObjectReplicator.Get().ReplicateProperties(Bunch, ObjRepFlags);// 新的对象且没有属性修改,直接调用WriteContentBlockHeader,写入整个Objectif (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 );elseBunch.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 ) constUObject* Object = GetObjectPropertyValue(Data);// GUID相关信息UPackageMapClient::SerializeObject// Ar.IsSaving()FNetworkGUID NetGUID = GuidCache->GetOrAssignNetGUID( Object );InternalWriteObject( Ar, NetGUID, Object, TEXT( "" ), NULL );Ar << NetGUID;// 存在路径的对象,通过Path本地StaticLoad出来后再关联GUIDif (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、IsDefaultSubobjectIsSupportedForNetworking // Actor都是Supported,也就是说UObject需要考虑这个,UActorComponent用的是bReplicatesreturn FNetworkGUID();// 缓存的Object->GUIDFNetworkGUID NetGUID = NetGUIDLookup.FindRef(WeakObject);if (NetGUID.IsValid())// bReadOnly的处理return NetGUID;// 只在服务器允许生成GUIDif(!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 );// 创建FNetGuidCacheObjectFNetGuidCacheObject CacheObject;// 初始化 Object、OuterGUID、PathName、NetworkChecksum、bNoLoad// 加到ObjectLookup、NetGUIDLookupRegisterNetGUID_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出来后再关联GUIDif ( 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 );// 找到OuterPackageif ( CacheObjectPtr->OuterGUID.IsValid() )FNetGuidCacheObject * OuterCacheObject = ObjectLookup.Find( CacheObjectPtr->OuterGUID );ObjOuter = GetObjectFromNetGUID( CacheObjectPtr->OuterGUID, bIgnoreMustBeMapped );// 通过Package和PathName找到、加载ObjectObject = 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);// ClassUObject* SubObjClassObj = nullptr;if (bSerializeClass)Connection->PackageMap->SerializeObject(Bunch, UObject::StaticClass(), SubObjClassObj, &ClassNetGUID);UClass* SubObjClass = Cast< UClass >(SubObjClassObj);// OuterUObject*ObjOuter = Actor;if (!bActorIsOuter)Bunch << ObjOuter;// 本地创建Object,并关联GUIDif (!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的对象,加到将自己加到UnmappedReplicatorsif ( 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 ) constbool 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 ) constbool 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的对象,加到将自己加到UnmappedReplicatorsif ( 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(...)// 遍历所有的引用的对象GUIDfor (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);// 调用RepNotifyCallRepNotifies(false);

AActor同步

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

发送新的Actor

int64 UActorChannel::ReplicateActor()// ObjectReplicator设置的Flagif (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 )// 同步下新的Actorif( 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、NetGUIDLookupRegisterNetGUID_Internal( NetGUID, CacheObject );

设置ActorChannel信息

void UActorChannel::SetChannelActor(AActor* InActor, ESetChannelActorFlags Flags)Actor = InActor;bActorIsPendingKill = false;if (Actor)Connection->AddActorChannel(Actor, this);// 创建本地ActorReplicatorActorReplicator = 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.xdnf.cn/news/1551697.html

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

相关文章

php email功能实现:详细步骤与配置技巧?

php email发送功能详细教程&#xff1f;如何使用php email服务&#xff1f; 无论是用户注册、密码重置&#xff0c;还是订单确认&#xff0c;电子邮件都是与用户沟通的重要手段。AokSend将详细介绍如何实现php email功能&#xff0c;并提供一些配置技巧&#xff0c;帮助你更好…

读代码UNET

这个后面这个大小怎么算的&#xff0c;这参数怎么填&#xff0c;怎么来的&#xff1f; 这是怎么看怎么算的&#xff1f; 这些参数设置怎么设置&#xff1f;卷积多大&#xff0c;有什么讲究&#xff1f;

65.【C语言】联合体

目录 目录 1.定义 2.格式 3.例题 答案速查 分析 4.练习 答案速查 分析 5.相同成员的联合体和结构体的对比 6.联合体的大小计算 2条规则 答案速查 分析 练习 答案速查 分析 7.联合体的优点 8.匿名联合体 1.定义 和结构体有所不同,顾名思义:所有成员联合使用同…

软件设计师——计算机网络

&#x1f4d4;个人主页&#x1f4da;&#xff1a;秋邱-CSDN博客☀️专属专栏✨&#xff1a;软考——软件设计师&#x1f3c5;往期回顾&#x1f3c6;&#xff1a;&#x1f31f;其他专栏&#x1f31f;&#xff1a;C语言_秋邱 一、OSI/ RM七层模型(⭐⭐⭐) ​ 层次 名称 主要功…

【STM32单片机_(HAL库)】4-1【定时器TIM】定时器中断点灯实验

1.硬件 STM32单片机最小系统LED灯模块 2.软件 timer驱动文件添加定时器HAL驱动层文件添加GPIO常用函数定时器中断配置流程main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "timer.h"int main(void) {H…

latex打出邮箱图标和可点击的orcidID

如图所示&#xff1a; 邮箱的打法 \usepackage{bbding} \inst{(}\Envelope\inst{)}orcidID的打法 \newcommand{\myorcidID}[1]{\href{https://orcid.org/#1}{\includegraphics[width8pt]{res/orcid.png}}} \captionsetup[algorithm]{skip5pt} \definecolor{customblue}{RGB}{…

硬盘数据不翼而飞?2024四大神器帮您找回!

能看到这篇文章的&#xff0c;相信都是一不小心丢失了重要数据的小伙伴。不要慌&#xff0c;以下的这几个硬盘数据恢复软件大家都可以试试&#xff0c;它们大概率能帮助大家找回重要数据&#xff01; 福昕数据恢复 直达链接&#xff08;复制到浏览器打开&#xff09;&#xf…

鸿蒙开发(NEXT/API 12)【穿戴设备信息查询】手机侧应用开发

// 在使用Wear Engine服务前&#xff0c;请导入WearEngine与相关模块 import { wearEngine } from kit.WearEngine; import { BusinessError } from kit.BasicServicesKit;查询穿戴设备是否支持某种WearEngine能力集 注意 该接口的调用需要在开发者联盟申请设备基础信息权限。…

Threejs中使用A*算法寻路导航

<!DOCTYPE html> <html><head><title>Threejs中使用A*算法寻路导航&#xff0c;Threejs室内室外地图导航</title><script type"text/javascript" src"libs/three.js"></script><script type"text/javas…

zabbix7.0监控linux主机案例详解

前言 服务端配置 链接: rocky9.2部署zabbix服务端的详细过程 环境 主机ip应用zabbix-server192.168.10.11zabbix本体zabbix-client192.168.10.12zabbix-agent zabbix-server(服务端已配置) 具体实现过程 zabbix-client配置 安装zabbix-agent 添加扩展包 dnf -y instal…

AD软件的分屏显示功能

1.鼠标右键点击上面的窗格&#xff0c;选择“垂直分布”&#xff0c;即可以将AD软件分屏&#xff0c;左边选择原理图&#xff0c;右边选择PCB即可以方便去设计PCB的布局。实现原理图和pcb文件的同时查看。 还可以建立起2个图之间的联动关系。 比如我们在电路图里面选择stm32 m…

风险函数梳理工具

风险函数梳理工具 在日常的软件开发工作中&#xff0c;代码的安全性和质量至关重要。然而&#xff0c;面对庞大的代码库&#xff0c;手动查找潜在的风险函数不仅耗时&#xff0c;而且容易出错。特别是在团队协作中&#xff0c;代码审查和重构工作往往占据了大量宝贵的时间&…

心理咨询预约管理系统(含源码+sql+视频导入教程)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 心理咨询预约管理系统2拥有三个角色&#xff1a; 管理员端 首页 系统近况&#xff08;咨询师和注册来访者数量&#xff0c;预约数量&#xff09; 显示最新的消息、留言和公告&#xff0…

软件测试学习笔记丨Pytest 学习指南

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/32336 基本介绍 pytest框架是一个成熟&#xff0c;全面的测试框架&#xff0c;具有非常丰富的第三方插件&#xff0c;并且可以自定义扩展 比如&#xff1a;pytest-selenium , pytest-html ,…

spring模块都有哪些

Spring 框架是一个庞大而灵活的生态系统&#xff0c;它包含了多个模块&#xff0c;每个模块都提供了特定的功能和服务。以下是一些主要的 Spring 模块&#xff1a; Spring Core&#xff1a; 核心容器&#xff0c;提供了 IoC&#xff08;控制反转&#xff09;和 DI&#xff08;…

推荐4个精准高效的录音转文字软件。

录音转文字在很多的场景中都能够为我们提供便利&#xff0c;比如&#xff1a;可以将课堂录音转换为文字&#xff0c;方便复习和整理笔记&#xff0c;可以将会议录音转换为文字&#xff0c;快速准确地记录内容&#xff0c;可以将采访录音转成文字&#xff0c;提高新闻稿件的撰写…

被Karpathy誉为“蕴藏着类似ChatGPT的机会的AI产品Notebook LM”,它到底做对了什么?

就在昨天&#xff0c;Karpathy在X上连续发布了多条安利帖&#xff0c;强烈地给大家推荐一个AI产品NotebookLM。 嘶&#xff5e;给周围人疯狂种草并不稀奇&#xff0c;但Karpathy的推荐理由给NotebookLM戴了一个高帽子-他提到这款产品让人联想到ChatGPT。 这种就令人好奇&#…

JAVA开源项目 大学生就业招聘系统 计算机毕业设计

本文项目编号 T 058 &#xff0c;文末自助获取源码 \color{red}{T058&#xff0c;文末自助获取源码} T058&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 企…

DRF实操——项目部署

DRF实操——项目部署 一、Mysql集群1. 集群方式1)Replication集群2)PXC集群2. Docker安装PXC知识补充:具名数据卷创建docker容器django后端接口服务二、Nginx概述作用安装配置三、uWSGI1. 概述2. 项目的配置3. 将本地项目及环境打包到服务器4. uwsgi的安装与启动5. 使用uwsg…

Vue Mini基于 Vue 3 的小程序框架

新的小程序框架 https://vuemini.org/ Vue Mini 是一个基于 Vue 3 的小程序框架&#xff0c;它允许开发者利用 Vue 3 的强大功能来构建微信小程序。Vue Mini 的核心优势在于它的响应式系统和组合式 API&#xff0c;这些特性让开发者能够以一种更声明式、更高效的方式来编写和…