// Copyright Epic Games, Inc. All Rights Reserved. #include "ReplicationSystemServerClientTestFixture.h" #include "HAL/IConsoleManager.h" #include "Iris/Core/IrisLog.h" #include "Iris/Metrics/NetMetrics.h" #include "Iris/ReplicationSystem/ReplicationRecord.h" #include "Iris/ReplicationSystem/ReplicationSystem.h" #include "Iris/ReplicationSystem/ReplicationSystemInternal.h" #include "Iris/Serialization/NetBitStreamReader.h" #include "Iris/Serialization/NetBitStreamWriter.h" #include "Logging/LogScopedVerbosityOverride.h" #include "Misc/ScopeExit.h" #include "Net/Core/NetToken/NetToken.h" #include "UObject/CoreNetTypes.h" namespace UE::Net::Private { UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, ReplicateAndDestroySingleObject) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0,0); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that created server handle now also exists on client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObject->NetRefHandle)); // Destroy the spawned object on server Server->DestroyObject(ServerObject); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that object now is destroyed on client as well UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObject->NetRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestDroppedDestroyed) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0,0); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that created server handle now also exists on client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObject->NetRefHandle)); // Destroy the spawned object on server Server->DestroyObject(ServerObject); // Send and drop packet Server->UpdateAndSend({Client}, DoNotDeliverPacket); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that object now is destroyed on client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObject->NetRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestDestroyWhilePendingCreate) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0,0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Send and drop packet Server->UpdateAndSend({Client}, DoNotDeliverPacket); // Destroy Server->DestroyObject(ServerObject); // Verify that the object does not exist on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObjectRefHandle)); // Send and drop packet Server->UpdateAndSend({Client}, DoNotDeliverPacket); // Send and deliver packet Server->UpdateAndSend({Client}, DeliverPacket); // Verify the object still doesn't exist on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestDestroyWhileWaitingOnCreate) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0,0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Write packet with create Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Destroy while we are waiting for confirmation Server->DestroyObject(ServerObject); // Write packet with destroy Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Drop packet with create Server->DeliverTo(Client, DoNotDeliverPacket); // Verify that the object does not exists on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObjectRefHandle)); // Deliver packet with destroy Server->DeliverTo(Client, DeliverPacket); // Verify the object still doesn't exist on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestDestroyWithDataInFlight) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; Server->GetReplicationSystem()->SetStaticPriority(ServerObjectRefHandle, 1.f); // Send packet with create Server->UpdateAndSend({Client}); // Verify that the object exists on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); // Modify some data to mark object dirty ServerObject->IntA = 13; // Write a packet with updated data Server->NetUpdate(); UE_NET_ASSERT_TRUE(Server->SendTo(Client)); Server->PostSendUpdate(); // Destroy while we are waiting for ack on update Server->DestroyObject(ServerObject); // Write packet with destroy Server->NetUpdate(); UE_NET_ASSERT_TRUE(Server->SendTo(Client)); Server->PostSendUpdate(); // Drop and report packet with update as lost Server->DeliverTo(Client, DoNotDeliverPacket); // Verify that the object still exists on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); // Deliver packet with destroy Server->DeliverTo(Client, DeliverPacket); // Verify that the object is destroyed on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, ReplicateAndDestroySubObject) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0,0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Spawn second object on server as a subobject UReplicatedTestObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); const FNetRefHandle ServerSubObjectRefHandle = ServerSubObject->NetRefHandle; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that created server objects now also exist on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerSubObjectRefHandle)); // Destroy the spawned subobject on server Server->DestroyObject(ServerSubObject); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that only the subobject is destroyed on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerSubObjectRefHandle)); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, ReplicateAndDestroyMultipleSubObjects) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Spawn subobject on server UReplicatedTestObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); FNetRefHandle ServerSubObjectRefHandle = ServerSubObject->NetRefHandle; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that created server objects now also exist on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerSubObjectRefHandle)); // Destroy the spawned subobject on server Server->DestroyObject(ServerSubObject); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that the subobject is destroyed on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerSubObjectRefHandle)); // Spawn second object on server as a subobject ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); ServerSubObjectRefHandle = ServerSubObject->NetRefHandle; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that second subobject replicated properly to server UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerSubObjectRefHandle)); // Destroy the spawned object on server Server->DestroyObject(ServerSubObject); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that the second subobjects object is destroyed on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerSubObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, ReplicateAndDestroySubObjectAndDestroyOwner) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Spawn subobject on server UReplicatedTestObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); const FNetRefHandle ServerSubObjectRefHandle = ServerSubObject->NetRefHandle; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that created server handles now also exist on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerSubObjectRefHandle)); // Destroy the spawned subobject on server Server->DestroyObject(ServerSubObject); // Destroy owner after spawned subobject on server Server->DestroyObject(ServerObject); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that the subobject is destroyed on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerSubObjectRefHandle)); // Verify that the root object is destroyed on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, ReplicateAndDestroySubObjectAndDestroyOwnerWithDataInFlight) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Spawn subobject on server UReplicatedTestObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); const FNetRefHandle ServerSubObjectRefHandle = ServerSubObject->NetRefHandle; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that created server handles now also exist on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerSubObjectRefHandle)); // Destroy the spawned subobject on server Server->DestroyObject(ServerSubObject); // Send and drop packet Server->UpdateAndSend({Client}, DoNotDeliverPacket); // Destroy owner after we spawned subobject on server Server->DestroyObject(ServerObject); // Send and deliver packet Server->UpdateAndSend({Client}, DeliverPacket); // Verify that the subobject is destroyed on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerSubObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, ReplicateAndDestroySubObjectWithLostData) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Spawn subobject on server UReplicatedTestObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); const FNetRefHandle ServerSubObjectRefHandle = ServerSubObject->NetRefHandle; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that created server objects exist on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerSubObjectRefHandle)); // Destroy the spawned subobject on server Server->DestroyObject(ServerSubObject); // Send and drop packet Server->UpdateAndSend({Client}, DoNotDeliverPacket); // Send and deliver packet Server->UpdateAndSend({Client}, DeliverPacket); // Verify that subobject is destroyed on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerSubObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, ReplicateAndDestroySubObjectPendingCreateConfirmation) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Spawn subobject on server UReplicatedTestObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); const FNetRefHandle ServerSubObjectRefHandle = ServerSubObject->NetRefHandle; uint32 NumUnAcknowledgedPackets = 0; // Write a packet Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Destroy the spawned subobject on server Server->DestroyObject(ServerSubObject); // We have no data to send but we want to tick ReplicationSystem to capture state change Server->NetUpdate(); UE_NET_ASSERT_FALSE(Server->SendTo(Client)); Server->PostSendUpdate(); // Drop creation packet Server->DeliverTo(Client, DoNotDeliverPacket); // As the second update did not send any data we do not have anything to deliver //Server->DeliverTo(Client, true); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that the subobject does not exist on the client UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerSubObjectRefHandle)); // The root object should exist on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); } // In this test we're going to create a subobject after the root has been created on the client. Then create it with a bit of latency and destroy root prior to subobject being created on the client. UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, LateCreatedSubObjectIsDestroyedWithRoot) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(UTestReplicatedIrisObject::FComponents{}); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; // Send and deliver packet Server->UpdateAndSend({Client}); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); // Spawn subobject UReplicatedTestObject* ServerSubObject = Server->CreateSubObject(ServerObjectRefHandle, UTestReplicatedIrisObject::FComponents{}); const FNetRefHandle ServerSubObjectRefHandle = ServerSubObject->NetRefHandle; // Put subobject in WaitOnCreateConfirmation state Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); // Destroy the root object Server->DestroyObject(ServerObject); // Write a packet Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); // Deliver first packet Server->DeliverTo(Client, DeliverPacket); // Verify the root and subobject are created/still created on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerSubObjectRefHandle)); // Deliver second packet Server->DeliverTo(Client, DeliverPacket); // Verify the root and subobject are created/still created on the client UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerSubObjectRefHandle)); // Update and send Server->UpdateAndSend({Client}); // Verify the root and subobject are fully destroyed UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObjectRefHandle)); UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerSubObjectRefHandle)); } // In this test we're going to try destroying an object with thousands of subobjects atomically. UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, CanDestroyObjectHierarchyAtomically) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(UTestReplicatedIrisObject::FComponents{}); const FNetRefHandle ServerObjectRefHandle = ServerObject->NetRefHandle; constexpr unsigned SubObjectCount = 2001; TArray ServerSubObjectRefHandles; ServerSubObjectRefHandles.Reserve(SubObjectCount); // Spawn thousands of subobjects over several frames to avoid huge object path. for (;ServerSubObjectRefHandles.Num() < SubObjectCount;) { constexpr uint32 MaxSubObjectCreationCountPerFrame = 15; for (unsigned It = 0; It < MaxSubObjectCreationCountPerFrame && ServerSubObjectRefHandles.Num() < SubObjectCount; ++It) { UReplicatedTestObject* ServerSubObject = Server->CreateSubObject(ServerObjectRefHandle); ServerSubObjectRefHandles.Add(ServerSubObject->NetRefHandle); } Server->UpdateAndSend({Client}); } // We expect to be done creating the object hierarchy by now. Make sure of it. UE_NET_ASSERT_FALSE(Server->UpdateAndSend({ Client })); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerObjectRefHandle)); for (const FNetRefHandle SubObjectRefHandle : ServerSubObjectRefHandles) { UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(SubObjectRefHandle)); } // Destroy the root object Server->DestroyObject(ServerObject); for (unsigned It = 0, MaxTryCount = SubObjectCount/50; It < MaxTryCount; ++It) { const bool bDidSendSomething = Server->UpdateAndSend({ Client }); // Verify the object hierarchy is destroyed as a whole or not at all. Once we've stopped sending data we should have destroyed the object as a whole on the client. const bool bRootIsResolvable = Client->IsResolvableNetRefHandle(ServerObjectRefHandle); if (bRootIsResolvable && bDidSendSomething) { for (const FNetRefHandle SubObjectRefHandle : ServerSubObjectRefHandles) { UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(SubObjectRefHandle)); } } else { UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(ServerObjectRefHandle)); for (const FNetRefHandle SubObjectRefHandle : ReverseIterate(ServerSubObjectRefHandles)) { UE_NET_ASSERT_FALSE(Client->IsResolvableNetRefHandle(SubObjectRefHandle)); } } if (!bDidSendSomething) { break; } } } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestSubObjectDefaultReplicationOrder) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); // Spawn some subobjects UReplicatedSubObjectOrderObject* ServerSubObject0 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectOrderObject* ServerSubObject1 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectOrderObject* ServerSubObject2 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectOrderObject::RepOrderCounter = 0U; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that objects have replicated UReplicatedSubObjectOrderObject* ClientSubObject0 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject0->NetRefHandle)); UReplicatedSubObjectOrderObject* ClientSubObject1 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject1->NetRefHandle)); UReplicatedSubObjectOrderObject* ClientSubObject2 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject2->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject0, nullptr); UE_NET_ASSERT_NE(ClientSubObject1, nullptr); UE_NET_ASSERT_NE(ClientSubObject2, nullptr); // Verify that they have replicated in expected order UE_NET_ASSERT_EQ(ClientSubObject0->LastRepOrderCounter, 1U); UE_NET_ASSERT_GT(ClientSubObject1->LastRepOrderCounter, ClientSubObject0->LastRepOrderCounter); UE_NET_ASSERT_GT(ClientSubObject2->LastRepOrderCounter, ClientSubObject1->LastRepOrderCounter); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestSubObjectSpecifiedReplicationOrder) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); // Spawn some subobjects UReplicatedSubObjectOrderObject* ServerSubObject0 = Server->CreateSubObject(ServerObject->NetRefHandle); // Specify Subobject1 to replicate with SubObject0, which means that it will replicate before Subobjet0 is replicated UReplicatedSubObjectOrderObject* ServerSubObject1 = Server->CreateSubObject(ServerObject->NetRefHandle, ServerSubObject0->NetRefHandle, UReplicationBridge::ESubObjectInsertionOrder::ReplicateWith); // Specify SubObect 2 to replicate with no specific order (it will be added to the owner and thus replicate last) UReplicatedSubObjectOrderObject* ServerSubObject2 = Server->CreateSubObject(ServerObject->NetRefHandle, ServerSubObject1->NetRefHandle, UReplicationBridge::ESubObjectInsertionOrder::None); UReplicatedSubObjectOrderObject::RepOrderCounter = 0U; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that objects have replicated UReplicatedSubObjectOrderObject* ClientSubObject0 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject0->NetRefHandle)); UReplicatedSubObjectOrderObject* ClientSubObject1 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject1->NetRefHandle)); UReplicatedSubObjectOrderObject* ClientSubObject2 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject2->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject0, nullptr); UE_NET_ASSERT_NE(ClientSubObject1, nullptr); UE_NET_ASSERT_NE(ClientSubObject2, nullptr); // Verify that they have replicated in expected order setup earlier UE_NET_ASSERT_EQ(ClientSubObject1->LastRepOrderCounter, 1U); UE_NET_ASSERT_EQ(ClientSubObject0->LastRepOrderCounter, 2U); UE_NET_ASSERT_EQ(ClientSubObject2->LastRepOrderCounter, 3U); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestSubObjectInsertAtStart) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedObjectTestSubObjectCreationOrder* ServerObject = Server->CreateObject(); // Spawn a subobject UReplicatedSubObjectOrderObject* ServerSubObject0 = Server->CreateSubObject(ServerObject->NetRefHandle); // Spawn a subobject and make it replicate before SubObject0 UReplicatedSubObjectOrderObject* ServerSubObject1 = Server->CreateSubObject(ServerObject->NetRefHandle, ServerSubObject0->NetRefHandle, UReplicationBridge::ESubObjectInsertionOrder::ReplicateWith); // Spawn a subobject and make it replicate first UReplicatedSubObjectOrderObject* ServerSubObject2 = Server->CreateSubObject(ServerObject->NetRefHandle, FNetRefHandle::GetInvalid(), UReplicationBridge::ESubObjectInsertionOrder::InsertAtStart); UReplicatedSubObjectOrderObject::RepOrderCounter = 0U; // Send and deliver packet Server->UpdateAndSend({ Client }); // Verify that objects have replicated UReplicatedSubObjectOrderObject* ClientSubObject0 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject0->NetRefHandle)); UReplicatedSubObjectOrderObject* ClientSubObject1 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject1->NetRefHandle)); UReplicatedSubObjectOrderObject* ClientSubObject2 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject2->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject0, nullptr); UE_NET_ASSERT_NE(ClientSubObject1, nullptr); UE_NET_ASSERT_NE(ClientSubObject2, nullptr); // Verify that they were created in the expected order UE_NET_ASSERT_EQ(ClientSubObject2->CreationOrder, 1); UE_NET_ASSERT_EQ(ClientSubObject1->CreationOrder, 2); UE_NET_ASSERT_EQ(ClientSubObject0->CreationOrder, 3); // Verify that they have replicated in the expected order UE_NET_ASSERT_EQ(ClientSubObject2->LastRepOrderCounter, 1U); UE_NET_ASSERT_EQ(ClientSubObject1->LastRepOrderCounter, 2U); UE_NET_ASSERT_EQ(ClientSubObject0->LastRepOrderCounter, 3U); } class FTestNetTokensFixture : public FReplicationSystemServerClientTestFixture { public: FStringTokenStore* ServerStringTokenStore = nullptr; FStringTokenStore* ClientStringTokenStore = nullptr; FReplicationSystemTestClient* Client = nullptr; const FNetTokenStoreState* ClientRemoteNetTokenState; const FNetTokenStoreState* ServerRemoteNetTokenState; FNetToken CreateAndExportNetToken(const FString& TokenString) { FNetToken Token = ServerStringTokenStore->GetOrCreateToken(TokenString); UNetTokenDataStream* NetTokenDataStream = Cast(Server->GetReplicationSystem()->GetDataStream(Client->ConnectionIdOnServer, FName("NetToken"))); if (NetTokenDataStream) { NetTokenDataStream->AddNetTokenForExplicitExport(Token); } return Token; } FNetToken CreateAndExportNetTokenOnClient(const FString& TokenString) { FNetToken Token = ClientStringTokenStore->GetOrCreateToken(TokenString); UNetTokenDataStream* NetTokenDataStream = Cast(Client->GetReplicationSystem()->GetDataStream(Client->LocalConnectionId, FName("NetToken"))); if (NetTokenDataStream) { NetTokenDataStream->AddNetTokenForExplicitExport(Token); } return Token; } virtual void SetUp() override { FReplicationSystemServerClientTestFixture::SetUp(); Client = CreateClient(); { FNetTokenStore* ServerTokenStore = Server->GetReplicationSystem()->GetNetTokenStore(); ServerStringTokenStore = ServerTokenStore->GetDataStore(); ServerRemoteNetTokenState = ServerTokenStore->GetRemoteNetTokenStoreState(Client->ConnectionIdOnServer); } { FNetTokenStore* ClientTokenStore = Client->GetReplicationSystem()->GetNetTokenStore(); ClientStringTokenStore = ClientTokenStore->GetDataStore(); ClientRemoteNetTokenState = ClientTokenStore->GetRemoteNetTokenStoreState(Client->LocalConnectionId); } } }; UE_NET_TEST_FIXTURE(FTestNetTokensFixture, NetToken) { // Create token FString TokenStringA(TEXT("MyStringToken")); FNetToken StringTokenA = CreateAndExportNetToken(TokenStringA); // Send and drop packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); { LOG_SCOPE_VERBOSITY_OVERRIDE(LogNetToken, ELogVerbosity::Fatal); // Verify that we cannot resolve the token on the client UE_NET_ASSERT_NE(TokenStringA, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenA, *ClientRemoteNetTokenState))); } // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that we can resolve the token on the client UE_NET_ASSERT_EQ(TokenStringA, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenA, *ClientRemoteNetTokenState))); } UE_NET_TEST_FIXTURE(FTestNetTokensFixture, NetTokenResendWithFullPacket) { // Create token FString TokenStringA(TEXT("MyStringToken")); FNetToken StringTokenA = CreateAndExportNetToken(TokenStringA); // Send and drop packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); // Limit packet size Server->SetMaxSendPacketSize(128U); // Create a new token that will not fit in the packet and only fit the resend data FString TokenStringB(TEXT("MyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongString")); FNetToken StringTokenB = CreateAndExportNetToken(TokenStringB); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that we can resolve the token first token on the client even though second one should not fit UE_NET_ASSERT_EQ(TokenStringA, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenA, *ClientRemoteNetTokenState))); { LOG_SCOPE_VERBOSITY_OVERRIDE(LogNetToken, ELogVerbosity::Fatal); UE_NET_ASSERT_NE(TokenStringB, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenB, *ClientRemoteNetTokenState))); } // Restore packet size and make sure that we get the second token through Server->SetMaxSendPacketSize(1024U); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); UE_NET_ASSERT_EQ(TokenStringB, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenB, *ClientRemoteNetTokenState))); } UE_NET_TEST_FIXTURE(FTestNetTokensFixture, NetTokenResendWithFullPacketAfterFirstResend) { // Create token FString TestStringA(TEXT("MyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongString")); FNetToken StringTokenA = CreateAndExportNetToken(TestStringA); // Send and delay delivery Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Create a new token that will not fit in the packet and only fit the resend data FString TestStringB(TEXT("MyOtherLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongStringMyLongString")); FNetToken StringTokenB = CreateAndExportNetToken(TestStringB); // Send and delay delivery Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); Server->DeliverTo(Client, false); Server->DeliverTo(Client, false); // Verify that tokens has not been received { LOG_SCOPE_VERBOSITY_OVERRIDE(LogNetToken, ELogVerbosity::Fatal); UE_NET_ASSERT_NE(TestStringA, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenA, *ClientRemoteNetTokenState))); UE_NET_ASSERT_NE(TestStringB, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenB, *ClientRemoteNetTokenState))); } // Send and deliver packet which now should contain two entries in the resend queue Server->SetMaxSendPacketSize(1024); Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that we can resolve the token UE_NET_ASSERT_EQ(TestStringA, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenA, *ClientRemoteNetTokenState))); UE_NET_ASSERT_EQ(TestStringB, FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenB, *ClientRemoteNetTokenState))); } UE_NET_TEST_FIXTURE(FTestNetTokensFixture, NetTokenSequenceTest) { const FString TestStrings[] = { FString(TEXT("TokenA")), FString(TEXT("TokenB")), FString(TEXT("TokenC")), FString(TEXT("TokenD")), FString(TEXT("TokenE")), FString(TEXT("TokenF")), }; const uint32 TokenCount = UE_ARRAY_COUNT(TestStrings); // Create token FNetToken StringTokenA = CreateAndExportNetToken(TestStrings[0]); FNetToken StringTokenB = CreateAndExportNetToken(TestStrings[1]); // Send packet Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Create token FNetToken StringTokenC = CreateAndExportNetToken(TestStrings[2]); // Create token FNetToken StringTokenD = CreateAndExportNetToken(TestStrings[3]); // Send packet Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Drop packet Server->DeliverTo(Client, false); // Deliver packet Server->DeliverTo(Client, true); // Create local tokens ClientStringTokenStore->GetOrCreateToken(TEXT("LocalTokenA")); ClientStringTokenStore->GetOrCreateToken(TEXT("LocalTokenB")); // Send packet with resend data Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); UE_NET_ASSERT_EQ(TestStrings[0], FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenA, *ClientRemoteNetTokenState))); UE_NET_ASSERT_EQ(TestStrings[1], FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenB, *ClientRemoteNetTokenState))); UE_NET_ASSERT_EQ(TestStrings[2], FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenC, *ClientRemoteNetTokenState))); UE_NET_ASSERT_EQ(TestStrings[3], FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenD, *ClientRemoteNetTokenState))); } UE_NET_TEST_FIXTURE(FTestNetTokensFixture, NetTokenResendAndDataInSamePacketTest) { const FString TestStrings[] = { FString(TEXT("TokenA")), FString(TEXT("TokenB")), }; const uint32 TokenCount = UE_ARRAY_COUNT(TestStrings); // Create token FNetToken StringTokenA = CreateAndExportNetToken(TestStrings[0]); // Send packet Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // drop data Server->DeliverTo(Client, false); // Create token FNetToken StringTokenB = CreateAndExportNetToken(TestStrings[1]); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); UE_NET_ASSERT_EQ(TestStrings[0], FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenA, *ClientRemoteNetTokenState))); UE_NET_ASSERT_EQ(TestStrings[1], FString(ClientStringTokenStore->ResolveRemoteToken(StringTokenB, *ClientRemoteNetTokenState))); } UE_NET_TEST_FIXTURE(FTestNetTokensFixture, NetTokenAuthority) { // Create token FString TokenStringA(TEXT("MyStringToken")); FNetToken NonAuthToken = CreateAndExportNetTokenOnClient(TokenStringA); UE_NET_ASSERT_EQ(NonAuthToken.IsAssignedByAuthority(), false); // Send from server Server->UpdateAndSend({Client}); // Send from client Client->UpdateAndSend(Server); // We should be able to resolve the token on the server using remote UE_NET_ASSERT_EQ(TokenStringA, FString(ServerStringTokenStore->ResolveToken(NonAuthToken, ServerRemoteNetTokenState))); // Find server token. FNetToken AuthToken = CreateAndExportNetToken(TokenStringA); // It should be a different token as the server is authoriative UE_NET_ASSERT_FALSE(AuthToken == NonAuthToken); // Send from server Server->UpdateAndSend({Client}); // Client should be able to resolve ServerToken UE_NET_ASSERT_EQ(TokenStringA, FString(ClientStringTokenStore->ResolveToken(AuthToken, ClientRemoteNetTokenState))); // If we now try to create a token for the string also received from the authority we expect it to give us the server token and allow us to use that instead of the local exported token. FNetToken NewClientToken = ClientStringTokenStore->GetOrCreateToken(TokenStringA); // We expect the tokens to be identical. UE_NET_ASSERT_TRUE(AuthToken == NewClientToken); } UE_NET_TEST_FIXTURE(FTestNetTokensFixture, NetTokenAuthTokenIsNotExportedFromClient) { // Create token FString TokenStringA(TEXT("MyStringToken")); FNetToken AuthToken = CreateAndExportNetToken(TokenStringA); UE_NET_ASSERT_EQ(AuthToken.IsAssignedByAuthority(), true); // Send from server Server->UpdateAndSend({Client}); // Expect to get auth token FNetToken ClientExpectedAuthToken = CreateAndExportNetTokenOnClient(TokenStringA); UE_NET_ASSERT_EQ(ClientExpectedAuthToken.IsAssignedByAuthority(), true); // Send from client Client->UpdateAndSend(Server); // $TODO: Expose some stats that we can query for exports. } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, AddRemoveFromConnectionScopeTest) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Add to group FNetObjectGroupHandle Group = ReplicationSystem->CreateGroup(NAME_None); ReplicationSystem->AddToGroup(Group, ServerObject->NetRefHandle); ReplicationSystem->AddExclusionFilterGroup(Group); ReplicationSystem->SetGroupFilterStatus(Group, ENetFilterStatus::Allow); // Start replicating object // Send packet // Expected state to be WaitOnCreateConfirmation Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Make sure we have data in flight ++ServerObject->IntA; // Disallow group to trigger state change from PendingCreateConfirmation->PendingDestroy ReplicationSystem->SetGroupFilterStatus(Group, ENetFilterStatus::Disallow); // Expect client to create object Server->DeliverTo(Client, true); UE_NET_ASSERT_NE(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle), nullptr); // Send packet Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Allow group to trigger state to ensure that we restart replication ReplicationSystem->SetGroupFilterStatus(Group, ENetFilterStatus::Allow); // Expect client to destroy object Server->DeliverTo(Client, true); UE_NET_ASSERT_EQ(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle), nullptr); // Trigger replication ++ServerObject->IntA; // Send packet // WaitOnDestroyConfirmation -> WaitOnCreateConfirmation Server->UpdateAndSend({ Client }); // Verify that the object got created again UE_NET_ASSERT_NE(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle), nullptr); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestNetTemporary) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); UTestReplicatedIrisObject* ServerObject1 = Server->CreateObject(0,0); // Trigger replication ServerObject->IntA = 1; ServerObject1->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that client has received the data { UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObject != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObject->IntA); } { UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject1->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObject != nullptr); UE_NET_ASSERT_EQ(ServerObject1->IntA, ClientObject->IntA); } // Mark the object as a net temporary ReplicationSystem->SetIsNetTemporary(ServerObject->NetRefHandle); // Modify the value ServerObject->IntA = 2; ServerObject1->IntA = 2; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that client has not received the data for changed temporary { UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObject != nullptr); UE_NET_ASSERT_NE(ServerObject->IntA, ClientObject->IntA); } // Verify that client has received the data for normal object { UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject1->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObject != nullptr); UE_NET_ASSERT_EQ(ServerObject1->IntA, ClientObject->IntA); } // Test Late join // Add a client FReplicationSystemTestClient* Client2 = CreateClient(); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client2, true); Server->PostSendUpdate(); // We should now have the latest state for both objects { UTestReplicatedIrisObject* ClientObject = Cast(Client2->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObject != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObject->IntA); } // Verify that client has received the data for normal object { UTestReplicatedIrisObject* ClientObject = Cast(Client2->GetReplicationBridge()->GetReplicatedObject(ServerObject1->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObject != nullptr); UE_NET_ASSERT_EQ(ServerObject1->IntA, ClientObject->IntA); } } // Tests for TearOff // Test TearOff for existing confirmed object UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffExistingObject) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Trigger replication ServerObject->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to object UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); // Modify the value ServerObject->IntA = 2; // TearOff the object Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject is torn-off and that the final state was applied UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); } // Test TearOff for new object UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffOnNewlyCreatedObject) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // We should not have any created objects const int32 NumObjectsCreatedOnClientBeforeReplication = Client->CreatedObjects.Num(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Set state ServerObject->IntA = 1; // TearOff the object before first replication Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Client should have created a object UE_NET_ASSERT_EQ(NumObjectsCreatedOnClientBeforeReplication + 1, Client->CreatedObjects.Num()); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); // We should be able to get the object from the created objects array to validate the state UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->CreatedObjects[NumObjectsCreatedOnClientBeforeReplication].Get()); // Verify that we replicated the expected state UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); } // Test TearOff resend for existing confirmed object with no state changes UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffResendForExistingObjectWithoutDirtyState) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Trigger replication ServerObject->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Store Pointer to object UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObjectThatWillBeTornOff, nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); // TearOff the object Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Send and do not deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DoNotDeliverPacket); Server->PostSendUpdate(); // The ClientObject should still be found using the NetRefHandle UE_NET_ASSERT_NE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)), nullptr); Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Verify that ClientObject is torn-off UE_NET_ASSERT_EQ(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)), nullptr); } // Test TearOff for new object and resend, this requires creation info to be cached. UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffImmediateOnNewlyCreatedObjectResend) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // We should not have any created objects const int32 NumObjectsCreatedOnClientBeforeReplication = Client->CreatedObjects.Num(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Set state ServerObject->IntA = 1; // TearOff the object before first replication Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Send and deliver packet Server->UpdateAndSend({Client}, false); // Send and deliver packet Server->UpdateAndSend({Client}); // Client should have created a object UE_NET_ASSERT_EQ(NumObjectsCreatedOnClientBeforeReplication + 1, Client->CreatedObjects.Num()); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); // We should be able to get the object from the created objects array to validate the state UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->CreatedObjects[NumObjectsCreatedOnClientBeforeReplication].Get()); // Verify that we replicated the expected state UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); } // Test TearOff for new subobject and resend, this requires creation info to be cached. UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffImmediateOnNewlyCreatedSubObjectResend) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // We should not have any created objects const int32 NumObjectsCreatedOnClientBeforeReplication = Client->CreatedObjects.Num(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Set state ServerObject->IntA = 1; // Send and deliver packet Server->UpdateAndSend({Client}); // Spawn second object on server as a subobject UTestReplicatedIrisObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); // Set state ServerSubObject->IntA = 1; // TearOff the subobject before first replication Server->ReplicationBridge->EndReplication(ServerSubObject, EEndReplicationFlags::TearOff); // Send and drop Server->UpdateAndSend({Client}, false); // Send and deliver packet Server->UpdateAndSend({Client}); // Client should have created a object + subobject UE_NET_ASSERT_EQ(NumObjectsCreatedOnClientBeforeReplication + 2, Client->CreatedObjects.Num()); // But as we have torn off the subobject it should no longer be a replicated object UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) != nullptr); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) == nullptr); // We should be able to get the object from the created objects array to validate the state UTestReplicatedIrisObject* ClientSubObjectThatWillBeTornOff = Cast(Client->CreatedObjects[NumObjectsCreatedOnClientBeforeReplication + 1].Get()); // Verify that we replicated the expected state UE_NET_ASSERT_EQ(ServerSubObject->IntA, ClientSubObjectThatWillBeTornOff->IntA); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestDefferedTearOffOnNewlyCreatedObjectResend) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // We should not have any created objects const int32 NumObjectsCreatedOnClientBeforeReplication = Client->CreatedObjects.Num(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Set state ServerObject->IntA = 1; // TearOff the object before first replication Server->ReplicationSystem->TearOffNextUpdate(ServerObject->NetRefHandle); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // End replication and destroy object Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::Destroy); // Client should have created a object UE_NET_ASSERT_EQ(NumObjectsCreatedOnClientBeforeReplication + 1, Client->CreatedObjects.Num()); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); // We should be able to get the object from the created objects array to validate the state UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->CreatedObjects[NumObjectsCreatedOnClientBeforeReplication].Get()); // Verify that we replicated the expected state UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); } // Test TearOff for existing not yet confirmed object UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffObjectPendingCreateConfirmation) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Trigger replication ServerObject->IntA = 1; // Send packet to get put the object in flight Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // TearOff the object Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Deliver Object (should now be created) Server->DeliverTo(Client, true); // Store Pointer to object and verify initial state UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject is TornOFf and that the final state was applied UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); } // Test TearOff for existing object pending destroy (should do nothing) UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffExistingObjectPendingDestroy) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Trigger replication ServerObject->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to object UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); // Modify the value ServerObject->IntA = 2; // Mark the object for destroy Server->ReplicationBridge->EndReplication(ServerObject); // TearOff the object Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject is not tornOff and that the final state was not applied as we issued tearoff after ending replication UE_NET_ASSERT_NE(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); } // Test TearOff resend UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffResend) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Trigger replication ServerObject->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to object UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); // Modify the value ServerObject->IntA = 2; // TearOff the object Server->ReplicationSystem->TearOffNextUpdate(ServerObject->NetRefHandle); // Send and deliver packet, in this case the packet containing 2 was lost, but, we did not know that when we Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); // Object should now be torn-off, so it should not copy the latest state ServerObject->IntA = 3; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject is torn-off and that the expected final state was applied UE_NET_ASSERT_EQ(2, ClientObjectThatWillBeTornOff->IntA); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); } // Test TearOff does not pickup statechanges after tear off UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTornedOffObjectDoesNotCopyStateChanges) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Trigger replication ServerObject->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to object UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); // Modify the value ServerObject->IntA = 2; // TearOff the object Server->ReplicationSystem->TearOffNextUpdate(ServerObject->NetRefHandle); // Send and drop packet containing the value 2 Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); // Object should now be torn-off, so it should not copy the latest state but instead resend the last copied state (2) along with the tear-off ServerObject->IntA = 3; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject is TornOFf and that the expected final state was applied UE_NET_ASSERT_EQ(2, ClientObjectThatWillBeTornOff->IntA); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); } // Test TearOff and SubObjects, SubObjects must apply state? UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestImmediateTearOffExistingObjectWithSubObject) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Spawn second object on server as a subobject UTestReplicatedIrisObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); // Trigger replication ServerObject->IntA = 1; ServerSubObject->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to objects UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); UTestReplicatedIrisObject* ClientSubObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientSubObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerSubObject->IntA, ClientSubObjectThatWillBeTornOff->IntA); // Modify the value of subobject only ServerSubObject->IntA = 2; // TearOff the object using immediate tear-off Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject is torn-off and that the final state was applied to subObject UE_NET_ASSERT_EQ(ServerSubObject->IntA, ClientSubObjectThatWillBeTornOff->IntA); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) == nullptr); } // Test TearOff and SubObjects, SubObjects must apply state? UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestImmediateTearOffExistingObjectWithSubObjectDroppedData) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Spawn second object on server as a subobject UTestReplicatedIrisObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); // Trigger replication ServerObject->IntA = 1; ServerSubObject->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to objects UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); UTestReplicatedIrisObject* ClientSubObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientSubObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerSubObject->IntA, ClientSubObjectThatWillBeTornOff->IntA); // Modify the value of subobject only ServerSubObject->IntA = 2; // TearOff the object using immediate tear-off Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Send and do not deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject is torn-off and that the final state was applied to subObject UE_NET_ASSERT_EQ(ServerSubObject->IntA, ClientSubObjectThatWillBeTornOff->IntA); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) == nullptr); } // Test creating a subobject after the root is already torn off UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestCreatingSubobjectAfterRootObjectTearOff) { //LOG_SCOPE_VERBOSITY_OVERRIDE(LogIrisBridge, ELogVerbosity::Verbose); // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object with subobject on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(); UTestReplicatedIrisObject* ServerSubObject0 = Server->CreateSubObject(ServerObject->NetRefHandle); // Send and deliver packet Server->UpdateAndSend({Client}); // TearOff the object Server->ReplicationSystem->TearOffNextUpdate(ServerObject->NetRefHandle); // Send and deliver packet Server->NetUpdate(); Server->PostSendUpdate(); { FTestEnsureScope EnsureScope; // Add a new subobject. This shouldn't become a replicated subobject of the already torn off root object. UTestReplicatedIrisObject* ServerSubObject1 = Server->CreateSubObject(ServerObject->NetRefHandle); UE_NET_ASSERT_EQ(EnsureScope.GetCount(), 0); // The subobject should not start replicating. UE_NET_ASSERT_FALSE(ServerSubObject1->NetRefHandle.IsValid()); // Send and deliver packet Server->UpdateAndSend({ Client }); UE_NET_ASSERT_EQ(EnsureScope.GetCount(), 0); } } // Test dropped creation of subobject dirties owner UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestDroppedCreationForSubobjectDirtiesOwner) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to objects UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObject != nullptr); // Spawn second object on server as a subobject UTestReplicatedIrisObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); // Send and do not deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject now is created as expected UTestReplicatedIrisObject* ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientSubObject != nullptr); } // Test replicated destroy for not created object UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestReplicatedDestroyForNotCreatedObject) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Update and delay delivery Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Destroy object Server->ReplicationBridge->EndReplication(ServerObject); // Update and delay delivery Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Drop first packet containing creation info for object Server->DeliverTo(Client, false); // Deliver second packet that should contain destroy Server->DeliverTo(Client, true); // Verify that the object does not exist on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) == nullptr); } // Test replicated SubObjectDestroy for not created subobject UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestReplicatedSubObjectDestroyForNotCreatedObject) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Replicate object Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Spawn second object on server as a subobject UTestReplicatedIrisObjectWithNoReplicatedMembers* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle); // Update and delay delivery Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Destroy subobject Server->ReplicationBridge->EndReplication(ServerSubObject); // Update and delay delivery Server->NetUpdate(); Server->SendTo(Client); Server->PostSendUpdate(); // Drop first packet containing creation info for subobject Server->DeliverTo(Client, false); // Deliver second packet that should contain replicated subobject destroy Server->DeliverTo(Client, true); // Verify that the object still exists on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) != nullptr); // Verify that the subobject does not exist on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) == nullptr); } // Test replicated SubObjectDestroy for filtered out subobject UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestReplicatedSubObjectDestroyForFilteredOutSubObject) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Spawn second object on server as a subobject UTestReplicatedIrisObjectWithNoReplicatedMembers* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle); // Replicate object Server->UpdateAndSend({Client}); // Verify that the subobject does exist on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) != nullptr); // Set condition Server->ReplicationBridge->SetSubObjectNetCondition(ServerSubObject->NetRefHandle, ELifetimeCondition::COND_Never); // Destroy subobject Server->ReplicationBridge->EndReplication(ServerSubObject); // Replicate object Server->UpdateAndSend({Client}); // Verify that the object still exists on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) != nullptr); // Verify that the subobject does not exist on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) == nullptr); } // Test replicated SubObjectDestroy for filtered out subobject UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestReplicatedSubObjectDestroyForFilteredOutSubObjectBeforeSend) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Spawn second object on server as a subobject UTestReplicatedIrisObjectWithNoReplicatedMembers* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle); // Set condition Server->ReplicationBridge->SetSubObjectNetCondition(ServerSubObject->NetRefHandle, ELifetimeCondition::COND_Never); // Replicate object Server->UpdateAndSend({Client}); // Verify that the subobject does not exist on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) == nullptr); // Destroy subobject Server->ReplicationBridge->EndReplication(ServerSubObject); // Replicate object Server->UpdateAndSend({Client}); // Verify that the object still exists on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)) != nullptr); // Verify that the subobject does not exist on client UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) == nullptr); } // Test tear-off object in PendingCreate state to ensure that tear-off logic works as expected UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffObjectWithNoFragmentsDoesNotTriggerCheckIfPendingCreateWhenDestroyed) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObjectWithNoReplicatedMembers* ServerObject = Server->CreateObject(); Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); // Tear-off using immediate tear-off Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); // Trigger the next update but avoid sending any data so that we keep the object in the PendingCreation state while we flush the Handles PendingTearOff Array which occurs in PostSendUpdate Server->NetUpdate(); Server->PostSendUpdate(); } // Test tear-off subobject in PendingCreate state to ensure that tear-off logic works as expected UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffSubObjectWithNoFragmentsDoesNotTriggerCheckIfPendingCreateWhenDestroyed) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Spawn second object on server as a subobject UTestReplicatedIrisObjectWithNoReplicatedMembers* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle); // Update and drop Server->NetUpdate(); Server->SendAndDeliverTo(Client, false); Server->PostSendUpdate(); // Tear-off using immediate tear-off Server->ReplicationBridge->EndReplication(ServerSubObject, EEndReplicationFlags::TearOff); // Trigger the next update but avoid sending any data so that we keep the sub-object in the PendingCreation state while we flush the Handles PendingTearOff Array which occurs in PostSendUpdate Server->NetUpdate(); Server->PostSendUpdate(); } // Test TearOff and SubObjects, SubObjects must apply state? UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffNextUpdateExistingObjectWithSubObject) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Spawn second object on server as a subobject UTestReplicatedIrisObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); // Trigger replication ServerObject->IntA = 1; ServerSubObject->IntA = 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to objects UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerObject->IntA, ClientObjectThatWillBeTornOff->IntA); UTestReplicatedIrisObject* ClientSubObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientSubObjectThatWillBeTornOff != nullptr); UE_NET_ASSERT_EQ(ServerSubObject->IntA, ClientSubObjectThatWillBeTornOff->IntA); // Modify the value of subobject only ServerSubObject->IntA = 2; // TearOff the object using immediate tear-off Server->ReplicationSystem->TearOffNextUpdate(ServerObject->NetRefHandle); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that ClientObject is torn-off and that the final state was applied to subObject UE_NET_ASSERT_EQ(ServerSubObject->IntA, ClientSubObjectThatWillBeTornOff->IntA); UE_NET_ASSERT_TRUE(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject->NetRefHandle)) == nullptr); } // Test TearOff and destroy of SubObjects that are still pending create/tearoff UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestTearOffNextUpdateExistingObjectWithSubObjectPendingCreation) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; FNetRefHandleManager* NetRefHandleManager = &ReplicationSystem->GetReplicationSystemInternal()->GetNetRefHandleManager(); // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0,0); // Spawn second object on server as a subobject UTestReplicatedIrisObject* ServerSubObject = Server->CreateSubObject(ServerObject->NetRefHandle, 0, 0); // Trigger replication ServerObject->IntA = 1; ServerSubObject->IntA = 1; const FInternalNetRefIndex ServerObjectInternalIndex = NetRefHandleManager->GetInternalIndex(ServerObject->NetRefHandle); const FInternalNetRefIndex SubObjectObjectInternalIndex = NetRefHandleManager->GetInternalIndex(ServerSubObject->NetRefHandle); // Trigger presend without send to add the objects to scope Server->NetUpdate(); Server->PostSendUpdate(); UE_NET_ASSERT_EQ(uint16(1), NetRefHandleManager->GetNetObjectRefCount(ServerObjectInternalIndex)); UE_NET_ASSERT_EQ(uint16(1), NetRefHandleManager->GetNetObjectRefCount(SubObjectObjectInternalIndex)); // TearOff the object this will also tear-off subobject Server->ReplicationSystem->TearOffNextUpdate(ServerObject->NetRefHandle); // Update logic, object should be removed from scope but still exist as pending create in Server->NetUpdate(); Server->PostSendUpdate(); UE_NET_ASSERT_EQ(uint16(1), NetRefHandleManager->GetNetObjectRefCount(ServerObjectInternalIndex)); UE_NET_ASSERT_EQ(uint16(1), NetRefHandleManager->GetNetObjectRefCount(SubObjectObjectInternalIndex)); Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Destroy the object Server->DestroyObject(ServerObject); // Verify that we no longer have any references to the object UE_NET_ASSERT_EQ(uint16(0), NetRefHandleManager->GetNetObjectRefCount(ServerObjectInternalIndex)); UE_NET_ASSERT_EQ(uint16(0), NetRefHandleManager->GetNetObjectRefCount(SubObjectObjectInternalIndex)); } // Test that we can replicate an object with no replicated properties UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestReplicatedObjectWithNoReplicatedProperties) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObjectWithNoReplicatedMembers* ServerObject = Server->CreateObject(); const FNetRefHandle ServerHandle = ServerObject->NetRefHandle; UE_NET_ASSERT_TRUE(ServerHandle.IsValid()); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); UTestReplicatedIrisObjectWithNoReplicatedMembers* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerHandle)); UE_NET_ASSERT_TRUE(ClientObject != nullptr); // Destroy object Server->DestroyObject(ServerObject); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerHandle)); UE_NET_ASSERT_TRUE(ClientObject == nullptr); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestObjectPollFramePeriod) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = Server->CreateObject(0, 0); // Spawn second object on server that later will be added as a dependent object UObjectReplicationBridge::FRootObjectReplicationParams Params; Params.PollFrequency = Server->ConvertPollPeriodIntoFrequency(1U); UTestReplicatedIrisObject* ServerObjectPolledEveryOtherFrame = Server->CreateObject(Params); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store Pointer to objects and verify state after initial replication UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UTestReplicatedIrisObject* ClientObjectPolledEveryOtherFrame = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObjectPolledEveryOtherFrame->NetRefHandle)); UE_NET_ASSERT_NE(ClientObjectPolledEveryOtherFrame, nullptr); UE_NET_ASSERT_NE(ClientObject, nullptr); UE_NET_ASSERT_EQ(ClientObject->IntA, ServerObject->IntA); UE_NET_ASSERT_EQ(ClientObjectPolledEveryOtherFrame->IntA, ServerObjectPolledEveryOtherFrame->IntA); // After two value updates it's expected that the polling occurs exactly one time for the object with poll frame period 1 (meaning every other frame). bool SlowPollObjectHasBeenEqual = false; bool SlowPollObjectHasBeenInequal = false; // Update values ServerObject->IntA += 1; ServerObjectPolledEveryOtherFrame->IntA += 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); UE_NET_ASSERT_EQ(ClientObject->IntA, ServerObject->IntA); SlowPollObjectHasBeenEqual |= (ClientObjectPolledEveryOtherFrame->IntA == ServerObjectPolledEveryOtherFrame->IntA); SlowPollObjectHasBeenInequal |= (ClientObjectPolledEveryOtherFrame->IntA != ServerObjectPolledEveryOtherFrame->IntA); // Update values ServerObject->IntA += 1; ServerObjectPolledEveryOtherFrame->IntA += 1; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify that both objects now are in sync UE_NET_ASSERT_EQ(ClientObject->IntA, ServerObject->IntA); SlowPollObjectHasBeenEqual |= (ClientObjectPolledEveryOtherFrame->IntA == ServerObjectPolledEveryOtherFrame->IntA); SlowPollObjectHasBeenInequal |= (ClientObjectPolledEveryOtherFrame->IntA != ServerObjectPolledEveryOtherFrame->IntA); UE_NET_ASSERT_TRUE(SlowPollObjectHasBeenEqual); UE_NET_ASSERT_TRUE(SlowPollObjectHasBeenInequal); } // Test that broken objects can be skipped by client UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestClientCanSkipBrokenObject) { UReplicationSystem* ReplicationSystem = Server->ReplicationSystem; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UTestReplicatedIrisObject* ServerObjectA = Server->CreateObject(0,0); UTestReplicatedIrisObject* ServerObjectB = Server->CreateObject(0,0); { // Setup client to fail to create next remote object ServerObjectA->bForceFailToInstantiateOnRemote = true; // Suppress ensure that will occur due to failing to instantiate the object UReplicatedTestObjectBridge::FSupressCreateInstanceFailedEnsureScope SuppressEnsureScope(*Client->GetReplicationBridge()); // Disable error logging as we know we will fail. auto IrisLogVerbosity = UE_GET_LOG_VERBOSITY(LogIris); LogIris.SetVerbosity(ELogVerbosity::NoLogging); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Restore LogVerbosity LogIris.SetVerbosity(IrisLogVerbosity); } // We expect replication of ObjectA to have failed { UTestReplicatedIrisObject* ClientObjectA = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObjectA->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectA == nullptr); } // ObjectB should have been replicated ok { UTestReplicatedIrisObject* ClientObjectB = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObjectB->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectB != nullptr); } // Modify both objects to make them replicate again ++ServerObjectA->IntA; ++ServerObjectB->IntA; // Send and deliver packet to verify that client ignores the broken object Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // We expect replication of ObjectA to have failed { UTestReplicatedIrisObject* ClientObjectA = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObjectA->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectA == nullptr); } // Filter out ObjectA to tell the client that the object has gone out of scope ReplicationSystem->AddToGroup(ReplicationSystem->GetNotReplicatedNetObjectGroup(), ServerObjectA->NetRefHandle); // Send and deliver packet, the client should now remove the broken object from the list of broken objects Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Enable replication of ObjectA again to try to replicate it to server now that it should succeed ReplicationSystem->RemoveFromGroup(ReplicationSystem->GetNotReplicatedNetObjectGroup(), ServerObjectA->NetRefHandle); // Set ObjectA to be able instantiate on client again ServerObjectA->bForceFailToInstantiateOnRemote = false; // Client should now be able to instantiate the object Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // We expect replication of ObjectA to have succeeded this time { UTestReplicatedIrisObject* ClientObjectA = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObjectA->NetRefHandle)); UE_NET_ASSERT_TRUE(ClientObjectA == nullptr); } } // Test that PropertyReplication properly handles partial states during Apply UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestPartialDequantize) { // Enable cvars to exercise path that store previous state for OnReps to make sure we exercise path that accumulate dirty changes so that we have a complete state. IConsoleVariable* CVarUsePrevReceivedStateForOnReps = IConsoleManager::Get().FindConsoleVariable(TEXT("net.Iris.UsePrevReceivedStateForOnReps")); check(CVarUsePrevReceivedStateForOnReps != nullptr && CVarUsePrevReceivedStateForOnReps->IsVariableBool()); const bool bUsePrevReceivedStateForOnReps = CVarUsePrevReceivedStateForOnReps->GetBool(); CVarUsePrevReceivedStateForOnReps->Set(true, ECVF_SetByCode); // Make sure we allow partial dequantize IConsoleVariable* CVarForceFullDequantizeAndApply = IConsoleManager::Get().FindConsoleVariable(TEXT("net.iris.ForceFullDequantizeAndApply")); check(CVarForceFullDequantizeAndApply != nullptr && CVarForceFullDequantizeAndApply->IsVariableBool()); const bool bForceFullDequantizeAndApply = CVarForceFullDequantizeAndApply->GetBool(); CVarForceFullDequantizeAndApply->Set(false, ECVF_SetByCode); ON_SCOPE_EXIT { // Restore cvars CVarUsePrevReceivedStateForOnReps->Set(bUsePrevReceivedStateForOnReps, ECVF_SetByCode); CVarForceFullDequantizeAndApply->Set(bForceFullDequantizeAndApply, ECVF_SetByCode); }; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UTestReplicatedObjectWithRepNotifies* ServerObjectA = Server->CreateObject(); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Verify assumptions // Object should exist on client and have default state UTestReplicatedObjectWithRepNotifies* ClientObjectA = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObjectA->NetRefHandle)); UE_NET_ASSERT_NE(ClientObjectA, nullptr); UE_NET_ASSERT_EQ(ServerObjectA->IntA, ClientObjectA->IntA); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntAStoredInOnRep, -1); UE_NET_ASSERT_EQ(ServerObjectA->IntB, ClientObjectA->IntB); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntBStoredInOnRep, -1); UE_NET_ASSERT_EQ(ServerObjectA->IntC, ClientObjectA->IntC); // Modify only IntA ServerObjectA->IntA = 1; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify assumptions // Only IntA should have been modified UE_NET_ASSERT_EQ(ServerObjectA->IntA, ClientObjectA->IntA); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntAStoredInOnRep, -1); UE_NET_ASSERT_EQ(ServerObjectA->IntB, ClientObjectA->IntB); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntBStoredInOnRep, -1); UE_NET_ASSERT_EQ(ServerObjectA->IntC, ClientObjectA->IntC); // Modify only IntB ServerObjectA->IntB = 1; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify assumptions // Only IntA should have been modified UE_NET_ASSERT_EQ(ServerObjectA->IntA, ClientObjectA->IntA); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntAStoredInOnRep, -1); UE_NET_ASSERT_EQ(ServerObjectA->IntB, ClientObjectA->IntB); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntBStoredInOnRep, -1); UE_NET_ASSERT_EQ(ServerObjectA->IntC, ClientObjectA->IntC); // Modify only IntA ServerObjectA->IntA = 2; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify assumptions // IntA should have been modified, and if everything works correctly PrevIntAStoredInOnRep should be 1 UE_NET_ASSERT_EQ(ServerObjectA->IntA, ClientObjectA->IntA); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntAStoredInOnRep, 1); UE_NET_ASSERT_EQ(ServerObjectA->IntB, ClientObjectA->IntB); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntBStoredInOnRep, -1); UE_NET_ASSERT_EQ(ServerObjectA->IntC, ClientObjectA->IntC); // Verify that we do not apply repnotifies if we do not receive data from server by modifying values on the client and verifying that they do not get overwritten ServerObjectA->IntB = 2; ClientObjectA->IntA = -1; ClientObjectA->PrevIntAStoredInOnRep = -1; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify assumptions, since we messed with IntA and PrevIntAStoredInOnRep locally they have the value we set but IntB should be updated according to replicated state UE_NET_ASSERT_NE(ServerObjectA->IntA, ClientObjectA->IntA); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntAStoredInOnRep, -1); UE_NET_ASSERT_EQ(ServerObjectA->IntB, ClientObjectA->IntB); UE_NET_ASSERT_EQ(ClientObjectA->PrevIntBStoredInOnRep, 1); UE_NET_ASSERT_EQ(ServerObjectA->IntC, ClientObjectA->IntC); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestNetMetric) { { FNetMetric Metric(50.0); UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::Double); } { FNetMetric Metric(50.f); UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::Double); } { FNetMetric Metric; UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::None); float Value = 100.f; Metric.Set(Value); UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::Double); } { FNetMetric Metric(5U); UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::Unsigned); } { FNetMetric Metric; UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::None); uint32 Value = 100U; Metric.Set(Value); UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::Unsigned); } { FNetMetric Metric(-5); UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::Signed); } { FNetMetric Metric; UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::None); Metric.Set(5); UE_NET_ASSERT_TRUE(Metric.GetDataType() == FNetMetric::EDataType::Signed); } } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestReplicationRecordStarvation) { IConsoleVariable* CVarReplicationRecordStarvationThreshold = IConsoleManager::Get().FindConsoleVariable(TEXT("net.Iris.ReplicationWriterReplicationRecordStarvationThreshold")); UE_NET_ASSERT_NE(CVarReplicationRecordStarvationThreshold, nullptr); UE_NET_ASSERT_TRUE(CVarReplicationRecordStarvationThreshold->IsVariableInt()); const int32 PrevReplicationRecordStarvationThreshold = CVarReplicationRecordStarvationThreshold->GetInt(); ON_SCOPE_EXIT { CVarReplicationRecordStarvationThreshold->Set(PrevReplicationRecordStarvationThreshold, ECVF_SetByCode); }; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Set starvation threshold to highest possible CVarReplicationRecordStarvationThreshold->Set(FReplicationRecord::MaxReplicationRecordCount, ECVF_SetByCode); // Consume one ReplicationRecord to enter starvation UReplicatedTestObject* FirstObject = Server->CreateObject(UTestReplicatedIrisObject::FComponents{}); const FNetRefHandle FirstObjectRefHandle = FirstObject->NetRefHandle; Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); // Try creating a second object. This should not succeed but we won't be able to test until we've delivered packets. UReplicatedTestObject* SecondObject = Server->CreateObject(UTestReplicatedIrisObject::FComponents{}); const FNetRefHandle SecondObjectRefHandle = SecondObject->NetRefHandle; Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); Server->DeliverTo(Client, DeliverPacket); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(FirstObjectRefHandle)); // The second packet, if any, should not allow object replication due to starvation. Server->DeliverTo(Client, DeliverPacket); UE_NET_ASSERT_FALSE(Client->IsResolvableNetRefHandle(SecondObjectRefHandle)); // Now we should be able replicate objects again. Retry sending the second object. Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); // Try destroying the first object. This should not succeed. Server->DestroyObject(FirstObject); // Deliver the attempt to create the second object and verify it exists on the client. Server->DeliverTo(Client, DeliverPacket); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(SecondObjectRefHandle)); // The second packet, if any, should not allow object destruction due to starvation. Server->DeliverTo(Client, DeliverPacket); UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(FirstObjectRefHandle)); // Now we should be able to destroy objects again. Retry destroying the first object Server->UpdateAndSend({Client}); UE_NET_ASSERT_FALSE(Client->IsValidNetRefHandle(FirstObjectRefHandle)); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestSubObjectReplicatedDestroyBeforePostNetReceive) { // Make sure that bOldImmediateDispatchEndReplicationForSubObjects is set to true IConsoleVariable* LocalCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("net.Iris.ImmediateDispatchEndReplicationForSubObjects")); check(LocalCVar != nullptr && LocalCVar->IsVariableBool()); const bool bOldImmediateDispatchEndReplicationForSubObjects = LocalCVar->GetBool(); LocalCVar->Set(true, ECVF_SetByCode); ON_SCOPE_EXIT { // Restore cvars LocalCVar->Set(bOldImmediateDispatchEndReplicationForSubObjects, ECVF_SetByCode); }; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); // Spawn some subobjects UReplicatedSubObjectDestroyOrderObject* ServerSubObject0 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectDestroyOrderObject* ServerSubObject1 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectDestroyOrderObject* ServerSubObject2 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectDestroyOrderObject::RepOrderCounter = 0U; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that objects have replicated UReplicatedSubObjectDestroyOrderObject* ClientSubObject0 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject0->NetRefHandle)); UReplicatedSubObjectDestroyOrderObject* ClientSubObject1 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject1->NetRefHandle)); UReplicatedSubObjectDestroyOrderObject* ClientSubObject2 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject2->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject0, nullptr); UE_NET_ASSERT_NE(ClientSubObject1, nullptr); UE_NET_ASSERT_NE(ClientSubObject2, nullptr); // Verify that they have replicated in expected order UE_NET_ASSERT_EQ(ClientSubObject0->LastRepOrderCounter, 1U); UE_NET_ASSERT_GT(ClientSubObject1->LastRepOrderCounter, ClientSubObject0->LastRepOrderCounter); UE_NET_ASSERT_GT(ClientSubObject2->LastRepOrderCounter, ClientSubObject1->LastRepOrderCounter); // setup a watch on the client ClientSubObject2->SetObjectExpectedToBeDestroyed(ClientSubObject1); // Dirty some data on server and destroy SubObject1 ++ServerSubObject2->IntA; Server->ReplicationBridge->EndReplication(ServerSubObject1, EEndReplicationFlags::Destroy); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify assumptions, replicated subobject destroy should have been issued before ClientSubObjects2 s call to PostNetReceive UE_NET_ASSERT_TRUE(ClientSubObject2->bObjectExistedInPreNetReceive); UE_NET_ASSERT_FALSE(ClientSubObject2->bObjectExistedInPostNetReceive); UE_NET_ASSERT_FALSE(ClientSubObject2->ObjectToWatch->IsValid()); } UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestSubObjectReplicatedDestroyAfterPostNetReceive) { // Make sure that bOldImmediateDispatchEndReplicationForSubObjects is set to false IConsoleVariable* LocalCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("net.Iris.ImmediateDispatchEndReplicationForSubObjects")); check(LocalCVar != nullptr && LocalCVar->IsVariableBool()); const bool bOldImmediateDispatchEndReplicationForSubObjects = LocalCVar->GetBool(); LocalCVar->Set(false, ECVF_SetByCode); ON_SCOPE_EXIT { // Restore cvars LocalCVar->Set(bOldImmediateDispatchEndReplicationForSubObjects, ECVF_SetByCode); }; // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UReplicatedTestObject* ServerObject = Server->CreateObject(0, 0); // Spawn some subobjects UReplicatedSubObjectDestroyOrderObject* ServerSubObject0 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectDestroyOrderObject* ServerSubObject1 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectDestroyOrderObject* ServerSubObject2 = Server->CreateSubObject(ServerObject->NetRefHandle); UReplicatedSubObjectDestroyOrderObject::RepOrderCounter = 0U; // Send and deliver packet Server->UpdateAndSend({Client}); // Verify that objects have replicated UReplicatedSubObjectDestroyOrderObject* ClientSubObject0 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject0->NetRefHandle)); UReplicatedSubObjectDestroyOrderObject* ClientSubObject1 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject1->NetRefHandle)); UReplicatedSubObjectDestroyOrderObject* ClientSubObject2 = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObject2->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject0, nullptr); UE_NET_ASSERT_NE(ClientSubObject1, nullptr); UE_NET_ASSERT_NE(ClientSubObject2, nullptr); // Verify that they have replicated in expected order UE_NET_ASSERT_EQ(ClientSubObject0->LastRepOrderCounter, 1U); UE_NET_ASSERT_GT(ClientSubObject1->LastRepOrderCounter, ClientSubObject0->LastRepOrderCounter); UE_NET_ASSERT_GT(ClientSubObject2->LastRepOrderCounter, ClientSubObject1->LastRepOrderCounter); // setup a watch on the client ClientSubObject2->SetObjectExpectedToBeDestroyed(ClientSubObject1); // Dirty some data on server and destroy SubObject1 ++ServerSubObject2->IntA; Server->ReplicationBridge->EndReplication(ServerSubObject1, EEndReplicationFlags::Destroy); // Send and deliver packet Server->UpdateAndSend({Client}); // Verify assumptions, replicated subobject destroy should have been issued after ClientSubObjects2 s call to PostNetReceive UE_NET_ASSERT_TRUE(ClientSubObject2->bObjectExistedInPreNetReceive); UE_NET_ASSERT_TRUE(ClientSubObject2->bObjectExistedInPostNetReceive); UE_NET_ASSERT_FALSE(ClientSubObject2->ObjectToWatch->IsValid()); } } // end namespace UE::Net::Private