/* * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Multiplayer { using namespace testing; using namespace ::UnitTest; /* * Parent -> Child */ class ServerSimpleHierarchyTests : public HierarchyTests { public: void SetUp() override { HierarchyTests::SetUp(); m_root = AZStd::make_unique(1, "root", NetEntityId{ 1 }, EntityInfo::Role::Root); m_child = AZStd::make_unique(2, "child", NetEntityId{ 2 }, EntityInfo::Role::Child); CreateSimpleHierarchy(*m_root, *m_child); m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // now the two entities are under one hierarchy } void TearDown() override { m_child.reset(); m_root.reset(); HierarchyTests::TearDown(); } void CreateSimpleHierarchy(EntityInfo& root, EntityInfo& child) { PopulateHierarchicalEntity(root); SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority); PopulateHierarchicalEntity(child); SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); // Create an entity replicator for the child entity const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); child.m_replicator->Initialize(childHandle); // Create an entity replicator for the root entity const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); root.m_replicator->Initialize(rootHandle); root.m_entity->Activate(); child.m_entity->Activate(); } AZStd::unique_ptr m_root; AZStd::unique_ptr m_child; }; TEST_F(ServerSimpleHierarchyTests, Server_Sets_Appropriate_Network_Fields_For_Clients) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), NetEntityId{ 1 } ); } TEST_F(ServerSimpleHierarchyTests, Root_Is_Top_Level_Root) { EXPECT_EQ( m_root->m_entity->FindComponent()->IsHierarchicalChild(), false ); } TEST_F(ServerSimpleHierarchyTests, Child_Has_Root_Set) { EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), NetEntityId{ 1 } ); } TEST_F(ServerSimpleHierarchyTests, Child_Has_Root_Cleared_On_Detach) { // now detach the child m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerSimpleHierarchyTests, Root_Has_Child_Reference) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } TEST_F(ServerSimpleHierarchyTests, Root_Has_Child_References_Removed_On_Detach) { // now detach the child m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerSimpleHierarchyTests, Root_Deactivates_Child_Has_No_References_To_Root) { StopEntity(m_root->m_entity); m_root->m_entity->Deactivate(); m_root->m_entity.reset(); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerSimpleHierarchyTests, Child_Deactivates_Root_Has_No_References_To_Child) { m_child.reset(); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerSimpleHierarchyTests, Root_Deactivates_IsHierarchyEnabled_Is_False) { EXPECT_EQ( m_root->m_entity->FindComponent()->IsHierarchyEnabled(), true ); StopEntity(m_root->m_entity); m_root->m_entity->Deactivate(); EXPECT_EQ( m_root->m_entity->FindComponent()->IsHierarchyEnabled(), false ); m_root->m_entity.reset(); } TEST_F(ServerSimpleHierarchyTests, Child_Deactivates_IsHierarchyEnabled_Is_False) { EXPECT_EQ( m_child->m_entity->FindComponent()->IsHierarchyEnabled(), true ); StopEntity(m_child->m_entity); m_child->m_entity->Deactivate(); EXPECT_EQ( m_child->m_entity->FindComponent()->IsHierarchyEnabled(), false ); m_child->m_entity.reset(); } TEST_F(ServerSimpleHierarchyTests, ChildPointsToRootAfterReattachment) { m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), m_root->m_entity->FindComponent()->GetNetEntityId() ); } TEST_F(ServerSimpleHierarchyTests, ChildHasOwningConnectionIdOfParent) { // disconnect and assign new connection ids m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); m_root->m_entity->FindComponent()->SetOwningConnectionId(ConnectionId{ 1 }); m_child->m_entity->FindComponent()->SetOwningConnectionId(ConnectionId{ 2 }); const ConnectionId previousConnectionId = m_child->m_entity->FindComponent()->GetOwningConnectionId(); // re-attach, child's owning connection id should then be root's connection id m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_child->m_entity->FindComponent()->GetOwningConnectionId(), m_root->m_entity->FindComponent()->GetOwningConnectionId() ); // detach, the child should roll back to his previous owning connection id m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_child->m_entity->FindComponent()->GetOwningConnectionId(), previousConnectionId ); } /* * Parent -> Child -> ChildOfChild */ class ServerDeepHierarchyTests : public HierarchyTests { public: const NetEntityId RootNetEntityId = NetEntityId{ 1 }; const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; void SetUp() override { HierarchyTests::SetUp(); m_root = AZStd::make_unique((1), "root", RootNetEntityId, EntityInfo::Role::Root); m_child = AZStd::make_unique((2), "child", ChildNetEntityId, EntityInfo::Role::Child); m_childOfChild = AZStd::make_unique((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child); CreateDeepHierarchy(*m_root, *m_child, *m_childOfChild); m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { m_childOfChild.reset(); m_child.reset(); m_root.reset(); HierarchyTests::TearDown(); } AZStd::unique_ptr m_root; AZStd::unique_ptr m_child; AZStd::unique_ptr m_childOfChild; }; TEST_F(ServerDeepHierarchyTests, Root_Is_Top_Level_Root) { EXPECT_EQ( m_root->m_entity->FindComponent()->IsHierarchicalChild(), false ); } TEST_F(ServerDeepHierarchyTests, Root_Has_Child_References) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], m_root->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], m_child->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], m_childOfChild->m_entity.get() ); } } TEST_F(ServerDeepHierarchyTests, Root_Has_Child_Of_Child_Reference_Removed_On_Detach) { m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_Removed_On_Detach_Of_Mid_Child) { m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_If_Mid_Child_Added_With_Child) { m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); // reconnect m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_If_Child_Of_Child_Added) { m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); // reconnect m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Points_To_Root_After_Attach) { m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); // reconnect m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerDeepHierarchyTests, All_New_Children_Point_To_Root_If_Mid_Child_Added_With_Child) { m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); // reconnect m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); EXPECT_EQ( m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerDeepHierarchyTests, Children_Clear_Reference_To_Root_After_Mid_Child_Detached) { m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Clears_Reference_To_Root_After_Detached) { m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerDeepHierarchyTests, Root_Deactivates_Children_Have_No_References_To_Root) { m_root.reset(); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Deactivates_Root_Removes_References_To_It) { m_childOfChild.reset(); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } TEST_F(ServerDeepHierarchyTests, Testing_Limiting_Hierarchy_Maximum_Size) { uint32_t currentMaxLimit = 0; m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); m_console->PerformCommand("bg_hierarchyEntityMaxLimit 2"); // remake the hierarchy m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); m_console->PerformCommand((AZStd::string("bg_hierarchyEntityMaxLimit ") + AZStd::to_string(currentMaxLimit)).c_str()); m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); } TEST_F(ServerDeepHierarchyTests, ReattachMiddleChildRebuildInvokedTwice) { MockNetworkHierarchyCallbackHandler mock; EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_root->m_entity->GetId())).Times(2); m_root->m_entity->FindComponent()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler); m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); } /* * Parent -> Child -> Child Of Child * -> Child2 -> Child Of Child2 * -> Child2 Of Child2 */ class ServerBranchedHierarchyTests : public HierarchyTests { public: const NetEntityId RootNetEntityId = NetEntityId{ 1 }; const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; const NetEntityId Child2NetEntityId = NetEntityId{ 4 }; const NetEntityId ChildOfChild2NetEntityId = NetEntityId{ 5 }; const NetEntityId Child2OfChild2NetEntityId = NetEntityId{ 6 }; void SetUp() override { HierarchyTests::SetUp(); m_root = AZStd::make_unique((1), "root", RootNetEntityId, EntityInfo::Role::Root); m_child = AZStd::make_unique((2), "child", ChildNetEntityId, EntityInfo::Role::Child); m_childOfChild = AZStd::make_unique((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child); m_child2 = AZStd::make_unique((4), "child2", Child2NetEntityId, EntityInfo::Role::Child); m_childOfChild2 = AZStd::make_unique((5), "child of child2", ChildOfChild2NetEntityId, EntityInfo::Role::Child); m_child2OfChild2 = AZStd::make_unique((6), "child2 of child2", Child2OfChild2NetEntityId, EntityInfo::Role::Child); CreateBranchedHierarchy(*m_root, *m_child, *m_childOfChild, *m_child2, *m_childOfChild2, *m_child2OfChild2); m_child2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); m_child2OfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { m_child2OfChild2.reset(); m_childOfChild2.reset(); m_child2.reset(); m_childOfChild.reset(); m_child.reset(); m_root.reset(); HierarchyTests::TearDown(); } void CreateBranchedHierarchy(EntityInfo& root, EntityInfo& child, EntityInfo& childOfChild, EntityInfo& child2, EntityInfo& childOfChild2, EntityInfo& child2OfChild2) { PopulateHierarchicalEntity(root); PopulateHierarchicalEntity(child); PopulateHierarchicalEntity(childOfChild); PopulateHierarchicalEntity(child2); PopulateHierarchicalEntity(childOfChild2); PopulateHierarchicalEntity(child2OfChild2); SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority); SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Authority); SetupEntity(child2.m_entity, child2.m_netId, NetEntityRole::Authority); SetupEntity(childOfChild2.m_entity, childOfChild2.m_netId, NetEntityRole::Authority); SetupEntity(child2OfChild2.m_entity, child2OfChild2.m_netId, NetEntityRole::Authority); // Create entity replicators const NetworkEntityHandle childOfChild2Handle(childOfChild2.m_entity.get(), m_networkEntityTracker.get()); childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChild2Handle); childOfChild.m_replicator->Initialize(childOfChild2Handle); const NetworkEntityHandle child2OfChild2Handle(child2OfChild2.m_entity.get(), m_networkEntityTracker.get()); childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, child2OfChild2Handle); childOfChild.m_replicator->Initialize(child2OfChild2Handle); const NetworkEntityHandle child2Handle(child2.m_entity.get(), m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, child2Handle); child.m_replicator->Initialize(child2Handle); const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get()); childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChildHandle); childOfChild.m_replicator->Initialize(childOfChildHandle); const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); child.m_replicator->Initialize(childHandle); const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); root.m_replicator->Initialize(rootHandle); root.m_entity->Activate(); child.m_entity->Activate(); childOfChild.m_entity->Activate(); child2.m_entity->Activate(); childOfChild2.m_entity->Activate(); child2OfChild2.m_entity->Activate(); } AZStd::unique_ptr m_root; AZStd::unique_ptr m_child; AZStd::unique_ptr m_childOfChild; AZStd::unique_ptr m_child2; AZStd::unique_ptr m_childOfChild2; AZStd::unique_ptr m_child2OfChild2; }; TEST_F(ServerBranchedHierarchyTests, Sanity_Check) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 6) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], m_root->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], m_child->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], m_child2->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[3], m_childOfChild->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[4], m_child2OfChild2->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[5], m_childOfChild2->m_entity.get() ); } } TEST_F(ServerBranchedHierarchyTests, Detach_Child_While_Child2_Remains_Attached) { m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 4 ); if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 4) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], m_root->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], m_child2->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], m_child2OfChild2->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[3], m_childOfChild2->m_entity.get() ); } EXPECT_EQ( m_child2->m_entity->FindComponent()->GetHierarchicalRoot(), m_root->m_entity.get() ); EXPECT_EQ( m_child->m_entity->FindComponent()->GetHierarchicalRoot(), nullptr ); EXPECT_EQ( m_childOfChild->m_entity->FindComponent()->GetHierarchicalRoot(), nullptr ); } TEST_F(ServerBranchedHierarchyTests, Detach_Child_Then_Attach_To_Child2) { m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); m_child->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } /* * Sets up 2 deep hierarchies. */ class ServerHierarchyOfHierarchyTests : public ServerDeepHierarchyTests { public: const NetEntityId Root2NetEntityId = NetEntityId{ 4 }; const NetEntityId Child2NetEntityId = NetEntityId{ 5 }; const NetEntityId ChildOfChild2NetEntityId = NetEntityId{ 6 }; void SetUp() override { ServerDeepHierarchyTests::SetUp(); m_root2 = AZStd::make_unique((4), "root 2", Root2NetEntityId, EntityInfo::Role::Root); m_child2 = AZStd::make_unique((5), "child 2", Child2NetEntityId, EntityInfo::Role::Child); m_childOfChild2 = AZStd::make_unique((6), "child of child 2", ChildOfChild2NetEntityId, EntityInfo::Role::Child); CreateDeepHierarchy(*m_root2, *m_child2, *m_childOfChild2); m_child2->m_entity->FindComponent()->SetParent(m_root2->m_entity->GetId()); m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { m_childOfChild2.reset(); m_child2.reset(); m_root2.reset(); ServerDeepHierarchyTests::TearDown(); } AZStd::unique_ptr m_root2; AZStd::unique_ptr m_child2; AZStd::unique_ptr m_childOfChild2; }; TEST_F(ServerHierarchyOfHierarchyTests, Hierarchies_Are_Not_Related) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], m_root->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], m_child->m_entity.get() ); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], m_childOfChild->m_entity.get() ); } EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); if (m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[0], m_root2->m_entity.get() ); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[1], m_child2->m_entity.get() ); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[2], m_childOfChild2->m_entity.get() ); } } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Is_Not_Top_Level_Root) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->IsHierarchicalChild(), false ); EXPECT_EQ( m_root2->m_entity->FindComponent()->IsHierarchicalChild(), true ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Root) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Child) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Child_Of_Child) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Root) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->IsHierarchicalChild(), true ); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Child) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->IsHierarchicalChild(), true ); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Child_Of_Child) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->IsHierarchicalChild(), true ); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Doesnt_Keep_Child_References) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 0 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Top_Root) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // detach m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); if (m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[0], m_root2->m_entity.get() ); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[1], m_child2->m_entity.get() ); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[2], m_childOfChild2->m_entity.get() ); } } TEST_F(ServerHierarchyOfHierarchyTests, InnerChildrenPointToInnerRootAfterDetachmentFromTopRoot) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // detach m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_child2->m_entity->FindComponent()->GetHierarchyRoot(), m_root2->m_entity->FindComponent()->GetNetEntityId() ); EXPECT_EQ( m_childOfChild2->m_entity->FindComponent()->GetHierarchyRoot(), m_root2->m_entity->FindComponent()->GetNetEntityId() ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Top_Root_Deactivates) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_root.reset(); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Child_Of_Top_Root_Deactivates) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_child.reset(); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Child_Of_Child_Deactivates) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_childOfChild.reset(); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Stress_Test_Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child) { for (int i = 0; i < 100; ++i) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); } EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Detachment_Of_Child_Of_Child_In_Inner_Hierarchy) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 5 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Attachment_Of_Child_Of_Child_In_Inner_Hierarchy) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); // re-connect m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Child_Of_Child_Changed_Hierarchies) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); // connect to a different hierarchy m_childOfChild2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Detachment_Of_Child_In_Inner_Hierarchy) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach m_child2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 4 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Child_Changed_Hierarchies) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach m_child2->m_entity->FindComponent()->SetParent(AZ::EntityId()); // connect to a different hierarchy m_child2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_No_Child_References_After_All_Children_Moved_To_Another_Hierarchy) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_child2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // detach m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Child_Deactivated_Top_Root_Has_No_Child_Reference_To_It) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_child2.reset(); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 4 ); } TEST_F(ServerHierarchyOfHierarchyTests, Testing_Limiting_Hierarchy_Maximum_Size) { uint32_t currentMaxLimit = 0; m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); m_console->PerformCommand("bg_hierarchyEntityMaxLimit 2"); // remake the top level hierarchy m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); m_console->PerformCommand((AZStd::string("bg_hierarchyEntityMaxLimit ") + AZStd::to_string(currentMaxLimit)).c_str()); m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); } TEST_F(ServerHierarchyOfHierarchyTests, InnerRootAndItsChildrenHaveOwningConnectionIdOfTopRoot) { // Assign new connection ids. m_root->m_entity->FindComponent()->SetOwningConnectionId(ConnectionId{ 1 }); m_root2->m_entity->FindComponent()->SetOwningConnectionId(ConnectionId{ 2 }); // Attach then inner hierarchy's owning connection id should then be top root's connection id. m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetOwningConnectionId(), m_root->m_entity->FindComponent()->GetOwningConnectionId() ); EXPECT_EQ( m_child2->m_entity->FindComponent()->GetOwningConnectionId(), m_root->m_entity->FindComponent()->GetOwningConnectionId() ); EXPECT_EQ( m_childOfChild2->m_entity->FindComponent()->GetOwningConnectionId(), m_root->m_entity->FindComponent()->GetOwningConnectionId() ); } TEST_F(ServerHierarchyOfHierarchyTests, InnerRootAndItsChildrenHaveTheirOriginalOwningConnectionIdAfterDetachingFromTopRoot) { // Assign new connection ids. m_root->m_entity->FindComponent()->SetOwningConnectionId(ConnectionId{ 1 }); m_root2->m_entity->FindComponent()->SetOwningConnectionId(ConnectionId{ 2 }); // Attach then inner hierarchy's owning connection id should then be top root's connection id. m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach, inner hierarchy should roll back to his previous owning connection id m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetOwningConnectionId(), ConnectionId{ 2 } ); EXPECT_EQ( m_child2->m_entity->FindComponent()->GetOwningConnectionId(), m_root2->m_entity->FindComponent()->GetOwningConnectionId() ); EXPECT_EQ( m_childOfChild2->m_entity->FindComponent()->GetOwningConnectionId(), m_root2->m_entity->FindComponent()->GetOwningConnectionId() ); } /* * Parent -> Child -> ChildOfChild (not marked as in a hierarchy) */ class ServerMixedDeepHierarchyTests : public HierarchyTests { public: void SetUp() override { HierarchyTests::SetUp(); m_root = AZStd::make_unique((1), "root", NetEntityId{ 1 }, EntityInfo::Role::Root); m_child = AZStd::make_unique((2), "child", NetEntityId{ 2 }, EntityInfo::Role::Child); m_childOfChild = AZStd::make_unique((3), "child of child", NetEntityId{ 3 }, EntityInfo::Role::None); CreateDeepHierarchy(*m_root, *m_child, *m_childOfChild); m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { m_childOfChild.reset(); m_child.reset(); m_root.reset(); HierarchyTests::TearDown(); } AZStd::unique_ptr m_root; AZStd::unique_ptr m_child; AZStd::unique_ptr m_childOfChild; }; TEST_F(ServerMixedDeepHierarchyTests, Top_Root_Ignores_Non_Hierarchical_Entities) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } TEST_F(ServerMixedDeepHierarchyTests, Detaching_Non_Hierarchical_Entity_Has_No_Effect_On_Top_Root) { m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } TEST_F(ServerMixedDeepHierarchyTests, Attaching_Non_Hierarchical_Entity_Has_No_Effect_On_Top_Root) { m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } /* * 1st hierarchy: Parent -> Child -> ChildOfChild (not marked as in a hierarchy) * 2nd hierarchy: Parent2 -> Child2 (not marked as in a hierarchy) -> ChildOfChild2 */ class ServerMixedHierarchyOfHierarchyTests : public ServerMixedDeepHierarchyTests { public: void SetUp() override { ServerMixedDeepHierarchyTests::SetUp(); m_root2 = AZStd::make_unique((4), "root 2", NetEntityId{ 4 }, EntityInfo::Role::Root); m_child2 = AZStd::make_unique((5), "child 2", NetEntityId{ 5 }, EntityInfo::Role::None); m_childOfChild2 = AZStd::make_unique((6), "child of child 2", NetEntityId{ 6 }, EntityInfo::Role::Child); CreateDeepHierarchy(*m_root2, *m_child2, *m_childOfChild2); m_child2->m_entity->FindComponent()->SetParent(m_root2->m_entity->GetId()); m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { m_childOfChild2.reset(); m_child2.reset(); m_root2.reset(); ServerMixedDeepHierarchyTests::TearDown(); } AZStd::unique_ptr m_root2; AZStd::unique_ptr m_child2; AZStd::unique_ptr m_childOfChild2; }; TEST_F(ServerMixedHierarchyOfHierarchyTests, Sanity_Check_Ingore_Children_Without_Hierarchy_Components) { EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerMixedHierarchyOfHierarchyTests, Adding_Mixed_Hierarchy_Ingores_Children_Without_Hierarchy_Components) { m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerMixedHierarchyOfHierarchyTests, Attaching_Hierarchy_To_Non_Hierarchical_Entity_Does_Not_Merge_Hierarchies) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->IsHierarchicalChild(), false ); } /* * Sets up a hierarchy with 3 roots, 2 of them being inner roots. */ class ServerHierarchyWithThreeRoots : public ServerHierarchyOfHierarchyTests { public: const NetEntityId Root3NetEntityId = NetEntityId{ 7 }; const NetEntityId Child3NetEntityId = NetEntityId{ 8 }; const NetEntityId ChildOfChild3NetEntityId = NetEntityId{ 9 }; void SetUp() override { ServerHierarchyOfHierarchyTests::SetUp(); m_root3 = AZStd::make_unique((7), "root 3", Root3NetEntityId, EntityInfo::Role::Root); m_child3 = AZStd::make_unique((8), "child 3", Child3NetEntityId, EntityInfo::Role::Child); m_childOfChild3 = AZStd::make_unique((9), "child of child 3", ChildOfChild3NetEntityId, EntityInfo::Role::Child); CreateDeepHierarchy(*m_root3, *m_child3, *m_childOfChild3); m_child3->m_entity->FindComponent()->SetParent(m_root3->m_entity->GetId()); m_childOfChild3->m_entity->FindComponent()->SetParent(m_child3->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { m_childOfChild3.reset(); m_child3.reset(); m_root3.reset(); ServerHierarchyOfHierarchyTests::TearDown(); } AZStd::unique_ptr m_root3; AZStd::unique_ptr m_child3; AZStd::unique_ptr m_childOfChild3; }; TEST_F(ServerHierarchyWithThreeRoots, Top_Root_Active_Then_Inner_Roots_Have_No_Child_References) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 0 ); EXPECT_EQ( m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), 0 ); } TEST_F(ServerHierarchyWithThreeRoots, Top_Root_Deactivates_Inner_Roots_Have_Child_References) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_root.reset(); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); EXPECT_EQ( m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyWithThreeRoots, Child_Of_Top_Root_Deactivates_Inner_Roots_Have_Child_References) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_child.reset(); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); EXPECT_EQ( m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyWithThreeRoots, Child_Of_Child_Of_Top_Root_Deactivates_Inner_Roots_Have_Child_References) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_childOfChild.reset(); EXPECT_EQ( m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); EXPECT_EQ( m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyWithThreeRoots, InnerRootLeftTopRootThenLastChildGetsJoinedEventOnce) { m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); MockNetworkHierarchyCallbackHandler mock; EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_root3->m_entity->GetId())); m_childOfChild3->m_entity->FindComponent()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler); m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); } }