bool FObjectReplicator::SerializeCustomDeltaProperty( UNetConnection * Connection, void * Src, UProperty * Property, int32 ArrayDim, FNetBitWriter & OutBunch, TSharedPtr<INetDeltaBaseState> &NewFullState, TSharedPtr<INetDeltaBaseState> & OldState ) { check( NewFullState.IsValid() == false ); // NewState is passed in as NULL and instantiated within this function if necessary SCOPE_CYCLE_COUNTER( STAT_NetSerializeItemDeltaTime ); UStructProperty * StructProperty = CastChecked< UStructProperty >( Property ); //------------------------------------------------ // Custom NetDeltaSerialization //------------------------------------------------ if ( !ensure( ( StructProperty->Struct->StructFlags & STRUCT_NetDeltaSerializeNative ) != 0 ) ) { return false; } FNetDeltaSerializeInfo Parms; FNetSerializeCB NetSerializeCB( Connection->Driver ); Parms.OutBunch = &OutBunch; Parms.Map = Connection->PackageMap; Parms.OldState = OldState.Get(); Parms.NewState = &NewFullState; Parms.NetSerializeCB = &NetSerializeCB; UScriptStruct::ICppStructOps * CppStructOps = StructProperty->Struct->GetCppStructOps(); check(CppStructOps); // else should not have STRUCT_NetSerializeNative check(!StructProperty->Struct->InheritedCppStructOps()); // else should not have STRUCT_NetSerializeNative Parms.Struct = StructProperty->Struct; return CppStructOps->NetDeltaSerialize( Parms, Property->ContainerPtrToValuePtr<void>( Src, ArrayDim ) ); }
bool FObjectReplicator::ReceivedBunch( FInBunch& Bunch, const FReplicationFlags& RepFlags, bool& bOutHasUnmapped ) { UObject* Object = GetObject(); if ( Object == NULL ) { UE_LOG(LogNet, Verbose, TEXT("ReceivedBunch: Object == NULL")); return false; } UPackageMap * PackageMap = OwningChannel->Connection->PackageMap; const bool bIsServer = ( OwningChannel->Connection->Driver->ServerConnection == NULL ); const FClassNetCache * ClassCache = OwningChannel->Connection->Driver->NetCache->GetClassNetCache( ObjectClass ); if ( ClassCache == NULL ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: ClassCache == NULL: %s"), *Object->GetFullName()); return false; } bool bThisBunchReplicatedProperties = false; // Read first field const FFieldNetCache * FieldCache = ReadField( ClassCache, Bunch ); if ( Bunch.IsError() ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: Error reading field 1: %s"), *Object->GetFullName()); return false; } if ( FieldCache == NULL ) { // There are no actual replicated properties or functions in this bunch. That is ok - we may have gotten this // actor/sub-object because we want the client to spawn one (but we aren't actually replicating properties on it) return true; } while ( FieldCache ) { // Receive properties from the net. UProperty* ReplicatedProp = NULL; while ( FieldCache && ( ReplicatedProp = Cast< UProperty >( FieldCache->Field ) ) != NULL ) { NET_CHECKSUM( Bunch ); // Server shouldn't receive properties. if ( bIsServer ) { UE_LOG(LogNet, Error, TEXT("Server received unwanted property value %s in %s"), *ReplicatedProp->GetName(), *Object->GetFullName()); return false; } bThisBunchReplicatedProperties = true; if ( !bHasReplicatedProperties ) { bHasReplicatedProperties = true; // Persistent, not reset until PostNetReceive is called PreNetReceive(); } bool DebugProperty = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("net.Replication.DebugProperty")); if (CVar && !CVar->GetString().IsEmpty() && ReplicatedProp->GetName().Contains(CVar->GetString()) ) { UE_LOG(LogRep, Log, TEXT("Replicating Property[%d] %s on %s"), ReplicatedProp->RepIndex, *ReplicatedProp->GetName(), *Object->GetName()); DebugProperty = true; } } #endif if ( !Retirement[ ReplicatedProp->RepIndex ].CustomDelta ) { bool bLocalHasUnmapped = false; // We hijack a non custom delta property to signify we are using FRepLayout to read the entire property block if ( !RepLayout->ReceiveProperties( ObjectClass, RepState, (void*)Object, Bunch, bLocalHasUnmapped ) ) { UE_LOG(LogRep, Error, TEXT("ReceiveProperties FAILED %s in %s"), *ReplicatedProp->GetName(), *Object->GetFullName()); return false; } if ( bLocalHasUnmapped ) { bOutHasUnmapped = true; } } else { // Receive array index. uint32 Element = 0; if ( ReplicatedProp->ArrayDim != 1 ) { check( ReplicatedProp->ArrayDim >= 2 ); Bunch.SerializeIntPacked( Element ); if ( Element >= (uint32)ReplicatedProp->ArrayDim ) { UE_LOG(LogRep, Error, TEXT("Element index too large %s in %s"), *ReplicatedProp->GetName(), *Object->GetFullName()); return false; } } // Pointer to destination. uint8* Data = ReplicatedProp->ContainerPtrToValuePtr<uint8>((uint8*)Object, Element); TArray<uint8> MetaData; const PTRINT DataOffset = Data - (uint8*)Object; // Receive custom delta property. UStructProperty * StructProperty = Cast< UStructProperty >( ReplicatedProp ); if ( StructProperty == NULL ) { // This property isn't custom delta UE_LOG(LogRepTraffic, Error, TEXT("Property isn't custom delta %s"), *ReplicatedProp->GetName()); return false; } UScriptStruct * InnerStruct = StructProperty->Struct; if ( !( InnerStruct->StructFlags & STRUCT_NetDeltaSerializeNative ) ) { // This property isn't custom delta UE_LOG(LogRepTraffic, Error, TEXT("Property isn't custom delta %s"), *ReplicatedProp->GetName()); return false; } UScriptStruct::ICppStructOps * CppStructOps = InnerStruct->GetCppStructOps(); check( CppStructOps ); check( !InnerStruct->InheritedCppStructOps() ); FNetDeltaSerializeInfo Parms; FNetSerializeCB NetSerializeCB( OwningChannel->Connection->Driver ); Parms.DebugName = StructProperty->GetName(); Parms.Struct = InnerStruct; Parms.Map = PackageMap; Parms.Reader = &Bunch; Parms.NetSerializeCB = &NetSerializeCB; // Call the custom delta serialize function to handle it CppStructOps->NetDeltaSerialize( Parms, Data ); if ( Bunch.IsError() ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: NetDeltaSerialize - Bunch.IsError() == true: %s"), *Object->GetFullName()); return false; } if ( Parms.bOutHasMoreUnmapped ) { UnmappedCustomProperties.Add( DataOffset, StructProperty ); bOutHasUnmapped = true; } // Successfully received it. UE_LOG(LogRepTraffic, Log, TEXT(" %s - %s"), *Object->GetName(), *ReplicatedProp->GetName()); // Notify the Object if this var is RepNotify QueuePropertyRepNotify( Object, ReplicatedProp, Element, MetaData ); } // Read next field FieldCache = ReadField( ClassCache, Bunch ); if ( Bunch.IsError() ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: Error reading field 2: %s"), *Object->GetFullName()); return false; } } // Handle function calls. if ( FieldCache && Cast< UFunction >( FieldCache->Field ) ) { FName Message = FieldCache->Field->GetFName(); UFunction * Function = Object->FindFunction( Message ); if ( Function == NULL ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: Function == NULL: %s"), *Object->GetFullName()); return false; } if ( ( Function->FunctionFlags & FUNC_Net ) == 0 ) { UE_LOG(LogRep, Error, TEXT("Rejected non RPC function %s in %s"), *Message.ToString(), *Object->GetFullName()); return false; } if ( ( Function->FunctionFlags & ( bIsServer ? FUNC_NetServer : ( FUNC_NetClient | FUNC_NetMulticast ) ) ) == 0 ) { UE_LOG(LogRep, Error, TEXT("Rejected RPC function due to access rights %s in %s"), *Message.ToString(), *Object->GetFullName()); return false; } UE_LOG(LogRepTraffic, Log, TEXT(" Received RPC: %s"), *Message.ToString()); // Get the parameters. FMemMark Mark(FMemStack::Get()); uint8* Parms = new(FMemStack::Get(),MEM_Zeroed,Function->ParmsSize)uint8; // Use the replication layout to receive the rpc parameter values TSharedPtr<FRepLayout> FuncRepLayout = OwningChannel->Connection->Driver->GetFunctionRepLayout( Function ); FuncRepLayout->ReceivePropertiesForRPC( Object, Function, OwningChannel, Bunch, Parms ); if ( Bunch.IsError() ) { UE_LOG(LogRep, Error, TEXT("ReceivedBunch: ReceivePropertiesForRPC - Bunch.IsError() == true: Function: %s, Object: %s"), *Message.ToString(), *Object->GetFullName()); return false; } // validate that the function is callable here const bool bCanExecute = ( !bIsServer || RepFlags.bNetOwner ); // we are client or net owner if ( bCanExecute ) { // Call the function. RPC_ResetLastFailedReason(); Object->ProcessEvent( Function, Parms ); if ( RPC_GetLastFailedReason() != NULL ) { UE_LOG(LogRep, Error, TEXT("ReceivedBunch: RPC_GetLastFailedReason: %s"), RPC_GetLastFailedReason()); return false; } } else { UE_LOG(LogRep, Verbose, TEXT("Rejected unwanted function %s in %s"), *Message.ToString(), *Object->GetFullName()); if ( !OwningChannel->Connection->TrackLogsPerSecond() ) // This will disconnect the client if we get here too often { UE_LOG(LogRep, Error, TEXT("Rejected too many unwanted functions %s in %s"), *Message.ToString(), *Object->GetFullName()); return false; } } // Destroy the parameters. //warning: highly dependent on UObject::ProcessEvent freeing of parms! for ( UProperty * Destruct=Function->DestructorLink; Destruct; Destruct=Destruct->DestructorLinkNext ) { if( Destruct->IsInContainer(Function->ParmsSize) ) { Destruct->DestroyValue_InContainer(Parms); } } Mark.Pop(); if ( Object == NULL || Object->IsPendingKill() ) { // replicated function destroyed Object return true; // FIXME: Should this return false to kick connection? Seems we'll cause a read misalignment here if we don't } // Next. FieldCache = ReadField( ClassCache, Bunch ); if ( Bunch.IsError() ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: Error reading field 3: %s"), *Object->GetFullName()); return false; } } else if ( FieldCache ) { UE_LOG(LogRep, Error, TEXT("ReceivedBunch: Invalid replicated field %i in %s"), FieldCache->FieldNetIndex, *Object->GetFullName()); return false; } } return true; }
bool FObjectReplicator::ReceivedBunch( FInBunch &Bunch, const FReplicationFlags & RepFlags ) { UObject * Object = GetObject(); UPackageMap * PackageMap = OwningChannel->Connection->PackageMap; if ( Object == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Object == NULL" ) ); return false; } const bool bIsServer = ( OwningChannel->Connection->Driver->ServerConnection == NULL ); FClassNetCache * ClassCache = PackageMap->GetClassNetCache( ObjectClass ); if ( ClassCache == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: ClassCache == NULL: %s" ), *Object->GetFullName() ); return false; } bool bThisBunchReplicatedProperties = false; // First RepIndex. int32 RepIndex = Bunch.ReadInt( ClassCache->GetMaxIndex() + 1 ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Error reading bunch 1: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex == ClassCache->GetMaxIndex() ) { // There are no actual replicated properties or functions in this bunch. That is ok - we may have gotten this // actor/subobject because we want the client to spawn one (but we arent actually replicating properties on it) return true; } if ( RepIndex > ClassCache->GetMaxIndex() ) { // We shouldn't be receiving this bunch of this object has no properties or RPC functions to process UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: RepIndex too large: %s" ), *Object->GetFullName() ); return false; } FFieldNetCache * FieldCache = ClassCache->GetFromIndex( RepIndex ); if ( FieldCache == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: FieldCache == NULL: %s" ), *Object->GetFullName() ); return false; } while ( FieldCache ) { // Receive properties from the net. UProperty * ReplicatedProp = NULL; int32 LastIndex = 0; while ( FieldCache && ( ReplicatedProp = Cast< UProperty >( FieldCache->Field ) ) != NULL ) { NET_CHECKSUM( Bunch ); // Server shouldn't receive properties. if ( bIsServer ) { UE_LOG( LogNet, Error, TEXT( "Server received unwanted property value %s in %s" ), *ReplicatedProp->GetName(), *Object->GetFullName() ); return false; } bThisBunchReplicatedProperties = true; if ( !bHasReplicatedProperties ) { bHasReplicatedProperties = true; // Persistent, not reset until PostNetReceive is called PreNetReceive(); } bool DebugProperty = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("net.Replication.DebugProperty")); if (CVar && !CVar->GetString().IsEmpty() && ReplicatedProp->GetName().Contains(CVar->GetString()) ) { UE_LOG(LogNet, Log, TEXT("Replicating Property[%d] %s on %s"), RepIndex, *ReplicatedProp->GetName(), *Object->GetName()); DebugProperty = true; } } #endif if ( !Retirement[ ReplicatedProp->RepIndex ].CustomDelta ) { // We hijack a non custom delta property to signify we are using FRepLayout to read the entire property block FPropertyRetirement & Retire = Retirement[ ReplicatedProp->RepIndex ]; bool bDiscardLayout = false; if ( Bunch.PacketId >= Retire.InPacketId ) //!! problem with reliable pkts containing dynamic references, being retransmitted, and overriding newer versions. Want "OriginalPacketId" for retransmissions? { // Receive this new property. Retire.InPacketId = Bunch.PacketId; } else { bDiscardLayout = true; } if ( !RepLayout->ReceiveProperties( ObjectClass, RepState, (void*)Object, Bunch, bDiscardLayout ) ) { UE_LOG( LogNet, Error, TEXT( "ReceiveProperties FAILED %s in %s" ), *ReplicatedProp->GetName(), *Object->GetFullName() ); return false; } } else { // Receive array index. int32 Element = 0; if ( ReplicatedProp->ArrayDim != 1 ) { // Serialize index as delta from previous index to increase chance we'll only use 1 byte uint32 idx; Bunch.SerializeIntPacked( idx ); Element = static_cast< int32 >( idx ) + LastIndex; LastIndex = Element; if ( Element >= ReplicatedProp->ArrayDim ) { UE_LOG( LogNet, Error, TEXT( "Element index too large %s in %s" ), *ReplicatedProp->GetName(), *Object->GetFullName() ); return false; } } // Pointer to destination. uint8 * DestObj = (uint8*)Object; uint8 * DestRecent = RepState->StaticBuffer.Num() ? RepState->StaticBuffer.GetTypedData() : NULL; // Check property ordering. FPropertyRetirement & Retire = Retirement[ ReplicatedProp->RepIndex + Element ]; if ( Bunch.PacketId >= Retire.InPacketId ) //!! problem with reliable pkts containing dynamic references, being retransmitted, and overriding newer versions. Want "OriginalPacketId" for retransmissions? { // Receive this new property. Retire.InPacketId = Bunch.PacketId; } else { // Skip this property, because it's out-of-date. UE_LOG( LogNetTraffic, Log, TEXT( "Received out-of-date %s" ), *ReplicatedProp->GetName() ); DestObj = NULL; DestRecent = NULL; } FMemMark Mark(FMemStack::Get()); uint8 * Data = DestObj ? ReplicatedProp->ContainerPtrToValuePtr<uint8>(DestObj, Element) : NewZeroed<uint8>(FMemStack::Get(),ReplicatedProp->ElementSize); TArray<uint8> MetaData; PTRINT Offset = 0; // Copy current value over to Recent for comparison if ( DestRecent ) { Offset = ReplicatedProp->ContainerPtrToValuePtr<uint8>(DestRecent, Element) - DestRecent; check( Offset >= 0 && Offset < RepState->StaticBuffer.Num() ); //@todo if we move properties outside of the memory block, then this will not work anyway ReplicatedProp->CopySingleValue( DestRecent + Offset, Data ); } // Receive custom delta property. UStructProperty * StructProperty = Cast< UStructProperty >( ReplicatedProp ); if ( StructProperty == NULL ) { // This property isn't custom delta UE_LOG( LogNetTraffic, Error, TEXT( "Property isn't custom delta %s" ), *ReplicatedProp->GetName() ); return false; } UScriptStruct * InnerStruct = StructProperty->Struct; if ( !( InnerStruct->StructFlags & STRUCT_NetDeltaSerializeNative ) ) { // This property isn't custom delta UE_LOG( LogNetTraffic, Error, TEXT( "Property isn't custom delta %s" ), *ReplicatedProp->GetName() ); return false; } UScriptStruct::ICppStructOps * CppStructOps = InnerStruct->GetCppStructOps(); check( CppStructOps ); check( !InnerStruct->InheritedCppStructOps() ); FNetDeltaSerializeInfo Parms; FNetSerializeCB NetSerializeCB( OwningChannel->Connection->Driver ); Parms.DebugName = StructProperty->GetName(); Parms.Struct = InnerStruct; Parms.Map = PackageMap; Parms.InArchive = &Bunch; Parms.NetSerializeCB = &NetSerializeCB; // Call the custom delta serialize function to handle it CppStructOps->NetDeltaSerialize( Parms, Data ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: NetDeltaSerialize - Bunch.IsError() == true: %s" ), *Object->GetFullName() ); return false; } // See if it changed from our local value bool PropertyChanged = true; if ( DestRecent ) { // POD types can do a memcmp with a call to Identical if ( ReplicatedProp->Identical( DestRecent + Offset, Data ) ) { PropertyChanged = false; } } Mark.Pop(); // Successfully received it. UE_LOG( LogNetTraffic, Log, TEXT( " %s - %s - Change: %d" ), *Object->GetName(), *ReplicatedProp->GetName(), PropertyChanged ); // Notify the Object if this var is RepNotify if ( PropertyChanged ) { QueuePropertyRepNotify( Object, ReplicatedProp, Element, MetaData ); } } // Next. RepIndex = Bunch.ReadInt( ClassCache->GetMaxIndex() + 1 ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Error reading bunch 2: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex > ClassCache->GetMaxIndex() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: RepIndex too large: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex == ClassCache->GetMaxIndex() ) { // We're done FieldCache = NULL; } else { FieldCache = ClassCache->GetFromIndex( RepIndex ); if ( FieldCache == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: FieldCache == NULL: %s" ), *Object->GetFullName() ); return false; } } } // Handle function calls. if ( FieldCache && Cast< UFunction >( FieldCache->Field ) ) { FName Message = FieldCache->Field->GetFName(); UFunction * Function = Object->FindFunction( Message ); if ( Function == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Function == NULL: %s" ), *Object->GetFullName() ); return false; } if ( ( Function->FunctionFlags & FUNC_Net ) == 0 ) { UE_LOG( LogNet, Error, TEXT( "Rejected non RPC function %s in %s" ), *Message.ToString(), *Object->GetFullName() ); return false; } if ( ( Function->FunctionFlags & ( bIsServer ? FUNC_NetServer : ( FUNC_NetClient | FUNC_NetMulticast ) ) ) == 0 ) { UE_LOG( LogNet, Error, TEXT( "Rejected RPC function due to access rights %s in %s" ), *Message.ToString(), *Object->GetFullName() ); return false; } UE_LOG( LogNetTraffic, Log, TEXT( " Received RPC: %s" ), *Message.ToString() ); // Get the parameters. FMemMark Mark(FMemStack::Get()); uint8* Parms = new(FMemStack::Get(),MEM_Zeroed,Function->ParmsSize)uint8; // Use the replication layout to receive the rpc parameter values TSharedPtr<FRepLayout> FuncRepLayout = OwningChannel->Connection->Driver->GetFunctionRepLayout( Function ); FuncRepLayout->ReceivePropertiesForRPC( Object, Function, OwningChannel, Bunch, Parms ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: ReceivePropertiesForRPC - Bunch.IsError() == true: %s" ), *Object->GetFullName() ); return false; } // validate that the function is callable here const bool bCanExecute = ( ( !bIsServer || RepFlags.bNetOwner ) ); // we are client or net owner if ( bCanExecute ) { // Call the function. RPC_ResetLastFailedReason(); Object->ProcessEvent( Function, Parms ); if ( RPC_GetLastFailedReason() != NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: RPC_GetLastFailedReason: %s" ), RPC_GetLastFailedReason() ); return false; } } else { UE_LOG( LogNet, Warning, TEXT( "Rejected unwanted function %s in %s" ), *Message.ToString(), *Object->GetFullName() ); if ( !OwningChannel->Connection->TrackLogsPerSecond() ) // This will disconnect the client if we get her too often { return false; } } // Destroy the parameters. //warning: highly dependent on UObject::ProcessEvent freeing of parms! for ( UProperty * Destruct=Function->DestructorLink; Destruct; Destruct=Destruct->DestructorLinkNext ) { if( Destruct->IsInContainer(Function->ParmsSize) ) { Destruct->DestroyValue_InContainer(Parms); } } Mark.Pop(); if ( Object == NULL || Object->IsPendingKill() ) { // replicated function destroyed Object return true; // FIXME: Should this return false to kick connection? Seems we'll cause a read misalignment here if we don't } // Next. RepIndex = Bunch.ReadInt( ClassCache->GetMaxIndex() + 1 ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Error reading bunch 2: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex > ClassCache->GetMaxIndex() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: RepIndex too large: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex == ClassCache->GetMaxIndex() ) { // We're done FieldCache = NULL; } else { FieldCache = ClassCache->GetFromIndex( RepIndex ); if ( FieldCache == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: FieldCache == NULL: %s" ), *Object->GetFullName() ); return false; } } } else if ( FieldCache ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Invalid replicated field %i in %s" ), RepIndex, *Object->GetFullName() ); return false; } } return true; }
void FObjectReplicator::UpdateUnmappedObjects( bool & bOutHasMoreUnmapped ) { UObject* Object = GetObject(); if ( Object == NULL || Object->IsPendingKill() ) { bOutHasMoreUnmapped = false; return; } if ( Connection->State == USOCK_Closed ) { UE_LOG(LogNet, Warning, TEXT("FObjectReplicator::UpdateUnmappedObjects: Connection->State == USOCK_Closed")); return; } checkf( RepState->RepNotifies.Num() == 0, TEXT("Failed RepState RepNotifies check. Num=%d. Object=%s"), RepState->RepNotifies.Num(), *Object->GetFullName() ); checkf( RepNotifies.Num() == 0, TEXT("Failed replicator RepNotifies check. Num=%d. Object=%s."), RepNotifies.Num(), *Object->GetFullName() ); bool bSomeObjectsWereMapped = false; // Let the rep layout update any unmapped properties RepLayout->UpdateUnmappedObjects( RepState, Connection->PackageMap, Object, bSomeObjectsWereMapped, bOutHasMoreUnmapped ); // Update unmapped objects for custom properties (currently just fast tarray) for ( auto It = UnmappedCustomProperties.CreateIterator(); It; ++It ) { const int32 Offset = It.Key(); UStructProperty* StructProperty = It.Value(); UScriptStruct* InnerStruct = StructProperty->Struct; check( InnerStruct->StructFlags & STRUCT_NetDeltaSerializeNative ); UScriptStruct::ICppStructOps* CppStructOps = InnerStruct->GetCppStructOps(); check( CppStructOps ); check( !InnerStruct->InheritedCppStructOps() ); FNetDeltaSerializeInfo Parms; FNetSerializeCB NetSerializeCB( OwningChannel->Connection->Driver ); Parms.DebugName = StructProperty->GetName(); Parms.Struct = InnerStruct; Parms.Map = Connection->PackageMap; Parms.NetSerializeCB = &NetSerializeCB; Parms.bUpdateUnmappedObjects = true; Parms.bCalledPreNetReceive = bSomeObjectsWereMapped; // RepLayout used this to flag whether PreNetReceive was called Parms.Object = Object; // Call the custom delta serialize function to handle it CppStructOps->NetDeltaSerialize( Parms, (uint8*)Object + Offset ); // Merge in results bSomeObjectsWereMapped |= Parms.bOutSomeObjectsWereMapped; bOutHasMoreUnmapped |= Parms.bOutHasMoreUnmapped; if ( Parms.bOutSomeObjectsWereMapped ) { // If we mapped a property, call the rep notify TArray<uint8> MetaData; QueuePropertyRepNotify( Object, StructProperty, 0, MetaData ); } // If this property no longer has unmapped objects, we can stop checking it if ( !Parms.bOutHasMoreUnmapped ) { It.RemoveCurrent(); } } // Call any rep notifies that need to happen when object pointers change // Pass in false to override the check for queued bunches. Otherwise, if the owning channel has queued bunches, // the RepNotifies will remain in the list and the check for 0 RepNotifies above will fail next time. CallRepNotifies(false); if ( bSomeObjectsWereMapped ) { // If we mapped some objects, make sure to call PostNetReceive (some game code will need to think this was actually replicated to work) PostNetReceive(); } }