Hierarchical Components, phase 1 with unit tests

- Added fundamental Hierarchical Components
- 69 unit tests for various hierarchical scenarios
This commit is contained in:
Olex Lozitskiy
2021-09-22 10:05:39 -04:00
committed by GitHub
20 changed files with 3190 additions and 43 deletions
@@ -199,6 +199,8 @@ namespace Multiplayer
friend class NetworkEntityManager;
friend class EntityReplicationManager;
friend class HierarchyTests;
};
bool NetworkRoleHasController(NetEntityRole networkRole);
@@ -0,0 +1,48 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
namespace Multiplayer
{
using NetworkHierarchyChangedEvent = AZ::Event<const AZ::EntityId&>;
using NetworkHierarchyLeaveEvent = AZ::Event<>;
class NetworkHierarchyRequests
: public AZ::ComponentBus
{
public:
//! @returns true if the entity a hierarchical component attached should be considered for inclusion in a hierarchy
//! this should return false when an entity is deactivating
virtual bool IsHierarchyEnabled() const = 0;
//! @returns hierarchical entities, the first element is the top level root
virtual AZStd::vector<AZ::Entity*> GetHierarchicalEntities() const = 0;
//! @returns the top level root of a hierarchy, or nullptr if this entity is not in a hierarchy
virtual AZ::Entity* GetHierarchicalRoot() const = 0;
//! @return true if this entity is a child entity within a hierarchy
virtual bool IsHierarchicalChild() const = 0;
//! @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;
}
@@ -0,0 +1,86 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <Multiplayer/Components/NetworkHierarchyBus.h>
#include <Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.h>
namespace Multiplayer
{
class NetworkHierarchyRootComponent;
//! @class NetworkHierarchyChildComponent
//! @brief Component that declares network dependency on the parent of this entity
/*
* The parent of this entity should have @NetworkHierarchyChildComponent (or @NetworkHierarchyRootComponent).
* A network hierarchy is a collection of entities with one @NetworkHierarchyRootComponent at the top parent
* and one or more @NetworkHierarchyChildComponent on its child entities.
*/
class NetworkHierarchyChildComponent final
: public NetworkHierarchyChildComponentBase
, public NetworkHierarchyRequestBus::Handler
{
friend class NetworkHierarchyRootComponent;
public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyChildComponent, s_networkHierarchyChildComponentConcreteUuid, Multiplayer::NetworkHierarchyChildComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
NetworkHierarchyChildComponent();
//! NetworkHierarchyChildComponentBase overrides.
//! @{
void OnInit() override;
void OnActivate(EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(EntityIsMigrating entityIsMigrating) override;
//! @}
//! NetworkHierarchyRequestBus overrides.
//! @{
bool IsHierarchyEnabled() const override;
bool IsHierarchicalChild() const override;
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:
//! Used by @NetworkHierarchyRootComponent
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
private:
AZ::ChildChangedEvent::Handler m_childChangedHandler;
AZ::ParentChangedEvent::Handler m_parentChangedHandler;
void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child);
void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId parent);
//! Points to the top level root.
AZ::Entity* m_rootEntity = nullptr;
AZ::Event<NetEntityId>::Handler m_hierarchyRootNetIdChanged;
void OnHierarchyRootNetIdChanged(NetEntityId rootNetId);
NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent;
NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent;
//! Set to false when deactivating or otherwise not to be included in hierarchy considerations.
bool m_isHierarchyEnabled = true;
void NotifyChildrenHierarchyDisbanded();
};
}
@@ -0,0 +1,100 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <Multiplayer/Components/NetworkHierarchyBus.h>
#include <Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.h>
namespace Multiplayer
{
//! @class NetworkHierarchyRootComponent
//! @brief Component that declares the top level entity of a network hierarchy.
/*
* Call @GetHierarchicalEntities to get the list of hierarchical entities.
* A network hierarchy is meant to be a small group of entities. You can control the maximum supported size of
* a network hierarchy by modifying CVar @bg_hierarchyEntityMaxLimit.
*
* A root component marks either a top most root of a hierarchy, or an inner root of an attach hierarchy.
*/
class NetworkHierarchyRootComponent final
: public NetworkHierarchyRootComponentBase
, public NetworkHierarchyRequestBus::Handler
{
friend class NetworkHierarchyChildComponent;
public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
NetworkHierarchyRootComponent();
//! NetworkHierarchyRootComponentBase overrides.
//! @{
void OnInit() override;
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
//! @}
//! NetworkHierarchyRequestBus overrides.
//! @{
bool IsHierarchyEnabled() const override;
bool IsHierarchicalRoot() const override;
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:
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
private:
AZ::ChildChangedEvent::Handler m_childChangedHandler;
AZ::ParentChangedEvent::Handler m_parentChangedHandler;
void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child);
void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId parent);
NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent;
NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent;
//! Points to the top level root, if this root is an inner root in this hierarchy.
AZ::Entity* m_rootEntity = nullptr;
AZStd::vector<AZ::Entity*> m_hierarchicalEntities;
//! Rebuilds hierarchy starting from this root component's entity.
void RebuildHierarchy();
//! @param underEntity Walk the child entities that belong to @underEntity and consider adding them to the hierarchy
//! @param currentEntityCount The total number of entities in the hierarchy prior to calling this method,
//! used to avoid adding too many entities to the hierarchy while walking recursively the relevant entities.
//! @currentEntityCount will be modified to reflect the total entity count upon completion of this method.
//! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise
bool RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount);
//! @param entity Add the child entity and any of its relevant children to the hierarchy
//! @param currentEntityCount The total number of entities in the hierarchy prior to calling this method,
//! used to avoid adding too many entities to the hierarchy while walking recursively the relevant entities.
//! @currentEntityCount will be modified to reflect the total entity count upon completion of this method.
//! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise
bool RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount);
void SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity);
//! Set to false when deactivating or otherwise not to be included in hierarchy considerations.
bool m_isHierarchyEnabled = true;
};
}
@@ -31,9 +31,11 @@ namespace Multiplayer
private:
void OnPreRender(float deltaTime);
void OnCorrection();
void OnParentChanged(NetEntityId parentId);
EntityPreRenderEvent::Handler m_entityPreRenderEventHandler;
EntityCorrectionEvent::Handler m_entityCorrectionEventHandler;
AZ::Event<NetEntityId>::Handler m_parentChangedEventHandler;
Multiplayer::HostFrameId m_targetHostFrameId = HostFrameId(0);
};
@@ -49,7 +51,9 @@ namespace Multiplayer
private:
void OnTransformChangedEvent(const AZ::Transform& worldTm);
void OnParentIdChangedEvent(AZ::EntityId oldParent, AZ::EntityId newParent);
AZ::TransformChangedEvent::Handler m_transformChangedHandler;
AZ::ParentChangedEvent::Handler m_parentIdChangedHandler;
};
}
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<Component
Name="NetworkHierarchyChildComponent"
Namespace="Multiplayer"
OverrideComponent="true"
OverrideController="false"
OverrideInclude="Multiplayer/Components/NetworkHierarchyChildComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<NetworkProperty Type="NetEntityId" Name="hierarchyRoot" Init="InvalidNetEntityId" ReplicateFrom="Authority" ReplicateTo="Client" IsRewindable="false" IsPredictable="false" IsPublic="true" Container="Object" ExposeToEditor="false" ExposeToScript="false" GenerateEventBindings="true" />
</Component>
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<Component
Name="NetworkHierarchyRootComponent"
Namespace="Multiplayer"
OverrideComponent="true"
OverrideController="false"
OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<NetworkProperty Type="NetEntityId" Name="hierarchyRoot" Init="InvalidNetEntityId" ReplicateFrom="Authority" ReplicateTo="Client" IsRewindable="false" IsPredictable="false" IsPublic="true" Container="Object" ExposeToEditor="false" ExposeToScript="false" GenerateEventBindings="true" />
</Component>
@@ -0,0 +1,222 @@
/*
* 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 <AzCore/Serialization/EditContext.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyBus.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
namespace Multiplayer
{
void NetworkHierarchyChildComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetworkHierarchyChildComponent, NetworkHierarchyChildComponentBase>()
->Version(1);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<NetworkHierarchyChildComponent>(
"Network Hierarchy Child", "Declares a network dependency on the root of this hierarchy.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Multiplayer")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
;
}
}
NetworkHierarchyChildComponentBase::Reflect(context);
}
void NetworkHierarchyChildComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("NetworkTransformComponent"));
}
void NetworkHierarchyChildComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent"));
}
void NetworkHierarchyChildComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent"));
incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent"));
}
NetworkHierarchyChildComponent::NetworkHierarchyChildComponent()
: m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); })
, m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); })
, m_hierarchyRootNetIdChanged([this](NetEntityId rootNetId) {OnHierarchyRootNetIdChanged(rootNetId); })
{
}
void NetworkHierarchyChildComponent::OnInit()
{
}
void NetworkHierarchyChildComponent::OnActivate([[maybe_unused]] EntityIsMigrating entityIsMigrating)
{
m_isHierarchyEnabled = true;
HierarchyRootAddEvent(m_hierarchyRootNetIdChanged);
NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId());
if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent<AzFramework::TransformComponent>())
{
transformComponent->BindChildChangedEventHandler(m_childChangedHandler);
transformComponent->BindParentChangedEventHandler(m_parentChangedHandler);
}
}
void NetworkHierarchyChildComponent::OnDeactivate([[maybe_unused]] EntityIsMigrating entityIsMigrating)
{
m_isHierarchyEnabled = false;
if (m_rootEntity)
{
if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
NotifyChildrenHierarchyDisbanded();
NetworkHierarchyRequestBus::Handler::BusDisconnect();
}
bool NetworkHierarchyChildComponent::IsHierarchyEnabled() const
{
return m_isHierarchyEnabled;
}
bool NetworkHierarchyChildComponent::IsHierarchicalChild() const
{
return GetHierarchyRoot() != InvalidNetEntityId;
}
AZ::Entity* NetworkHierarchyChildComponent::GetHierarchicalRoot() const
{
return m_rootEntity;
}
AZStd::vector<AZ::Entity*> NetworkHierarchyChildComponent::GetHierarchicalEntities() const
{
if (m_rootEntity)
{
return m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities();
}
return {};
}
void NetworkHierarchyChildComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler)
{
handler.Connect(m_networkHierarchyChangedEvent);
}
void NetworkHierarchyChildComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler)
{
handler.Connect(m_networkHierarchyLeaveEvent);
}
void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot)
{
m_rootEntity = hierarchyRoot;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
if (m_rootEntity)
{
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId());
controller->SetHierarchyRoot(netRootId);
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
}
else
{
controller->SetHierarchyRoot(InvalidNetEntityId);
m_networkHierarchyLeaveEvent.Signal();
}
}
if (m_rootEntity == nullptr)
{
NotifyChildrenHierarchyDisbanded();
}
}
void NetworkHierarchyChildComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child)
{
if (m_rootEntity)
{
if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
}
void NetworkHierarchyChildComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, [[maybe_unused]] AZ::EntityId parent)
{
if (m_rootEntity)
{
if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
}
void NetworkHierarchyChildComponent::OnHierarchyRootNetIdChanged(NetEntityId rootNetId)
{
ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(rootNetId);
if (rootHandle.Exists())
{
AZ::Entity* newRoot = rootHandle.GetEntity();
if (m_rootEntity != newRoot)
{
m_rootEntity = newRoot;
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
}
}
else
{
m_isHierarchyEnabled = false;
m_rootEntity = nullptr;
m_networkHierarchyLeaveEvent.Signal();
}
}
void NetworkHierarchyChildComponent::NotifyChildrenHierarchyDisbanded()
{
AZStd::vector<AZ::EntityId> allChildren;
AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren);
for (const AZ::EntityId& childEntityId : allChildren)
{
if (const AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(childEntityId))
{
if (auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>())
{
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr);
}
else if (auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>())
{
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr);
}
}
}
}
}
@@ -0,0 +1,329 @@
/*
* 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 <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
AZ_CVAR(uint32_t, bg_hierarchyEntityMaxLimit, 16, nullptr, AZ::ConsoleFunctorFlags::Null,
"Maximum allowed size of network entity hierarchies, including top level entity.");
namespace Multiplayer
{
void NetworkHierarchyRootComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetworkHierarchyRootComponent, NetworkHierarchyRootComponentBase>()
->Version(1);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<NetworkHierarchyRootComponent>(
"Network Hierarchy Root", "Marks the entity as the root of an entity hierarchy.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Multiplayer")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
;
}
}
NetworkHierarchyRootComponentBase::Reflect(context);
}
void NetworkHierarchyRootComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("NetworkTransformComponent"));
}
void NetworkHierarchyRootComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent"));
}
void NetworkHierarchyRootComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent"));
incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent"));
}
NetworkHierarchyRootComponent::NetworkHierarchyRootComponent()
: m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); })
, m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); })
{
}
void NetworkHierarchyRootComponent::OnInit()
{
}
void NetworkHierarchyRootComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
m_isHierarchyEnabled = true;
m_hierarchicalEntities.push_back(GetEntity());
NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId());
if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent<AzFramework::TransformComponent>())
{
transformComponent->BindChildChangedEventHandler(m_childChangedHandler);
transformComponent->BindParentChangedEventHandler(m_parentChangedHandler);
}
}
void NetworkHierarchyRootComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
m_isHierarchyEnabled = false;
if (m_rootEntity)
{
// Tell parent to re-build the hierarchy
if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
else
{
// Notify children that the hierarchy is disbanding
AZStd::vector<AZ::EntityId> allChildren;
AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren);
for (const AZ::EntityId& childEntityId : allChildren)
{
if (const AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(childEntityId))
{
SetRootForEntity(nullptr, childEntity);
}
}
}
m_childChangedHandler.Disconnect();
m_parentChangedHandler.Disconnect();
NetworkHierarchyRequestBus::Handler::BusDisconnect();
m_hierarchicalEntities.clear();
m_rootEntity = nullptr;
}
bool NetworkHierarchyRootComponent::IsHierarchyEnabled() const
{
return m_isHierarchyEnabled;
}
bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const
{
return GetHierarchyRoot() == InvalidNetEntityId;
}
bool NetworkHierarchyRootComponent::IsHierarchicalChild() const
{
return !IsHierarchicalRoot();
}
AZStd::vector<AZ::Entity*> NetworkHierarchyRootComponent::GetHierarchicalEntities() const
{
return m_hierarchicalEntities;
}
AZ::Entity* NetworkHierarchyRootComponent::GetHierarchicalRoot() const
{
if (m_rootEntity)
{
return m_rootEntity;
}
return GetEntity();
}
void NetworkHierarchyRootComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler)
{
handler.Connect(m_networkHierarchyChangedEvent);
}
void NetworkHierarchyRootComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler)
{
handler.Connect(m_networkHierarchyLeaveEvent);
}
void NetworkHierarchyRootComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child)
{
if (IsHierarchicalRoot())
{
// Parent-child notifications are not reliable enough to avoid duplicate notifications,
// so we will rebuild from scratch to avoid duplicate entries in @m_hierarchicalEntities.
RebuildHierarchy();
}
else if (NetworkHierarchyRootComponent* root = GetHierarchicalRoot()->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent)
{
// If the parent is part of a hierarchy, it will detect this entity as a new child and rebuild hierarchy.
// Thus, we only need to take care of a case when the parent is not part of a hierarchy,
// in which case, this entity will be a new root of a new hierarchy.
if (AZ::Entity* parentEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(newParent))
{
if (parentEntity->FindComponent<NetworkHierarchyRootComponent>() == nullptr &&
parentEntity->FindComponent<NetworkHierarchyChildComponent>() == nullptr)
{
RebuildHierarchy();
}
else
{
m_hierarchicalEntities.clear();
}
}
else
{
// Detached from parent
RebuildHierarchy();
}
}
void NetworkHierarchyRootComponent::RebuildHierarchy()
{
AZStd::vector<AZ::Entity*> previousEntities;
m_hierarchicalEntities.swap(previousEntities);
m_hierarchicalEntities.push_back(GetEntity()); // Add the root.
uint32_t currentEntityCount = aznumeric_cast<uint32_t>(m_hierarchicalEntities.size());
RecursiveAttachHierarchicalEntities(GetEntityId(), currentEntityCount);
bool hierarchyChanged = false;
// Send out join and leave events.
for (AZ::Entity* currentEntity : m_hierarchicalEntities)
{
const auto prevEntityIterator = AZStd::find(previousEntities.begin(), previousEntities.end(), currentEntity);
if (prevEntityIterator != previousEntities.end())
{
// This entity was here before the build of the hierarchy.
previousEntities.erase(prevEntityIterator);
}
else
{
// This is a newly added entity to the network hierarchy.
hierarchyChanged = true;
SetRootForEntity(GetEntity(), currentEntity);
}
}
// These entities were removed since last rebuild.
for (const AZ::Entity* previousEntity : previousEntities)
{
SetRootForEntity(nullptr, previousEntity);
}
if (!previousEntities.empty())
{
hierarchyChanged = true;
}
if (hierarchyChanged)
{
m_networkHierarchyChangedEvent.Signal(GetEntityId());
}
}
void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity)
{
if (auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>())
{
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(root);
}
else if (auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>())
{
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(root);
}
}
bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount)
{
AZStd::vector<AZ::EntityId> allChildren;
AZ::TransformBus::EventResult(allChildren, underEntity, &AZ::TransformBus::Events::GetChildren);
for (const AZ::EntityId& newChildId : allChildren)
{
if (!RecursiveAttachHierarchicalChild(newChildId, currentEntityCount))
{
return false;
}
}
return true;
}
bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount)
{
if (currentEntityCount >= bg_hierarchyEntityMaxLimit)
{
AZLOG_WARN("Entity %s is trying to build a network hierarchy that is too large. bg_hierarchyEntityMaxLimit is currently set to (%u)",
GetEntity()->GetName().c_str(), static_cast<uint32_t>(bg_hierarchyEntityMaxLimit));
return false;
}
if (AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entity))
{
auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>();
auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>();
if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchyEnabled()) ||
(hierarchyRootComponent && hierarchyRootComponent->IsHierarchyEnabled()))
{
m_hierarchicalEntities.push_back(childEntity);
++currentEntityCount;
if (!RecursiveAttachHierarchicalEntities(entity, currentEntityCount))
{
return false;
}
}
}
return true;
}
void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot)
{
m_rootEntity = hierarchyRoot;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
if (hierarchyRoot)
{
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetId());
controller->SetHierarchyRoot(netRootId);
}
else
{
controller->SetHierarchyRoot(InvalidNetEntityId);
}
}
if (m_rootEntity == nullptr)
{
// We lost the parent hierarchical entity, so as a root we need to re-build our own hierarchy.
RebuildHierarchy();
}
}
}
@@ -28,6 +28,7 @@ namespace Multiplayer
NetworkTransformComponent::NetworkTransformComponent()
: m_entityPreRenderEventHandler([this](float deltaTime) { OnPreRender(deltaTime); })
, m_entityCorrectionEventHandler([this]() { OnCorrection(); })
, m_parentChangedEventHandler([this](NetEntityId parentId) { OnParentChanged(parentId); })
{
;
}
@@ -41,6 +42,7 @@ namespace Multiplayer
{
GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler);
GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler);
ParentEntityIdAddEvent(m_parentChangedEventHandler);
}
void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
@@ -97,10 +99,26 @@ namespace Multiplayer
}
}
void NetworkTransformComponent::OnParentChanged(NetEntityId parentId)
{
const ConstNetworkEntityHandle parentEntityHandle = GetNetworkEntityManager()->GetEntity(parentId);
if (parentEntityHandle.Exists())
{
if (const AZ::Entity* parentEntity = parentEntityHandle.GetEntity())
{
GetEntity()->GetTransform()->SetParent(parentEntity->GetId());
}
}
else
{
GetEntity()->GetTransform()->SetParent(AZ::EntityId());
}
}
NetworkTransformComponentController::NetworkTransformComponentController(NetworkTransformComponent& parent)
: NetworkTransformComponentControllerBase(parent)
, m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformChangedEvent(worldTm); })
, m_parentIdChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId newParent) { OnParentIdChangedEvent(oldParent, newParent); })
{
;
}
@@ -109,6 +127,9 @@ namespace Multiplayer
{
GetParent().GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler);
OnTransformChangedEvent(GetParent().GetTransformComponent()->GetWorldTM());
GetParent().GetTransformComponent()->BindParentChangedEventHandler(m_parentIdChangedHandler);
OnParentIdChangedEvent(AZ::EntityId(), GetParent().GetTransformComponent()->GetParentId());
}
void NetworkTransformComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
@@ -122,4 +143,14 @@ namespace Multiplayer
SetTranslation(worldTm.GetTranslation());
SetScale(worldTm.GetUniformScale());
}
void NetworkTransformComponentController::OnParentIdChangedEvent([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent)
{
AZ::Entity* parentEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(newParent);
if (parentEntity)
{
const ConstNetworkEntityHandle parentHandle(parentEntity, GetNetworkEntityTracker());
SetParentEntityId(parentHandle.GetNetEntityId());
}
}
}
@@ -6,12 +6,14 @@
*
*/
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <Source/MultiplayerGem.h>
#include <Source/MultiplayerSystemComponent.h>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
namespace Multiplayer
{
@@ -75,6 +75,8 @@ namespace Multiplayer
void EntityReplicationManager::ActivatePendingEntities()
{
AZStd::vector<NetEntityId> notReadyEntities;
const AZ::TimeMs endTimeMs = AZ::GetElapsedTimeMs() + m_entityActivationTimeSliceMs;
while (!m_entitiesPendingActivation.empty())
{
@@ -83,7 +85,14 @@ namespace Multiplayer
EntityReplicator* entityReplicator = GetEntityReplicator(entityId);
if (entityReplicator && !entityReplicator->IsMarkedForRemoval())
{
entityReplicator->ActivateNetworkEntity();
if (entityReplicator->IsReadyToActivate())
{
entityReplicator->ActivateNetworkEntity();
}
else
{
notReadyEntities.push_back(entityId);
}
}
if (m_entityActivationTimeSliceMs > AZ::TimeMs{ 0 } && AZ::GetElapsedTimeMs() > endTimeMs)
{
@@ -91,6 +100,11 @@ namespace Multiplayer
break;
}
}
for (NetEntityId netEntityId : notReadyEntities)
{
m_entitiesPendingActivation.push_back(netEntityId);
}
}
void EntityReplicationManager::SendUpdates(AZ::TimeMs hostTimeMs)
@@ -249,15 +263,15 @@ namespace Multiplayer
void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs hostTimeMs)
{
EntityReplicatorList toSendList = GenerateEntityUpdateList();
AZLOG(NET_ReplicationInfo, "Sending %zd updates from %d to %d", toSendList.size(), (uint8_t)GetNetworkEntityManager()->GetHostId(), (uint8_t)GetRemoteHostId());
// prep a replication record for send, at this point, everything needs to be sent
for (EntityReplicator* replicator : toSendList)
{
replicator->GetPropertyPublisher()->PrepareSerialization();
}
// While our to send list is not empty, build up another packet to send
do
{
@@ -524,7 +538,7 @@ namespace Multiplayer
bool EntityReplicationManager::HandlePropertyChangeMessage
(
AzNetworking::IConnection* invokingConnection,
AzNetworking::IConnection* invokingConnection,
EntityReplicator* entityReplicator,
AzNetworking::PacketId packetId,
NetEntityId netEntityId,
@@ -1137,7 +1151,7 @@ namespace Multiplayer
AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> outputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast<uint32_t>(message.m_propertyUpdateData.GetSize()));
if (!HandlePropertyChangeMessage
(
invokingConnection,
invokingConnection,
replicator,
AzNetworking::InvalidPacketId,
message.m_entityId,
@@ -6,23 +6,25 @@
*
*/
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
#include <Source/NetworkEntity/EntityReplication/PropertyPublisher.h>
#include <Source/NetworkEntity/EntityReplication/PropertySubscriber.h>
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <Multiplayer/Components/NetworkTransformComponent.h>
#include <Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/EntityReplication/PropertyPublisher.h>
#include <Source/NetworkEntity/EntityReplication/PropertySubscriber.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/PacketLayer/IPacket.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Console/IConsole.h>
@@ -48,7 +50,7 @@ namespace Multiplayer
, m_onForwardRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
, m_onSendAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
, m_onForwardAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
, m_onEntityStopHandler([this](const ConstNetworkEntityHandle &) { OnEntityRemovedEvent(); })
, m_onEntityStopHandler([this](const ConstNetworkEntityHandle&) { OnEntityRemovedEvent(); })
, m_proxyRemovalEvent([this] { OnProxyRemovalTimedEvent(); }, AZ::Name("ProxyRemovalTimedEvent"))
{
if (auto localEnt = m_entityHandle.GetEntity())
@@ -119,12 +121,12 @@ namespace Multiplayer
{
m_replicationManager.AddReplicatorToPendingSend(*this);
m_propertyPublisher = AZStd::make_unique<PropertyPublisher>
(
GetRemoteNetworkRole(),
!RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False,
m_netBindComponent,
*m_connection
);
(
GetRemoteNetworkRole(),
!RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False,
m_netBindComponent,
*m_connection
);
m_netBindComponent->AddEntityDirtiedEventHandler(m_onEntityDirtiedHandler);
}
else
@@ -279,7 +281,7 @@ namespace Multiplayer
AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent");
bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
&& (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client;
bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous;
if (isAuthority || isClient || isAutonomous)
@@ -306,9 +308,9 @@ namespace Multiplayer
bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const
{
bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority);
&& (GetRemoteNetworkRole() == NetEntityRole::Authority);
bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client)
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
return isServer || isClient;
}
@@ -405,6 +407,62 @@ namespace Multiplayer
return m_replicationManager.GetResendTimeoutTimeMs();
}
bool EntityReplicator::IsReadyToActivate() const
{
const AZ::Entity* entity = m_entityHandle.GetEntity();
AZ_Assert(entity, "Entity replicator entity unexpectedly missing");
const NetworkHierarchyChildComponent* hierarchyChildComponent = entity->FindComponent<NetworkHierarchyChildComponent>();
const NetworkHierarchyRootComponent* hierarchyRootComponent = nullptr;
if (hierarchyChildComponent == nullptr)
{
// Child and root hierarchy components are mutually exclusive
hierarchyRootComponent = entity->FindComponent<NetworkHierarchyRootComponent>();
}
if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchicalChild())
|| (hierarchyRootComponent && hierarchyRootComponent->IsHierarchicalChild()))
{
// If hierarchy is enabled for the entity, check if the parent is available
if (const NetworkTransformComponent* networkTransform = entity->FindComponent<NetworkTransformComponent>())
{
const NetEntityId parentId = networkTransform->GetParentEntityId();
/*
* For root entities attached to a level, a network parent won't be set.
* In this case, this entity is the root entity of the hierarchy and it will be activated first.
*/
if (parentId != InvalidNetEntityId)
{
ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId);
const AZ::Entity* parentEntity = parentHandle.GetEntity();
if (parentEntity && parentEntity->GetState() == AZ::Entity::State::Active)
{
AZLOG
(
NET_HierarchyActivationInfo,
"Hierchical entity %s asking for activation - granted",
entity->GetName().c_str()
);
return true;
}
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;
}
}
}
return true;
}
NetworkEntityUpdateMessage EntityReplicator::GenerateUpdatePacket()
{
if (IsMarkedForRemoval() && OwnsReplicatorLifetime()) // TODO: clean this up
@@ -36,7 +36,7 @@ namespace Multiplayer
{
public:
EntityReplicator(EntityReplicationManager& replicationManager, AzNetworking::IConnection* connection, NetEntityRole remoteNetworkRole, const ConstNetworkEntityHandle& entityHandle);
virtual ~EntityReplicator();
~EntityReplicator() override;
NetEntityRole GetBoundLocalNetworkRole() const;
NetEntityRole GetRemoteNetworkRole() const;
@@ -62,6 +62,8 @@ namespace Multiplayer
bool IsDeletionAcknowledged() const;
bool WasMigrated() const;
void SetWasMigrated(bool wasMigrated);
// If an entity is part of a network hierarchy then it is only ready to activate when its direct parent entity is active.
bool IsReadyToActivate() const;
NetworkEntityUpdateMessage GenerateUpdatePacket();
@@ -0,0 +1,390 @@
/*
* 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 <CommonHierarchySetup.h>
#include <MockInterfaces.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Name/Name.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzTest/AzTest.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <NetworkEntity/EntityReplication/EntityReplicator.h>
namespace Multiplayer
{
using namespace testing;
using namespace ::UnitTest;
/*
* Test NetBindComponent activation. This must work before more complicated tests.
*/
TEST_F(HierarchyTests, On_Client_NetBindComponent_Activate)
{
AZStd::unique_ptr<AZ::Entity> entity = AZStd::make_unique<AZ::Entity>();
entity->CreateComponent<NetBindComponent>();
SetupEntity(entity, NetEntityId{ 1 }, NetEntityRole::Client);
entity->Activate();
StopEntity(entity);
entity->Deactivate();
}
/*
* Hierarchy test - a child entity on a client delaying activation until its hierarchical parent has been activated
*/
TEST_F(HierarchyTests, On_Client_EntityReplicator_DontActivate_BeforeParent)
{
// Create a child entity that will be tested for activation inside a hierarchy
AZStd::unique_ptr<AZ::Entity> childEntity = AZStd::make_unique<AZ::Entity>();
CreateEntityWithChildHierarchy(childEntity);
SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client);
// child entity is not activated on purpose here, we are about to test conditional activation check
// we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller)
SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 });
SetHierarchyRootFieldOnNetworkHierarchyChild<NetworkHierarchyChildComponent>(childEntity, NetEntityId{ 1 });
// Create an entity replicator for the child entity
const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get());
EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle);
entityReplicator.Initialize(childHandle);
// Entity replicator should not be ready to activate the entity because its parent does not exist
EXPECT_EQ(entityReplicator.IsReadyToActivate(), false);
}
TEST_F(HierarchyTests, On_Client_EntityReplicator_DontActivate_Inner_Root_Before_Top_Root)
{
// Create a child entity that will be tested for activation inside a hierarchy
AZStd::unique_ptr<AZ::Entity> innerRootEntity = AZStd::make_unique<AZ::Entity>();
CreateEntityWithRootHierarchy(innerRootEntity);
SetupEntity(innerRootEntity, NetEntityId{ 2 }, NetEntityRole::Client);
// child entity is not activated on purpose here, we are about to test conditional activation check
// we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller)
SetParentIdOnNetworkTransform(innerRootEntity, NetEntityId{ 1 });
SetHierarchyRootFieldOnNetworkHierarchyChild<NetworkHierarchyRootComponent>(innerRootEntity, NetEntityId{ 1 });
// Create an entity replicator for the child entity
const NetworkEntityHandle innerRootHandle(innerRootEntity.get(), m_networkEntityTracker.get());
EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, innerRootHandle);
entityReplicator.Initialize(innerRootHandle);
// Entity replicator should not be ready to activate the entity because its parent does not exist
EXPECT_EQ(entityReplicator.IsReadyToActivate(), false);
}
TEST_F(HierarchyTests, On_Client_Not_In_Hierarchy_EntityReplicator_Ignores_Parent)
{
// Create a child entity that will be tested for activation inside a hierarchy
AZStd::unique_ptr<AZ::Entity> childEntity = AZStd::make_unique<AZ::Entity>();
CreateEntityWithChildHierarchy(childEntity);
SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client);
// child entity is not activated on purpose here, we are about to test conditional activation check
// we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller)
SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 });
SetHierarchyRootFieldOnNetworkHierarchyChild<NetworkHierarchyChildComponent>(childEntity, InvalidNetEntityId);
// Create an entity replicator for the child entity
const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get());
EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle);
entityReplicator.Initialize(childHandle);
// Entity replicator should not be ready to activate the entity because its parent does not exist
EXPECT_EQ(entityReplicator.IsReadyToActivate(), true);
}
/*
* Hierarchy test - a child entity on a client allowing activation when its hierarchical parent is active
*/
TEST_F(HierarchyTests, On_Client_EntityReplicator_Activates_AfterParent)
{
AZStd::unique_ptr<AZ::Entity> childEntity = AZStd::make_unique<AZ::Entity>();
CreateEntityWithChildHierarchy(childEntity);
SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client);
// we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller)
SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 });
SetHierarchyRootFieldOnNetworkHierarchyChild<NetworkHierarchyChildComponent>(childEntity, NetEntityId{ 1 });
// Create an entity replicator for the child entity
const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get());
EntityReplicator childEntityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle);
childEntityReplicator.Initialize(childHandle);
// Now let's create a parent entity and activate it
AZStd::unique_ptr<AZ::Entity> parentEntity = AZStd::make_unique<AZ::Entity>();
CreateEntityWithRootHierarchy(parentEntity);
SetupEntity(parentEntity, NetEntityId{ 1 }, NetEntityRole::Client);
// Create an entity replicator for the parent entity
const NetworkEntityHandle parentHandle(parentEntity.get(), m_networkEntityTracker.get());
ON_CALL(*m_mockNetworkEntityManager, GetEntity(_)).WillByDefault(Return(parentHandle));
EntityReplicator parentEntityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, parentHandle);
parentEntityReplicator.Initialize(parentHandle);
parentEntity->Activate();
// The child should be ready to be activated
EXPECT_EQ(childEntityReplicator.IsReadyToActivate(), true);
StopEntity(parentEntity);
parentEntity->Deactivate();
}
/*
* Parent -> Child
*/
class ClientSimpleHierarchyTests : public HierarchyTests
{
public:
const NetEntityId RootNetEntityId = NetEntityId{ 1 };
const NetEntityId ChildNetEntityId = NetEntityId{ 2 };
void SetUp() override
{
HierarchyTests::SetUp();
m_root = AZStd::make_unique<EntityInfo>(1, "root", RootNetEntityId, EntityInfo::Role::Root);
m_child = AZStd::make_unique<EntityInfo>(2, "child", ChildNetEntityId, EntityInfo::Role::Child);
CreateSimpleHierarchy(*m_root, *m_child);
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->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::Client);
PopulateHierarchicalEntity(child);
SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Client);
// we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller)
SetParentIdOnNetworkTransform(child.m_entity, root.m_netId);
SetHierarchyRootFieldOnNetworkHierarchyChild<NetworkHierarchyChildComponent>(child.m_entity, root.m_netId);
// Create an entity replicator for the child entity
const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get());
child.m_replicator = AZStd::make_unique<EntityReplicator>(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, 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<EntityReplicator>(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, rootHandle);
root.m_replicator->Initialize(rootHandle);
root.m_entity->Activate();
child.m_entity->Activate();
}
void SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(const AZStd::unique_ptr<AZ::Entity>& entity, NetEntityId value)
{
/* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */
constexpr int totalBits = 1 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::Count*/;
constexpr int inHierarchyBit = 0 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::hierarchyRoot_DirtyFlag*/;
ReplicationRecord currentRecord(NetEntityRole::Client);
currentRecord.m_authorityToClient.AddBits(totalBits);
currentRecord.m_authorityToClient.SetBit(inHierarchyBit, true);
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
inSerializer.Serialize(reinterpret_cast<uint32_t&>(value),
"hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */
AZStd::numeric_limits<uint32_t>::min(), AZStd::numeric_limits<uint32_t>::max());
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
ReplicationRecord notifyRecord = currentRecord;
entity->FindComponent<NetworkHierarchyChildComponent>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
entity->FindComponent<NetworkHierarchyChildComponent>()->NotifyStateDeltaChanges(notifyRecord);
}
AZStd::unique_ptr<EntityInfo> m_root;
AZStd::unique_ptr<EntityInfo> m_child;
};
TEST_F(ClientSimpleHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields)
{
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchyRoot(),
InvalidNetEntityId
);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
RootNetEntityId
);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchicalRoot(),
m_root->m_entity.get()
);
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities().size(),
2
);
if (m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities().size() == 2)
{
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[0],
m_root->m_entity.get()
);
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[1],
m_child->m_entity.get()
);
}
}
TEST_F(ClientSimpleHierarchyTests, Client_Detaches_Child_When_Server_Detaches)
{
// simulate server detaching child entity
SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId);
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
InvalidNetEntityId
);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchicalRoot(),
nullptr
);
}
TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Updated_Event_On_Child_Detached_On_Server)
{
MockNetworkHierarchyCallbackHandler mock;
EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_root->m_entity->GetId()));
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler);
// simulate server detaching a child entity
SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId);
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId);
}
TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Leave_Event_On_Child_Detached_On_Server)
{
MockNetworkHierarchyCallbackHandler mock;
EXPECT_CALL(mock, OnNetworkHierarchyLeave);
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->BindNetworkHierarchyLeaveEventHandler(mock.m_leaveHandler);
// simulate server detaching a child entity
SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId);
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId);
}
/*
* Parent -> Child -> ChildOfChild
*/
class ClientDeepHierarchyTests : public ClientSimpleHierarchyTests
{
public:
static const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 };
void SetUp() override
{
ClientSimpleHierarchyTests::SetUp();
m_childOfChild = AZStd::make_unique<EntityInfo>((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child);
CreateDeepHierarchyOnClient(*m_childOfChild);
m_childOfChild->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_child->m_entity->GetId());
}
void TearDown() override
{
m_childOfChild.reset();
ClientSimpleHierarchyTests::TearDown();
}
void CreateDeepHierarchyOnClient(EntityInfo& childOfChild)
{
PopulateHierarchicalEntity(childOfChild);
SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Client);
// we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller)
SetParentIdOnNetworkTransform(childOfChild.m_entity, m_childOfChild->m_netId);
SetHierarchyRootFieldOnNetworkHierarchyChild<NetworkHierarchyChildComponent>(childOfChild.m_entity, m_root->m_netId);
// Create an entity replicator for the child entity
const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get());
childOfChild.m_replicator = AZStd::make_unique<EntityReplicator>(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childOfChildHandle);
childOfChild.m_replicator->Initialize(childOfChildHandle);
childOfChild.m_entity->Activate();
}
AZStd::unique_ptr<EntityInfo> m_childOfChild;
};
TEST_F(ClientDeepHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields)
{
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchyRoot(),
InvalidNetEntityId
);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
RootNetEntityId
);
EXPECT_EQ(
m_childOfChild->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
RootNetEntityId
);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchicalRoot(),
m_root->m_entity.get()
);
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities().size(),
3
);
if (m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities().size() == 3)
{
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[0],
m_root->m_entity.get()
);
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[1],
m_child->m_entity.get()
);
EXPECT_EQ(
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[2],
m_childOfChild->m_entity.get()
);
}
}
}
@@ -0,0 +1,414 @@
/*
* 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
*
*/
#pragma once
#include <IMultiplayerConnectionMock.h>
#include <MockInterfaces.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Name/Name.h>
#include <AzCore/Name/NameDictionary.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzTest/AzTest.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <Multiplayer/Components/NetworkTransformComponent.h>
#include <NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <NetworkEntity/NetworkEntityTracker.h>
#include <NetworkEntity/EntityReplication/EntityReplicationManager.h>
#include <NetworkEntity/EntityReplication/EntityReplicator.h>
namespace Multiplayer
{
using namespace testing;
using namespace ::UnitTest;
class NetworkHierarchyCallbacks
{
public:
virtual ~NetworkHierarchyCallbacks() = default;
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
{
public:
void SetUp() override
{
SetupAllocator();
AZ::NameDictionary::Create();
m_mockComponentApplicationRequests = AZStd::make_unique<NiceMock<MockComponentApplicationRequests>>();
AZ::Interface<AZ::ComponentApplicationRequests>::Register(m_mockComponentApplicationRequests.get());
ON_CALL(*m_mockComponentApplicationRequests, AddEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::AddEntity));
ON_CALL(*m_mockComponentApplicationRequests, FindEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::FindEntity));
// register components involved in testing
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
m_transformDescriptor.reset(AzFramework::TransformComponent::CreateDescriptor());
m_transformDescriptor->Reflect(m_serializeContext.get());
m_netBindDescriptor.reset(NetBindComponent::CreateDescriptor());
m_netBindDescriptor->Reflect(m_serializeContext.get());
m_hierarchyRootDescriptor.reset(NetworkHierarchyRootComponent::CreateDescriptor());
m_hierarchyRootDescriptor->Reflect(m_serializeContext.get());
m_hierarchyChildDescriptor.reset(NetworkHierarchyChildComponent::CreateDescriptor());
m_hierarchyChildDescriptor->Reflect(m_serializeContext.get());
m_netTransformDescriptor.reset(NetworkTransformComponent::CreateDescriptor());
m_netTransformDescriptor->Reflect(m_serializeContext.get());
m_mockMultiplayer = AZStd::make_unique<NiceMock<MockMultiplayer>>();
AZ::Interface<IMultiplayer>::Register(m_mockMultiplayer.get());
EXPECT_NE(AZ::Interface<IMultiplayer>::Get(), nullptr);
// Create space for replication stats
// Without Multiplayer::RegisterMultiplayerComponents() the stats go to invalid id, which is fine for unit tests
GetMultiplayer()->GetStats().ReserveComponentStats(Multiplayer::InvalidNetComponentId, 50, 0);
m_mockNetworkEntityManager = AZStd::make_unique<NiceMock<MockNetworkEntityManager>>();
ON_CALL(*m_mockNetworkEntityManager, AddEntityToEntityMap(_, _)).WillByDefault(Invoke(this, &HierarchyTests::AddEntityToEntityMap));
ON_CALL(*m_mockNetworkEntityManager, GetEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::GetEntity));
ON_CALL(*m_mockNetworkEntityManager, GetNetEntityIdById(_)).WillByDefault(Invoke(this, &HierarchyTests::GetNetEntityIdById));
m_mockTime = AZStd::make_unique<NiceMock<MockTime>>();
AZ::Interface<AZ::ITime>::Register(m_mockTime.get());
m_mockNetworkTime = AZStd::make_unique<NiceMock<MockNetworkTime>>();
AZ::Interface<INetworkTime>::Register(m_mockNetworkTime.get());
ON_CALL(*m_mockMultiplayer, GetNetworkEntityManager()).WillByDefault(Return(m_mockNetworkEntityManager.get()));
EXPECT_NE(AZ::Interface<IMultiplayer>::Get()->GetNetworkEntityManager(), nullptr);
const IpAddress address("localhost", 1, ProtocolType::Udp);
m_mockConnection = AZStd::make_unique<NiceMock<IMultiplayerConnectionMock>>(ConnectionId{ 1 }, address, ConnectionRole::Connector);
m_mockConnectionListener = AZStd::make_unique<MockConnectionListener>();
m_networkEntityTracker = AZStd::make_unique<NetworkEntityTracker>();
ON_CALL(*m_mockNetworkEntityManager, GetNetworkEntityTracker()).WillByDefault(Return(m_networkEntityTracker.get()));
m_networkEntityAuthorityTracker = AZStd::make_unique<NetworkEntityAuthorityTracker>(*m_mockNetworkEntityManager);
ON_CALL(*m_mockNetworkEntityManager, GetNetworkEntityAuthorityTracker()).WillByDefault(Return(m_networkEntityAuthorityTracker.get()));
m_entityReplicationManager = AZStd::make_unique<EntityReplicationManager>(*m_mockConnection, *m_mockConnectionListener, EntityReplicationManager::Mode::LocalClientToRemoteServer);
m_console.reset(aznew AZ::Console());
AZ::Interface<AZ::IConsole>::Register(m_console.get());
m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead());
m_multiplayerComponentRegistry = AZStd::make_unique<MultiplayerComponentRegistry>();
ON_CALL(*m_mockNetworkEntityManager, GetMultiplayerComponentRegistry()).WillByDefault(Return(m_multiplayerComponentRegistry.get()));
RegisterMultiplayerComponents();
}
void TearDown() override
{
m_multiplayerComponentRegistry.reset();
AZ::Interface<AZ::IConsole>::Unregister(m_console.get());
m_console.reset();
m_networkEntityMap.clear();
m_entities.clear();
m_entityReplicationManager.reset();
m_mockConnection.reset();
m_mockConnectionListener.reset();
m_networkEntityTracker.reset();
m_networkEntityAuthorityTracker.reset();
AZ::Interface<INetworkTime>::Unregister(m_mockNetworkTime.get());
AZ::Interface<AZ::ITime>::Unregister(m_mockTime.get());
AZ::Interface<IMultiplayer>::Unregister(m_mockMultiplayer.get());
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(m_mockComponentApplicationRequests.get());
m_mockTime.reset();
m_mockNetworkEntityManager.reset();
m_mockMultiplayer.reset();
m_transformDescriptor.reset();
m_netTransformDescriptor.reset();
m_hierarchyRootDescriptor.reset();
m_hierarchyChildDescriptor.reset();
m_netBindDescriptor.reset();
m_serializeContext.reset();
m_mockComponentApplicationRequests.reset();
AZ::NameDictionary::Destroy();
TeardownAllocator();
}
AZStd::unique_ptr<AZ::IConsole> m_console;
AZStd::unique_ptr<NiceMock<MockComponentApplicationRequests>> m_mockComponentApplicationRequests;
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_transformDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_netBindDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_hierarchyRootDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_hierarchyChildDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_netTransformDescriptor;
AZStd::unique_ptr<NiceMock<MockMultiplayer>> m_mockMultiplayer;
AZStd::unique_ptr<MockNetworkEntityManager> m_mockNetworkEntityManager;
AZStd::unique_ptr<NiceMock<MockTime>> m_mockTime;
AZStd::unique_ptr<NiceMock<MockNetworkTime>> m_mockNetworkTime;
AZStd::unique_ptr<NiceMock<IMultiplayerConnectionMock>> m_mockConnection;
AZStd::unique_ptr<MockConnectionListener> m_mockConnectionListener;
AZStd::unique_ptr<NetworkEntityTracker> m_networkEntityTracker;
AZStd::unique_ptr<NetworkEntityAuthorityTracker> m_networkEntityAuthorityTracker;
AZStd::unique_ptr<EntityReplicationManager> m_entityReplicationManager;
AZStd::unique_ptr<MultiplayerComponentRegistry> m_multiplayerComponentRegistry;;
mutable AZStd::map<NetEntityId, AZ::Entity*> m_networkEntityMap;
NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity)
{
m_networkEntityMap[netEntityId] = entity;
return NetworkEntityHandle(entity, netEntityId, m_networkEntityTracker.get());
}
ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const
{
AZ::Entity* entity = m_networkEntityMap[netEntityId];
return ConstNetworkEntityHandle(entity, m_networkEntityTracker.get());
}
NetEntityId GetNetEntityIdById(const AZ::EntityId& entityId) const
{
for (const auto& pair : m_networkEntityMap)
{
if (pair.second->GetId() == entityId)
{
return pair.first;
}
}
return InvalidNetEntityId;
}
AZStd::map<AZ::EntityId, AZ::Entity*> m_entities;
bool AddEntity(AZ::Entity* entity)
{
m_entities[entity->GetId()] = entity;
return true;
}
AZ::Entity* FindEntity(AZ::EntityId entityId)
{
const auto iterator = m_entities.find(entityId);
if (iterator != m_entities.end())
{
return iterator->second;
}
return nullptr;
}
void SetupEntity(const AZStd::unique_ptr<AZ::Entity>& entity, NetEntityId netId, NetEntityRole role)
{
const auto netBindComponent = entity->FindComponent<Multiplayer::NetBindComponent>();
EXPECT_NE(netBindComponent, nullptr);
netBindComponent->PreInit(entity.get(), PrefabEntityId{ AZ::Name("test"), 1 }, netId, role);
entity->Init();
}
static void StopEntity(const AZStd::unique_ptr<AZ::Entity>& entity)
{
const auto netBindComponent = entity->FindComponent<Multiplayer::NetBindComponent>();
EXPECT_NE(netBindComponent, nullptr);
netBindComponent->StopEntity();
}
static void StopAndDeactivateEntity(AZStd::unique_ptr<AZ::Entity>& entity)
{
if (entity)
{
StopEntity(entity);
entity->Deactivate();
entity.reset();
}
}
void CreateEntityWithRootHierarchy(AZStd::unique_ptr<AZ::Entity>& rootEntity)
{
rootEntity->CreateComponent<AzFramework::TransformComponent>();
rootEntity->CreateComponent<NetBindComponent>();
rootEntity->CreateComponent<NetworkTransformComponent>();
rootEntity->CreateComponent<NetworkHierarchyRootComponent>();
}
void CreateEntityWithChildHierarchy(AZStd::unique_ptr<AZ::Entity>& childEntity)
{
childEntity->CreateComponent<AzFramework::TransformComponent>();
childEntity->CreateComponent<NetBindComponent>();
childEntity->CreateComponent<NetworkTransformComponent>();
childEntity->CreateComponent<NetworkHierarchyChildComponent>();
}
void SetParentIdOnNetworkTransform(const AZStd::unique_ptr<AZ::Entity>& entity, NetEntityId netParentId)
{
/* Derived from NetworkTransformComponent.AutoComponent.xml */
constexpr int totalBits = 6 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::Count*/;
constexpr int parentIdBit = 4 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::parentEntityId_DirtyFlag*/;
ReplicationRecord currentRecord;
currentRecord.m_authorityToClient.AddBits(totalBits);
currentRecord.m_authorityToClient.SetBit(parentIdBit, true);
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
inSerializer.Serialize(reinterpret_cast<uint32_t&>(netParentId),
"parentEntityId", /* Derived from NetworkTransformComponent.AutoComponent.xml */
AZStd::numeric_limits<uint32_t>::min(), AZStd::numeric_limits<uint32_t>::max());
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
ReplicationRecord notifyRecord = currentRecord;
entity->FindComponent<NetworkTransformComponent>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
entity->FindComponent<NetworkTransformComponent>()->NotifyStateDeltaChanges(notifyRecord);
}
template <typename Component>
void SetHierarchyRootFieldOnNetworkHierarchyChild(const AZStd::unique_ptr<AZ::Entity>& entity, NetEntityId value)
{
/* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */
constexpr int totalBits = 1 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::Count*/;
constexpr int inHierarchyBit = 0 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::hierarchyRoot_DirtyFlag*/;
ReplicationRecord currentRecord;
currentRecord.m_authorityToClient.AddBits(totalBits);
currentRecord.m_authorityToClient.SetBit(inHierarchyBit, true);
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
inSerializer.Serialize(reinterpret_cast<uint32_t&>(value),
"hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */
AZStd::numeric_limits<uint32_t>::min(), AZStd::numeric_limits<uint32_t>::max());
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
ReplicationRecord notifyRecord = currentRecord;
entity->FindComponent<Component>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
entity->FindComponent<Component>()->NotifyStateDeltaChanges(notifyRecord);
}
struct EntityInfo
{
enum class Role
{
Root,
Child,
None
};
EntityInfo(AZ::u64 entityId, const char* entityName, NetEntityId netId, Role role)
: m_entity(AZStd::make_unique<AZ::Entity>(AZ::EntityId(entityId), entityName))
, m_netId(netId)
, m_role(role)
{
}
~EntityInfo()
{
StopAndDeactivateEntity(m_entity);
}
AZStd::unique_ptr<AZ::Entity> m_entity;
NetEntityId m_netId;
AZStd::unique_ptr<EntityReplicator> m_replicator;
Role m_role = Role::None;
};
void PopulateHierarchicalEntity(const EntityInfo& entityInfo)
{
entityInfo.m_entity->CreateComponent<AzFramework::TransformComponent>();
entityInfo.m_entity->CreateComponent<NetBindComponent>();
entityInfo.m_entity->CreateComponent<NetworkTransformComponent>();
switch (entityInfo.m_role)
{
case EntityInfo::Role::Root:
entityInfo.m_entity->CreateComponent<NetworkHierarchyRootComponent>();
break;
case EntityInfo::Role::Child:
entityInfo.m_entity->CreateComponent<NetworkHierarchyChildComponent>();
break;
case EntityInfo::Role::None:
break;
}
}
void CreateDeepHierarchy(EntityInfo& root, EntityInfo& child, EntityInfo& childOfChild)
{
PopulateHierarchicalEntity(root);
PopulateHierarchicalEntity(child);
PopulateHierarchicalEntity(childOfChild);
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);
// Create an entity replicator for the child entity
const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get());
childOfChild.m_replicator = AZStd::make_unique<EntityReplicator>(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChildHandle);
childOfChild.m_replicator->Initialize(childOfChildHandle);
// Create an entity replicator for the child entity
const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get());
child.m_replicator = AZStd::make_unique<EntityReplicator>(*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<EntityReplicator>(*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();
}
};
}
@@ -0,0 +1,169 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Time/ITime.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <AzTest/AzTest.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h>
namespace UnitTest
{
class MockMultiplayer : public Multiplayer::IMultiplayer
{
public:
MOCK_CONST_METHOD0(GetCurrentBlendFactor, float ());
MOCK_CONST_METHOD0(GetAgentType, Multiplayer::MultiplayerAgentType());
MOCK_METHOD1(InitializeMultiplayer, void(Multiplayer::MultiplayerAgentType));
MOCK_METHOD2(StartHosting, bool(uint16_t, bool));
MOCK_METHOD2(Connect, bool(AZStd::string, uint16_t));
MOCK_METHOD1(Terminate, void(AzNetworking::DisconnectReason));
MOCK_METHOD1(AddClientDisconnectedHandler, void(AZ::Event<>::Handler&));
MOCK_METHOD1(AddConnectionAcquiredHandler, void(AZ::Event<Multiplayer::MultiplayerAgentDatum>::Handler&));
MOCK_METHOD1(AddSessionInitHandler, void(AZ::Event<AzNetworking::INetworkInterface*>::Handler&));
MOCK_METHOD1(AddSessionShutdownHandler, void(AZ::Event<AzNetworking::INetworkInterface*>::Handler&));
MOCK_METHOD1(SendReadyForEntityUpdates, void(bool));
MOCK_CONST_METHOD0(GetCurrentHostTimeMs, AZ::TimeMs());
MOCK_METHOD0(GetNetworkTime, Multiplayer::INetworkTime* ());
MOCK_METHOD0(GetNetworkEntityManager, Multiplayer::INetworkEntityManager* ());
MOCK_METHOD1(SetFilterEntityManager, void(Multiplayer::IFilterEntityManager*));
MOCK_METHOD0(GetFilterEntityManager, Multiplayer::IFilterEntityManager* ());
};
class MockNetworkEntityManager : public Multiplayer::INetworkEntityManager
{
public:
MOCK_METHOD2(RequestNetSpawnableInstantiation, AZStd::unique_ptr<AzFramework::EntitySpawnTicket> (const AZ::Data::Asset<AzFramework::Spawnable>&, const AZ::Transform&));
MOCK_METHOD4(
CreateEntitiesImmediate,
EntityList (const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ::Transform&, Multiplayer::AutoActivate));
MOCK_CONST_METHOD1(GetNetEntityIdById, Multiplayer::NetEntityId (const AZ::EntityId&));
MOCK_METHOD0(GetNetworkEntityTracker, Multiplayer::NetworkEntityTracker* ());
MOCK_METHOD0(GetNetworkEntityAuthorityTracker, Multiplayer::NetworkEntityAuthorityTracker* ());
MOCK_METHOD0(GetMultiplayerComponentRegistry, Multiplayer::MultiplayerComponentRegistry* ());
MOCK_CONST_METHOD0(GetHostId, Multiplayer::HostId());
MOCK_METHOD3(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ::
Transform&));
MOCK_METHOD5(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityId, Multiplayer::
NetEntityRole, Multiplayer::AutoActivate, const AZ::Transform&));
MOCK_METHOD3(SetupNetEntity, void(AZ::Entity*, Multiplayer::PrefabEntityId, Multiplayer::NetEntityRole));
MOCK_CONST_METHOD1(GetEntity, Multiplayer::ConstNetworkEntityHandle(Multiplayer::NetEntityId));
MOCK_CONST_METHOD0(GetEntityCount, uint32_t());
MOCK_METHOD2(AddEntityToEntityMap, Multiplayer::NetworkEntityHandle(Multiplayer::NetEntityId, AZ::Entity*));
MOCK_METHOD1(MarkForRemoval, void(const Multiplayer::ConstNetworkEntityHandle&));
MOCK_CONST_METHOD1(IsMarkedForRemoval, bool(const Multiplayer::ConstNetworkEntityHandle&));
MOCK_METHOD1(ClearEntityFromRemovalList, void(const Multiplayer::ConstNetworkEntityHandle&));
MOCK_METHOD0(ClearAllEntities, void());
MOCK_METHOD1(AddEntityMarkedDirtyHandler, void(AZ::Event<>::Handler&));
MOCK_METHOD1(AddEntityNotifyChangesHandler, void(AZ::Event<>::Handler&));
MOCK_METHOD1(AddEntityExitDomainHandler, void(AZ::Event<const Multiplayer::ConstNetworkEntityHandle&>::Handler&));
MOCK_METHOD1(AddControllersActivatedHandler, void(AZ::Event<const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::
EntityIsMigrating>::Handler&));
MOCK_METHOD1(AddControllersDeactivatedHandler, void(AZ::Event<const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::
EntityIsMigrating>::Handler&));
MOCK_METHOD0(NotifyEntitiesDirtied, void());
MOCK_METHOD0(NotifyEntitiesChanged, void());
MOCK_METHOD2(NotifyControllersActivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating));
MOCK_METHOD2(NotifyControllersDeactivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating));
MOCK_METHOD1(HandleLocalRpcMessage, void(Multiplayer::NetworkEntityRpcMessage&));
};
class MockConnectionListener : public AzNetworking::IConnectionListener
{
public:
MOCK_METHOD3(ValidateConnect, ConnectResult(const IpAddress&, const IPacketHeader&, ISerializer&));
MOCK_METHOD1(OnConnect, void(IConnection*));
MOCK_METHOD3(OnPacketReceived, PacketDispatchResult (IConnection*, const IPacketHeader&, ISerializer&));
MOCK_METHOD2(OnPacketLost, void(IConnection*, PacketId));
MOCK_METHOD3(OnDisconnect, void(IConnection*, DisconnectReason, TerminationEndpoint));
};
class MockTime : public AZ::ITime
{
public:
MOCK_CONST_METHOD0(GetElapsedTimeMs, AZ::TimeMs());
};
class MockNetworkTime : public Multiplayer::INetworkTime
{
public:
MOCK_METHOD2(ForceSetTime, void (Multiplayer::HostFrameId, AZ::TimeMs));
MOCK_CONST_METHOD0(GetHostBlendFactor, float ());
MOCK_METHOD1(AlterBlendFactor, void (float));
MOCK_CONST_METHOD0(IsTimeRewound, bool());
MOCK_CONST_METHOD0(GetHostFrameId, Multiplayer::HostFrameId());
MOCK_CONST_METHOD0(GetUnalteredHostFrameId, Multiplayer::HostFrameId());
MOCK_METHOD0(IncrementHostFrameId, void());
MOCK_CONST_METHOD0(GetHostTimeMs, AZ::TimeMs());
MOCK_CONST_METHOD0(GetRewindingConnectionId, AzNetworking::ConnectionId());
MOCK_CONST_METHOD1(GetHostFrameIdForRewindingConnection, Multiplayer::HostFrameId(AzNetworking::ConnectionId));
MOCK_METHOD4(AlterTime, void (Multiplayer::HostFrameId, AZ::TimeMs, float, AzNetworking::ConnectionId));
MOCK_METHOD1(SyncEntitiesToRewindState, void(const AZ::Aabb&));
MOCK_METHOD0(ClearRewoundEntities, void());
};
class MockComponentApplicationRequests : public AZ::ComponentApplicationRequests
{
public:
MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*));
MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*));
MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ());
MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::Event<AZ::Entity*>::Handler&));
MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::Event<AZ::Entity*>::Handler&));
MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::Event<AZ::Entity*>::Handler&));
MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::Event<AZ::Entity*>::Handler&));
MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*));
MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*));
MOCK_METHOD1(AddEntity, bool(AZ::Entity*));
MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*));
MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&));
MOCK_METHOD1(FindEntity, AZ::Entity* (const AZ::EntityId&));
MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&));
MOCK_METHOD1(EnumerateEntities, void(const EntityCallback&));
MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ());
MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ());
MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ());
MOCK_CONST_METHOD0(GetAppRoot, const char* ());
MOCK_CONST_METHOD0(GetEngineRoot, const char* ());
MOCK_CONST_METHOD0(GetExecutableFolder, const char* ());
MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ());
MOCK_METHOD1(ResolveModulePath, void(AZ::OSString&));
MOCK_METHOD0(GetAzCommandLine, AZ::CommandLine* ());
MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&));
};
class MockSerializer : public ISerializer
{
public:
MOCK_CONST_METHOD0(IsValid, bool ());
MOCK_CONST_METHOD0(GetSerializerMode, SerializerMode ());
MOCK_METHOD2(Serialize, bool (bool&, const char*));
MOCK_METHOD4(Serialize, bool (char&, const char*, char, char));
MOCK_METHOD4(Serialize, bool (int8_t&, const char*, int8_t, int8_t));
MOCK_METHOD4(Serialize, bool (int16_t&, const char*, int16_t, int16_t));
MOCK_METHOD4(Serialize, bool (int32_t&, const char*, int32_t, int32_t));
MOCK_METHOD4(Serialize, bool (int64_t&, const char*, int64_t, int64_t));
MOCK_METHOD4(Serialize, bool (uint8_t&, const char*, uint8_t, uint8_t));
MOCK_METHOD4(Serialize, bool (uint16_t&, const char*, uint16_t, uint16_t));
MOCK_METHOD4(Serialize, bool (uint32_t&, const char*, uint32_t, uint32_t));
MOCK_METHOD4(Serialize, bool (uint64_t&, const char*, uint64_t, uint64_t));
MOCK_METHOD4(Serialize, bool (float&, const char*, float, float));
MOCK_METHOD4(Serialize, bool (double&, const char*, double, double));
MOCK_METHOD5(SerializeBytes, bool (uint8_t*, uint32_t, bool, uint32_t&, const char*));
MOCK_METHOD2(BeginObject, bool (const char*, const char*));
MOCK_METHOD2(EndObject, bool (const char*, const char*));
MOCK_CONST_METHOD0(GetBuffer, const uint8_t* ());
MOCK_CONST_METHOD0(GetCapacity, uint32_t ());
MOCK_CONST_METHOD0(GetSize, uint32_t ());
MOCK_METHOD0(ClearTrackedChangesFlag, void ());
MOCK_CONST_METHOD0(GetTrackedChangesFlag, bool ());
};
}
File diff suppressed because it is too large Load Diff
+26 -15
View File
@@ -15,23 +15,31 @@ set(FILES
Include/Multiplayer/MultiplayerTypes.h
Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h
Include/Multiplayer/Components/MultiplayerComponent.h
Include/Multiplayer/Components/MultiplayerController.h
Include/Multiplayer/Components/MultiplayerComponentRegistry.h
Include/Multiplayer/Components/MultiplayerController.h
Include/Multiplayer/Components/NetBindComponent.h
Include/Multiplayer/Components/NetworkHierarchyChildComponent.h
Include/Multiplayer/Components/NetworkHierarchyRootComponent.h
Include/Multiplayer/Components/NetworkHierarchyBus.h
Include/Multiplayer/Components/NetworkCharacterComponent.h
Include/Multiplayer/Components/NetworkHitVolumesComponent.h
Include/Multiplayer/Components/NetworkRigidBodyComponent.h
Include/Multiplayer/Components/NetworkTransformComponent.h
Include/Multiplayer/ConnectionData/IConnectionData.h
Include/Multiplayer/EntityDomains/IEntityDomain.h
Include/Multiplayer/NetworkEntity/INetworkEntityManager.h
Include/Multiplayer/IMultiplayer.h
Include/Multiplayer/IMultiplayerTools.h
Include/Multiplayer/INetworkSpawnableLibrary.h
Include/Multiplayer/MultiplayerConstants.h
Include/Multiplayer/MultiplayerStats.h
Include/Multiplayer/MultiplayerTypes.h
Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h
Include/Multiplayer/NetworkEntity/IFilterEntityManager.h
Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h
Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h
Include/Multiplayer/NetworkEntity/INetworkEntityManager.h
Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h
Include/Multiplayer/NetworkEntity/NetworkEntityHandle.inl
Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h
Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h
Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h
Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h
Include/Multiplayer/NetworkInput/NetworkInput.h
Include/Multiplayer/NetworkTime/INetworkTime.h
@@ -43,14 +51,11 @@ set(FILES
Include/Multiplayer/NetworkTime/RewindableObject.inl
Include/Multiplayer/Physics/PhysicsUtils.h
Include/Multiplayer/ReplicationWindows/IReplicationWindow.h
Source/MultiplayerSystemComponent.cpp
Source/MultiplayerSystemComponent.h
Source/MultiplayerStats.cpp
Source/AutoGen/AutoComponent_Header.jinja
Source/AutoGen/AutoComponent_Source.jinja
Source/AutoGen/AutoComponent_Common.jinja
Source/AutoGen/AutoComponentTypes_Header.jinja
Source/AutoGen/AutoComponentTypes_Source.jinja
Source/AutoGen/AutoComponent_Common.jinja
Source/AutoGen/AutoComponent_Header.jinja
Source/AutoGen/AutoComponent_Source.jinja
Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml
Source/AutoGen/Multiplayer.AutoPackets.xml
Source/AutoGen/MultiplayerEditor.AutoPackets.xml
@@ -58,11 +63,15 @@ set(FILES
Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml
Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml
Source/AutoGen/NetworkTransformComponent.AutoComponent.xml
Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml
Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml
Source/Components/LocalPredictionPlayerInputComponent.cpp
Source/Components/MultiplayerComponent.cpp
Source/Components/MultiplayerController.cpp
Source/Components/MultiplayerComponentRegistry.cpp
Source/Components/MultiplayerController.cpp
Source/Components/NetBindComponent.cpp
Source/Components/NetworkHierarchyChildComponent.cpp
Source/Components/NetworkHierarchyRootComponent.cpp
Source/Components/NetworkCharacterComponent.cpp
Source/Components/NetworkHitVolumesComponent.cpp
Source/Components/NetworkRigidBodyComponent.cpp
@@ -77,6 +86,9 @@ set(FILES
Source/Editor/MultiplayerEditorConnection.h
Source/EntityDomains/FullOwnershipEntityDomain.cpp
Source/EntityDomains/FullOwnershipEntityDomain.h
Source/MultiplayerStats.cpp
Source/MultiplayerSystemComponent.cpp
Source/MultiplayerSystemComponent.h
Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp
Source/NetworkEntity/EntityReplication/EntityReplicationManager.h
Source/NetworkEntity/EntityReplication/EntityReplicator.cpp
@@ -92,13 +104,13 @@ set(FILES
Source/NetworkEntity/NetworkEntityHandle.cpp
Source/NetworkEntity/NetworkEntityManager.cpp
Source/NetworkEntity/NetworkEntityManager.h
Source/NetworkEntity/NetworkSpawnableLibrary.cpp
Source/NetworkEntity/NetworkSpawnableLibrary.h
Source/NetworkEntity/NetworkEntityRpcMessage.cpp
Source/NetworkEntity/NetworkEntityTracker.cpp
Source/NetworkEntity/NetworkEntityTracker.h
Source/NetworkEntity/NetworkEntityTracker.inl
Source/NetworkEntity/NetworkEntityUpdateMessage.cpp
Source/NetworkEntity/NetworkSpawnableLibrary.cpp
Source/NetworkEntity/NetworkSpawnableLibrary.h
Source/NetworkInput/NetworkInput.cpp
Source/NetworkInput/NetworkInputArray.cpp
Source/NetworkInput/NetworkInputArray.h
@@ -112,7 +124,6 @@ set(FILES
Source/NetworkTime/NetworkTime.h
Source/Pipeline/NetworkSpawnableHolderComponent.cpp
Source/Pipeline/NetworkSpawnableHolderComponent.h
Source/Physics/PhysicsUtils.cpp
Source/ReplicationWindows/NullReplicationWindow.cpp
Source/ReplicationWindows/NullReplicationWindow.h
Source/ReplicationWindows/ServerToClientReplicationWindow.cpp
@@ -8,6 +8,10 @@
set(FILES
Tests/Main.cpp
Tests/MockInterfaces.h
Tests/ClientHierarchyTests.cpp
Tests/ServerHierarchyTests.cpp
Tests/CommonHierarchySetup.h
Tests/IMultiplayerConnectionMock.h
Tests/MultiplayerSystemTests.cpp
Tests/RewindableContainerTests.cpp