From fcd5b3f184af27c4514a4f6eab2c6d912ae1d1e9 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Tue, 14 Sep 2021 11:50:51 -0400 Subject: [PATCH] Converted to use AZ::Events for hiearchy notifications. Added unittests. Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Components/NetworkHierarchyBus.h | 22 +++++++------- .../NetworkHierarchyChildComponent.h | 5 ++++ .../NetworkHierarchyRootComponent.h | 5 ++++ .../NetworkHierarchyChildComponent.cpp | 17 +++++++++-- .../NetworkHierarchyRootComponent.cpp | 14 +++++++++ .../Components/NetworkTransformComponent.cpp | 23 +++++++++++---- .../EntityReplication/EntityReplicator.cpp | 28 +++++++++--------- .../Code/Tests/ClientHierarchyTests.cpp | 25 +++++++++++++++- .../Code/Tests/CommonHierarchySetup.h | 29 +++++++++++++++++-- 9 files changed, 131 insertions(+), 37 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h index d644d6eb5a..1401856afe 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h @@ -12,18 +12,8 @@ namespace Multiplayer { - class NetworkHierarchyNotifications - : public AZ::ComponentBus - { - public: - //! Called when a hierarchy has been updated (a child added or removed, etc.) - virtual void OnNetworkHierarchyUpdated([[maybe_unused]] const AZ::EntityId& rootEntityId) {} - - //! Called when an entity has left a hierarchy - virtual void OnNetworkHierarchyLeave() {} - }; - - typedef AZ::EBus NetworkHierarchyNotificationBus; + using NetworkHierarchyChangedEvent = AZ::Event; + using NetworkHierarchyLeaveEvent = AZ::Event<>; class NetworkHierarchyRequests : public AZ::ComponentBus @@ -40,6 +30,14 @@ namespace Multiplayer //! @return true if this entity is the top level root of a hierarchy virtual bool IsHierarchicalRoot() const = 0; + + //! Binds the provided NetworkHierarchyChangedEvent handler to a Network Hierarchy component. + //! @param handler the handler to invoke when the entity's network hierarchy has been modified. + virtual void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) = 0; + + //! Binds the provided NetworkHierarchyLeaveEvent handler to a Network Hierarchy component. + //! @param handler the handler to invoke when the entity left its network hierarchy. + virtual void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) = 0; }; typedef AZ::EBus NetworkHierarchyRequestBus; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h index cfc3803d28..93ce3e25d0 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h @@ -52,6 +52,8 @@ namespace Multiplayer bool IsHierarchicalRoot() const override { return false; } AZ::Entity* GetHierarchicalRoot() const override; AZStd::vector GetHierarchicalEntities() const override; + void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override; + void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override; //! @} protected: @@ -63,5 +65,8 @@ namespace Multiplayer AZ::Event::Handler m_hierarchyRootNetIdChanged; void OnHierarchyRootNetIdChanged(NetEntityId rootNetId); + + NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; + NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; }; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h index 4be8b24d8a..228f953a32 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -50,6 +50,8 @@ namespace Multiplayer bool IsHierarchicalChild() const override; AZStd::vector GetHierarchicalEntities() const override; AZ::Entity* GetHierarchicalRoot() const override; + void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override; + void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override; //! @} protected: @@ -63,6 +65,9 @@ namespace Multiplayer void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); private: + NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; + NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; + AZ::Entity* m_higherRootEntity = nullptr; AZStd::vector m_hierarchicalEntities; diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index cbdf214dfc..93574ec5b4 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -98,6 +98,16 @@ namespace Multiplayer return {}; } + void NetworkHierarchyChildComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyChangedEvent); + } + + void NetworkHierarchyChildComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyLeaveEvent); + } + void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootComponent(NetworkHierarchyRootComponent* hierarchyRoot) { m_hierarchyRootComponent = hierarchyRoot; @@ -109,12 +119,13 @@ namespace Multiplayer const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetEntityId()); controller->SetHierarchyRoot(netRootId); - NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnNetworkHierarchyUpdated, hierarchyRoot->GetEntityId()); + m_networkHierarchyChangedEvent.Signal(hierarchyRoot->GetEntityId()); } else { controller->SetHierarchyRoot(InvalidNetEntityId); - NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnNetworkHierarchyLeave); + + m_networkHierarchyLeaveEvent.Signal(); } } } @@ -125,10 +136,12 @@ namespace Multiplayer if (rootHandle.Exists()) { m_hierarchyRootComponent = rootHandle.FindComponent(); + m_networkHierarchyChangedEvent.Signal(m_hierarchyRootComponent->GetEntityId()); } else { m_hierarchyRootComponent = nullptr; + m_networkHierarchyLeaveEvent.Signal(); } } } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index 5f546298cb..ced2c79bce 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -124,6 +124,16 @@ namespace Multiplayer return GetEntity(); } + void NetworkHierarchyRootComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyChangedEvent); + } + + void NetworkHierarchyRootComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyLeaveEvent); + } + void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent) { const AZ::EntityId entityBusId = *AZ::TransformNotificationBus::GetCurrentBusId(); @@ -168,6 +178,8 @@ namespace Multiplayer uint32_t currentEntityCount = aznumeric_cast(m_hierarchicalEntities.size()); RecursiveAttachHierarchicalEntities(GetEntityId(), currentEntityCount); + + m_networkHierarchyChangedEvent.Signal(GetEntityId()); } bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount) @@ -255,6 +267,8 @@ namespace Multiplayer } } } + + m_networkHierarchyChangedEvent.Signal(GetEntityId()); } void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index a37b6b52ed..8461f950fd 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -97,15 +97,28 @@ namespace Multiplayer void NetworkTransformComponent::OnParentIdChangedEvent([[maybe_unused]] NetEntityId newParent) { - const ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(newParent); - if (rootHandle.Exists()) + if (newParent == InvalidNetEntityId) { - const AZ::EntityId parentEntityId = rootHandle.GetEntity()->GetId(); if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) { - if (transformComponent->GetParentId() != parentEntityId) + if (transformComponent->GetParentId() != AZ::EntityId()) { - transformComponent->SetParent(parentEntityId); + transformComponent->SetParent(AZ::EntityId()); + } + } + } + else + { + const ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(newParent); + if (rootHandle.Exists()) + { + const AZ::EntityId parentEntityId = rootHandle.GetEntity()->GetId(); + if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) + { + if (transformComponent->GetParentId() != parentEntityId) + { + transformComponent->SetParent(parentEntityId); + } } } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index ad8c883f34..bd98d15923 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -32,8 +32,6 @@ #include -AZ_CVAR(bool, bg_debugHierarchyActivation, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Helpful messages when debugging network hierarchy behavior"); - namespace Multiplayer { EntityReplicator::EntityReplicator @@ -441,22 +439,22 @@ namespace Multiplayer const AZ::Entity* parentEntity = parentHandle.GetEntity(); if (parentEntity && parentEntity->GetState() == AZ::Entity::State::Active) { - if (bg_debugHierarchyActivation) - { - AZLOG_DEBUG( - "Entity %s asking for activation - granted", - entity->GetName().c_str()); - } + AZLOG + ( + NET_HierarchyActivationInfo, + "Hierchical entity %s asking for activation - granted", + entity->GetName().c_str() + ); return true; } - if (bg_debugHierarchyActivation) - { - AZLOG_DEBUG( - "Entity %s asking for activation - waiting on the parent %u", - entity->GetName().c_str(), - aznumeric_cast(parentId)); - } + AZLOG + ( + NET_HierarchyActivationInfo, + "Hierchical entity %s asking for activation - waiting on the parent %u", + entity->GetName().c_str(), + aznumeric_cast(parentId) + ); return false; } } diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index 29d4f7f809..1483f32023 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -226,7 +226,6 @@ namespace Multiplayer ReplicationRecord notifyRecord = currentRecord; entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); } @@ -287,6 +286,30 @@ namespace Multiplayer ); } + TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Updated_Event_On_Child_Detached_On_Server) + { + MockNetworkHierarchyCallbackHandler mock; + EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_rootEntity->GetId())); + + m_rootEntity->FindComponent()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler); + + // simulate server detaching a child entity + SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId); + } + + TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Leave_Event_On_Child_Detached_On_Server) + { + MockNetworkHierarchyCallbackHandler mock; + EXPECT_CALL(mock, OnNetworkHierarchyLeave); + + m_childEntity->FindComponent()->BindNetworkHierarchyLeaveEventHandler(mock.m_leaveHandler); + + // simulate server detaching a child entity + SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId); + } + /* * Parent -> Child -> ChildOfChild */ diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h index 89d0d32428..d4f9d218e2 100644 --- a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -35,6 +35,29 @@ namespace Multiplayer using namespace testing; using namespace ::UnitTest; + class NetworkHierarchyCallbacks + { + public: + virtual void OnNetworkHierarchyLeave() = 0; + virtual void OnNetworkHierarchyUpdated(const AZ::EntityId& hierarchyRootId) = 0; + }; + + class MockNetworkHierarchyCallbackHandler : public NetworkHierarchyCallbacks + { + public: + MockNetworkHierarchyCallbackHandler() + : m_leaveHandler([this]() { OnNetworkHierarchyLeave(); }) + , m_changedHandler([this](const AZ::EntityId& rootId) { OnNetworkHierarchyUpdated(rootId); }) + { + } + + NetworkHierarchyLeaveEvent::Handler m_leaveHandler; + NetworkHierarchyChangedEvent::Handler m_changedHandler; + + MOCK_METHOD0(OnNetworkHierarchyLeave, void()); + MOCK_METHOD1(OnNetworkHierarchyUpdated, void(const AZ::EntityId&)); + }; + class HierarchyTests : public AllocatorsFixture { @@ -282,8 +305,9 @@ namespace Multiplayer NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + ReplicationRecord notifyRecord = currentRecord; entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - // now the parent id is in the component + entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); } template @@ -306,8 +330,9 @@ namespace Multiplayer NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + ReplicationRecord notifyRecord = currentRecord; entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - // now the parent id is in the component + entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); } struct EntityInfo