/** Replicates properties to the Bunch. Returns true if it wrote anything */ bool FObjectReplicator::ReplicateProperties( FOutBunch & Bunch, FReplicationFlags RepFlags ) { UObject * Object = GetObject(); 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; int32 LastIndex = 0; // Replicate all the custom delta properties (fast arrays, etc) ReplicateCustomDeltaProperties( Bunch, LastIndex, bContentBlockWritten ); // Replicate properties in the layout RepLayout->ReplicateProperties( RepState, (uint8*)Object, ObjectClass, OwningChannel, Bunch, RepFlags, LastIndex, 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( LogNetTraffic, 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(); } // 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->PackageMap->GetClassNetCache( ObjectClass ) ); } return WroteImportantData; }
void FObjectReplicator::QueueRemoteFunctionBunch( UFunction* Func, FOutBunch &Bunch ) { // This is a pretty basic throttling method - just don't let same func be called more than // twice in one network update period. // // Long term we want to have priorities and stronger cross channel traffic management that // can handle this better int32 InfoIdx=INDEX_NONE; for (int32 i=0; i < RemoteFuncInfo.Num(); ++i) { if (RemoteFuncInfo[i].FuncName == Func->GetFName()) { InfoIdx = i; break; } } if (InfoIdx==INDEX_NONE) { InfoIdx = RemoteFuncInfo.AddUninitialized(); RemoteFuncInfo[InfoIdx].FuncName = Func->GetFName(); RemoteFuncInfo[InfoIdx].Calls = 0; } if (++RemoteFuncInfo[InfoIdx].Calls > CVarMaxRPCPerNetUpdate.GetValueOnGameThread()) { UE_LOG(LogRep, Verbose, TEXT("Too many calls to RPC %s within a single netupdate. Skipping. %s. LastCallTime: %.2f. CurrentTime: %.2f. LastRelevantTime: %.2f. LastUpdateTime: %.2f "), *Func->GetName(), *GetObject()->GetName(), RemoteFuncInfo[InfoIdx].LastCallTime, OwningChannel->Connection->Driver->Time, OwningChannel->RelevantTime, OwningChannel->LastUpdateTime ); return; } RemoteFuncInfo[InfoIdx].LastCallTime = OwningChannel->Connection->Driver->Time; if (RemoteFunctions == NULL) { RemoteFunctions = new FOutBunch(OwningChannel, 0); } RemoteFunctions->SerializeBits( Bunch.GetData(), Bunch.GetNumBits() ); if ( Connection != NULL && Connection->PackageMap != NULL ) { UPackageMapClient * PackageMapClient = CastChecked< UPackageMapClient >( Connection->PackageMap ); // We need to copy over any info that was obtained on the package map during serialization, and remember it until we actually call SendBunch if ( PackageMapClient->GetMustBeMappedGuidsInLastBunch().Num() ) { OwningChannel->QueuedMustBeMappedGuidsInLastBunch.Append( PackageMapClient->GetMustBeMappedGuidsInLastBunch() ); PackageMapClient->GetMustBeMappedGuidsInLastBunch().Empty(); } // Copy over any exported bunches PackageMapClient->AppendExportBunches( OwningChannel->QueuedExportBunches ); } }
void FObjectReplicator::QueueRemoteFunctionBunch( UFunction* Func, FOutBunch &Bunch ) { // This is a pretty basic throttling method - just don't let same func be called more than // twice in one network update period. // // Long term we want to have priorities and stronger cross channel traffic management that // can handle this better int32 InfoIdx=INDEX_NONE; for (int32 i=0; i < RemoteFuncInfo.Num(); ++i) { if (RemoteFuncInfo[i].FuncName == Func->GetFName()) { InfoIdx = i; break; } } if (InfoIdx==INDEX_NONE) { InfoIdx = RemoteFuncInfo.AddUninitialized(); RemoteFuncInfo[InfoIdx].FuncName = Func->GetFName(); RemoteFuncInfo[InfoIdx].Calls = 0; } if (++RemoteFuncInfo[InfoIdx].Calls > 2) { UE_LOG(LogNet, Log, TEXT("Too many calls to RPC %s within a single netupdate. Skipping. %s"), *Func->GetName(), *GetObject()->GetName() ); return; } if (RemoteFunctions == NULL) { RemoteFunctions = new FOutBunch(OwningChannel, 0); } RemoteFunctions->SerializeBits( Bunch.GetData(), Bunch.GetNumBits() ); }
void FObjectReplicator::ReplicateCustomDeltaProperties( FOutBunch & Bunch, int32 & LastIndex, bool & bContentBlockWritten ) { if ( LifetimeCustomDeltaProperties.Num() == 0 ) { // No custom properties return; } UObject * Object = GetObject(); check( Object ); check( OwningChannel ); UNetConnection * OwningChannelConnection = OwningChannel->Connection; // 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; 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. FPropertyRetirement ** Rec = UpdateAckedRetirements( Retire, OwningChannelConnection->OutAckPacketId ); ValidateRetirementHistory( Retire ); // Our temp writer should always be in a reset state here check( TempBitWriter->GetNumBits() == 0 ); //----------------------------------------- // Do delta serialization on dynamic properties //----------------------------------------- const bool WroteSomething = SerializeCustomDeltaProperty( OwningChannelConnection, (void*)Object, It, Index, *TempBitWriter, NewState, OldState ); if ( !WroteSomething ) { continue; } check( TempBitWriter->GetNumBits() > 0 ); *Rec = 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. (*Rec)->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, LastIndex, bContentBlockWritten ); // Send property. Bunch.SerializeBits( TempBitWriter->GetData(), TempBitWriter->GetNumBits() ); // Reset our temp bit writer TempBitWriter->Reset(); } }
void UPacketLimitTest::ExecuteClientUnitTest() { bool bLowLevelSend = TestStage == ELimitTestStage::LTS_LowLevel_AtLimit || TestStage == ELimitTestStage::LTS_LowLevel_OverLimit; bool bBunchSend = TestStage == ELimitTestStage::LTS_Bunch_AtLimit || TestStage == ELimitTestStage::LTS_Bunch_OverLimit; // You can't access LowLevelSend from UNetConnection, but you can from UIpConnection, as it's exported there UIpConnection* IpConn = Cast<UIpConnection>(UnitConn); if (IpConn != nullptr) { int32 PacketLimit = UnitConn->MaxPacket; int32 SocketLimit = UnitConn->MaxPacket; TArray<uint8> PacketData; if (bBunchSend) { int64 FreeBits = UnitConn->SendBuffer.GetMaxBits() - MAX_BUNCH_HEADER_BITS + MAX_PACKET_TRAILER_BITS; PacketLimit = FreeBits / 8; check(PacketLimit > 0) } if (TestStage == ELimitTestStage::LTS_LowLevel_OverLimit || TestStage == ELimitTestStage::LTS_Bunch_OverLimit) { // Nudge the packet over the MaxPacket limit (accurate for LowLevel, approximate for Bunch) PacketLimit++; SocketLimit++; } PacketData.AddZeroed(PacketLimit); // Randomize the packet data (except for last byte), to thwart any compression, which would cause infinite recursion below // (e.g. recursively adding just zero's, would mean the same compressed size almost every time) for (int32 i=0; i<PacketData.Num()-1; i++) { PacketData[i] = FMath::Rand() % 255; } // Iteratively run 'test' sends, where the packet is passed through all the netcode but not sent, // unless the final (post-PacketHandler) packet size matches SocketLimit. bool bPacketAtLimit = false; int32 TryCount = 0; int32 SendDelta = 0; // Blocks all socket sends not matching SocketLimit TargetSocketSendSize = SocketLimit; while (!bPacketAtLimit && TryCount < 16) { if (bLowLevelSend) { IpConn->LowLevelSend(PacketData.GetData(), PacketData.Num(), PacketData.Num() * 8); } else if (bBunchSend) { UUnitTestNetConnection* UnitTestConn = CastChecked<UUnitTestNetConnection>(UnitConn); // If the bunch is to go over the limit, disable asserts bool bBunchOverLimit = TestStage == ELimitTestStage::LTS_Bunch_OverLimit; UnitTestConn->bDisableValidateSend = bBunchOverLimit; UnitConn->FlushNet(); int32 DummyControlBunchSequence = 0; FOutBunch* TestBunch = NUTNet::CreateChannelBunch(DummyControlBunchSequence, UnitConn, CHTYPE_Control, 0); TestBunch->Serialize(PacketData.GetData(), PacketData.Num()); UnitConn->SendRawBunch(*TestBunch, false); if (bBunchOverLimit) { // For a successful test, the bunch must cause a send error if (UnitConn->SendBuffer.IsError()) { bPacketAtLimit = true; UNIT_LOG(ELogType::StatusSuccess, TEXT("Detected successful bunch overflow. Moving to next test.")); NextTestStage(); } else { UNIT_LOG(ELogType::StatusFailure, TEXT("Failed to detect bunch overflow, when one was expected.")); VerificationState = EUnitTestVerification::VerifiedNeedsUpdate; } break; } UnitConn->FlushNet(); UnitTestConn->bDisableValidateSend = false; } // If PacketHandlers have increased/decreased final packet size away from SocketLimit, trim/pad the packet and retry SendDelta = FMath::Max(1, (SendDelta == 0 ? FMath::Abs(LastSocketSendSize - SocketLimit) : SendDelta / 2)); if (LastSocketSendSize > SocketLimit) { PacketData.RemoveAt(PacketData.Num()-1-SendDelta, SendDelta, false); } else if (LastSocketSendSize < SocketLimit) { PacketData.InsertZeroed(PacketData.Num()-1, SendDelta); for (int32 i=PacketData.Num()-1-SendDelta; i<SendDelta; i++) { PacketData[i] = FMath::Rand() % 255; } } else // if (LastSocketSendSize == SocketLimit) { // Packet successfully sent bPacketAtLimit = true; } TryCount++; } // Re-enable sending packets TargetSocketSendSize = 0; if (!bPacketAtLimit) { UNIT_LOG(ELogType::StatusFailure, TEXT("Failed to send packet - reached packet testing iteration limit.")); VerificationState = EUnitTestVerification::VerifiedUnreliable; } }
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)); } }