/** Replicates properties to the Bunch. Returns true if it wrote anything */ bool FObjectReplicator::ReplicateProperties( FOutBunch & Bunch, FReplicationFlags RepFlags ) { UObject* Object = GetObject(); if ( Object == NULL ) { UE_LOG(LogRep, Verbose, TEXT("ReplicateProperties: Object == NULL")); return false; } check( Object ); check( OwningChannel ); check( RepLayout.IsValid() ); check( RepState ) check( RepState->StaticBuffer.Num() ); UNetConnection* OwningChannelConnection = OwningChannel->Connection; const int32 StartingBitNum = Bunch.GetNumBits(); bool bContentBlockWritten = false; // Replicate all the custom delta properties (fast arrays, etc) ReplicateCustomDeltaProperties( Bunch, RepFlags, bContentBlockWritten ); // Replicate properties in the layout RepLayout->ReplicateProperties( RepState, (uint8*)Object, ObjectClass, OwningChannel, Bunch, RepFlags, bContentBlockWritten ); // LastUpdateEmpty - this is done before dequeing the multicasted unreliable functions on purpose as they should not prevent // an actor channel from going dormant. bLastUpdateEmpty = ( Bunch.GetNumBits() == StartingBitNum ); // Replicate Queued (unreliable functions) if ( RemoteFunctions != NULL && RemoteFunctions->GetNumBits() > 0 ) { static const auto* CVar = IConsoleManager::Get().FindTConsoleVariableDataInt( TEXT( "net.RPC.Debug" ) ); if ( CVar && CVar->GetValueOnGameThread() == 1 ) { UE_LOG( LogRepTraffic, Warning, TEXT(" Sending queued RPCs: %s. Channel[%d] [%.1f bytes]"), *Object->GetName(), OwningChannel->ChIndex, RemoteFunctions->GetNumBits() / 8.f ); } if ( !bContentBlockWritten ) { OwningChannel->BeginContentBlock( Object, Bunch ); bContentBlockWritten = true; } Bunch.SerializeBits( RemoteFunctions->GetData(), RemoteFunctions->GetNumBits() ); RemoteFunctions->Reset(); RemoteFuncInfo.Empty(); NETWORK_PROFILER(GNetworkProfiler.FlushQueuedRPCs(OwningChannelConnection, Object)); } // See if we wrote something important (anything but the 'end' int below). // Note that queued unreliable functions are considered important (WroteImportantData) but not for bLastUpdateEmpty. LastUpdateEmpty // is used for dormancy purposes. WroteImportantData is for determining if we should not include a component in replication. const bool WroteImportantData = ( Bunch.GetNumBits() != StartingBitNum ); if ( WroteImportantData ) { OwningChannel->EndContentBlock( Object, Bunch, OwningChannelConnection->Driver->NetCache->GetClassNetCache( ObjectClass ) ); } return WroteImportantData; }
void FObjectReplicator::ReplicateCustomDeltaProperties( FOutBunch & Bunch, FReplicationFlags RepFlags, bool & bContentBlockWritten ) { if ( LifetimeCustomDeltaProperties.Num() == 0 ) { // No custom properties return; } UObject* Object = GetObject(); check( Object ); check( OwningChannel ); UNetConnection * OwningChannelConnection = OwningChannel->Connection; // Initialize a map of which conditions are valid bool ConditionMap[COND_Max]; const bool bIsInitial = RepFlags.bNetInitial ? true : false; const bool bIsOwner = RepFlags.bNetOwner ? true : false; const bool bIsSimulated = RepFlags.bNetSimulated ? true : false; const bool bIsPhysics = RepFlags.bRepPhysics ? true : false; ConditionMap[COND_None] = true; ConditionMap[COND_InitialOnly] = bIsInitial; ConditionMap[COND_OwnerOnly] = bIsOwner; ConditionMap[COND_SkipOwner] = !bIsOwner; ConditionMap[COND_SimulatedOnly] = bIsSimulated; ConditionMap[COND_AutonomousOnly] = !bIsSimulated; ConditionMap[COND_SimulatedOrPhysics] = bIsSimulated || bIsPhysics; ConditionMap[COND_InitialOrOwner] = bIsInitial || bIsOwner; ConditionMap[COND_Custom] = true; // Replicate those properties. for ( int32 i = 0; i < LifetimeCustomDeltaProperties.Num(); i++ ) { // Get info. const int32 RetireIndex = LifetimeCustomDeltaProperties[i]; FPropertyRetirement & Retire = Retirement[RetireIndex]; FRepRecord * Rep = &ObjectClass->ClassReps[RetireIndex]; UProperty * It = Rep->Property; int32 Index = Rep->Index; if (LifetimeCustomDeltaPropertyConditions.IsValidIndex(i)) { // Check the replication condition here ELifetimeCondition RepCondition = LifetimeCustomDeltaPropertyConditions[i]; check(RepCondition >= 0 && RepCondition < COND_Max); if (!ConditionMap[RepCondition]) { // We didn't pass the condition so don't replicate us continue; } } const int32 BitsWrittenBeforeThis = Bunch.GetNumBits(); // If this is a dynamic array, we do the delta here TSharedPtr<INetDeltaBaseState> & OldState = RecentCustomDeltaState.FindOrAdd( RetireIndex ); TSharedPtr<INetDeltaBaseState> NewState; // Update Retirement records with this new state so we can handle packet drops. // LastNext will be pointer to the last "Next" pointer in the list (so pointer to a pointer) FPropertyRetirement ** LastNext = UpdateAckedRetirements( Retire, OwningChannelConnection->OutAckPacketId ); check( LastNext != NULL ); check( *LastNext == NULL ); ValidateRetirementHistory( Retire ); FNetBitWriter TempBitWriter( OwningChannel->Connection->PackageMap, 0 ); //----------------------------------------- // Do delta serialization on dynamic properties //----------------------------------------- const bool WroteSomething = SerializeCustomDeltaProperty( OwningChannelConnection, (void*)Object, It, Index, TempBitWriter, NewState, OldState ); if ( !WroteSomething ) { continue; } *LastNext = new FPropertyRetirement(); // Remember what the old state was at this point in time. If we get a nak, we will need to revert back to this. (*LastNext)->DynamicState = OldState; // Save NewState into the RecentCustomDeltaState array (old state is a reference into our RecentCustomDeltaState map) OldState = NewState; // Write header, and data to send to the actual bunch RepLayout->WritePropertyHeader( Object, ObjectClass, OwningChannel, It, Bunch, Index, bContentBlockWritten ); const int NumStartingBits = Bunch.GetNumBits(); // Send property. Bunch.SerializeBits( TempBitWriter.GetData(), TempBitWriter.GetNumBits() ); NETWORK_PROFILER(GNetworkProfiler.TrackReplicateProperty(It, Bunch.GetNumBits() - NumStartingBits)); } }