You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/GridMate/Tests/ReplicaBehavior.cpp

1425 lines
45 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "Tests.h"
#include <GridMate/Memory.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/smart_ptr/scoped_ptr.h>
#include <GridMate/Carrier/DefaultSimulator.h>
#include <GridMate/Containers/unordered_map.h>
#include <GridMate/Containers/unordered_set.h>
#include <GridMate/Replica/Interpolators.h>
#include <GridMate/Replica/Replica.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Replica/ReplicaMgr.h>
#include <GridMate/Replica/ReplicaDrillerEvents.h>
#include <GridMate/Serialize/CompressionMarshal.h>
using namespace GridMate;
namespace GridMate
{
class CustomInt
{
public:
CustomInt() : m_value(0) {}
explicit CustomInt(int value) : m_value(value) {}
bool operator==(const CustomInt& other) const
{
return other.m_value == m_value;
}
int m_value;
};
template<>
class Marshaler<CustomInt>
{
public:
Marshaler()
: m_marshalCalls(0)
, m_unmarshalCalls(0)
{
}
/// Defines the size that is written to the wire. This is only valid for fixed size marshalers, marshalers for dynamic objects don't define it.
static const AZStd::size_t MarshalSize = 0;
void Marshal(WriteBuffer& wb, const CustomInt& value) const
{
wb.Write(value.m_value);
m_marshalCalls++;
}
void Unmarshal(CustomInt& value, ReadBuffer& rb) const
{
rb.Read(value.m_value);
m_unmarshalCalls++;
}
mutable size_t m_marshalCalls;
mutable size_t m_unmarshalCalls;
};
}
namespace UnitTest {
namespace ReplicaBehavior {
#define GM_REPLICA_TEST_SESSION_CHANNEL 1
class AbleToSetDirtyDataSet : public DataSet<int>
{
public:
AbleToSetDirtyDataSet(const char* name, int value) : DataSet<int>(name, value) {}
void ForceDirtyLikeScriptsDo()
{
DataSetBase::SetDirty();
}
};
class RegularTestChunk
: public ReplicaChunk
{
public:
GM_CLASS_ALLOCATOR(RegularTestChunk);
RegularTestChunk()
{
}
typedef AZStd::intrusive_ptr<RegularTestChunk> Ptr;
static const char* GetChunkName() { return "RegularTestChunk"; }
bool IsReplicaMigratable() override { return false; }
DataSet<AZ::u64> Data1 = { "Data1", 42 };
DataSet<AZ::u64> Data2 = { "Data2", 0 };
};
class CustomMarshalerTestChunk
: public ReplicaChunk
{
public:
GM_CLASS_ALLOCATOR(CustomMarshalerTestChunk);
CustomMarshalerTestChunk()
{
}
typedef AZStd::intrusive_ptr<CustomMarshalerTestChunk> Ptr;
static const char* GetChunkName() { return "CustomMarshalerTestChunk"; }
bool IsReplicaMigratable() override { return false; }
DataSet<CustomInt, Marshaler<CustomInt>> Data1 = { "Data1" };
};
class LargeChunkWithDefaults
: public ReplicaChunk
{
public:
GM_CLASS_ALLOCATOR(LargeChunkWithDefaults);
LargeChunkWithDefaults()
{
Data1.MarkAsDefaultValue();
Data2.MarkAsDefaultValue();
Data3.MarkAsDefaultValue();
}
typedef AZStd::intrusive_ptr<LargeChunkWithDefaults> Ptr;
static const char* GetChunkName() { return "LargeChunkWithDefaults"; }
bool IsReplicaMigratable() override { return false; }
DataSet<int> Data1 = { "Data1", 0 };
DataSet<int> Data2 = { "Data2", 0 };
DataSet<int> Data3 = { "Data3", 0 };
};
class ChunkWithBools
: public ReplicaChunk
{
public:
GM_CLASS_ALLOCATOR(ChunkWithBools);
ChunkWithBools()
{
}
typedef AZStd::intrusive_ptr<ChunkWithBools> Ptr;
static const char* GetChunkName() { return "ChunkWithBools"; }
bool IsReplicaMigratable() override { return false; }
DataSet<bool> Data1 = { "Data1", false };
DataSet<bool> Data2 = { "Data2", false };
DataSet<bool> Data3 = { "Data3", false };
DataSet<bool> Data4 = { "Data4", false };
DataSet<bool> Data5 = { "Data5", false };
DataSet<bool> Data6 = { "Data6", false };
DataSet<bool> Data7 = { "Data7", false };
DataSet<bool> Data8 = { "Data8", false };
DataSet<bool> Data9 = { "Data9", false };
DataSet<bool> Data10 = { "Data10", false };
};
class ChunkWithShortInts
: public ReplicaChunk
{
public:
GM_CLASS_ALLOCATOR(ChunkWithShortInts);
ChunkWithShortInts()
{
}
typedef AZStd::intrusive_ptr<ChunkWithShortInts> Ptr;
static const char* GetChunkName() { return "ChunkWithShortInts"; }
bool IsReplicaMigratable() override { return false; }
DataSet<AZ::u8> Data1 = { "Data1", 0 };
DataSet<AZ::u8> Data2 = { "Data2", 0 };
DataSet<AZ::u8> Data3 = { "Data3", 0 };
DataSet<AZ::u8> Data4 = { "Data4", 0 };
DataSet<AZ::u8> Data5 = { "Data5", 0 };
DataSet<AZ::u8> Data6 = { "Data6", 0 };
DataSet<AZ::u8> Data7 = { "Data7", 0 };
DataSet<AZ::u8> Data8 = { "Data8", 0 };
DataSet<AZ::u8> Data9 = { "Data9", 0 };
DataSet<AZ::u8> Data10 = { "Data10", 0 };
};
class ForcingDirtyTestChunk
: public ReplicaChunk
{
public:
GM_CLASS_ALLOCATOR(ForcingDirtyTestChunk);
void ForceDirtyLikeScriptsDo()
{
Data1.ForceDirtyLikeScriptsDo();
}
typedef AZStd::intrusive_ptr<ForcingDirtyTestChunk> Ptr;
static const char* GetChunkName() { return "ForcingDirtyTestChunk"; }
bool IsReplicaMigratable() override { return false; }
AbleToSetDirtyDataSet Data1 = { "Data1", 42 };
};
typedef DataSet<int> EntityLikeScriptDataSetType;
class EntityLikeScriptDataSet
: public EntityLikeScriptDataSetType
{
static const char* GetDataSetName();
public:
GM_CLASS_ALLOCATOR(EntityLikeScriptDataSet);
EntityLikeScriptDataSet();
void SetIsEnabled(bool isEnabled)
{
m_isEnabled = isEnabled;
}
bool IsEnabled() const
{
return m_isEnabled;
}
PrepareDataResult PrepareData(GridMate::EndianType endianType, AZ::u32 marshalFlags) override
{
if (!IsEnabled())
{
return PrepareDataResult(false, false, false, false);
}
return EntityLikeScriptDataSetType::PrepareData(endianType, marshalFlags);
}
void SetDirty() override
{
if (!IsEnabled())
{
return;
}
EntityLikeScriptDataSetType::SetDirty();
}
private:
bool m_isEnabled;
};
class EntityLikeScriptReplicaChunk
: public GridMate::ReplicaChunk
{
public:
static const int k_maxScriptableDataSets = GM_MAX_DATASETS_IN_CHUNK;
private:
friend class EntityLikeScriptDataSet;
friend class EntityReplica;
typedef AZStd::unordered_map<AZStd::string, int> DataSetIndexMapping;
public:
GM_CLASS_ALLOCATOR(EntityLikeScriptReplicaChunk);
EntityLikeScriptReplicaChunk();
~EntityLikeScriptReplicaChunk() = default;
//////////////////////////////////////////////////////////////////////
//! GridMate::ReplicaChunk overrides.
static const char* GetChunkName() { return "EntityLikeScriptReplicaChunk"; }
void UpdateChunk(const GridMate::ReplicaContext& rc) override { (void)rc; }
void OnReplicaActivate(const GridMate::ReplicaContext& rc) override { AZ_UNUSED(rc); }
void OnReplicaDeactivate(const GridMate::ReplicaContext& rc) override { (void)rc; }
void UpdateFromChunk(const GridMate::ReplicaContext& rc) override { (void)rc; }
bool IsReplicaMigratable() override { return false; }
//////////////////////////////////////////////////////////////////////
int GetMaxServerProperties() const { return k_maxScriptableDataSets; }
AZ::u32 CalculateDirtyDataSetMask(MarshalContext& marshalContext);
EntityLikeScriptDataSet m_scriptDataSets[k_maxScriptableDataSets];
AZ::u32 m_enabledDataSetMask;
};
EntityLikeScriptDataSet::EntityLikeScriptDataSet()
: EntityLikeScriptDataSetType(GetDataSetName())
, m_isEnabled(false)
{
}
const char* EntityLikeScriptDataSet::GetDataSetName()
{
static int s_chunkIndex = 0;
static const char* s_nameArray[] = {
"DataSet1","DataSet2","DataSet3","DataSet4","DataSet5",
"DataSet6","DataSet7","DataSet8","DataSet9","DataSet10",
"DataSet11","DataSet12","DataSet13","DataSet14","DataSet15",
"DataSet16","DataSet17","DataSet18","DataSet19","DataSet20",
"DataSet21","DataSet22","DataSet23","DataSet24","DataSet25",
"DataSet26","DataSet27","DataSet28","DataSet29","DataSet30",
"DataSet31","DataSet32"
};
static_assert(EntityLikeScriptReplicaChunk::k_maxScriptableDataSets <= AZ_ARRAY_SIZE(s_nameArray), "Insufficient number of names supplied to EntityLikeScriptDataSet::GetDataSetName()");
if (s_chunkIndex > EntityLikeScriptReplicaChunk::k_maxScriptableDataSets)
{
s_chunkIndex = s_chunkIndex%EntityLikeScriptReplicaChunk::k_maxScriptableDataSets;
}
return s_nameArray[s_chunkIndex++];
}
/////////////////////////////
// EntityLikeScriptReplicaChunk
/////////////////////////////
EntityLikeScriptReplicaChunk::EntityLikeScriptReplicaChunk()
: m_enabledDataSetMask(0)
{
}
AZ::u32 EntityLikeScriptReplicaChunk::CalculateDirtyDataSetMask(MarshalContext& marshalContext)
{
if ((marshalContext.m_marshalFlags & ReplicaMarshalFlags::ForceDirty))
{
return m_enabledDataSetMask;
}
return (m_enabledDataSetMask & GridMate::ReplicaChunk::CalculateDirtyDataSetMask(marshalContext));
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class MPSession
: public CarrierEventBus::Handler
{
public:
ReplicaManager& GetReplicaMgr() { return m_rm; }
void SetTransport(Carrier* transport) { m_pTransport = transport; CarrierEventBus::Handler::BusConnect(transport->GetGridMate()); }
Carrier* GetTransport() { return m_pTransport; }
void SetClient(bool isClient) { m_client = isClient; }
void AcceptConn(bool accept) { m_acceptConn = accept; }
void Update()
{
char buf[1500];
for (ConnectionSet::iterator iConn = m_connections.begin(); iConn != m_connections.end(); ++iConn)
{
ConnectionID conn = *iConn;
Carrier::ReceiveResult result = m_pTransport->Receive(buf, 1500, conn, GM_REPLICA_TEST_SESSION_CHANNEL);
if (result.m_state == Carrier::ReceiveResult::RECEIVED)
{
if (strcmp(buf, "IM_A_CLIENT") == 0)
{
m_rm.AddPeer(conn, Mode_Client);
}
else if (strcmp(buf, "IM_A_PEER") == 0)
{
m_rm.AddPeer(conn, Mode_Peer);
}
}
}
}
template<typename T>
typename T::Ptr GetChunkFromReplica(ReplicaId id)
{
ReplicaPtr replica = GetReplicaMgr().FindReplica(id);
if (!replica)
{
return nullptr;
}
return replica->FindReplicaChunk<T>();
}
//////////////////////////////////////////////////////////////////////////
// CarrierEventBus
void OnConnectionEstablished(Carrier* carrier, ConnectionID id) override
{
if (carrier != m_pTransport)
{
return; // not for us
}
m_connections.insert(id);
if (m_client)
{
m_pTransport->Send("IM_A_CLIENT", 12, id, Carrier::SEND_RELIABLE, Carrier::PRIORITY_NORMAL, GM_REPLICA_TEST_SESSION_CHANNEL);
}
else
{
m_pTransport->Send("IM_A_PEER", 10, id, Carrier::SEND_RELIABLE, Carrier::PRIORITY_NORMAL, GM_REPLICA_TEST_SESSION_CHANNEL);
}
}
void OnDisconnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason /*reason*/) override
{
if (carrier != m_pTransport)
{
return; // not for us
}
m_rm.RemovePeer(id);
m_connections.erase(id);
}
void OnDriverError(Carrier* carrier, ConnectionID id, const DriverError& error) override
{
(void)error;
if (carrier != m_pTransport)
{
return; // not for us
}
m_pTransport->Disconnect(id);
}
void OnSecurityError(Carrier* carrier, ConnectionID id, const SecurityError& error) override
{
(void)carrier;
(void)id;
(void)error;
//Ignore security warnings in unit tests
}
//////////////////////////////////////////////////////////////////////////
ReplicaManager m_rm;
Carrier* m_pTransport;
typedef unordered_set<ConnectionID> ConnectionSet;
ConnectionSet m_connections;
bool m_client;
bool m_acceptConn;
};
const int k_delay = 50;
enum class TestStatus
{
Running,
Completed,
};
class Integ_SimpleBehaviorTest
: public UnitTest::GridMateMPTestFixture
{
public:
//GM_CLASS_ALLOCATOR(SimpleBehaviorTest);
Integ_SimpleBehaviorTest()
: m_sessionCount(0) { }
virtual int GetNumSessions() { return 0; }
virtual int GetHostSession() { return 0; }
virtual void PreInit() { }
virtual void PreConnect() { }
virtual void PostInit() { }
virtual TestStatus Tick(int ticks) = 0;
void run()
{
AZ_TracePrintf("GridMate", "\n");
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(ForcingDirtyTestChunk::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<ForcingDirtyTestChunk>();
}
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(EntityLikeScriptReplicaChunk::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<EntityLikeScriptReplicaChunk>();
}
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(RegularTestChunk::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<RegularTestChunk>();
}
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(LargeChunkWithDefaults::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<LargeChunkWithDefaults>();
}
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(ChunkWithBools::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<ChunkWithBools>();
}
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(ChunkWithShortInts::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<ChunkWithShortInts>();
}
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(CustomMarshalerTestChunk::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<CustomMarshalerTestChunk>();
}
// Setting up simulator with 50% outgoing packet loss
DefaultSimulator defaultSimulator;
defaultSimulator.SetOutgoingPacketLoss(0, 0);
m_sessionCount = GetNumSessions();
PreInit();
// initialize transport
int basePort = 4427;
for (int i = 0; i < m_sessionCount; ++i)
{
CarrierDesc desc;
desc.m_port = basePort + i;
desc.m_enableDisconnectDetection = false;
desc.m_simulator = &defaultSimulator;
// initialize replica managers
m_sessions[i].SetTransport(DefaultCarrier::Create(desc, m_gridMate));
m_sessions[i].AcceptConn(true);
m_sessions[i].SetClient(false);
m_sessions[i].GetReplicaMgr().Init(ReplicaMgrDesc(i + 1, m_sessions[i].GetTransport(), 0, i == GetHostSession() ? ReplicaMgrDesc::Role_SyncHost : 0));
m_sessions[i].GetReplicaMgr().RegisterUserContext(12345, reinterpret_cast<void*>(static_cast<size_t>(i + 1)));
}
m_sessions[GetHostSession()].GetReplicaMgr().SetLocalLagAmt(1);
PreConnect();
for (int i = 1; i < m_sessionCount; ++i)
{
m_sessions[i].GetTransport()->Connect("127.0.0.1", basePort);
}
PostInit();
// main test loop
int count = 0;
for (;; )
{
if (Tick(count) == TestStatus::Completed)
{
break;
}
++count;
// tick everything
for (int i = 0; i < m_sessionCount; ++i)
{
m_sessions[i].Update();
m_sessions[i].GetReplicaMgr().Unmarshal();
}
for (int i = 0; i < m_sessionCount; ++i)
{
m_sessions[i].GetReplicaMgr().UpdateReplicas();
}
for (int i = 0; i < m_sessionCount; ++i)
{
m_sessions[i].GetReplicaMgr().UpdateFromReplicas();
m_sessions[i].GetReplicaMgr().Marshal();
}
for (int i = 0; i < m_sessionCount; ++i)
{
m_sessions[i].GetTransport()->Update();
}
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(k_delay));
}
for (int i = 0; i < m_sessionCount; ++i)
{
m_sessions[i].GetReplicaMgr().Shutdown();
DefaultCarrier::Destroy(m_sessions[i].GetTransport());
}
}
int m_sessionCount;
AZStd::array<MPSession, 10> m_sessions;
};
/*
* A hook to intercept the payload size of a replica and it's contents.
*/
class ReplicaDrillerHook
: public Debug::ReplicaDrillerBus::Handler
{
public:
ReplicaDrillerHook()
{
}
void OnSendReplicaEnd(Replica* /*replica*/, const void* /*data*/, size_t len) override
{
m_replicaLengths.push_back(len);
}
void ResetCounts([[maybe_unused]] bool trace = false)
{
#if defined(AZ_ENABLE_TRACING)
if (trace && m_replicaLengths.size() > 0)
{
AZ_TracePrintf("GridMate", "Driller saw replicas with the following byte sizes:\n");
for (auto length : m_replicaLengths)
{
AZ_TracePrintf("GridMate", "\t\t\t %d \n", length);
}
}
#endif
m_replicaLengths.clear();
}
AZStd::vector<AZ::u64> m_replicaLengths;
};
template <typename ReplicaChunkType>
class FilteredHook : public ReplicaDrillerHook
{
public:
void OnSendReplicaEnd(Replica* replica, const void* /*data*/, size_t len) override
{
if (ContainsChunkTypeWeWant(replica))
{
m_replicaLengths.push_back(len);
}
}
private:
bool ContainsChunkTypeWeWant(Replica* replica) const
{
auto numChunks = replica->GetNumChunks();
for (size_t i = 0; i < numChunks; i++)
{
auto chunk = replica->GetChunkByIndex(i);
if (chunk->GetDescriptor()->GetChunkName() == ReplicaChunkType::GetChunkName())
{
return true;
}
}
return false;
}
};
/*
* The most basic functionality test for sending datasets that have a default value and have not yet been modified
* from their constructor values.
*
* This is a simple sanity check to ensure the logic sends the update when it's necessary.
*/
class Integ_Replica_DontSendDataSets_WithNoDiffFromCtorData
: public Integ_SimpleBehaviorTest
{
public:
Integ_Replica_DontSendDataSets_WithNoDiffFromCtorData()
: m_replicaIdDefault(InvalidReplicaId), m_replicaIdModified(InvalidReplicaId)
{
}
enum
{
sHost,
s2,
nSessions
};
int GetNumSessions() override { return nSessions; }
void PreConnect() override
{
m_driller.BusConnect();
{
ReplicaPtr replica = Replica::CreateReplica(nullptr);
auto chunk = CreateAndAttachReplicaChunk<LargeChunkWithDefaults>(replica);
AZ_TEST_ASSERT(chunk);
AZ_TEST_ASSERT(chunk->Data1.IsDefaultValue());
AZ_TEST_ASSERT(chunk->Data2.IsDefaultValue());
m_replicaIdDefault = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica);
}
}
const int ExpectedReplicaSizeWithDefaults = 37;
const int ExpectedReplicaSizeWithNonDefaults = 46;
TestStatus Tick(int tick) override
{
switch (tick)
{
case 20:
{
{
ReplicaPtr rep = m_sessions[s2].GetReplicaMgr().FindReplica(m_replicaIdDefault);
AZ_TEST_ASSERT(rep);
auto chunk = rep->FindReplicaChunk<LargeChunkWithDefaults>();
AZ_TEST_ASSERT(chunk);
auto replicaSize = m_driller.m_replicaLengths[0];
AZ_TEST_ASSERT(replicaSize == ExpectedReplicaSizeWithDefaults);
m_driller.ResetCounts();
}
// create another replica with non-default values
{
ReplicaPtr replica = Replica::CreateReplica(nullptr);
auto chunk = CreateAndAttachReplicaChunk<LargeChunkWithDefaults>(replica);
AZ_TEST_ASSERT(chunk);
AZ_TEST_ASSERT(chunk->Data1.IsDefaultValue());
AZ_TEST_ASSERT(chunk->Data2.IsDefaultValue());
chunk->Data1.Set(4242);
chunk->Data2.Set(4242);
AZ_TEST_ASSERT(!chunk->Data1.IsDefaultValue());
AZ_TEST_ASSERT(!chunk->Data2.IsDefaultValue());
m_replicaIdModified = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica);
}
break;
}
case 40:
{
{
ReplicaPtr rep = m_sessions[s2].GetReplicaMgr().FindReplica(m_replicaIdModified);
AZ_TEST_ASSERT(rep);
auto chunk = rep->FindReplicaChunk<LargeChunkWithDefaults>();
AZ_TEST_ASSERT(chunk);
auto replicaSize = m_driller.m_replicaLengths[0];
AZ_TEST_ASSERT(replicaSize == ExpectedReplicaSizeWithNonDefaults);
m_driller.ResetCounts();
// check that non-default values are set for the dataset
{
AZ_TEST_ASSERT(!chunk->Data1.IsDefaultValue());
auto value = chunk->Data1.Get();
AZ_TEST_ASSERT(value == 4242);
}
{
AZ_TEST_ASSERT(!chunk->Data2.IsDefaultValue());
auto value = chunk->Data2.Get();
AZ_TEST_ASSERT(value == 4242);
}
}
m_driller.ResetCounts(true);
break;
}
case 45:
{
{
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaIdDefault)->Destroy();
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaIdModified)->Destroy();
}
break;
}
case 50:
return TestStatus::Completed;
default:
break;
}
return TestStatus::Running;
}
ReplicaId m_replicaIdDefault;
ReplicaId m_replicaIdModified;
FilteredHook<LargeChunkWithDefaults> m_driller;
};
TEST(Integ_Replica_DontSendDataSets_WithNoDiffFromCtorData, Integ_Replica_DontSendDataSets_WithNoDiffFromCtorData)
{
Integ_Replica_DontSendDataSets_WithNoDiffFromCtorData tester;
tester.run();
}
/*
* This test checks the actual size of the replica as marshalled in the binary payload.
* The assessment of the payload size is done using driller EBus.
*/
class Integ_ReplicaDefaultDataSetDriller
: public Integ_SimpleBehaviorTest
{
public:
Integ_ReplicaDefaultDataSetDriller()
: m_replicaId(InvalidReplicaId)
{
}
enum
{
sHost,
s2,
nSessions
};
int GetNumSessions() override { return nSessions; }
static const int NonDefaultValue = 4242;
void PreConnect() override
{
m_driller.BusConnect();
ReplicaPtr replica = Replica::CreateReplica(nullptr);
LargeChunkWithDefaults* chunk = CreateAndAttachReplicaChunk<LargeChunkWithDefaults>(replica);
AZ_TEST_ASSERT(chunk);
m_replicaId = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica);
}
~Integ_ReplicaDefaultDataSetDriller()
{
m_driller.BusDisconnect();
}
TestStatus Tick(int tick) override
{
switch (tick)
{
case 10:
{
auto rep = m_sessions[s2].GetReplicaMgr().FindReplica(m_replicaId);
AZ_TEST_ASSERT(rep);
m_driller.ResetCounts();
break;
}
case 15:
{
ReplicaPtr replica = m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId);
auto chunk = replica->FindReplicaChunk<LargeChunkWithDefaults>();
int nonDefaultValue = NonDefaultValue;
auto touch = [nonDefaultValue](DataSet<int>& dataSet) { dataSet.Set(nonDefaultValue); };
touch(chunk->Data1);
touch(chunk->Data2);
touch(chunk->Data3);
m_driller.ResetCounts();
break;
}
case 20:
{
auto repLengths = m_driller.m_replicaLengths;
m_driller.ResetCounts();
// check exact expected sizes
const auto countUnreliable = 4;
const auto countReliable = 1;
const auto expectedReplicaSize = 22;
AZ_TEST_ASSERT(repLengths.size() == countUnreliable + countReliable);
for (auto length : repLengths)
{
AZ_TEST_ASSERT(length == expectedReplicaSize);
}
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId)->Destroy();
break;
}
case 25:
{
return TestStatus::Completed;
}
default:
break;
}
return TestStatus::Running;
}
ReplicaDrillerHook m_driller;
ReplicaId m_replicaId;
};
const int Integ_ReplicaDefaultDataSetDriller::NonDefaultValue;
TEST(Integ_ReplicaDefaultDataSetDriller, Integ_ReplicaDefaultDataSetDriller)
{
Integ_ReplicaDefaultDataSetDriller tester;
tester.run();
}
/*
* This test checks the actual size of the replica as marshalled in the binary payload.
* The assessment of the payload size is done using driller EBus.
*/
class Integ_Replica_ComparePackingBoolsVsU8
: public Integ_SimpleBehaviorTest
{
public:
Integ_Replica_ComparePackingBoolsVsU8()
: m_replicaBoolsId(InvalidReplicaId)
, m_replicaU8Id(InvalidReplicaId)
{
}
enum
{
sHost,
s2,
nSessions
};
int GetNumSessions() override { return nSessions; }
void PreConnect() override
{
m_driller.BusConnect();
ReplicaPtr replica1 = Replica::CreateReplica(nullptr);
ChunkWithBools* chunk1 = CreateAndAttachReplicaChunk<ChunkWithBools>(replica1);
AZ_TEST_ASSERT(chunk1);
m_replicaBoolsId = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica1);
ReplicaPtr replica2 = Replica::CreateReplica(nullptr);
ChunkWithShortInts* chunk2 = CreateAndAttachReplicaChunk<ChunkWithShortInts>(replica2);
AZ_TEST_ASSERT(chunk2);
m_replicaU8Id = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica2);
}
~Integ_Replica_ComparePackingBoolsVsU8()
{
m_driller.BusDisconnect();
}
TestStatus Tick(int tick) override
{
switch (tick)
{
case 10:
{
auto rep1 = m_sessions[s2].GetReplicaMgr().FindReplica(m_replicaBoolsId);
AZ_TEST_ASSERT(rep1);
auto rep2 = m_sessions[s2].GetReplicaMgr().FindReplica(m_replicaU8Id);
AZ_TEST_ASSERT(rep2);
break;
}
case 15:
{
// we have to poke the values so that they become non-default
{
ReplicaPtr replica = m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaBoolsId);
auto chunk = replica->FindReplicaChunk<ChunkWithBools>();
auto touch = [](DataSet<bool>& dataSet) { dataSet.Set(true); };
touch(chunk->Data1);
touch(chunk->Data2);
touch(chunk->Data3);
touch(chunk->Data4);
touch(chunk->Data5);
touch(chunk->Data6);
touch(chunk->Data7);
touch(chunk->Data8);
touch(chunk->Data9);
touch(chunk->Data10);
}
{
ReplicaPtr replica = m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaU8Id);
auto chunk = replica->FindReplicaChunk<ChunkWithShortInts>();
auto touch = [](DataSet<AZ::u8>& dataSet) { dataSet.Set(42); };
touch(chunk->Data1);
touch(chunk->Data2);
touch(chunk->Data3);
touch(chunk->Data4);
touch(chunk->Data5);
touch(chunk->Data6);
touch(chunk->Data7);
touch(chunk->Data8);
touch(chunk->Data9);
touch(chunk->Data10);
}
m_driller.ResetCounts();
break;
}
case 30:
{
auto repLengths = m_driller.m_replicaLengths;
m_driller.ResetCounts();
// check exact expected sizes
const auto expectedReplicaSizeWithBools = 12;
const auto expectedReplicaSizeWithShortInts = 20;
AZ_TEST_ASSERT(repLengths.size() >= 2);
AZ_TEST_ASSERT(AZStd::find(repLengths.begin(), repLengths.end(), expectedReplicaSizeWithBools));
AZ_TEST_ASSERT(AZStd::find(repLengths.begin(), repLengths.end(), expectedReplicaSizeWithShortInts));
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaBoolsId)->Destroy();
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaU8Id)->Destroy();
break;
}
case 35:
{
//auto boolDatasetSize = m_driller.m_boolChunkLengths[1];
//auto u8DatasetSize = m_driller.m_u8ChunkLengths[1];
//AZ_TEST_ASSERT(boolDatasetSize < u8DatasetSize); // Observed example: 5bytes < 13bytes
return TestStatus::Completed;
}
default:
break;
}
return TestStatus::Running;
}
ReplicaDrillerHook m_driller;
ReplicaId m_replicaBoolsId;
ReplicaId m_replicaU8Id;
};
TEST(Integ_Replica_ComparePackingBoolsVsU8, Integ_Replica_ComparePackingBoolsVsU8)
{
Integ_Replica_ComparePackingBoolsVsU8 tester;
tester.run();
}
class Integ_CheckDataSetStreamIsntWrittenMoreThanNecessary
: public Integ_SimpleBehaviorTest
{
public:
Integ_CheckDataSetStreamIsntWrittenMoreThanNecessary()
: m_replicaId(InvalidReplicaId)
{
}
enum
{
sHost,
s2,
nSessions
};
int GetNumSessions() override { return nSessions; }
static const int NonDefaultValue = 4242;
void PreConnect() override
{
m_driller.BusConnect();
ReplicaPtr replica = Replica::CreateReplica(nullptr);
auto chunk = CreateAndAttachReplicaChunk<CustomMarshalerTestChunk>(replica);
AZ_TEST_ASSERT(chunk);
m_replicaId = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica);
}
~Integ_CheckDataSetStreamIsntWrittenMoreThanNecessary()
{
m_driller.BusDisconnect();
}
CustomMarshalerTestChunk::Ptr GetHostChunk()
{
ReplicaPtr replica = m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId);
auto chunk = replica->FindReplicaChunk<CustomMarshalerTestChunk>();
return chunk;
}
TestStatus Tick(int tick) override
{
switch (tick)
{
case 10:
{
auto rep = m_sessions[s2].GetReplicaMgr().FindReplica(m_replicaId);
AZ_TEST_ASSERT(rep);
break;
}
case 15:
{
auto chunk = GetHostChunk();
//chunk->Data1.Set(CustomInt(41));
const auto& m = chunk->Data1.GetMarshaler();
// Only the initial setup call should have occurred
AZ_TEST_ASSERT(m.m_marshalCalls == 1);
m.m_marshalCalls = 0;
m_driller.ResetCounts();
break;
}
case 42:
{
auto chunk = GetHostChunk();
const auto& m = chunk->Data1.GetMarshaler();
// No reason for any new calls to occur
AZ_TEST_ASSERT(m.m_marshalCalls == 0);
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId)->Destroy();
break;
}
case 45:
{
return TestStatus::Completed;
}
default:
break;
}
return TestStatus::Running;
}
ReplicaDrillerHook m_driller;
ReplicaId m_replicaId;
};
TEST(Integ_CheckDataSetStreamIsntWrittenMoreThanNecessary, Integ_CheckDataSetStreamIsntWrittenMoreThanNecessary)
{
Integ_CheckDataSetStreamIsntWrittenMoreThanNecessary tester;
tester.run();
}
class Integ_CheckDataSetStreamIsntWrittenMoreThanNecessaryOnceDirty
: public Integ_SimpleBehaviorTest
{
public:
Integ_CheckDataSetStreamIsntWrittenMoreThanNecessaryOnceDirty()
: m_replicaId(InvalidReplicaId)
{
}
enum
{
sHost,
s2,
nSessions
};
int GetNumSessions() override { return nSessions; }
static const int NonDefaultValue = 4242;
void PreConnect() override
{
m_driller.BusConnect();
ReplicaPtr replica = Replica::CreateReplica(nullptr);
auto chunk = CreateAndAttachReplicaChunk<CustomMarshalerTestChunk>(replica);
AZ_TEST_ASSERT(chunk);
m_replicaId = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica);
}
~Integ_CheckDataSetStreamIsntWrittenMoreThanNecessaryOnceDirty()
{
m_driller.BusDisconnect();
}
CustomMarshalerTestChunk::Ptr GetHostChunk()
{
ReplicaPtr replica = m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId);
auto chunk = replica->FindReplicaChunk<CustomMarshalerTestChunk>();
return chunk;
}
TestStatus Tick(int tick) override
{
switch (tick)
{
case 10:
{
auto rep = m_sessions[s2].GetReplicaMgr().FindReplica(m_replicaId);
AZ_TEST_ASSERT(rep);
break;
}
case 15:
{
auto chunk = GetHostChunk();
chunk->Data1.Set(CustomInt(41));
const auto& m = chunk->Data1.GetMarshaler();
// Only the initial setup call
AZ_TEST_ASSERT(m.m_marshalCalls == 1);
m.m_marshalCalls = 0;
m_driller.ResetCounts();
break;
}
case 42:
{
auto chunk = GetHostChunk();
const auto& m = chunk->Data1.GetMarshaler();
AZ_TEST_ASSERT(m.m_marshalCalls == 6 /* 5 unreliables + 1 reliable */);
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId)->Destroy();
break;
}
case 45:
{
return TestStatus::Completed;
}
default:
break;
}
return TestStatus::Running;
}
ReplicaDrillerHook m_driller;
ReplicaId m_replicaId;
};
TEST(Integ_CheckDataSetStreamIsntWrittenMoreThanNecessaryOnceDirty, Integ_CheckDataSetStreamIsntWrittenMoreThanNecessaryOnceDirty)
{
Integ_CheckDataSetStreamIsntWrittenMoreThanNecessaryOnceDirty tester;
tester.run();
}
class Integ_CheckReplicaIsntSentWithNoChanges
: public Integ_SimpleBehaviorTest
{
public:
Integ_CheckReplicaIsntSentWithNoChanges()
: m_replicaId(InvalidReplicaId)
{
}
enum
{
sHost,
s2,
nSessions
};
int GetNumSessions() override { return nSessions; }
void PreConnect() override
{
m_driller.BusConnect();
ReplicaPtr replica = Replica::CreateReplica(nullptr);
ForcingDirtyTestChunk* chunk = CreateAndAttachReplicaChunk<ForcingDirtyTestChunk>(replica);
AZ_TEST_ASSERT(chunk);
m_replicaId = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica);
}
~Integ_CheckReplicaIsntSentWithNoChanges()
{
m_driller.BusDisconnect();
}
const int NewValue = 999;
const int MomentaryValue = 1;
const int ExpectedNumberReplicasSent = 6;
ReplicaPtr GetHostReplica()
{
return m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId);
}
TestStatus Tick(int tick) override
{
switch (tick)
{
case 9:
{
auto rep = GetHostReplica();
AZ_TEST_ASSERT(rep);
m_driller.ResetCounts();
auto chunk = rep->FindReplicaChunk<ForcingDirtyTestChunk>();
chunk->Data1.Set(NewValue);
break;
}
case 15:
{
auto rep = GetHostReplica();
AZ_TEST_ASSERT(rep);
auto counts = m_driller.m_replicaLengths.size();
AZ_TEST_ASSERT(counts == ExpectedNumberReplicasSent);
m_driller.ResetCounts();
auto chunk = rep->FindReplicaChunk<ForcingDirtyTestChunk>();
chunk->Data1.Set(MomentaryValue);
break;
}
case 16:
{
auto rep = GetHostReplica();
AZ_TEST_ASSERT(rep);
auto chunk = rep->FindReplicaChunk<ForcingDirtyTestChunk>();
chunk->Data1.Set(NewValue);
auto counts = m_driller.m_replicaLengths.size();
AZ_TEST_ASSERT(counts == 1);
m_driller.ResetCounts();
break;
}
case 100:
{
auto counts = m_driller.m_replicaLengths.size();
AZ_TEST_ASSERT(counts == ExpectedNumberReplicasSent);
m_driller.ResetCounts();
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId)->Destroy();
return TestStatus::Completed;
}
default:
break;
}
return TestStatus::Running;
}
FilteredHook<ForcingDirtyTestChunk> m_driller;
ReplicaId m_replicaId;
};
TEST(Integ_CheckReplicaIsntSentWithNoChanges, Integ_CheckReplicaIsntSentWithNoChanges)
{
Integ_CheckReplicaIsntSentWithNoChanges tester;
tester.run();
}
class Integ_CheckEntityScriptReplicaIsntSentWithNoChanges
: public Integ_SimpleBehaviorTest
{
public:
Integ_CheckEntityScriptReplicaIsntSentWithNoChanges()
: m_replicaId(InvalidReplicaId)
{
}
enum
{
sHost,
s2,
nSessions
};
int GetNumSessions() override { return nSessions; }
void PreConnect() override
{
m_driller.BusConnect();
ReplicaPtr replica = Replica::CreateReplica(nullptr);
auto chunk = CreateAndAttachReplicaChunk<EntityLikeScriptReplicaChunk>(replica);
AZ_TEST_ASSERT(chunk);
m_replicaId = m_sessions[sHost].GetReplicaMgr().AddPrimary(replica);
}
~Integ_CheckEntityScriptReplicaIsntSentWithNoChanges()
{
m_driller.BusDisconnect();
}
const int NewValue = 999;
const int MomentaryValue = 1;
const int ExpectedNumberReplicasSent = 6;
ReplicaPtr GetHostReplica()
{
return m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId);
}
TestStatus Tick(int tick) override
{
switch (tick)
{
case 10:
{
auto rep = GetHostReplica();
AZ_TEST_ASSERT(rep);
m_driller.ResetCounts();
auto chunk = rep->FindReplicaChunk<EntityLikeScriptReplicaChunk>();
// mimicing behavior of entity script chunk
chunk->m_scriptDataSets[0].SetIsEnabled(true);
chunk->m_scriptDataSets[0].Set(NewValue);
break;
}
case 60:
{
auto counts = m_driller.m_replicaLengths.size();
AZ_TEST_ASSERT(counts == ExpectedNumberReplicasSent);
m_driller.ResetCounts();
m_sessions[sHost].GetReplicaMgr().FindReplica(m_replicaId)->Destroy();
return TestStatus::Completed;
}
default:
break;
}
return TestStatus::Running;
}
ReplicaDrillerHook m_driller;
ReplicaId m_replicaId;
};
TEST(Integ_CheckEntityScriptReplicaIsntSentWithNoChanges, Integ_CheckEntityScriptReplicaIsntSentWithNoChanges)
{
Integ_CheckEntityScriptReplicaIsntSentWithNoChanges tester;
tester.run();
}
} // namespace ReplicaBehavior
} // namespace UnitTest