// Copyright Epic Games, Inc. All Rights Reserved. #include "Tests/Serialization/TestInstancedStructNetSerializer.h" #include "Tests/Serialization/TestNetSerializerFixture.h" #include "Tests/ReplicationSystem/ReplicationSystemServerClientTestFixture.h" #include "Iris/ReplicationSystem/ReplicationFragmentUtil.h" #include "Iris/ReplicationSystem/ReplicationSystemInternal.h" #include "Iris/ReplicationState/PropertyNetSerializerInfoRegistry.h" #include "Iris/ReplicationState/ReplicationStateDescriptorBuilder.h" #include "Iris/Serialization/InternalNetSerializationContext.h" #include "Iris/Serialization/NetReferenceCollector.h" #include "Iris/Serialization/NetSerializers.h" #include "Net/UnrealNetwork.h" namespace UE::Net { FTestMessage& operator<<(FTestMessage& Message, const FInstancedStruct& InstancedStruct) { FString String; InstancedStruct.ExportTextItem(String, FInstancedStruct(), nullptr, 0, nullptr); return Message << String; } struct FStructForInstancedStructTestWithCustomApplyNetSerializer { // Version static const uint32 Version = 0; // Types typedef FStructForInstancedStructTestWithCustomApply SourceType; typedef int QuantizedType; typedef FNetSerializerConfig ConfigType; inline static const ConfigType DefaultConfig; // static void Serialize(FNetSerializationContext&, const FNetSerializeArgs&); static void Deserialize(FNetSerializationContext&, const FNetDeserializeArgs&); static void Quantize(FNetSerializationContext&, const FNetQuantizeArgs&); static void Dequantize(FNetSerializationContext&, const FNetDequantizeArgs&); static bool IsEqual(FNetSerializationContext&, const FNetIsEqualArgs&); static void Apply(FNetSerializationContext&, const FNetApplyArgs&); private: class FNetSerializerRegistryDelegates final : private UE::Net::FNetSerializerRegistryDelegates { public: virtual ~FNetSerializerRegistryDelegates(); private: virtual void OnPreFreezeNetSerializerRegistry() override; inline static const FName StructForInstancedStructTestWithCustomApplyNetSerializer = FName("StructForInstancedStructTestWithCustomApply"); UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO(StructForInstancedStructTestWithCustomApplyNetSerializer, FStructForInstancedStructTestWithCustomApplyNetSerializer); }; inline static FStructForInstancedStructTestWithCustomApplyNetSerializer::FNetSerializerRegistryDelegates NetSerializerRegistryDelegates; }; UE_NET_IMPLEMENT_SERIALIZER(FStructForInstancedStructTestWithCustomApplyNetSerializer); struct FStructForInstancedStructTestWithCustomSerializerNetSerializer { // Version static const uint32 Version = 0; // Types typedef FStructForInstancedStructTestWithCustomSerializer SourceType; typedef int QuantizedType; typedef FNetSerializerConfig ConfigType; inline static const ConfigType DefaultConfig; // static void Serialize(FNetSerializationContext&, const FNetSerializeArgs&); static void Deserialize(FNetSerializationContext&, const FNetDeserializeArgs&); static void Quantize(FNetSerializationContext&, const FNetQuantizeArgs&); static void Dequantize(FNetSerializationContext&, const FNetDequantizeArgs&); static bool IsEqual(FNetSerializationContext&, const FNetIsEqualArgs&); private: class FNetSerializerRegistryDelegates final : private UE::Net::FNetSerializerRegistryDelegates { public: virtual ~FNetSerializerRegistryDelegates(); private: virtual void OnPreFreezeNetSerializerRegistry() override; inline static const FName StructForInstancedStructTestWithCustomSerializerNetSerializer = FName("StructForInstancedStructTestWithCustomSerializer"); UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO(StructForInstancedStructTestWithCustomSerializerNetSerializer, FStructForInstancedStructTestWithCustomSerializerNetSerializer); }; inline static FStructForInstancedStructTestWithCustomSerializerNetSerializer::FNetSerializerRegistryDelegates NetSerializerRegistryDelegates; }; UE_NET_IMPLEMENT_SERIALIZER(FStructForInstancedStructTestWithCustomSerializerNetSerializer); } namespace UE::Net::Private { FTestMessage& PrintInstancedStructNetSerializerConfig(FTestMessage& Message, const FNetSerializerConfig& InConfig) { const FInstancedStructNetSerializerConfig& Config = static_cast(InConfig); for (const auto& SupportedType : Config.SupportedTypes) { Message << SupportedType.Get()->GetFullName(); } return Message; } class FTestInstancedStructNetSerializerFixture : public FReplicationSystemServerClientTestFixture { public: FTestInstancedStructNetSerializerFixture() : FReplicationSystemServerClientTestFixture() {} protected: virtual void SetUp() override; virtual void TearDown() override; // Composable operations for testing the serializer void Serialize(); void Deserialize(); void SerializeDelta(); void DeserializeDelta(); void Quantize(); void QuantizeTwoStates(); void Dequantize(); bool IsEqual(bool bQuantized); void Clone(); void Validate(); void FreeQuantizedState(); // Instantiates a struct with a modified property on InstancedStruct0 void SetNonDefaultInstanceState(); // Adds multiple elements, at least one uninitialized and at least two initialized with at least one modified property, to InstancedStructArray0 void SetNonDefaultArrayState(); bool AreEqual(const FInstancedStruct* Value0, const FInstancedStruct* Value1); const FNetSerializer* GetInstancedStructNetSerializer(bool bIsArray) const; const FNetSerializerConfig* GetInstancedStructNetSerializerConfig(bool bIsArray) const; protected: FNetSerializationContext NetSerializationContext; FInternalNetSerializationContext InternalNetSerializationContext; FInstancedStruct InstancedStruct0; FInstancedStruct InstancedStruct1; TArray InstancedStructArray0; TArray InstancedStructArray1; FStructNetSerializerConfig InstancedStructNetSerializerConfig; FStructNetSerializerConfig InstancedStructArrayNetSerializerConfig; alignas(16) uint8 QuantizedBuffer[2][128]; alignas(16) uint8 ClonedQuantizedBuffer[2][128]; alignas(16) uint8 BitStreamBuffer[2048]; bool bHasQuantizedState = false; bool bHasClonedQuantizedState = false; bool bIsTestingArray = false; uint32 QuantizedStateCount = 0; uint32 ClonedQuantizedStateCount = 0; FNetBitStreamWriter Writer; FNetBitStreamReader Reader; }; } namespace UE::Net::Private { // Instance tests UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestQuantizeUninitialized) { Quantize(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestQuantizeInitialized) { SetNonDefaultInstanceState(); Quantize(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDequantizeUninitialized) { Quantize(); Dequantize(); UE_NET_ASSERT_EQ(InstancedStruct0, InstancedStruct1); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDequantizeInitialized) { SetNonDefaultInstanceState(); Quantize(); Dequantize(); UE_NET_ASSERT_EQ(InstancedStruct0, InstancedStruct1); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestSerializeUninitialized) { Quantize(); Serialize(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestSerializeInitialized) { SetNonDefaultInstanceState(); Quantize(); Serialize(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDeserializeUninitialized) { Quantize(); Serialize(); FreeQuantizedState(); Deserialize(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDeserializeInitialized) { SetNonDefaultInstanceState(); Quantize(); Serialize(); FreeQuantizedState(); Deserialize(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDequantizeSerializedUninitializedState) { Quantize(); Serialize(); FreeQuantizedState(); Deserialize(); Dequantize(); UE_NET_ASSERT_EQ(InstancedStruct0, InstancedStruct1); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDequantizeSerializedInitializedState) { SetNonDefaultInstanceState(); Quantize(); Serialize(); FreeQuantizedState(); Deserialize(); Dequantize(); UE_NET_ASSERT_EQ(InstancedStruct0, InstancedStruct1); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestSerializeDeltaEqualStates) { QuantizeTwoStates(); SerializeDelta(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestSerializeDeltaNonEqualStates) { SetNonDefaultInstanceState(); QuantizeTwoStates(); SerializeDelta(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDeserializeDeltaEqualStates) { QuantizeTwoStates(); SerializeDelta(); DeserializeDelta(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDeserializeDeltaNonEqualStates) { SetNonDefaultInstanceState(); QuantizeTwoStates(); SerializeDelta(); DeserializeDelta(); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDequantizeDeltaSerializedState) { SetNonDefaultInstanceState(); QuantizeTwoStates(); SerializeDelta(); DeserializeDelta(); Dequantize(); UE_NET_ASSERT_EQ(InstancedStruct0, InstancedStruct1); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestCollectReferencesUninitialized) { Quantize(); FNetReferenceCollector Collector; FNetCollectReferencesArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Source = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Collector = NetSerializerValuePointer(&Collector); GetInstancedStructNetSerializer(bIsTestingArray)->CollectNetReferences(NetSerializationContext, Args); UE_NET_ASSERT_EQ(Collector.GetCollectedReferences().Num(), 0); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestCollectReferencesStructNoRef) { InstancedStruct0.InitializeAs(); Quantize(); FNetReferenceCollector Collector(ENetReferenceCollectorTraits::IncludeInvalidReferences); FNetCollectReferencesArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Source = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Collector = NetSerializerValuePointer(&Collector); GetInstancedStructNetSerializer(bIsTestingArray)->CollectNetReferences(NetSerializationContext, Args); UE_NET_ASSERT_EQ(Collector.GetCollectedReferences().Num(), 1); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestCollectReferencesStructWithRef) { InstancedStruct0.InitializeAs(); InstancedStruct0.GetMutable().SomeObject = FStructForInstancedStructTestWithObjectReference::StaticStruct(); Quantize(); FNetReferenceCollector Collector(ENetReferenceCollectorTraits::IncludeInvalidReferences); FNetCollectReferencesArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Source = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Collector = NetSerializerValuePointer(&Collector); GetInstancedStructNetSerializer(bIsTestingArray)->CollectNetReferences(NetSerializationContext, Args); UE_NET_ASSERT_GE(Collector.GetCollectedReferences().Num(), 2); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestIsEqualExternal) { constexpr bool bUseQuantizedState = false; // Default state compared to default state InstancedStruct0.Reset(); InstancedStruct1.Reset(); UE_NET_ASSERT_TRUE(IsEqual(bUseQuantizedState)); // Non-default state compared to default state SetNonDefaultInstanceState(); UE_NET_ASSERT_FALSE(IsEqual(bUseQuantizedState)); // Non-default state compared to non-default state InstancedStruct1 = InstancedStruct0; UE_NET_ASSERT_TRUE(IsEqual(bUseQuantizedState)); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestIsEqualQuantized) { constexpr bool bUseQuantizedState = true; // Default state compared to default state InstancedStruct0.Reset(); Quantize(); Clone(); UE_NET_ASSERT_TRUE(IsEqual(bUseQuantizedState)); // Non-default state compared to default state SetNonDefaultInstanceState(); Quantize(); UE_NET_ASSERT_FALSE(IsEqual(bUseQuantizedState)); // Non-default state compared to non-default state Clone(); UE_NET_ASSERT_TRUE(IsEqual(bUseQuantizedState)); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestValidate) { Validate(); } // Array tests. There's no custom FInstancedStructArrayNetSerializer so we just add the one test until we require in-depth testing. UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, TestDequantizeSerializedInitializedArrayState) { bIsTestingArray = true; SetNonDefaultArrayState(); Quantize(); Serialize(); FreeQuantizedState(); Deserialize(); Dequantize(); UE_NET_ASSERT_TRUE(InstancedStruct0 == InstancedStruct1); } // End-to-end tests UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, ModifyInstance) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn object on server UInstancedStructNetSerializerTestObject* ServerObject = Server->CreateObject(); ServerObject->InstancedStruct.InitializeAs(); ServerObject->InstancedStruct.GetMutable().SomeFloat = 12.0f; // Replicate Server->UpdateAndSend({ Client }); auto ClientObject = Client->GetObjectAs(ServerObject->NetRefHandle); UE_NET_ASSERT_NE(ClientObject, nullptr); CA_ASSUME(ClientObject != nullptr); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct, ServerObject->InstancedStruct); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().SomeFloat, ServerObject->InstancedStruct.Get().SomeFloat); // Modify ServerObject->InstancedStruct.GetMutable().SomeFloat += 1.0f; // Replicate Server->UpdateAndSend({ Client }); // Verify that we detected the modification UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().SomeFloat, ServerObject->InstancedStruct.Get().SomeFloat); // Switch type ServerObject->InstancedStruct.InitializeAs(); ServerObject->InstancedStruct.GetMutable().SomeUint16 = 100; // Replicate Server->UpdateAndSend({ Client }); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct, ServerObject->InstancedStruct); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().SomeUint16, ServerObject->InstancedStruct.Get().SomeUint16); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, ModifyArray) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UInstancedStructNetSerializerTestObject* ServerObject = Server->CreateObject(); // Add entries to the array ServerObject->InstancedStructArray.Add(FInstancedStruct::Make()); ServerObject->InstancedStructArray.Add(FInstancedStruct::Make()); ServerObject->InstancedStructArray.Add(FInstancedStruct::Make()); ServerObject->InstancedStructArray.Add(FInstancedStruct::Make()); ServerObject->InstancedStructArray[1].GetMutable().SomeFloat = 13.0f; // Replicate Server->UpdateAndSend({ Client }); auto ClientObject = Client->GetObjectAs(ServerObject->NetRefHandle); UE_NET_ASSERT_NE(ClientObject, nullptr); CA_ASSUME(ClientObject != nullptr); UE_NET_ASSERT_EQ(ClientObject->InstancedStructArray.Num(), ServerObject->InstancedStructArray.Num()); UE_NET_ASSERT_EQ(ClientObject->InstancedStructArray[1].Get().SomeFloat, 13.0f); // Modify value and see that it is replicated as expected ServerObject->InstancedStructArray[1].GetMutable().SomeFloat += 2.0f; // Replicate Server->UpdateAndSend({ Client }); // Verified that the client got the modified value UE_NET_ASSERT_EQ(ClientObject->InstancedStructArray[1].Get().SomeFloat, ServerObject->InstancedStructArray[1].Get().SomeFloat); // Switch type ServerObject->InstancedStructArray[2].InitializeAs(); ServerObject->InstancedStructArray[2].GetMutable().SomeUint16 += 1; // Replicate Server->UpdateAndSend({ Client }); // Verified that the client got the modified value UE_NET_ASSERT_EQ(ClientObject->InstancedStructArray[2], ClientObject->InstancedStructArray[2]); UE_NET_ASSERT_EQ(ClientObject->InstancedStructArray[2].Get().SomeUint16, ServerObject->InstancedStructArray[2].Get().SomeUint16); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, NonReplicatedPropertiesAreNotClobbered) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UInstancedStructNetSerializerTestObject* ServerObject = Server->CreateObject(); // Initialize struct with some non-replicated properties. ServerObject->InstancedStruct.InitializeAs(); ServerObject->InstancedStruct.GetMutable().IntA = 1333; // Replicate Server->UpdateAndSend({ Client }); UInstancedStructNetSerializerTestObject* ClientObject = Client->GetObjectAs(ServerObject->NetRefHandle); UE_NET_ASSERT_NE(ClientObject, nullptr); CA_ASSUME(ClientObject != nullptr); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().IntA, ServerObject->InstancedStruct.Get().IntA); // Modify value and verify that it's replicated as expected ServerObject->InstancedStruct.GetMutable().IntA = 1337; // Modify non-replicated property on the client ClientObject->InstancedStruct.GetMutable().IntB = 1331; // Replicate Server->UpdateAndSend({ Client }); // Verified that the client got the modified value UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().IntA, ServerObject->InstancedStruct.Get().IntA); // Verified that the non-replicated property remained unchanged on the client UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().IntB, 1331); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, NetSerializerApplyIsCalled) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UInstancedStructNetSerializerTestObject* ServerObject = Server->CreateObject(); // Initialize struct with custom apply. ServerObject->InstancedStruct.InitializeAs(); ServerObject->InstancedStruct.GetMutable().Uint = 1333; // Replicate Server->UpdateAndSend({ Client }); UInstancedStructNetSerializerTestObject* ClientObject = Client->GetObjectAs(ServerObject->NetRefHandle); UE_NET_ASSERT_NE(ClientObject, nullptr); CA_ASSUME(ClientObject != nullptr); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().Uint, ServerObject->InstancedStruct.Get().Uint); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().ApplyCallCount, 1U); // Modify value and verify that it's replicated as expected ServerObject->InstancedStruct.GetMutable().Uint = 1337; // Modify Apply handled property and verify that it doesn't overwrite the client value ServerObject->InstancedStruct.GetMutable().ApplyCallCount = 1337; // Replicate Server->UpdateAndSend({ Client }); // Verified that the client got the modified value UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().Uint, ServerObject->InstancedStruct.Get().Uint); // Verified that the custom handled property in Apply is as expected UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().ApplyCallCount, 2U); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, CustomSerializedStructIsOverwritten) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UInstancedStructNetSerializerTestObject* ServerObject = Server->CreateObject(); // Initialize struct with custom serializer. ServerObject->InstancedStruct.InitializeAs(); ServerObject->InstancedStruct.GetMutable().Uint = 1333; // Replicate Server->UpdateAndSend({ Client }); UInstancedStructNetSerializerTestObject* ClientObject = Client->GetObjectAs(ServerObject->NetRefHandle); UE_NET_ASSERT_NE(ClientObject, nullptr); CA_ASSUME(ClientObject != nullptr); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().Uint, ServerObject->InstancedStruct.Get().Uint); // Modify value and verify that it's replicated as expected ServerObject->InstancedStruct.GetMutable().Uint = 1337; // Modify not replicated client values and verify they're overwritten since it's a custom serializer without apply ClientObject->InstancedStruct.GetMutable().NotReplicatedUint = 1338; ClientObject->InstancedStruct.GetMutable().NotPropertyUint = 1339; // Replicate Server->UpdateAndSend({ Client }); // Verified that the client got the modified value UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().Uint, ServerObject->InstancedStruct.Get().Uint); // Verified that the not replicated properties did get overwritten UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().NotReplicatedUint, ServerObject->InstancedStruct.Get().NotReplicatedUint); UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().NotPropertyUint, ServerObject->InstancedStruct.Get().NotPropertyUint); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, NotFullyReplicatedStructIsAppliedCorrectly) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UInstancedStructNetSerializerTestObject* ServerObject = Server->CreateObject(); // Initialize with a not fully replicated struct ServerObject->InstancedStruct.InitializeAs(); // Replicate Server->UpdateAndSend({ Client }); UInstancedStructNetSerializerTestObject* ClientObject = Client->GetObjectAs(ServerObject->NetRefHandle); // Modify not replicated value on the client ClientObject->InstancedStruct.GetMutable().IntB = 1337; // Modify value on server to trigger replication and apply of the struct ServerObject->InstancedStruct.GetMutable().IntA += 1; // Replicate Server->UpdateAndSend({ Client }); // Verified that the client got the modified value UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().IntA, ServerObject->InstancedStruct.Get().IntA); // Verify the not replicated value remained as previously set UE_NET_ASSERT_EQ(ClientObject->InstancedStruct.Get().IntB, 1337); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, NotFullyReplicatedStructInArrayIsAppliedCorrectly) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UInstancedStructNetSerializerTestObject* ServerObject = Server->CreateObject(); // Initialize an array entry with a not fully replicated struct ServerObject->InstancedStructArray.Add(FInstancedStruct::Make()); // Replicate Server->UpdateAndSend({ Client }); UInstancedStructNetSerializerTestObject* ClientObject = Client->GetObjectAs(ServerObject->NetRefHandle); // Modify not replicated value on the client ClientObject->InstancedStructArray[0].GetMutable().IntB = 1337; // Modify value on server to trigger replication and apply of the struct ServerObject->InstancedStructArray[0].GetMutable().IntA += 1; // Replicate Server->UpdateAndSend({ Client }); // Verified that the client got the modified value UE_NET_ASSERT_EQ(ClientObject->InstancedStructArray[0].Get().IntA, ServerObject->InstancedStructArray[0].Get().IntA); // Verify the not replicated value remained as previously set UE_NET_ASSERT_EQ(ClientObject->InstancedStructArray[0].Get().IntB, 1337); } UE_NET_TEST_FIXTURE(FTestInstancedStructNetSerializerFixture, NotFullyReplicatedStructInArrayInStructIsAppliedCorrectly) { // Add a client FReplicationSystemTestClient* Client = CreateClient(); // Spawn objects on server UInstancedStructNetSerializerTestObject* ServerObject = Server->CreateObject(); // Initialize an array entry with a not fully replicated struct ServerObject->StructWithInstancedStructArray.InstancedStructArray.Add(FInstancedStruct::Make()); // Replicate Server->UpdateAndSend({ Client }); UInstancedStructNetSerializerTestObject* ClientObject = Client->GetObjectAs(ServerObject->NetRefHandle); // Modify not replicated value on the client ClientObject->StructWithInstancedStructArray.InstancedStructArray[0].GetMutable().IntB = 1337; // Modify value on server to trigger replication and apply of the struct ServerObject->StructWithInstancedStructArray.InstancedStructArray[0].GetMutable().IntA += 1; // Replicate Server->UpdateAndSend({ Client }); // Verified that the client got the modified value UE_NET_ASSERT_EQ(ClientObject->StructWithInstancedStructArray.InstancedStructArray[0].Get().IntA, ServerObject->StructWithInstancedStructArray.InstancedStructArray[0].Get().IntA); // Verify the not replicated value remained as previously set UE_NET_ASSERT_EQ(ClientObject->StructWithInstancedStructArray.InstancedStructArray[0].Get().IntB, 1337); } // Fixture implementation void FTestInstancedStructNetSerializerFixture::SetUp() { FReplicationSystemServerClientTestFixture::SetUp(); // Init default serialization context InternalNetSerializationContext.ReplicationSystem = Server->ReplicationSystem; FInternalNetSerializationContext TempInternalNetSerializationContext; FInternalNetSerializationContext::FInitParameters TempInternalNetSerializationContextInitParams; TempInternalNetSerializationContextInitParams.ReplicationSystem = Server->ReplicationSystem; TempInternalNetSerializationContextInitParams.ObjectResolveContext.RemoteNetTokenStoreState = Server->ReplicationSystem->GetNetTokenStore()->GetLocalNetTokenStoreState(); TempInternalNetSerializationContext.Init(TempInternalNetSerializationContextInitParams); InternalNetSerializationContext = MoveTemp(TempInternalNetSerializationContext); NetSerializationContext.SetInternalContext(&InternalNetSerializationContext); FMemory::Memzero(QuantizedBuffer, sizeof(QuantizedBuffer)); bHasQuantizedState = false; bHasClonedQuantizedState = false; if (!InstancedStructNetSerializerConfig.StateDescriptor.IsValid()) { FReplicationStateDescriptorBuilder::FParameters Params; InstancedStructNetSerializerConfig.StateDescriptor = FReplicationStateDescriptorBuilder::CreateDescriptorForStruct(FTestInstancedStruct::StaticStruct(), Params); } if (!InstancedStructArrayNetSerializerConfig.StateDescriptor.IsValid()) { FReplicationStateDescriptorBuilder::FParameters Params; InstancedStructArrayNetSerializerConfig.StateDescriptor = FReplicationStateDescriptorBuilder::CreateDescriptorForStruct(FTestInstancedStructArray::StaticStruct(), Params); } } void FTestInstancedStructNetSerializerFixture::TearDown() { InstancedStruct0.Reset(); InstancedStruct1.Reset(); InstancedStructArray0.Reset(); InstancedStructArray1.Reset(); FreeQuantizedState(); FReplicationSystemServerClientTestFixture::TearDown(); } void FTestInstancedStructNetSerializerFixture::Serialize() { // Must have run quantize before this UE_NET_ASSERT_TRUE(bHasQuantizedState); // Serialize data Writer.InitBytes(BitStreamBuffer, sizeof(BitStreamBuffer)); FNetSerializationContext Context(&Writer); Context.SetInternalContext(NetSerializationContext.GetInternalContext()); FNetSerializeArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Source = NetSerializerValuePointer(&QuantizedBuffer[0]); GetInstancedStructNetSerializer(bIsTestingArray)->Serialize(Context, Args); Writer.CommitWrites(); UE_NET_ASSERT_FALSE(Context.HasError()); UE_NET_ASSERT_GT(Writer.GetPosBits(), 0U); } void FTestInstancedStructNetSerializerFixture::Deserialize() { // Check pre-conditions UE_NET_ASSERT_FALSE(bHasQuantizedState); UE_NET_ASSERT_GT(Writer.GetPosBytes(), 0U); Reader.InitBits(BitStreamBuffer, Writer.GetPosBits()); FNetSerializationContext Context(&Reader); Context.SetInternalContext(NetSerializationContext.GetInternalContext()); FNetDeserializeArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Target = NetSerializerValuePointer(&QuantizedBuffer[0]); GetInstancedStructNetSerializer(bIsTestingArray)->Deserialize(Context, Args); bHasQuantizedState = true; UE_NET_ASSERT_FALSE(Context.HasErrorOrOverflow()); UE_NET_ASSERT_GT(Reader.GetPosBits(), 0U); } void FTestInstancedStructNetSerializerFixture::SerializeDelta() { // Check pre-conditions UE_NET_ASSERT_TRUE(bHasQuantizedState); UE_NET_ASSERT_EQ(QuantizedStateCount, 2U); // Serialize data Writer.InitBytes(BitStreamBuffer, sizeof(BitStreamBuffer)); FNetSerializationContext Context(&Writer); Context.SetInternalContext(NetSerializationContext.GetInternalContext()); FNetSerializeDeltaArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Source = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Prev = NetSerializerValuePointer(&QuantizedBuffer[1]); GetInstancedStructNetSerializer(bIsTestingArray)->SerializeDelta(Context, Args); Writer.CommitWrites(); UE_NET_ASSERT_FALSE(Context.HasErrorOrOverflow()); UE_NET_ASSERT_GT(Writer.GetPosBits(), 0U); } void FTestInstancedStructNetSerializerFixture::DeserializeDelta() { // Check pre-conditions UE_NET_ASSERT_GT(Writer.GetPosBytes(), 0U); Reader.InitBits(BitStreamBuffer, Writer.GetPosBits()); FNetSerializationContext Context(&Reader); Context.SetInternalContext(NetSerializationContext.GetInternalContext()); FNetDeserializeDeltaArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Target = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Prev = NetSerializerValuePointer(&QuantizedBuffer[1]); GetInstancedStructNetSerializer(bIsTestingArray)->DeserializeDelta(Context, Args); bHasQuantizedState = true; QuantizedStateCount = 1; UE_NET_ASSERT_FALSE(Context.HasErrorOrOverflow()); UE_NET_ASSERT_GT(Reader.GetPosBits(), 0U); } void FTestInstancedStructNetSerializerFixture::Quantize() { FNetQuantizeArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Target = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Source = bIsTestingArray ? NetSerializerValuePointer(&InstancedStructArray0) : NetSerializerValuePointer(&InstancedStruct0); GetInstancedStructNetSerializer(bIsTestingArray)->Quantize(NetSerializationContext, Args); bHasQuantizedState = true; QuantizedStateCount = 1; UE_NET_ASSERT_FALSE(NetSerializationContext.HasError()); } void FTestInstancedStructNetSerializerFixture::QuantizeTwoStates() { Quantize(); FNetQuantizeArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Target = NetSerializerValuePointer(&QuantizedBuffer[1]); Args.Source = bIsTestingArray ? NetSerializerValuePointer(&InstancedStructArray1) : NetSerializerValuePointer(&InstancedStruct1); GetInstancedStructNetSerializer(bIsTestingArray)->Quantize(NetSerializationContext, Args); bHasQuantizedState = true; QuantizedStateCount = 2; UE_NET_ASSERT_FALSE(NetSerializationContext.HasError()); } void FTestInstancedStructNetSerializerFixture::Clone() { // Check pre-conditions UE_NET_ASSERT_TRUE(bHasQuantizedState); FMemory::Memcpy(ClonedQuantizedBuffer[0], QuantizedBuffer[0], sizeof(QuantizedBuffer[0])); FNetCloneDynamicStateArgs Args = {}; Args.Source = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Target = NetSerializerValuePointer(&ClonedQuantizedBuffer[0]); Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); GetInstancedStructNetSerializer(bIsTestingArray)->CloneDynamicState(NetSerializationContext, Args); bHasClonedQuantizedState = true; ClonedQuantizedStateCount = 1; } void FTestInstancedStructNetSerializerFixture::FreeQuantizedState() { FNetFreeDynamicStateArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); if (bHasQuantizedState) { for (uint32 StateIt = 0, StateEndIt = QuantizedStateCount; StateIt != StateEndIt; ++StateIt) { Args.Source = NetSerializerValuePointer(&QuantizedBuffer[StateIt]); GetInstancedStructNetSerializer(bIsTestingArray)->FreeDynamicState(NetSerializationContext, Args); FMemory::Memzero(&QuantizedBuffer[StateIt], sizeof(QuantizedBuffer[StateIt])); } bHasQuantizedState = false; } if (bHasClonedQuantizedState) { for (uint32 StateIt = 0, StateEndIt = ClonedQuantizedStateCount; StateIt != StateEndIt; ++StateIt) { Args.Source = NetSerializerValuePointer(&ClonedQuantizedBuffer[StateIt]); GetInstancedStructNetSerializer(bIsTestingArray)->FreeDynamicState(NetSerializationContext, Args); FMemory::Memzero(&ClonedQuantizedBuffer[StateIt], sizeof(ClonedQuantizedBuffer[StateIt])); } bHasClonedQuantizedState = false; } } void FTestInstancedStructNetSerializerFixture::Dequantize() { UE_NET_ASSERT_TRUE(bHasQuantizedState); FNetDequantizeArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Source = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Target = bIsTestingArray ? NetSerializerValuePointer(&InstancedStructArray1) : NetSerializerValuePointer(&InstancedStruct1); GetInstancedStructNetSerializer(bIsTestingArray)->Dequantize(NetSerializationContext, Args); } bool FTestInstancedStructNetSerializerFixture::IsEqual(bool bQuantized) { if (bQuantized) { UE_NET_EXPECT_TRUE(bHasQuantizedState); if (!bHasQuantizedState) { return false; } UE_NET_EXPECT_TRUE(bHasClonedQuantizedState); if (!bHasClonedQuantizedState) { return false; } } FNetIsEqualArgs Args = {}; if (bQuantized) { Args.Source0 = NetSerializerValuePointer(&QuantizedBuffer[0]); Args.Source1 = NetSerializerValuePointer(&ClonedQuantizedBuffer[0]); } else { Args.Source0 = bIsTestingArray ? NetSerializerValuePointer(&InstancedStructArray0) : NetSerializerValuePointer(&InstancedStruct0); Args.Source1 = bIsTestingArray ? NetSerializerValuePointer(&InstancedStructArray1) : NetSerializerValuePointer(&InstancedStruct1); } Args.bStateIsQuantized = bQuantized; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); return GetInstancedStructNetSerializer(bIsTestingArray)->IsEqual(NetSerializationContext, Args); } void FTestInstancedStructNetSerializerFixture::Validate() { FNetValidateArgs Args = {}; Args.NetSerializerConfig = NetSerializerConfigParam(GetInstancedStructNetSerializerConfig(bIsTestingArray)); Args.Source = bIsTestingArray ? NetSerializerValuePointer(&InstancedStructArray0) : NetSerializerValuePointer(&InstancedStruct0); GetInstancedStructNetSerializer(bIsTestingArray)->Validate(NetSerializationContext, Args); } void FTestInstancedStructNetSerializerFixture::SetNonDefaultInstanceState() { InstancedStruct0.InitializeAs(); InstancedStruct0.GetMutable().SomeUint16 += 4711U; } void FTestInstancedStructNetSerializerFixture::SetNonDefaultArrayState() { InstancedStructArray0.Empty(7); InstancedStructArray0.SetNum(7); InstancedStructArray0[0].InitializeAs(); InstancedStructArray0[1].InitializeAs(); InstancedStructArray0[1].GetMutable().SomeFloat = 1234.5f; InstancedStructArray0[2].Reset(); InstancedStructArray0[3].InitializeAs(); InstancedStructArray0[3].GetMutable().SomeBool ^= true; InstancedStructArray0[4].Reset(); InstancedStructArray0[5].InitializeAs(); InstancedStructArray0[6].InitializeAs(); } const FNetSerializer* FTestInstancedStructNetSerializerFixture::GetInstancedStructNetSerializer(bool bIsArray) const { if (bIsArray) { if (const FReplicationStateDescriptor* Desc = InstancedStructArrayNetSerializerConfig.StateDescriptor.GetReference()) { return Desc->MemberSerializerDescriptors[0].Serializer; } } else { if (const FReplicationStateDescriptor* Desc = InstancedStructNetSerializerConfig.StateDescriptor.GetReference()) { return Desc->MemberSerializerDescriptors[0].Serializer; } } return nullptr; } const FNetSerializerConfig* FTestInstancedStructNetSerializerFixture::GetInstancedStructNetSerializerConfig(bool bIsArray) const { if (bIsArray) { if (const FReplicationStateDescriptor* Desc = InstancedStructArrayNetSerializerConfig.StateDescriptor.GetReference()) { return Desc->MemberSerializerDescriptors[0].SerializerConfig; } } else { if (const FReplicationStateDescriptor* Desc = InstancedStructNetSerializerConfig.StateDescriptor.GetReference()) { return Desc->MemberSerializerDescriptors[0].SerializerConfig; } } return nullptr; } } namespace UE::Net { void FStructForInstancedStructTestWithCustomApplyNetSerializer::Serialize(FNetSerializationContext& Context, const FNetSerializeArgs& Args) { const QuantizedType Value = *reinterpret_cast(Args.Source); Context.GetBitStreamWriter()->WriteBits(Value, 32); } void FStructForInstancedStructTestWithCustomApplyNetSerializer::Deserialize(FNetSerializationContext& Context, const FNetDeserializeArgs& Args) { QuantizedType& Target = *reinterpret_cast(Args.Target); Target = Context.GetBitStreamReader()->ReadBits(32); } void FStructForInstancedStructTestWithCustomApplyNetSerializer::Quantize(FNetSerializationContext&, const FNetQuantizeArgs& Args) { const SourceType& Source = *reinterpret_cast(Args.Source); QuantizedType& Target = *reinterpret_cast(Args.Target); Target = Source.Uint; } void FStructForInstancedStructTestWithCustomApplyNetSerializer::Dequantize(FNetSerializationContext&, const FNetDequantizeArgs& Args) { const QuantizedType& Source = *reinterpret_cast(Args.Source); SourceType& Target = *reinterpret_cast(Args.Target); Target.Uint = Source; } bool FStructForInstancedStructTestWithCustomApplyNetSerializer::IsEqual(FNetSerializationContext&, const FNetIsEqualArgs& Args) { if (Args.bStateIsQuantized) { const QuantizedType& QuantizedValue0 = *reinterpret_cast(Args.Source0); const QuantizedType& QuantizedValue1 = *reinterpret_cast(Args.Source1); return QuantizedValue0 == QuantizedValue1; } else { const SourceType& SourceValue0 = *reinterpret_cast(Args.Source0); const SourceType& SourceValue1 = *reinterpret_cast(Args.Source1); return SourceValue0.Uint == SourceValue1.Uint; } } void FStructForInstancedStructTestWithCustomApplyNetSerializer::Apply(FNetSerializationContext& Context, const FNetApplyArgs& Args) { const SourceType& Source = *reinterpret_cast(Args.Source); SourceType& Target = *reinterpret_cast(Args.Target); Target.Uint = Source.Uint; Target.ApplyCallCount++; } FStructForInstancedStructTestWithCustomApplyNetSerializer::FNetSerializerRegistryDelegates::~FNetSerializerRegistryDelegates() { UE_NET_UNREGISTER_NETSERIALIZER_INFO(StructForInstancedStructTestWithCustomApplyNetSerializer); } void FStructForInstancedStructTestWithCustomApplyNetSerializer::FNetSerializerRegistryDelegates::OnPreFreezeNetSerializerRegistry() { UE_NET_REGISTER_NETSERIALIZER_INFO(StructForInstancedStructTestWithCustomApplyNetSerializer); } void FStructForInstancedStructTestWithCustomSerializerNetSerializer::Serialize(FNetSerializationContext& Context, const FNetSerializeArgs& Args) { const QuantizedType Value = *reinterpret_cast(Args.Source); Context.GetBitStreamWriter()->WriteBits(Value, 32); } void FStructForInstancedStructTestWithCustomSerializerNetSerializer::Deserialize(FNetSerializationContext& Context, const FNetDeserializeArgs& Args) { QuantizedType& Target = *reinterpret_cast(Args.Target); Target = Context.GetBitStreamReader()->ReadBits(32); } void FStructForInstancedStructTestWithCustomSerializerNetSerializer::Quantize(FNetSerializationContext&, const FNetQuantizeArgs& Args) { const SourceType& Source = *reinterpret_cast(Args.Source); QuantizedType& Target = *reinterpret_cast(Args.Target); Target = Source.Uint; } void FStructForInstancedStructTestWithCustomSerializerNetSerializer::Dequantize(FNetSerializationContext&, const FNetDequantizeArgs& Args) { const QuantizedType& Source = *reinterpret_cast(Args.Source); SourceType& Target = *reinterpret_cast(Args.Target); Target.Uint = Source; } bool FStructForInstancedStructTestWithCustomSerializerNetSerializer::IsEqual(FNetSerializationContext&, const FNetIsEqualArgs& Args) { if (Args.bStateIsQuantized) { const QuantizedType& QuantizedValue0 = *reinterpret_cast(Args.Source0); const QuantizedType& QuantizedValue1 = *reinterpret_cast(Args.Source1); return QuantizedValue0 == QuantizedValue1; } else { const SourceType& SourceValue0 = *reinterpret_cast(Args.Source0); const SourceType& SourceValue1 = *reinterpret_cast(Args.Source1); return SourceValue0.Uint == SourceValue1.Uint; } } FStructForInstancedStructTestWithCustomSerializerNetSerializer::FNetSerializerRegistryDelegates::~FNetSerializerRegistryDelegates() { UE_NET_UNREGISTER_NETSERIALIZER_INFO(StructForInstancedStructTestWithCustomSerializerNetSerializer); } void FStructForInstancedStructTestWithCustomSerializerNetSerializer::FNetSerializerRegistryDelegates::OnPreFreezeNetSerializerRegistry() { UE_NET_REGISTER_NETSERIALIZER_INFO(StructForInstancedStructTestWithCustomSerializerNetSerializer); } } // void UInstancedStructNetSerializerTestObject::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { FDoRepLifetimeParams Params; Params.bIsPushBased = false; DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, InstancedStruct, Params); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, InstancedStructArray, Params); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, StructWithInstancedStructArray, Params); } void UInstancedStructNetSerializerTestObject::RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags) { UE::Net::FReplicationFragmentUtil::CreateAndRegisterFragmentsForObject(this, Context, RegistrationFlags); }