Hierarchical components, phase 1, unittests
Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com>
This commit is contained in:
@@ -199,6 +199,8 @@ namespace Multiplayer
|
||||
|
||||
friend class NetworkEntityManager;
|
||||
friend class EntityReplicationManager;
|
||||
|
||||
friend class HierarchyTests;
|
||||
};
|
||||
|
||||
bool NetworkRoleHasController(NetEntityRole networkRole);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
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 OnLeavingNetworkHierarchy() {}
|
||||
};
|
||||
|
||||
typedef AZ::EBus<NetworkHierarchyNotifications> NetworkHierarchyNotificationBus;
|
||||
|
||||
class NetworkHierarchyRequests
|
||||
: public AZ::ComponentBus
|
||||
{
|
||||
public:
|
||||
//! @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;
|
||||
};
|
||||
|
||||
typedef AZ::EBus<NetworkHierarchyRequests> NetworkHierarchyRequestBus;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 <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();
|
||||
|
||||
//! @{
|
||||
void OnInit() override;
|
||||
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
|
||||
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
|
||||
//! @}
|
||||
|
||||
//! NetworkHierarchyRequestBus overrides
|
||||
//! @{
|
||||
bool IsHierarchicalChild() const override;
|
||||
bool IsHierarchicalRoot() const override { return false; }
|
||||
AZ::Entity* GetHierarchicalRoot() const override;
|
||||
AZStd::vector<AZ::Entity*> GetHierarchicalEntities() const override;
|
||||
//! @}
|
||||
|
||||
protected:
|
||||
//! Used by @NetworkHierarchyRootComponent
|
||||
void SetTopLevelHierarchyRootComponent(NetworkHierarchyRootComponent* hierarchyRoot);
|
||||
|
||||
private:
|
||||
const NetworkHierarchyRootComponent* m_hierarchyRootComponent = nullptr;
|
||||
|
||||
AZ::Event<NetEntityId>::Handler m_hierarchyRootNetIdChanged;
|
||||
void OnHierarchyRootNetIdChanged(NetEntityId rootNetId);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 <Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.h>
|
||||
#include <Multiplayer/Components/NetworkHierarchyBus.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
//! @class NetworkHierarchyRootComponent
|
||||
//! @brief Component that declares the top level entity of a network hierarchy.
|
||||
/*
|
||||
* Call @GetHierarchyChildren 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
|
||||
, protected AZ::TransformNotificationBus::MultiHandler
|
||||
{
|
||||
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);
|
||||
|
||||
//! NetworkHierarchyRootComponentBase overrides.
|
||||
//! @{
|
||||
void OnInit() override;
|
||||
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
|
||||
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
|
||||
//! @}
|
||||
|
||||
//! NetworkHierarchyRequestBus overrides.
|
||||
//! @{
|
||||
bool IsHierarchicalRoot() const override;
|
||||
bool IsHierarchicalChild() const override;
|
||||
AZStd::vector<AZ::Entity*> GetHierarchicalEntities() const override;
|
||||
AZ::Entity* GetHierarchicalRoot() const override;
|
||||
//! @}
|
||||
|
||||
protected:
|
||||
//! AZ::TransformNotificationBus::Handler overrides.
|
||||
//! @{
|
||||
void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId newParent) override;
|
||||
void OnChildAdded(AZ::EntityId child) override;
|
||||
void OnChildRemoved(AZ::EntityId childRemovedId) override;
|
||||
//! @}
|
||||
|
||||
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
|
||||
|
||||
private:
|
||||
AZ::Entity* m_higherRootEntity = nullptr;
|
||||
AZStd::vector<AZ::Entity*> m_hierarchicalEntities;
|
||||
|
||||
void RebuildHierarchy();
|
||||
|
||||
//! @returns false if the maximum supported hierarchy size has been reached
|
||||
bool RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount);
|
||||
//! @returns false if the maximum supported hierarchy size has been reached
|
||||
bool RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount);
|
||||
};
|
||||
}
|
||||
@@ -36,6 +36,7 @@ namespace Multiplayer
|
||||
void OnTranslationChangedEvent(const AZ::Vector3& translation);
|
||||
void OnScaleChangedEvent(float scale);
|
||||
void OnResetCountChangedEvent();
|
||||
void OnParentIdChangedEvent(NetEntityId newParent);
|
||||
|
||||
void UpdateTargetHostFrameId();
|
||||
|
||||
@@ -46,6 +47,7 @@ namespace Multiplayer
|
||||
AZ::Event<AZ::Vector3>::Handler m_translationEventHandler;
|
||||
AZ::Event<float>::Handler m_scaleEventHandler;
|
||||
AZ::Event<uint8_t>::Handler m_resetCountEventHandler;
|
||||
AZ::Event<NetEntityId>::Handler m_parentIdChangedEventHandler;
|
||||
|
||||
EntityPreRenderEvent::Handler m_entityPreRenderEventHandler;
|
||||
EntityCorrectionEvent::Handler m_entityCorrectionEventHandler;
|
||||
@@ -64,7 +66,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,134 @@
|
||||
/*
|
||||
* 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 <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_hierarchyRootNetIdChanged([this](NetEntityId rootNetId) {OnHierarchyRootNetIdChanged(rootNetId); })
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void NetworkHierarchyChildComponent::OnInit()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkHierarchyChildComponent::OnActivate([[maybe_unused]] EntityIsMigrating entityIsMigrating)
|
||||
{
|
||||
HierarchyRootAddEvent(m_hierarchyRootNetIdChanged);
|
||||
NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId());
|
||||
}
|
||||
|
||||
void NetworkHierarchyChildComponent::OnDeactivate([[maybe_unused]] EntityIsMigrating entityIsMigrating)
|
||||
{
|
||||
NetworkHierarchyRequestBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
bool NetworkHierarchyChildComponent::IsHierarchicalChild() const
|
||||
{
|
||||
if (GetHierarchyRoot() != InvalidNetEntityId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::Entity* NetworkHierarchyChildComponent::GetHierarchicalRoot() const
|
||||
{
|
||||
return m_hierarchyRootComponent ? m_hierarchyRootComponent->GetEntity() : nullptr;
|
||||
}
|
||||
|
||||
AZStd::vector<AZ::Entity*> NetworkHierarchyChildComponent::GetHierarchicalEntities() const
|
||||
{
|
||||
if (m_hierarchyRootComponent)
|
||||
{
|
||||
return m_hierarchyRootComponent->GetHierarchicalEntities();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootComponent(NetworkHierarchyRootComponent* hierarchyRoot)
|
||||
{
|
||||
m_hierarchyRootComponent = hierarchyRoot;
|
||||
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
|
||||
{
|
||||
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
|
||||
if (hierarchyRoot)
|
||||
{
|
||||
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetEntityId());
|
||||
controller->SetHierarchyRoot(netRootId);
|
||||
|
||||
NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnNetworkHierarchyUpdated, hierarchyRoot->GetEntityId());
|
||||
}
|
||||
else
|
||||
{
|
||||
controller->SetHierarchyRoot(InvalidNetEntityId);
|
||||
NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnLeavingNetworkHierarchy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkHierarchyChildComponent::OnHierarchyRootNetIdChanged(NetEntityId rootNetId)
|
||||
{
|
||||
const ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(rootNetId);
|
||||
if (rootHandle.Exists())
|
||||
{
|
||||
m_hierarchyRootComponent = rootHandle.FindComponent<NetworkHierarchyRootComponent>();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hierarchyRootComponent = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* 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 <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"));
|
||||
}
|
||||
|
||||
void NetworkHierarchyRootComponent::OnInit()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkHierarchyRootComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
|
||||
{
|
||||
m_hierarchicalEntities.push_back(GetEntity());
|
||||
|
||||
NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId());
|
||||
AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId());
|
||||
}
|
||||
|
||||
void NetworkHierarchyRootComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
|
||||
{
|
||||
AZ::TransformNotificationBus::MultiHandler::BusDisconnect();
|
||||
NetworkHierarchyRequestBus::Handler::BusDisconnect();
|
||||
|
||||
for (const AZ::Entity* childEntity : m_hierarchicalEntities)
|
||||
{
|
||||
auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>();
|
||||
auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>();
|
||||
|
||||
if (hierarchyChildComponent)
|
||||
{
|
||||
hierarchyChildComponent->SetTopLevelHierarchyRootComponent(nullptr);
|
||||
}
|
||||
if (hierarchyRootComponent)
|
||||
{
|
||||
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
m_hierarchicalEntities.clear();
|
||||
m_higherRootEntity = nullptr;
|
||||
}
|
||||
|
||||
bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const
|
||||
{
|
||||
if (GetHierarchyRoot() != InvalidNetEntityId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetworkHierarchyRootComponent::IsHierarchicalChild() const
|
||||
{
|
||||
return !IsHierarchicalRoot();
|
||||
}
|
||||
|
||||
AZStd::vector<AZ::Entity*> NetworkHierarchyRootComponent::GetHierarchicalEntities() const
|
||||
{
|
||||
return m_hierarchicalEntities;
|
||||
}
|
||||
|
||||
AZ::Entity* NetworkHierarchyRootComponent::GetHierarchicalRoot() const
|
||||
{
|
||||
if (m_higherRootEntity)
|
||||
{
|
||||
return m_higherRootEntity;
|
||||
}
|
||||
|
||||
return GetEntity();
|
||||
}
|
||||
|
||||
void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent)
|
||||
{
|
||||
const AZ::EntityId entityBusId = *AZ::TransformNotificationBus::GetCurrentBusId();
|
||||
if (GetEntityId() != entityBusId)
|
||||
{
|
||||
return; // ignore parent changes of child entities
|
||||
}
|
||||
|
||||
if (AZ::Entity* parentEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(newParent))
|
||||
{
|
||||
if (parentEntity->FindComponent<NetworkHierarchyRootComponent>())
|
||||
{
|
||||
m_higherRootEntity = parentEntity;
|
||||
m_hierarchicalEntities.clear();
|
||||
AZ::TransformNotificationBus::MultiHandler::BusDisconnect();
|
||||
|
||||
// Should still listen for its events, such as when this root detaches
|
||||
AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// detached from parent
|
||||
RebuildHierarchy();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkHierarchyRootComponent::OnChildAdded([[maybe_unused]] AZ::EntityId child)
|
||||
{
|
||||
// Parent-child notifications from TransformNotificationBus are not reliable enough to avoid duplicate notifications,
|
||||
// so we will rebuild from scratch to avoid duplicate entries in @m_hierarchicalEntities.
|
||||
RebuildHierarchy();
|
||||
}
|
||||
|
||||
void NetworkHierarchyRootComponent::RebuildHierarchy()
|
||||
{
|
||||
m_hierarchicalEntities.clear();
|
||||
m_hierarchicalEntities.push_back(GetEntity()); // add the root itself
|
||||
|
||||
AZ::TransformNotificationBus::MultiHandler::BusDisconnect();
|
||||
AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId());
|
||||
|
||||
uint32_t currentEntityCount = aznumeric_cast<uint32_t>(m_hierarchicalEntities.size());
|
||||
RecursiveAttachHierarchicalEntities(GetEntityId(), currentEntityCount);
|
||||
}
|
||||
|
||||
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 || hierarchyRootComponent)
|
||||
{
|
||||
AZ::TransformNotificationBus::MultiHandler::BusConnect(entity);
|
||||
m_hierarchicalEntities.push_back(childEntity);
|
||||
++currentEntityCount;
|
||||
|
||||
if (hierarchyChildComponent)
|
||||
{
|
||||
hierarchyChildComponent->SetTopLevelHierarchyRootComponent(this);
|
||||
}
|
||||
else if (hierarchyRootComponent)
|
||||
{
|
||||
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(GetEntity());
|
||||
}
|
||||
|
||||
if (!RecursiveAttachHierarchicalEntities(entity, currentEntityCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkHierarchyRootComponent::OnChildRemoved(AZ::EntityId childRemovedId)
|
||||
{
|
||||
AZStd::vector<AZ::EntityId> allChildren;
|
||||
AZ::TransformBus::EventResult(allChildren, childRemovedId, &AZ::TransformBus::Events::GetEntityAndAllDescendants);
|
||||
|
||||
for (AZ::EntityId childId : allChildren)
|
||||
{
|
||||
AZ::TransformNotificationBus::MultiHandler::BusDisconnect(childId);
|
||||
|
||||
const AZ::Entity* childEntity = nullptr;
|
||||
|
||||
AZStd::erase_if(m_hierarchicalEntities, [childId, &childEntity](const AZ::Entity* entity)
|
||||
{
|
||||
if (entity->GetId() == childId)
|
||||
{
|
||||
childEntity = entity;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (childEntity)
|
||||
{
|
||||
if (NetworkHierarchyChildComponent* childComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>())
|
||||
{
|
||||
childComponent->SetTopLevelHierarchyRootComponent(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot)
|
||||
{
|
||||
m_higherRootEntity = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ namespace Multiplayer
|
||||
, m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); })
|
||||
, m_scaleEventHandler([this](float scale) { OnScaleChangedEvent(scale); })
|
||||
, m_resetCountEventHandler([this](const uint8_t&) { OnResetCountChangedEvent(); })
|
||||
, m_parentIdChangedEventHandler([this](NetEntityId newParent) { OnParentIdChangedEvent(newParent); })
|
||||
, m_entityPreRenderEventHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); })
|
||||
, m_entityCorrectionEventHandler([this]() { OnCorrection(); })
|
||||
{
|
||||
@@ -47,8 +48,12 @@ namespace Multiplayer
|
||||
TranslationAddEvent(m_translationEventHandler);
|
||||
ScaleAddEvent(m_scaleEventHandler);
|
||||
ResetCountAddEvent(m_resetCountEventHandler);
|
||||
GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler);
|
||||
GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler);
|
||||
ParentEntityIdAddEvent(m_parentIdChangedEventHandler);
|
||||
if (GetNetBindComponent())
|
||||
{
|
||||
GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler);
|
||||
GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler);
|
||||
}
|
||||
|
||||
// When coming into relevance, reset all blending factors so we don't interpolate to our start position
|
||||
OnResetCountChangedEvent();
|
||||
@@ -82,15 +87,33 @@ namespace Multiplayer
|
||||
|
||||
void NetworkTransformComponent::OnResetCountChangedEvent()
|
||||
{
|
||||
OnParentIdChangedEvent(GetParentEntityId());
|
||||
|
||||
m_targetTransform.SetRotation(GetRotation());
|
||||
m_targetTransform.SetTranslation(GetTranslation());
|
||||
m_targetTransform.SetUniformScale(GetScale());
|
||||
m_previousTransform = m_targetTransform;
|
||||
}
|
||||
|
||||
void NetworkTransformComponent::OnParentIdChangedEvent([[maybe_unused]] NetEntityId newParent)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTransformComponent::UpdateTargetHostFrameId()
|
||||
{
|
||||
HostFrameId currentHostFrameId = Multiplayer::GetNetworkTime()->GetHostFrameId();
|
||||
const HostFrameId currentHostFrameId = Multiplayer::GetNetworkTime()->GetHostFrameId();
|
||||
if (currentHostFrameId > m_targetHostFrameId)
|
||||
{
|
||||
m_targetHostFrameId = currentHostFrameId;
|
||||
@@ -137,6 +160,7 @@ namespace Multiplayer
|
||||
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); })
|
||||
{
|
||||
;
|
||||
}
|
||||
@@ -145,6 +169,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)
|
||||
@@ -158,4 +185,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,13 +6,15 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#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/NetBindMarkerComponent.h>
|
||||
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
|
||||
+20
-6
@@ -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>
|
||||
@@ -30,6 +32,8 @@
|
||||
|
||||
#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
|
||||
@@ -48,7 +52,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 +123,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 +283,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 +310,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 +409,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)
|
||||
{
|
||||
if (bg_debugHierarchyActivation)
|
||||
{
|
||||
AZLOG_DEBUG(
|
||||
"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));
|
||||
}
|
||||
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,379 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
AZ::Entity 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
|
||||
AZ::Entity childEntity;
|
||||
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, 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
|
||||
AZ::Entity innerRootEntity;
|
||||
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, 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
|
||||
AZ::Entity childEntity;
|
||||
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, 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)
|
||||
{
|
||||
AZ::Entity childEntity;
|
||||
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, 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
|
||||
AZ::Entity parentEntity;
|
||||
CreateEntityWithRootHierarchy(parentEntity);
|
||||
SetupEntity(parentEntity, NetEntityId{ 1 }, NetEntityRole::Client);
|
||||
|
||||
// Create an entity replicator for the parent entity
|
||||
const NetworkEntityHandle parentHandle(&parentEntity, 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:
|
||||
static const NetEntityId RootNetEntityId = NetEntityId{ 1 };
|
||||
static const NetEntityId ChildNetEntityId = NetEntityId{ 2 };
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
HierarchyTests::SetUp();
|
||||
|
||||
m_rootEntity = AZStd::make_unique<AZ::Entity>(AZ::EntityId(1), "root");
|
||||
m_childEntity = AZStd::make_unique<AZ::Entity>(AZ::EntityId(2), "child");
|
||||
|
||||
m_rootEntityInfo = AZStd::make_unique<EntityInfo>(*m_rootEntity.get(), RootNetEntityId, EntityInfo::Role::Root);
|
||||
m_childEntityInfo = AZStd::make_unique<EntityInfo>(*m_childEntity.get(), ChildNetEntityId, EntityInfo::Role::Child);
|
||||
|
||||
CreateSimpleHierarchy(*m_rootEntityInfo, *m_childEntityInfo);
|
||||
|
||||
m_childEntity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_rootEntity->GetId());
|
||||
// now the two entities are under one hierarchy
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_childEntityInfo.reset();
|
||||
m_rootEntityInfo.reset();
|
||||
|
||||
StopAndDeleteEntity(m_childEntity);
|
||||
StopAndDeleteEntity(m_rootEntity);
|
||||
|
||||
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, 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, 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 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<AZ::Entity> m_rootEntity;
|
||||
AZStd::unique_ptr<AZ::Entity> m_childEntity;
|
||||
|
||||
AZStd::unique_ptr<EntityInfo> m_rootEntityInfo;
|
||||
AZStd::unique_ptr<EntityInfo> m_childEntityInfo;
|
||||
};
|
||||
|
||||
TEST_F(ClientSimpleHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields)
|
||||
{
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchyRoot(),
|
||||
InvalidNetEntityId
|
||||
);
|
||||
|
||||
EXPECT_EQ(
|
||||
m_childEntity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
|
||||
RootNetEntityId
|
||||
);
|
||||
|
||||
EXPECT_EQ(
|
||||
m_childEntity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchicalRoot(),
|
||||
m_rootEntity.get()
|
||||
);
|
||||
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities().size(),
|
||||
2
|
||||
);
|
||||
if (m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities().size() == 2)
|
||||
{
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[0],
|
||||
m_rootEntity.get()
|
||||
);
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[1],
|
||||
m_childEntity.get()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ClientSimpleHierarchyTests, Client_Detaches_Child_When_Server_Detaches)
|
||||
{
|
||||
// simulate server detaching child entity
|
||||
SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId);
|
||||
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId);
|
||||
|
||||
EXPECT_EQ(
|
||||
m_childEntity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
|
||||
InvalidNetEntityId
|
||||
);
|
||||
EXPECT_EQ(
|
||||
m_childEntity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchicalRoot(),
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parent -> Child
|
||||
*/
|
||||
class ClientDeepHierarchyTests : public ClientSimpleHierarchyTests
|
||||
{
|
||||
public:
|
||||
static const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 };
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
ClientSimpleHierarchyTests::SetUp();
|
||||
|
||||
m_childOfChildEntity = AZStd::make_unique<AZ::Entity>(AZ::EntityId(3), "child of child");
|
||||
m_childOfChildEntityInfo = AZStd::make_unique<EntityInfo>(*m_childOfChildEntity.get(), ChildOfChildNetEntityId, EntityInfo::Role::Child);
|
||||
|
||||
CreateDeepHierarchyOnClient(*m_childOfChildEntityInfo);
|
||||
|
||||
m_childOfChildEntity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_childEntity->GetId());
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_childOfChildEntityInfo.reset();
|
||||
StopAndDeleteEntity(m_childOfChildEntity);
|
||||
|
||||
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_childEntityInfo->m_netId);
|
||||
SetHierarchyRootFieldOnNetworkHierarchyChild<NetworkHierarchyChildComponent>(childOfChild.m_entity, m_rootEntityInfo->m_netId);
|
||||
|
||||
// Create an entity replicator for the child entity
|
||||
const NetworkEntityHandle childOfChildHandle(&childOfChild.m_entity, 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<AZ::Entity> m_childOfChildEntity;
|
||||
AZStd::unique_ptr<EntityInfo> m_childOfChildEntityInfo;
|
||||
};
|
||||
|
||||
TEST_F(ClientDeepHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields)
|
||||
{
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchyRoot(),
|
||||
InvalidNetEntityId
|
||||
);
|
||||
EXPECT_EQ(
|
||||
m_childEntity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
|
||||
RootNetEntityId
|
||||
);
|
||||
EXPECT_EQ(
|
||||
m_childOfChildEntity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
|
||||
RootNetEntityId
|
||||
);
|
||||
|
||||
EXPECT_EQ(
|
||||
m_childEntity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchicalRoot(),
|
||||
m_rootEntity.get()
|
||||
);
|
||||
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities().size(),
|
||||
3
|
||||
);
|
||||
if (m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities().size() == 3)
|
||||
{
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[0],
|
||||
m_rootEntity.get()
|
||||
);
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[1],
|
||||
m_childEntity.get()
|
||||
);
|
||||
EXPECT_EQ(
|
||||
m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities()[2],
|
||||
m_childOfChildEntity.get()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* 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 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(AZ::Entity& entity, NetEntityId netId, NetEntityRole role)
|
||||
{
|
||||
const auto netBindComponent = entity.FindComponent<Multiplayer::NetBindComponent>();
|
||||
EXPECT_NE(netBindComponent, nullptr);
|
||||
netBindComponent->PreInit(&entity, PrefabEntityId{ AZ::Name("test"), 1 }, netId, role);
|
||||
entity.Init();
|
||||
}
|
||||
|
||||
void StopEntity(const AZ::Entity& entity)
|
||||
{
|
||||
const auto netBindComponent = entity.FindComponent<Multiplayer::NetBindComponent>();
|
||||
EXPECT_NE(netBindComponent, nullptr);
|
||||
netBindComponent->StopEntity();
|
||||
}
|
||||
|
||||
void StopAndDeleteEntity(AZStd::unique_ptr<AZ::Entity>& entity)
|
||||
{
|
||||
if (entity)
|
||||
{
|
||||
StopEntity(*entity);
|
||||
entity->Deactivate();
|
||||
entity.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void CreateEntityWithRootHierarchy(AZ::Entity& rootEntity)
|
||||
{
|
||||
rootEntity.CreateComponent<AzFramework::TransformComponent>();
|
||||
rootEntity.CreateComponent<NetBindComponent>();
|
||||
rootEntity.CreateComponent<NetworkTransformComponent>();
|
||||
rootEntity.CreateComponent<NetworkHierarchyRootComponent>();
|
||||
}
|
||||
|
||||
void CreateEntityWithChildHierarchy(AZ::Entity& childEntity)
|
||||
{
|
||||
childEntity.CreateComponent<AzFramework::TransformComponent>();
|
||||
childEntity.CreateComponent<NetBindComponent>();
|
||||
childEntity.CreateComponent<NetworkTransformComponent>();
|
||||
childEntity.CreateComponent<NetworkHierarchyChildComponent>();
|
||||
}
|
||||
|
||||
void SetParentIdOnNetworkTransform(const 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);
|
||||
|
||||
entity.FindComponent<NetworkTransformComponent>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
|
||||
// now the parent id is in the component
|
||||
}
|
||||
|
||||
template <typename Component>
|
||||
void SetHierarchyRootFieldOnNetworkHierarchyChild(const 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);
|
||||
|
||||
entity.FindComponent<Component>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
|
||||
// now the parent id is in the component
|
||||
}
|
||||
|
||||
struct EntityInfo
|
||||
{
|
||||
enum class Role
|
||||
{
|
||||
Root,
|
||||
Child,
|
||||
None
|
||||
};
|
||||
|
||||
EntityInfo(AZ::Entity& entity, NetEntityId netId, Role role)
|
||||
: m_entity(entity)
|
||||
, m_netId(netId)
|
||||
, m_role(role)
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
SetParentIdOnNetworkTransform(childOfChild.m_entity, child.m_netId);
|
||||
|
||||
// Create an entity replicator for the child entity
|
||||
const NetworkEntityHandle childOfChildHandle(&childOfChild.m_entity, 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, 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, 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,166 @@
|
||||
/*
|
||||
* 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_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_METHOD3(AlterTime, void(Multiplayer::HostFrameId, AZ::TimeMs, 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
@@ -15,20 +15,28 @@ 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/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
|
||||
@@ -40,23 +48,24 @@ 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
|
||||
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/NetworkTransformComponent.cpp
|
||||
Source/ConnectionData/ClientToServerConnectionData.cpp
|
||||
Source/ConnectionData/ClientToServerConnectionData.h
|
||||
@@ -68,6 +77,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
|
||||
@@ -83,13 +95,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
|
||||
@@ -101,11 +113,11 @@ set(FILES
|
||||
Source/NetworkInput/NetworkInputMigrationVector.h
|
||||
Source/NetworkTime/NetworkTime.cpp
|
||||
Source/NetworkTime/NetworkTime.h
|
||||
Source/Physics/PhysicsUtils.cpp
|
||||
Source/Pipeline/NetBindMarkerComponent.cpp
|
||||
Source/Pipeline/NetBindMarkerComponent.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
|
||||
|
||||
Reference in New Issue
Block a user