예제 #1
0
/** 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;
}
예제 #2
0
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 );
	}
}
예제 #3
0
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() );
}
예제 #4
0
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();
	}
}
예제 #5
0
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;
		}
	}
예제 #6
0
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));
	}
}