Files
UnrealEngine/Engine/Plugins/Runtime/ReplicationSystemTestPlugin/Source/Private/Tests/ReplicationState/TestFastArrayReplicationState.cpp
2025-05-18 13:04:45 +08:00

995 lines
41 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TestFastArrayReplicationState.h"
#include "Tests/ReplicationSystem/ReplicationSystemServerClientTestFixture.h"
#include "NetworkAutomationTest.h"
#include "NetworkAutomationTestMacros.h"
#include "Iris/Core/IrisLog.h"
#include "Iris/ReplicationState/InternalReplicationStateDescriptorUtils.h"
#include "Iris/ReplicationState/ReplicationStateDescriptorBuilder.h"
#include "Iris/ReplicationState/ReplicationStateUtil.h"
#include "Iris/ReplicationSystem/ReplicationSystem.h"
#include "Iris/ReplicationSystem/ReplicationFragmentUtil.h"
#include "Iris/Serialization/NetSerializers.h"
#include "Iris/Serialization/InternalNetSerializerUtils.h"
#include "Logging/LogScopedVerbosityOverride.h"
#include "Misc/EnumClassFlags.h"
#include "Net/UnrealNetwork.h"
#include "Iris/ReplicationSystem/Filtering/NetObjectFilter.h"
#include "Iris/ReplicationSystem/FastArrayReplicationFragment.h"
UTestFastArrayReplicationState_FastArray_TestClassFastArray::UTestFastArrayReplicationState_FastArray_TestClassFastArray()
: UReplicatedTestObject()
{
}
void UTestFastArrayReplicationState_FastArray_TestClassFastArray::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
FDoRepLifetimeParams Params;
Params.bIsPushBased = true;
DOREPLIFETIME_WITH_PARAMS_FAST(UTestFastArrayReplicationState_FastArray_TestClassFastArray, FastArray, Params);
}
void UTestFastArrayReplicationState_FastArray_TestClassFastArray::RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags)
{
UE::Net::FReplicationFragmentUtil::CreateAndRegisterFragmentsForObject(this, Context, RegistrationFlags);
}
UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty::UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty()
: UReplicatedTestObject()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Note: this is a test for something that should not be done. This is purely to avoid refactoring existing code before full transition to iris.
// A FastArraySerializer struct should not contain any additional data.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class FTestFastArrayReplicationState_FastArrayWithExtraProperty::FastArrayWithExtraPropertiesReplicationFragment : public UE::Net::TFastArrayReplicationFragment<typename TFastArrayTypeHelper<FTestFastArrayReplicationState_FastArrayWithExtraProperty>::FastArrayItemType, FTestFastArrayReplicationState_FastArrayWithExtraProperty>
{
public:
typedef TFastArrayReplicationFragment<typename TFastArrayTypeHelper<FTestFastArrayReplicationState_FastArrayWithExtraProperty>::FastArrayItemType, FTestFastArrayReplicationState_FastArrayWithExtraProperty> SuperT;
FastArrayWithExtraPropertiesReplicationFragment(UE::Net::EReplicationFragmentTraits InTraits, UObject* InOwner, const UE::Net::FReplicationStateDescriptor* InDescriptor)
: SuperT(InTraits, InOwner, InDescriptor, EAllowAdditionalPropertiesType::AllowAdditionalProperties)
{
}
virtual void ApplyReplicatedState(UE::Net::FReplicationStateApplyContext& Context) const override
{
using namespace UE::Net;
using namespace UE::Net::Private;
// Forward to fast array serializer
SuperT::ApplyReplicatedState(Context);
// Must explicitly deal with extra properties here, but there is a utility function to do it for us!
SuperT::ApplyReplicatedStateForExtraProperties(Context);
}
virtual bool PollReplicatedState(UE::Net::EReplicationFragmentPollFlags PollOption) override
{
// Lookup source data, we need the actual FastArraySerializer and the Array it is wrapping
FTestFastArrayReplicationState_FastArrayWithExtraProperty* SrcArraySerializer = this->GetFastArraySerializerFromOwner();
// Lookup destination data
FTestFastArrayReplicationState_FastArrayWithExtraProperty* DstArraySerializer = this->GetFastArraySerializerFromReplicationState();
// Explicit poll of additional properties in the fastarray and mark dirty if needed
if (SrcArraySerializer->ExtraInt != DstArraySerializer->ExtraInt)
{
this->MarkDirty();
}
DstArraySerializer->ExtraInt = SrcArraySerializer->ExtraInt;
return SuperT::PollReplicatedState(PollOption);
}
};
UE::Net::FReplicationFragment* FTestFastArrayReplicationState_FastArrayWithExtraProperty::CreateAndRegisterReplicationFragment(UObject* Owner, const UE::Net::FReplicationStateDescriptor* Descriptor, UE::Net::FFragmentRegistrationContext& Context)
{
using namespace UE::Net;
static_assert(TFastArrayTypeHelper<FTestFastArrayReplicationState_FastArrayWithExtraProperty>::HasValidFastArrayItemType(), "Invalid FastArrayItemType detected. Make sure that FastArraySerializer has a single replicated dynamic array");
if (FTestFastArrayReplicationState_FastArrayWithExtraProperty::FastArrayWithExtraPropertiesReplicationFragment* Fragment = new FTestFastArrayReplicationState_FastArrayWithExtraProperty::FastArrayWithExtraPropertiesReplicationFragment(Context.GetFragmentTraits(), Owner, Descriptor))
{
Fragment->Register(Context, EReplicationFragmentTraits::DeleteWithInstanceProtocol);
return Fragment;
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
void UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
FDoRepLifetimeParams Params;
Params.CreateAndRegisterReplicationFragmentFunction = &FTestFastArrayReplicationState_FastArrayWithExtraProperty::CreateAndRegisterReplicationFragment;
DOREPLIFETIME_WITH_PARAMS_FAST(UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty, FastArray, Params);
}
void UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty::RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags)
{
// Explicitly allow building descriptors for FastArray with additional properties to be allow to test this special case.
UE::Net::FReplicationFragmentUtil::CreateAndRegisterFragmentsForObject(this, Context, RegistrationFlags | UE::Net::EFragmentRegistrationFlags::AllowFastArraysWithAdditionalProperties);
}
void FTestFastArrayReplicationState_FastArrayItem::PostReplicatedAdd(const struct FTestFastArrayReplicationState_FastArraySerializer& InArraySerializer)
{
const_cast<FTestFastArrayReplicationState_FastArraySerializer&>(InArraySerializer).bHitReplicatedAdd = true;
}
void FTestFastArrayReplicationState_FastArrayItem::PostReplicatedChange(const struct FTestFastArrayReplicationState_FastArraySerializer& InArraySerializer)
{
const_cast<FTestFastArrayReplicationState_FastArraySerializer&>(InArraySerializer).bHitReplicatedChange = true;
}
void FTestFastArrayReplicationState_FastArrayItem::PreReplicatedRemove(const struct FTestFastArrayReplicationState_FastArraySerializer& InArraySerializer)
{
const_cast<FTestFastArrayReplicationState_FastArraySerializer&>(InArraySerializer).bHitReplicatedRemove = true;
}
namespace UE::Net::Private
{
struct FTestFastArrayReplicationStateContext : public FNetworkAutomationTestSuiteFixture
{
FReplicationStateDescriptorBuilder::FResult Descriptors;
const FReplicationStateDescriptor* Descriptor;
void InitDescriptor(UClass* StaticClass, int32 RepIndex = -1)
{
FReplicationStateDescriptorBuilder::FParameters BuilderParameters;
BuilderParameters.DescriptorRegistry = nullptr;
BuilderParameters.SinglePropertyIndex = RepIndex;
if (FReplicationStateDescriptorBuilder::CreateDescriptorsForClass(Descriptors, StaticClass, BuilderParameters))
{
Descriptor = Descriptors[0];
}
else
{
Descriptor = nullptr;
}
}
void ValidateFastArrayDescriptor(const FReplicationStateDescriptor* FastArrayDescriptor)
{
// Verify class descriptor
UE_NET_ASSERT_TRUE(FastArrayDescriptor != nullptr);
UE_NET_ASSERT_TRUE(EnumHasAnyFlags(FastArrayDescriptor->Traits, EReplicationStateTraits::IsFastArrayReplicationState));
UE_NET_ASSERT_EQ((uint32)FastArrayDescriptor->MemberCount, 1U);
UE_NET_ASSERT_TRUE(IsUsingStructNetSerializer(FastArrayDescriptor->MemberSerializerDescriptors[0]));
// Verify Fast array descriptor
const FReplicationStateDescriptor* FastArrayStructDescriptor = static_cast<const FStructNetSerializerConfig*>(FastArrayDescriptor->MemberSerializerDescriptors[0].SerializerConfig)->StateDescriptor;
UE_NET_ASSERT_EQ((uint32)FastArrayStructDescriptor->MemberCount, 1U);
// This is the struct descriptor describing the array property
UE_NET_ASSERT_TRUE(IsUsingArrayPropertyNetSerializer(FastArrayStructDescriptor->MemberSerializerDescriptors[0]));
const FReplicationStateDescriptor* FastArrayItemsDescriptor = static_cast<const FArrayPropertyNetSerializerConfig*>(FastArrayStructDescriptor->MemberSerializerDescriptors[0].SerializerConfig)->StateDescriptor;
UE_NET_ASSERT_EQ((uint32)FastArrayItemsDescriptor->MemberCount, 1U);
UE_NET_ASSERT_TRUE(IsUsingStructNetSerializer(FastArrayItemsDescriptor->MemberSerializerDescriptors[0]));
const FReplicationStateDescriptor* FastArrayItemDescriptor = static_cast<const FStructNetSerializerConfig*>(FastArrayItemsDescriptor->MemberSerializerDescriptors[0].SerializerConfig)->StateDescriptor;
UE_NET_ASSERT_EQ((uint32)FastArrayItemDescriptor->MemberCount, 4U);
}
};
UE_NET_TEST_FIXTURE(FTestFastArrayReplicationStateContext, BuildClassDescriptorForSingleProperty)
{
InitDescriptor(UTestFastArrayReplicationState_FastArray_TestClassFastArray::StaticClass(), (int32)UTestFastArrayReplicationState_FastArray_TestClassFastArray::ENetFields_Private::FastArray);
ValidateFastArrayDescriptor(Descriptor);
}
UE_NET_TEST_FIXTURE(FTestFastArrayReplicationStateContext, BuildClassDescriptorForSinglePropertyForFastArrayWithExtraProperty)
{
// We expect this test to fail as we do not pass the flag allowing fastarrays to contain extra properties
{
LOG_SCOPE_VERBOSITY_OVERRIDE(LogIris, ELogVerbosity::Fatal);
InitDescriptor(UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty::StaticClass(), (int32)UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty::ENetFields_Private::FastArray);
}
UE_NET_ASSERT_FALSE(EnumHasAnyFlags(Descriptor->Traits, EReplicationStateTraits::IsFastArrayReplicationState));
}
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestAddItem)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify initial replication
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// Add data to array
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
ServerObject->FastArray.Edit().Add(NewEntry);
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 1U;
ServerObject->FastArray.Edit().Add(NewEntry);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// In this case we expect order to be the same
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
}
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestRemoveItem)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify initial replication
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// Add data to array
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
ServerObject->FastArray.Edit().Add(NewEntry);
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 1U;
ServerObject->FastArray.Edit().Add(NewEntry);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// In this case we expect order to be the same
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Remove Item
ServerObject->FastArray.Edit().Remove(0);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedRemove);
}
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestLocalItemArePreserved)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify initial replication
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// Add local data to client array
{
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
ClientObject->FastArray.Edit().AddLocal(NewEntry);
}
// Add data to array
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 1U;
ServerObject->FastArray.Edit().Add(NewEntry);
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 2U;
ServerObject->FastArray.Edit().Add(NewEntry);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(3, ClientObject->FastArray.GetItemArray().Num());
// Verify local item
UE_NET_ASSERT_EQ(0, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[1].RepInt32);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[1].RepInt32, ClientObject->FastArray.GetItemArray()[2].RepInt32);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Remove Item
ServerObject->FastArray.Edit().Remove(0);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the remove
UE_NET_ASSERT_EQ(2, ClientObject->FastArray.GetItemArray().Num());
// Verify local item
UE_NET_ASSERT_EQ(0, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[1].RepInt32);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedRemove);
}
// Add test for change item
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestModifyItem)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify initial replication
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// Add data to array
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
ServerObject->FastArray.Edit().Add(NewEntry);
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 1U;
ServerObject->FastArray.Edit().Add(NewEntry);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// In this case we expect order to be the same
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
ClientObject->FastArray.bHitReplicatedAdd = false;
ClientObject->FastArray.bHitReplicatedChange = false;
ClientObject->FastArray.bHitReplicatedRemove = false;
// Modify data
ServerObject->FastArray.Edit()[0].RepInt32 = 3U;
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the modified data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
}
// Add test for change item
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestInsertItem)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify initial replication
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// Add data to array
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
ServerObject->FastArray.Edit().Add(NewEntry);
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 12U;
ServerObject->FastArray.Edit().AddLocal(NewEntry);
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 1U;
ServerObject->FastArray.Edit().Add(NewEntry);
// Dirty middle
ServerObject->FastArray.Edit()[1].RepInt32 = 13U;
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// In this case we expect order to be the same
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
ClientObject->FastArray.bHitReplicatedAdd = false;
ClientObject->FastArray.bHitReplicatedChange = false;
ClientObject->FastArray.bHitReplicatedRemove = false;
// Modify data
ServerObject->FastArray.Edit()[0].RepInt32 = 3U;
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the modified data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
}
// Add test for change item
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestModifyItemDoesNotEverwriteLocalVariables)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify initial replication
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// Grab array editor
auto FastArrayEditor = ServerObject->FastArray.Edit();
// Add data to array
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
FastArrayEditor.Add(NewEntry);
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 1U;
FastArrayEditor.Add(NewEntry);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(FastArrayEditor.Num(), ClientObject->FastArray.GetItemArray().Num());
// In this case we expect order to be the same
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Store some local data in the not replicated var
const int32 LocallyStoredInt32 = 32;
ClientObject->FastArray.Edit().EditLocal(0).NotRepInt32 = LocallyStoredInt32;
ClientObject->FastArray.bHitReplicatedAdd = false;
ClientObject->FastArray.bHitReplicatedChange = false;
ClientObject->FastArray.bHitReplicatedRemove = false;
// Modify data
FastArrayEditor.Edit(0).RepInt32 = 3;
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the modified data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
// Verify that the locally stored data was not overwritten
UE_NET_ASSERT_EQ(LocallyStoredInt32, ClientObject->FastArray.GetItemArray()[0].NotRepInt32);
}
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestAddItemWithoutMarkDirty)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
ServerObject->FastArray.Edit().Add(NewEntry);
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 1U;
// Modify item without marking it dirty
ServerObject->FastArray.Edit().AddLocal(NewEntry);
// Verify that only first entry is assigned ReplicationID
UE_NET_ASSERT_NE(ServerObject->FastArray.GetItemArray()[0].ReplicationID, -1);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[1].ReplicationID, -1);
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
// Verify that poll has modified the index
UE_NET_ASSERT_NE(ServerObject->FastArray.GetItemArray()[0].ReplicationID, -1);
UE_NET_ASSERT_NE(ServerObject->FastArray.GetItemArray()[1].ReplicationID, -1);
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify that we received the data
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// In this case we expect order to be the same
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
}
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestChangeMaskWrapAround)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify initial replication
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
// Create FastArrayEditor, note this is only valid as long as the FastArray is valid
auto FastArrayEditor = ServerObject->FastArray.Edit();
// Add data to array
constexpr int32 InitialItemCount = 32;
for (int32 ItemIt = 0; ItemIt < InitialItemCount; ++ItemIt)
{
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = ItemIt;
FastArrayEditor.Add(NewEntry);
}
int32 CurrentItemCount = FastArrayEditor.Num();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(CurrentItemCount, ClientObject->FastArray.GetItemArray().Num());
// In this case we expect order to be the same
for (int32 ItemIt = 0; ItemIt < InitialItemCount; ++ItemIt)
{
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[ItemIt].RepInt32, ClientObject->FastArray.GetItemArray()[ItemIt].RepInt32);
}
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Add Item that reuses a bit
{
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 32;
FastArrayEditor.Add(NewEntry);
++CurrentItemCount;
}
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[CurrentItemCount - 1].RepInt32, ClientObject->FastArray.GetItemArray()[CurrentItemCount - 1].RepInt32);
// Add another item and modify the one sharing a changemask
{
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 33;
FastArrayEditor.Add(NewEntry);
++CurrentItemCount;
FastArrayEditor.Edit(1).bRepBool = true;
}
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// We should both have a change and an add
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[CurrentItemCount - 1].RepInt32, ClientObject->FastArray.GetItemArray()[CurrentItemCount - 1].RepInt32);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[1].bRepBool, ClientObject->FastArray.GetItemArray()[1].bRepBool);
}
static_assert(TModels_V<FFastArraySerializer::CPostReplicatedReceiveFuncable, FTestFastArrayReplicationState_FastArraySerializer, const FFastArraySerializer::FPostReplicatedReceiveParameters&>, "FTestFastArrayReplicationState_FastArraySerializer should have a function matching the requirements of FFastArraySerializer::CPostReplicatedReceiveFuncable");
// Test partial out of order resolve of references in fast array
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestPostReplicatedReceiveCallback)
{
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Spawn objects on server
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Spawn objects to reference
UReplicatedTestObject* ServerReferencedObjectA = Server->CreateObject(0 ,0);
UReplicatedTestObject* ServerReferencedObjectB = Server->CreateObject(0 ,0);
UReplicatedTestObject* ServerReferencedObjectC = Server->CreateObject(0 ,0);
// Setup references
auto FastArrayEditor = ServerObject->FastArray.Edit();
FTestFastArrayReplicationState_FastArrayItem Item;
Item.ObjectRef = ServerReferencedObjectA;
FastArrayEditor.Add(Item);
Item.ObjectRef = ServerReferencedObjectB;
FastArrayEditor.Add(Item);
// Make sure that references are not replicated to client
Server->ReplicationSystem->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerReferencedObjectA->NetRefHandle);
Server->ReplicationSystem->AddToGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerReferencedObjectB->NetRefHandle);
// Replicate
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that object has been spawned on client
auto ClientObject = Client->GetObjectAs<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(ServerObject->NetRefHandle);
UE_NET_ASSERT_TRUE(ClientObject != nullptr);
auto& ClientFastArray = ClientObject->FastArray;
// Verify that array has been replicated
UE_NET_ASSERT_EQ(2, ClientFastArray.GetItemArray().Num());
// Verify that objects could not be resolved
UE_NET_ASSERT_TRUE(ClientFastArray.GetItemArray()[0].ObjectRef == nullptr);
UE_NET_ASSERT_TRUE(ClientFastArray.GetItemArray()[1].ObjectRef == nullptr);
// Verify that we hit the callback
UE_NET_ASSERT_TRUE(ClientFastArray.bHitPostReplicatedReceive);
UE_NET_ASSERT_TRUE(ClientFastArray.bHitDerivedPostReplicatedReceive);
UE_NET_ASSERT_TRUE(ClientFastArray.bPostReplicatedReceiveWasHitWithUnresolvedReferences);
// Reset
ClientFastArray.bHitPostReplicatedReceive = false;
ClientFastArray.bPostReplicatedReceiveWasHitWithUnresolvedReferences = false;
// Enable replication for ServerReferenceObjectA
Server->ReplicationSystem->RemoveFromGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerReferencedObjectA->NetRefHandle);
// Replicate
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we managed to resolve objectA even though we have other unresolved objects in the array
UE_NET_ASSERT_TRUE(ClientFastArray.GetItemArray()[0].ObjectRef != nullptr);
UE_NET_ASSERT_TRUE(ClientFastArray.GetItemArray()[1].ObjectRef == nullptr);
UE_NET_ASSERT_TRUE(ClientFastArray.bHitPostReplicatedReceive);
UE_NET_ASSERT_TRUE(ClientFastArray.bPostReplicatedReceiveWasHitWithUnresolvedReferences);
// Reset
ClientFastArray.bHitPostReplicatedReceive = false;
ClientFastArray.bPostReplicatedReceiveWasHitWithUnresolvedReferences = false;
// Enable replication for ServerReferenceObjectB
Server->ReplicationSystem->RemoveFromGroup(Server->GetReplicationSystem()->GetNotReplicatedNetObjectGroup(), ServerReferencedObjectB->NetRefHandle);
// Replicate
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we managed to resolve both objects and that the
UE_NET_ASSERT_TRUE(ClientFastArray.GetItemArray()[0].ObjectRef != nullptr);
UE_NET_ASSERT_TRUE(ClientFastArray.GetItemArray()[1].ObjectRef != nullptr);
// Verify that we hit the callback with no more unresolved references
UE_NET_ASSERT_TRUE(ClientFastArray.bHitPostReplicatedReceive);
UE_NET_ASSERT_FALSE(ClientFastArray.bPostReplicatedReceiveWasHitWithUnresolvedReferences);
}
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestFastArrayWithExtraProperty)
{
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty>();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArrayWithExtraProperty>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Verify initial replication
UE_NET_ASSERT_TRUE(ClientObject!= nullptr);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
UE_NET_ASSERT_EQ(ServerObject->FastArray.ExtraInt, ClientObject->FastArray.ExtraInt);
// Add data to array
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
ServerObject->FastArray.Edit().Add(NewEntry);
NewEntry.bRepBool = true;
NewEntry.RepInt32 = 1U;
ServerObject->FastArray.Edit().Add(NewEntry);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
UE_NET_ASSERT_EQ(ServerObject->FastArray.ExtraInt, ClientObject->FastArray.ExtraInt);
// In this case we expect order to be the same
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_TRUE(ClientObject->FastArray.bHitReplicatedAdd);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedChange);
UE_NET_ASSERT_FALSE(ClientObject->FastArray.bHitReplicatedRemove);
// Modify extraint
ServerObject->FastArray.ExtraInt = 3;
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the data
UE_NET_ASSERT_EQ(ServerObject->FastArray.ExtraInt, ClientObject->FastArray.ExtraInt);
}
UE_NET_TEST_FIXTURE(FReplicationSystemServerClientTestFixture, TestLostDataDirtiesPropertyBitWithDataInFlight)
{
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ServerObject = Server->CreateObject<UTestFastArrayReplicationState_FastArray_TestClassFastArray>();
// Add a client
FReplicationSystemTestClient* Client = CreateClient();
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Store Pointer to objects
UTestFastArrayReplicationState_FastArray_TestClassFastArray* ClientObject = Cast<UTestFastArrayReplicationState_FastArray_TestClassFastArray>(Client->GetReplicationBridge()->GetReplicatedObject(ServerObject->NetRefHandle));
// Add entry to array
FTestFastArrayReplicationState_FastArrayItem NewEntry;
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 0U;
ServerObject->FastArray.Edit().Add(NewEntry);
NewEntry.bRepBool = false;
NewEntry.RepInt32 = 3U;
ServerObject->FastArray.Edit().Add(NewEntry);
// Put data on wire, but hold off delivery
Server->NetUpdate();
Server->SendTo(Client);
Server->PostSendUpdate();
// Modify entry to trigger new update
++ServerObject->FastArray.Edit()[0].RepInt32;
// Put latest state on the wire
Server->NetUpdate();
Server->SendTo(Client);
Server->PostSendUpdate();
// Notify that we dropped the first packet, which should mark property dirty even if we already have the latest state in flight
Server->DeliverTo(Client, false);
// Notify that we delivered second packet
Server->DeliverTo(Client, true);
// Send and deliver packet
Server->NetUpdate();
Server->SendAndDeliverTo(Client, true);
Server->PostSendUpdate();
// Verify that we received the expected data, for both elements
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray().Num(), ClientObject->FastArray.GetItemArray().Num());
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[0].RepInt32, ClientObject->FastArray.GetItemArray()[0].RepInt32);
UE_NET_ASSERT_EQ(ServerObject->FastArray.GetItemArray()[1].RepInt32, ClientObject->FastArray.GetItemArray()[1].RepInt32);
}
}