Converted to use AZ::Events for hiearchy notifications. Added unittests.

Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com>
This commit is contained in:
AMZN-Olex
2021-09-14 11:50:51 -04:00
parent 088511577b
commit fcd5b3f184
9 changed files with 131 additions and 37 deletions
@@ -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<NetworkHierarchyNotifications> NetworkHierarchyNotificationBus;
using NetworkHierarchyChangedEvent = AZ::Event<const AZ::EntityId&>;
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<NetworkHierarchyRequests> NetworkHierarchyRequestBus;
@@ -52,6 +52,8 @@ namespace Multiplayer
bool IsHierarchicalRoot() const override { return false; }
AZ::Entity* GetHierarchicalRoot() const override;
AZStd::vector<AZ::Entity*> GetHierarchicalEntities() const override;
void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override;
void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override;
//! @}
protected:
@@ -63,5 +65,8 @@ namespace Multiplayer
AZ::Event<NetEntityId>::Handler m_hierarchyRootNetIdChanged;
void OnHierarchyRootNetIdChanged(NetEntityId rootNetId);
NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent;
NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent;
};
}
@@ -50,6 +50,8 @@ namespace Multiplayer
bool IsHierarchicalChild() const override;
AZStd::vector<AZ::Entity*> 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<AZ::Entity*> m_hierarchicalEntities;
@@ -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<NetworkHierarchyRootComponent>();
m_networkHierarchyChangedEvent.Signal(m_hierarchyRootComponent->GetEntityId());
}
else
{
m_hierarchyRootComponent = nullptr;
m_networkHierarchyLeaveEvent.Signal();
}
}
}
@@ -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<uint32_t>(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)
@@ -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<AzFramework::TransformComponent>())
{
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<AzFramework::TransformComponent>())
{
if (transformComponent->GetParentId() != parentEntityId)
{
transformComponent->SetParent(parentEntityId);
}
}
}
}
@@ -32,8 +32,6 @@
#include <AzFramework/Components/TransformComponent.h>
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<uint32_t>(parentId));
}
AZLOG
(
NET_HierarchyActivationInfo,
"Hierchical entity %s asking for activation - waiting on the parent %u",
entity->GetName().c_str(),
aznumeric_cast<uint32_t>(parentId)
);
return false;
}
}
@@ -226,7 +226,6 @@ namespace Multiplayer
ReplicationRecord notifyRecord = currentRecord;
entity.FindComponent<NetworkHierarchyChildComponent>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
entity.FindComponent<NetworkHierarchyChildComponent>()->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<NetworkHierarchyRootComponent>()->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<NetworkHierarchyChildComponent>()->BindNetworkHierarchyLeaveEventHandler(mock.m_leaveHandler);
// simulate server detaching a child entity
SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId);
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId);
}
/*
* Parent -> Child -> ChildOfChild
*/
@@ -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<NetworkTransformComponent>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
// now the parent id is in the component
entity.FindComponent<NetworkTransformComponent>()->NotifyStateDeltaChanges(notifyRecord);
}
template <typename Component>
@@ -306,8 +330,9 @@ namespace Multiplayer
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
ReplicationRecord notifyRecord = currentRecord;
entity.FindComponent<Component>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
// now the parent id is in the component
entity.FindComponent<Component>()->NotifyStateDeltaChanges(notifyRecord);
}
struct EntityInfo