// Copyright Epic Games, Inc. All Rights Reserved. #include "HAL/IConsoleManager.h" #include "Iris/ReplicationSystem/NetBlob/NetObjectBlobHandler.h" #include "Iris/ReplicationSystem/ReplicationRecord.h" #include "Misc/ScopeExit.h" #include "NetBlob/NetBlobTestFixture.h" #include "NetBlob/MockNetBlob.h" #include "NetBlob/MockNetObjectAttachment.h" namespace UE::Net::Private { class FSplitObjectTestFixture : public FNetBlobTestFixture { typedef FNetBlobTestFixture Super; public: enum : uint32 { HugeObjectPayloadByteCount = 16384, HugeObjectMaxNetTickCountToArrive = 32U, }; public: FSplitObjectTestFixture() { } protected: virtual void SetUp() override { AddNetBlobHandlerDefinitions(); Super::SetUp(); RegisterNetBlobHandlers(Server); } virtual void TearDown() override { Super::TearDown(); } void RegisterNetBlobHandlers(FReplicationSystemTestNode* Node) { UReplicationSystem* RepSys = Node->GetReplicationSystem(); const bool bIsServer = RepSys->IsServer(); FNetBlobHandlerManager* NetBlobHandlerManager = &RepSys->GetReplicationSystemInternal()->GetNetBlobHandlerManager(); { UMockNetObjectAttachmentHandler* BlobHandler = NewObject(); const bool bMockNetObjectAttachmentHandlerWasRegistered = RegisterNetBlobHandler(RepSys, BlobHandler); check(bMockNetObjectAttachmentHandlerWasRegistered); if (bIsServer) { ServerMockNetObjectAttachmentHandler = TStrongObjectPtr(BlobHandler); } else { ClientMockNetObjectAttachmentHandler = TStrongObjectPtr(BlobHandler); } } } void SetObjectPayloadByteCount(UTestReplicatedIrisObject* Object, uint32 ByteCount) { UTestReplicatedIrisDynamicStatePropertyComponent* Component = Object->DynamicStateComponents[0].Get(); Component->IntArray.SetNumZeroed(ByteCount/4U); } UTestReplicatedIrisObject* CreateObject(FReplicationSystemTestNode* Node) { UTestReplicatedIrisObject::FComponents Components; Components.DynamicStateComponentCount = 1; UTestReplicatedIrisObject* Object = Node->CreateObject(Components); return Object; } UTestReplicatedIrisObject* CreateSubObject(FReplicationSystemTestNode* Node, FNetRefHandle Parent) { UTestReplicatedIrisObject::FComponents Components; Components.DynamicStateComponentCount = 1; UTestReplicatedIrisObject* Object = Node->CreateSubObject(Parent, Components); return Object; } UTestReplicatedIrisObject* CreateHugeObject(FReplicationSystemTestNode* Node) { UTestReplicatedIrisObject* Object = CreateObject(Node); SetObjectPayloadByteCount(Object, HugeObjectPayloadByteCount); return Object; } private: void AddNetBlobHandlerDefinitions() { AddMockNetBlobHandlerDefinition(); const FNetBlobHandlerDefinition NetBlobHandlerDefinitions[] = { {TEXT("MockNetObjectAttachmentHandler"),}, // The proper partial attachment and net object blob handlers are needed for splitting huge objects and attachments. {TEXT("PartialNetObjectAttachmentHandler"),}, {TEXT("NetObjectBlobHandler"),}, }; Super::AddNetBlobHandlerDefinitions(NetBlobHandlerDefinitions, UE_ARRAY_COUNT(NetBlobHandlerDefinitions)); } protected: TStrongObjectPtr ServerMockNetObjectAttachmentHandler; TStrongObjectPtr ClientMockNetObjectAttachmentHandler; }; // Test that huge object state can be replicated on creation. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitHugeObjectOnCreation) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateHugeObject(Server); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // As the payload is huge we don't expect the whole payload to arrive the first frame const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_EXPECT_EQ(ClientObject, nullptr); for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && ClientObject == nullptr; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); } UE_NET_ASSERT_NE(ClientObject, nullptr); } // Test that huge object state can be replicated after an object has been created. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitHugeObjectAfterCreation) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateObject(Server); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObject, nullptr); SetObjectPayloadByteCount(ServerObject, HugeObjectPayloadByteCount); // Clear function call status so we can easily verify we get the huge payload. UTestReplicatedIrisDynamicStatePropertyComponent* Component = ClientObject->DynamicStateComponents[0].Get(); Component->CallCounts = {}; for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && Component->CallCounts.IntArrayRepNotifyCounter == 0; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); } UE_NET_ASSERT_GT(Component->CallCounts.IntArrayRepNotifyCounter, 0U); } // Test that object with huge subobjects can be replicated on creation. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitObjectWithHugeSubObjectsOnCreation) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateObject(Server); constexpr uint32 SubObjectCount = 3; UTestReplicatedIrisObject* ServerSubObjects[SubObjectCount]; for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { ServerSubObjects[SubObjectIt] = CreateSubObject(Server, ServerObject->NetRefHandle); SetObjectPayloadByteCount(ServerSubObjects[SubObjectIt], 4096U); } // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // As the payload is huge we don't expect the whole payload to arrive the first frame const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_EXPECT_EQ(ClientObject, nullptr); for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && ClientObject == nullptr; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); } UE_NET_ASSERT_NE(ClientObject, nullptr); // Verify the subobjects made it through as well. for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { const UTestReplicatedIrisObject* ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObjects[SubObjectIt]->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject, nullptr); } } // Test that object with lots of subobjects with attachments can be sent on creation. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitObjectWithSubObjectsWithHugeAttachmentsOnCreation) { FReplicationSystemTestClient* Client = CreateClient(); RegisterNetBlobHandlers(Client); constexpr uint32 SubObjectCount = 16; constexpr uint32 SubObjectPayloadByteCount = 128U; constexpr uint32 SubObjectAttachmentPayloadByteCount = 128U; UTestReplicatedIrisObject* ServerObject = CreateObject(Server); UTestReplicatedIrisObject* ServerSubObjects[SubObjectCount]; for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { UTestReplicatedIrisObject* ServerSubObject = CreateSubObject(Server, ServerObject->NetRefHandle); ServerSubObjects[SubObjectIt] = ServerSubObject; SetObjectPayloadByteCount(ServerSubObject, SubObjectPayloadByteCount); TRefCountPtr Attachment; // Alternate between reliable and unreliable attachments if ((SubObjectIt & 1U) != 0) { Attachment = ServerMockNetObjectAttachmentHandler->CreateReliableNetObjectAttachment(SubObjectPayloadByteCount*8U); } else { Attachment = ServerMockNetObjectAttachmentHandler->CreateUnreliableNetObjectAttachment(SubObjectPayloadByteCount*8U); } FNetObjectReference AttachmentTarget = FObjectReferenceCache::MakeNetObjectReference(ServerSubObject->NetRefHandle); Server->GetReplicationSystem()->QueueNetObjectAttachment(Client->ConnectionIdOnServer, AttachmentTarget, Attachment); } // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // As the payload is huge we don't expect the whole payload to arrive the first frame const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_EXPECT_EQ(ClientObject, nullptr); for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && ClientObject == nullptr; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); } UE_NET_ASSERT_NE(ClientObject, nullptr); // Verify the subobjects made it through. for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { const UTestReplicatedIrisObject* ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObjects[SubObjectIt]->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject, nullptr); } // Wait for attachments for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); } // Verify the attachments made it through. UMockNetObjectAttachmentHandler::FCallCounts AttachmentCallCounts = ClientMockNetObjectAttachmentHandler->GetFunctionCallCounts(); UE_NET_ASSERT_EQ(AttachmentCallCounts.OnNetBlobReceived, SubObjectCount); } // Test that object with lots of subobjects with attachments can be sent after creation. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitObjectWithSubObjectsWithHugeAttachmentsAfterCreation) { FReplicationSystemTestClient* Client = CreateClient(); RegisterNetBlobHandlers(Client); constexpr uint32 SubObjectCount = 16; constexpr uint32 SubObjectPayloadByteCount = 128U; constexpr uint32 SubObjectAttachmentPayloadByteCount = 128U; UTestReplicatedIrisObject* ServerObject = CreateObject(Server); UTestReplicatedIrisObject* ServerSubObjects[SubObjectCount]; for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { UTestReplicatedIrisObject* ServerSubObject = CreateSubObject(Server, ServerObject->NetRefHandle); ServerSubObjects[SubObjectIt] = ServerSubObject; } // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // As the payload is huge we don't expect the whole payload to arrive the first frame const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObject, nullptr); // Verify the subobjects made it through. for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { const UTestReplicatedIrisObject* ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObjects[SubObjectIt]->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject, nullptr); } // Now create huge payload and attachments for each subobject. for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { UTestReplicatedIrisObject* ServerSubObject = ServerSubObjects[SubObjectIt]; SetObjectPayloadByteCount(ServerSubObject, SubObjectPayloadByteCount); TRefCountPtr Attachment; // Alternate between reliable and unreliable attachments if ((SubObjectIt & 1U) != 0) { Attachment = ServerMockNetObjectAttachmentHandler->CreateReliableNetObjectAttachment(SubObjectPayloadByteCount*8U); } else { Attachment = ServerMockNetObjectAttachmentHandler->CreateUnreliableNetObjectAttachment(SubObjectPayloadByteCount*8U); } FNetObjectReference AttachmentTarget = FObjectReferenceCache::MakeNetObjectReference(ServerSubObject->NetRefHandle); Server->GetReplicationSystem()->QueueNetObjectAttachment(Client->ConnectionIdOnServer, AttachmentTarget, Attachment); } bool bHasReceivedHugeState = false; for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && !bHasReceivedHugeState; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Assume that if one subobject has received its huge state then all of them have const UTestReplicatedIrisObject* ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObjects[SubObjectCount - 1U]->NetRefHandle)); if (ClientSubObject->DynamicStateComponents[0]->IntArray.Num() > 0) { bHasReceivedHugeState = true; } } UE_NET_ASSERT_TRUE(bHasReceivedHugeState); // Verify the attachments made it through. UMockNetObjectAttachmentHandler::FCallCounts AttachmentCallCounts = ClientMockNetObjectAttachmentHandler->GetFunctionCallCounts(); UE_NET_ASSERT_EQ(AttachmentCallCounts.OnNetBlobReceived, SubObjectCount); } // Test that object can have consecutive huge states. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, HugeObjectStateCanBeSentBackToBack) { FReplicationSystemTestClient* Client = CreateClient(); RegisterNetBlobHandlers(Client); constexpr uint32 SubObjectCount = 16; constexpr uint32 SubObjectPayloadByteCount = 128U; constexpr uint32 SubObjectAttachmentPayloadByteCount = 128U; UTestReplicatedIrisObject* ServerObject = CreateObject(Server); UTestReplicatedIrisObject* ServerSubObjects[SubObjectCount]; for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { UTestReplicatedIrisObject* ServerSubObject = CreateSubObject(Server, ServerObject->NetRefHandle); ServerSubObjects[SubObjectIt] = ServerSubObject; } // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // As the payload is huge we don't expect the whole payload to arrive the first frame const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObject, nullptr); // Verify the subobjects made it through. for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { const UTestReplicatedIrisObject* ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObjects[SubObjectIt]->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject, nullptr); } // Now create huge payload and attachments for each subobject. for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { UTestReplicatedIrisObject* ServerSubObject = ServerSubObjects[SubObjectIt]; SetObjectPayloadByteCount(ServerSubObject, SubObjectPayloadByteCount); TRefCountPtr Attachment; // Alternate between reliable and unreliable attachments if ((SubObjectIt & 1U) != 0) { Attachment = ServerMockNetObjectAttachmentHandler->CreateReliableNetObjectAttachment(SubObjectPayloadByteCount*8U); } else { Attachment = ServerMockNetObjectAttachmentHandler->CreateUnreliableNetObjectAttachment(SubObjectPayloadByteCount*8U); } FNetObjectReference AttachmentTarget = FObjectReferenceCache::MakeNetObjectReference(ServerSubObject->NetRefHandle); Server->GetReplicationSystem()->QueueNetObjectAttachment(Client->ConnectionIdOnServer, AttachmentTarget, Attachment); } bool bHasReceivedHugeState = false; for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && !bHasReceivedHugeState; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Assume that if one subobject has received its huge state then all of them have const UTestReplicatedIrisObject* ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerSubObjects[SubObjectCount - 1U]->NetRefHandle)); if (ClientSubObject->DynamicStateComponents[0]->IntArray.Num() > 0) { bHasReceivedHugeState = true; } } UE_NET_ASSERT_TRUE(bHasReceivedHugeState); // Verify the attachments made it through. UMockNetObjectAttachmentHandler::FCallCounts AttachmentCallCounts = ClientMockNetObjectAttachmentHandler->GetFunctionCallCounts(); UE_NET_ASSERT_EQ(AttachmentCallCounts.OnNetBlobReceived, SubObjectCount); } // Test that we can send one huge object after another. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitObjectCanBeSentBackToBack) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateHugeObject(Server); // Send and deliver packet. This will initiate huge object transfer. Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // As the payload is huge we don't expect the whole payload to arrive the first frame const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_EQ(ClientObject, nullptr); const int OriginalArrayCount = ServerObject->DynamicStateComponents[0]->IntArray.Num(); // Modify the payload which will cause the same object to require a huge object transfer again. ServerObject->DynamicStateComponents[0]->IntArray.Add(1); for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && ClientObject == nullptr; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); } UE_NET_ASSERT_NE(ClientObject, nullptr); UE_NET_ASSERT_EQ(ClientObject->DynamicStateComponents[0]->IntArray.Num(), OriginalArrayCount); bool bHasReceivedSecondHugeState = false; for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && !bHasReceivedSecondHugeState; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); if (ClientObject->DynamicStateComponents[0]->IntArray.Num() == OriginalArrayCount + 1) { bHasReceivedSecondHugeState = true; } } UE_NET_ASSERT_TRUE(bHasReceivedSecondHugeState); } // Test that a huge object can be deleted. Currently we assume the object must be created before deleted. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitObjectIsDeletedAfterBeingCreated) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateHugeObject(Server); const FNetRefHandle ServerNetRefHandle = ServerObject->NetRefHandle; // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // As the payload is huge we don't expect the whole payload to arrive the first frame const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerNetRefHandle)); UE_NET_ASSERT_EQ(ClientObject, nullptr); Server->DestroyObject(ServerObject); for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && ClientObject == nullptr; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerNetRefHandle)); } UE_NET_ASSERT_NE(ClientObject, nullptr); // The object should be destroyed after the next net update. Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerNetRefHandle)); UE_NET_ASSERT_EQ(ClientObject, nullptr); } // Test that a subobject to a huge object can be deleted properly. Currently we assume the huge object payload must have been received before the subobject can be deleted. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SubObjectToHugeObjectCanBeDeleted) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateObject(Server); constexpr uint32 SubObjectCount = 3; UTestReplicatedIrisObject* ServerSubObjects[SubObjectCount]; for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { ServerSubObjects[SubObjectIt] = CreateSubObject(Server, ServerObject->NetRefHandle); } // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); const UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObject, nullptr); const FNetRefHandle SubObjectNetRefHandle = ServerSubObjects[0]->NetRefHandle; const UTestReplicatedIrisObject* ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject, nullptr); // Make subobject payloads huge for (uint32 SubObjectIt=0; SubObjectIt != SubObjectCount; ++SubObjectIt) { UTestReplicatedIrisObject* ServerSubObject = ServerSubObjects[SubObjectIt]; SetObjectPayloadByteCount(ServerSubObjects[SubObjectIt], 4096U); } // Initiate sending so that we have huge data in flight with the subobject we are going to destroy. Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); Server->DestroyObject(ServerSubObjects[0]); bool bHasReceivedHugeState = false; for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && !bHasReceivedHugeState; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Assume that if one subobject has received its huge state then all of them have ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(SubObjectNetRefHandle)); UE_NET_ASSERT_NE(ClientSubObject, nullptr); if (ClientSubObject->DynamicStateComponents[0]->IntArray.Num() > 0) { bHasReceivedHugeState = true; } } UE_NET_ASSERT_TRUE(bHasReceivedHugeState); // Now the subobject can safely be destroyed Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); ClientSubObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(SubObjectNetRefHandle)); UE_NET_ASSERT_EQ(ClientSubObject, nullptr); } // Test TearOff for new huge object UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TearOffOnCreation) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateHugeObject(Server); // TearOff the object Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); const int32 NumObjectsCreatedOnClientBeforeReplication = Client->CreatedObjects.Num(); bool bHasHugeObjectBeenCreated = false; for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && !bHasHugeObjectBeenCreated; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); if (Client->CreatedObjects.Num() > NumObjectsCreatedOnClientBeforeReplication) { bHasHugeObjectBeenCreated = true; } } UE_NET_ASSERT_EQ(Client->CreatedObjects.Num(), NumObjectsCreatedOnClientBeforeReplication + 1); // Verify that ClientObject is torn-off and that the final state was applied UTestReplicatedIrisObject* ClientObjectThatWasTornOff = Cast(Client->CreatedObjects[NumObjectsCreatedOnClientBeforeReplication].Get()); UE_NET_ASSERT_EQ(ClientObjectThatWasTornOff->DynamicStateComponents[0]->IntArray.Num(), ServerObject->DynamicStateComponents[0]->IntArray.Num()); UE_NET_ASSERT_EQ(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)), nullptr); } // Test TearOff for existing confirmed object during huge object state send UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TearOffCreatedObjectWithHugePayload) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateObject(Server); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Store client object while it can still be found using the server net handle. UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObjectThatWillBeTornOff, nullptr); // Set huge payload and TearOff the object SetObjectPayloadByteCount(ServerObject, HugeObjectPayloadByteCount); Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); bool bHasReceivedHugeState = false; for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && !bHasReceivedHugeState; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); if (ClientObjectThatWillBeTornOff->DynamicStateComponents[0]->IntArray.Num() > 0) { bHasReceivedHugeState = true; } } // Verify that ClientObject is torn-off and that the final state was applied UE_NET_ASSERT_EQ(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)), nullptr); UE_NET_ASSERT_EQ(ClientObjectThatWillBeTornOff->DynamicStateComponents[0]->IntArray.Num(), ServerObject->DynamicStateComponents[0]->IntArray.Num()); } // Test TearOff while huge object state is still sending. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TearOffWhileHugeObjectStateIsSending) { FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateHugeObject(Server); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, true); Server->PostSendUpdate(); // Tear off object before it has been created on the client. ServerObject->IntA ^= 1; Server->ReplicationBridge->EndReplication(ServerObject, EEndReplicationFlags::TearOff); const int32 NumObjectsCreatedOnClientBeforeReplication = Client->CreatedObjects.Num(); bool bHasHugeObjectBeenCreated = false; for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive && !bHasHugeObjectBeenCreated; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); if (Client->CreatedObjects.Num() > NumObjectsCreatedOnClientBeforeReplication) { bHasHugeObjectBeenCreated = true; } } UE_NET_ASSERT_EQ(Client->CreatedObjects.Num(), NumObjectsCreatedOnClientBeforeReplication + 1); // Verify we have the previous state const UTestReplicatedIrisObject* ClientObjectThatWillBeTornOff = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_EQ((ClientObjectThatWillBeTornOff->IntA ^ 1), ServerObject->IntA); 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(Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)), nullptr); UE_NET_ASSERT_EQ(ClientObjectThatWillBeTornOff->IntA, ServerObject->IntA); } UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TestCancelPendingDestroyOfHugeObjectDuringWaitOnCreateConfirmationWithoutPacketLoss) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = CreateHugeObject(Server); // Write packets for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); } // Filter out object to cause a PendingDestroy Server->GetReplicationSystem()->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->PostSendUpdate(); // Remove object from filter to cause object to end up in CancelPendingDestroy Server->GetReplicationSystem()->RemoveFromGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->PostSendUpdate(); // Deliver object creation packets { SIZE_T PacketCount = 0; const auto& ConnectionInfo = Server->GetConnectionInfo(Client->ConnectionIdOnServer); PacketCount = ConnectionInfo.WrittenPackets.Count(); for (SIZE_T PacketIt = 0; PacketIt != PacketCount; ++PacketIt) { Server->DeliverTo(Client, DeliverPacket); } } // Verify that the object now exists on client UE_NET_ASSERT_NE(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle), nullptr); // Modify a property on the object and make sure it's replicated as the object should now be confirmed created ServerObject->IntA ^= 1; Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObject, nullptr); UE_NET_ASSERT_EQ(ClientObject->IntA, ServerObject->IntA); } UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TestCancelPendingDestroyOfHugeObjectDuringWaitOnCreateConfirmationWithPacketLoss) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = CreateHugeObject(Server); // Write packets for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); } // Filter out object to cause a PendingDestroy Server->GetReplicationSystem()->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->PostSendUpdate(); // Remove object from filter to cause object to end up in CancelPendingDestroy Server->GetReplicationSystem()->RemoveFromGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->PostSendUpdate(); // Cause packet loss on object creation { SIZE_T PacketCount = 0; const auto& ConnectionInfo = Server->GetConnectionInfo(Client->ConnectionIdOnServer); PacketCount = ConnectionInfo.WrittenPackets.Count(); for (SIZE_T PacketIt = 0; PacketIt != PacketCount; ++PacketIt) { Server->DeliverTo(Client, DoNotDeliverPacket); } } // Write and send packets and verify object is created { const int32 NumObjectsCreatedOnClientBeforeReplication = Client->CreatedObjects.Num(); for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); if (Client->CreatedObjects.Num() > NumObjectsCreatedOnClientBeforeReplication) { break; } } } UE_NET_ASSERT_NE(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle), nullptr); } UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TestCancelPendingDestroyDuringHugeObjectStateUpdate) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UTestReplicatedIrisObject* ServerObject = CreateObject(Server); // Write and send packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Force huge object state SetObjectPayloadByteCount(ServerObject, HugeObjectPayloadByteCount); // Write packets for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); } // Filter out object to cause a PendingDestroy Server->GetReplicationSystem()->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->PostSendUpdate(); // Remove object from filter to cause object to end up in CancelPendingDestroy Server->GetReplicationSystem()->RemoveFromGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->PostSendUpdate(); // Modify a property on the object and make sure it's replicated as the object should still be created ServerObject->IntA ^= 1; // Deliver huge state { SIZE_T PacketCount = 0; const auto& ConnectionInfo = Server->GetConnectionInfo(Client->ConnectionIdOnServer); PacketCount = ConnectionInfo.WrittenPackets.Count(); for (SIZE_T PacketIt = 0; PacketIt != PacketCount; ++PacketIt) { Server->DeliverTo(Client, DeliverPacket); } } // Deliver latest state Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObject, nullptr); UE_NET_ASSERT_EQ(ClientObject->IntA, ServerObject->IntA); } UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TestReliableAttachmentIsDeliveredDespiteHugeObjectBeingDestroyed) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); RegisterNetBlobHandlers(Client); // Spawn object on server UTestReplicatedIrisObject* ServerObject = CreateObject(Server); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Force huge object state SetObjectPayloadByteCount(ServerObject, HugeObjectPayloadByteCount); // Add reliable attachment { TRefCountPtr Attachment = ServerMockNetObjectAttachmentHandler->CreateReliableNetObjectAttachment(1U); FNetObjectReference AttachmentTarget = FObjectReferenceCache::MakeNetObjectReference(ServerObject->NetRefHandle); Server->GetReplicationSystem()->QueueNetObjectAttachment(Client->ConnectionIdOnServer, AttachmentTarget, Attachment); } // Write packets for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); } // Filter out object to cause object to be set in state WaitOnFlush Server->GetReplicationSystem()->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); // Deliver huge state { SIZE_T PacketCount = 0; const auto& ConnectionInfo = Server->GetConnectionInfo(Client->ConnectionIdOnServer); PacketCount = ConnectionInfo.WrittenPackets.Count(); for (SIZE_T PacketIt = 0; PacketIt != PacketCount; ++PacketIt) { Server->DeliverTo(Client, DeliverPacket); } } // Deliver latest state Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Verify the attachment made it through, despite the wish to destroy the object. const UMockNetObjectAttachmentHandler::FCallCounts AttachmentCallCounts = ClientMockNetObjectAttachmentHandler->GetFunctionCallCounts(); UE_NET_ASSERT_EQ(AttachmentCallCounts.OnNetBlobReceived, 1U); // The object should not exist UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_EQ(ClientObject, nullptr); } UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TestHugeObjectIsFlushedAndNotDestroyedWhenFilteredOutAndThenIn) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); RegisterNetBlobHandlers(Client); // Spawn object on server UTestReplicatedIrisObject* ServerObject = CreateObject(Server); // Send and deliver packet Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Force huge object state SetObjectPayloadByteCount(ServerObject, HugeObjectPayloadByteCount); // Add reliable attachment { TRefCountPtr Attachment = ServerMockNetObjectAttachmentHandler->CreateReliableNetObjectAttachment(1U); FNetObjectReference AttachmentTarget = FObjectReferenceCache::MakeNetObjectReference(ServerObject->NetRefHandle); Server->GetReplicationSystem()->QueueNetObjectAttachment(Client->ConnectionIdOnServer, AttachmentTarget, Attachment); } // Write packets for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); } // Filter out object to cause object to be set in state WaitOnFlush Server->GetReplicationSystem()->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); // Remove object from filter to cause object to be set in state Created Server->GetReplicationSystem()->RemoveFromGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerObject->NetRefHandle); Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); // Deliver huge state { SIZE_T PacketCount = 0; const auto& ConnectionInfo = Server->GetConnectionInfo(Client->ConnectionIdOnServer); PacketCount = ConnectionInfo.WrittenPackets.Count(); for (SIZE_T PacketIt = 0; PacketIt != PacketCount; ++PacketIt) { Server->DeliverTo(Client, DeliverPacket); } } // Modify a property on the object and make sure it's replicated as the object should still be created. ServerObject->IntA += 1; // Deliver latest state Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Verify the attachment made it through const UMockNetObjectAttachmentHandler::FCallCounts AttachmentCallCounts = ClientMockNetObjectAttachmentHandler->GetFunctionCallCounts(); UE_NET_ASSERT_EQ(AttachmentCallCounts.OnNetBlobReceived, 1U); UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_NE(ClientObject, nullptr); UE_NET_ASSERT_EQ(ClientObject->IntA, ServerObject->IntA); } // Test that huge object state can be replicated on creation. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitManyHugeObjectsOnCreation) { FReplicationSystemTestClient* Client = CreateClient(); constexpr uint32 HugeObjectCount = 37U; UTestReplicatedIrisObject* ServerObjects[HugeObjectCount]; for (UTestReplicatedIrisObject*& ServerObject : ServerObjects) { ServerObject = CreateHugeObject(Server); } // Send and deliver packets until all huge objects have arrived. UTestReplicatedIrisObject* ClientObjects[HugeObjectCount] = {}; uint32 ClientObjectCount = 0; for (uint32 RetryIt = 0; RetryIt != (HugeObjectMaxNetTickCountToArrive*HugeObjectCount) && ClientObjectCount < HugeObjectCount; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); for (UTestReplicatedIrisObject*& ClientObject : ClientObjects) { if (ClientObject) { continue; } const SIZE_T ObjectIndex = &ClientObject - ClientObjects; ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObjects[ObjectIndex]->NetRefHandle)); ClientObjectCount += (ClientObject != nullptr); } } UE_NET_ASSERT_EQ(ClientObjectCount, HugeObjectCount); } // Test that huge object state can be replicated after an object has been created. UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, SplitManyHugeObjectsAfterCreation) { FReplicationSystemTestClient* Client = CreateClient(); constexpr uint32 HugeObjectCount = 37U; UTestReplicatedIrisObject* ServerObjects[HugeObjectCount]; for (UTestReplicatedIrisObject*& ServerObject : ServerObjects) { ServerObject = CreateObject(Server); } // Send and deliver packets until all huge objects have been received on the client. UTestReplicatedIrisObject* ClientObjects[HugeObjectCount] = {}; uint32 ClientObjectCount = 0; for (uint32 RetryIt = 0; RetryIt != HugeObjectCount && ClientObjectCount < HugeObjectCount; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); for (UTestReplicatedIrisObject*& ClientObject : ClientObjects) { if (ClientObject) { continue; } const SIZE_T ObjectIndex = &ClientObject - ClientObjects; ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObjects[ObjectIndex]->NetRefHandle)); ClientObjectCount += (ClientObject != nullptr); } } UE_NET_ASSERT_EQ(ClientObjectCount, HugeObjectCount); // Make all objects huge. for (UTestReplicatedIrisObject*& ServerObject : ServerObjects) { SetObjectPayloadByteCount(ServerObject, HugeObjectPayloadByteCount); } // Clear function call status so we can easily verify we get the huge payload. for (UTestReplicatedIrisObject* ClientObject : ClientObjects) { UTestReplicatedIrisDynamicStatePropertyComponent* Component = ClientObject->DynamicStateComponents[0].Get(); Component->CallCounts = {}; } uint32 ClientObjectsWithHugeArraysCount = 0; for (uint32 RetryIt = 0; RetryIt != (HugeObjectMaxNetTickCountToArrive*HugeObjectCount) && ClientObjectsWithHugeArraysCount < HugeObjectCount; ++RetryIt) { Server->NetUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); ClientObjectsWithHugeArraysCount = 0; for (UTestReplicatedIrisObject* ClientObject : ClientObjects) { UTestReplicatedIrisDynamicStatePropertyComponent* Component = ClientObject->DynamicStateComponents[0].Get(); ClientObjectsWithHugeArraysCount += (Component->CallCounts.IntArrayRepNotifyCounter > 0); } } UE_NET_ASSERT_EQ(ClientObjectsWithHugeArraysCount, HugeObjectCount); } UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TestDependentObjectCannotBeDestroyedWhileWaitingForCreation) { UReplicatedTestObjectBridge* ServerBridge = Server->GetReplicationBridge(); FReplicationSystemTestClient* Client = CreateClient(); UTestReplicatedIrisObject* ServerObject = CreateHugeObject(Server); UTestReplicatedIrisObject* ServerDependentObject = Server->CreateObject(UObjectReplicationBridge::FRootObjectReplicationParams{}); ServerBridge->AddDependentObject(ServerObject->NetRefHandle, ServerDependentObject->NetRefHandle); // Introduce latency by not immediately delivering packets. Server->NetUpdate(); Server->SendTo(Client, TEXT("Create HugeObject + Dependent")); Server->PostSendUpdate(); // Filter out dependent object to cause it to end up being destroyed Server->ReplicationSystem->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerDependentObject->NetRefHandle); Server->NetUpdate(); Server->SendTo(Client, TEXT("Try destroy Dependent")); Server->PostSendUpdate(); // Make sure at least one of the packets required for object creation is lost. Server->DeliverTo(Client, DoNotDeliverPacket); // Deliver all pending packets for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->DeliverTo(Client, DeliverPacket); } // Make sure we replicate the full state of all objects for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->UpdateAndSend({ Client }, DeliverPacket); } // Make sure the dependent object was destroyed. const UTestReplicatedIrisObject* ClientDependentObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerDependentObject->NetRefHandle)); UE_NET_ASSERT_EQ(ClientDependentObject, nullptr); } UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TestHugeObjectCanBeSentDuringReplicationRecordStarvation) { 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 a few ReplicationRecords to enter starvation for (int It : {0, 1, 2}) { Server->CreateObject(UTestReplicatedIrisObject::FComponents{}); } // Create huge object so that the ReplicationWriter will use the huge object path. UTestReplicatedIrisObject* ServerHugeObject = CreateHugeObject(Server); // Write packet Server->NetUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->NetUpdate(); const bool bPacketWasWritten = Server->SendUpdate(Client->ConnectionIdOnServer, TEXT("Create HugeObject")); Server->PostSendUpdate(); if (!bPacketWasWritten) { break; } } // Deliver object creation packets { const auto& ConnectionInfo = Server->GetConnectionInfo(Client->ConnectionIdOnServer); const SIZE_T PacketCount = ConnectionInfo.WrittenPackets.Count(); for (SIZE_T PacketIt = 0; PacketIt != PacketCount; ++PacketIt) { Server->DeliverTo(Client, DeliverPacket); } } // The huge object should have been delivered despite the ReplicationRecord starvation UE_NET_ASSERT_TRUE(Client->IsResolvableNetRefHandle(ServerHugeObject->NetRefHandle)); } // Below test will fail as we only have a special path for reliable attachments for objects that stopped replicating, not for being filtered out. #if 0 UE_NET_TEST_FIXTURE(FSplitObjectTestFixture, TestReliableAttachmentAddedAfterSplittingHugeObjectIsDeliveredBeforeObjectIsFilteredOut) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); RegisterNetBlobHandlers(Client); // Spawn object on server UTestReplicatedIrisObject* ServerObject = CreateObject(Server); // Send and deliver packet Server->PreSendUpdate(); Server->SendAndDeliverTo(Client, DeliverPacket); Server->PostSendUpdate(); // Force huge object state SetObjectPayloadByteCount(ServerObject, HugeObjectPayloadByteCount); // Write packets for (uint32 RetryIt = 0; RetryIt != HugeObjectMaxNetTickCountToArrive; ++RetryIt) { Server->PreSendUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); } // Add reliable attachment { TRefCountPtr Attachment = ServerMockNetObjectAttachmentHandler->CreateReliableNetObjectAttachment(1U); FNetObjectReference AttachmentTarget = FObjectReferenceCache::MakeNetObjectReference(ServerObject->NetRefHandle); Server->GetReplicationSystem()->QueueNetObjectAttachment(Client->ConnectionIdOnServer, AttachmentTarget, Attachment); } // Filter out object to cause object to be set in state WaitOnFlush Server->GetReplicationSystem()->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedGroup(), ServerObject->NetRefHandle); Server->PreSendUpdate(); Server->SendUpdate(Client->ConnectionIdOnServer); Server->PostSendUpdate(); // Deliver huge state { SIZE_T PacketCount = 0; const auto& ConnectionInfo = Server->GetConnectionInfo(Client->ConnectionIdOnServer); PacketCount = ConnectionInfo.WrittenPackets.Count(); for (SIZE_T PacketIt = 0; PacketIt != PacketCount; ++PacketIt) { Server->DeliverTo(Client, DeliverPacket); } } // Verify the attachment made it through, despite the wish to destroy the object. const UMockNetObjectAttachmentHandler::FCallCounts AttachmentCallCounts = ClientMockNetObjectAttachmentHandler->GetFunctionCallCounts(); UE_NET_ASSERT_EQ(AttachmentCallCounts.OnNetBlobReceived, 1U); // The object should not exist UTestReplicatedIrisObject* ClientObject = Cast(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle)); UE_NET_ASSERT_EQ(ClientObject, nullptr); } #endif }