/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include AZ_CVAR(uint32_t, bg_hierarchyEntityMaxLimit, 16, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum allowed size of network entity hierarchies, including top level entity."); static constexpr int CommonHierarchyEntityMaxLimit = 16; // Should match @bg_hierarchyEntityMaxLimit namespace Multiplayer { void NetworkHierarchyRootComponent::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() ->Version(1); if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class( "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")); provided.push_back(AZ_CRC_CE("MultiplayerInputDriver")); } void NetworkHierarchyRootComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); } NetworkHierarchyRootComponent::NetworkHierarchyRootComponent() : m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); }) , m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); }) { } void NetworkHierarchyRootComponent::OnInit() { } void NetworkHierarchyRootComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { m_isHierarchyEnabled = true; m_hierarchicalEntities.push_back(GetEntity()); NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) { transformComponent->BindChildChangedEventHandler(m_childChangedHandler); transformComponent->BindParentChangedEventHandler(m_parentChangedHandler); } } void NetworkHierarchyRootComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { m_isHierarchyEnabled = false; if (m_rootEntity) { // Tell parent to re-build the hierarchy if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) { root->RebuildHierarchy(); } } else { // Notify children that the hierarchy is disbanding AZStd::vector allChildren; AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren); for (const AZ::EntityId& childEntityId : allChildren) { if (const AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(childEntityId)) { SetRootForEntity(GetEntity(), nullptr, childEntity); } } } m_childChangedHandler.Disconnect(); m_parentChangedHandler.Disconnect(); NetworkHierarchyRequestBus::Handler::BusDisconnect(); m_hierarchicalEntities.clear(); m_rootEntity = nullptr; } bool NetworkHierarchyRootComponent::IsHierarchyEnabled() const { return m_isHierarchyEnabled; } bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const { return GetHierarchyRoot() == InvalidNetEntityId; } bool NetworkHierarchyRootComponent::IsHierarchicalChild() const { return !IsHierarchicalRoot(); } AZStd::vector NetworkHierarchyRootComponent::GetHierarchicalEntities() const { return m_hierarchicalEntities; } AZ::Entity* NetworkHierarchyRootComponent::GetHierarchicalRoot() const { if (m_rootEntity) { return m_rootEntity; } return GetEntity(); } void NetworkHierarchyRootComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) { handler.Connect(m_networkHierarchyChangedEvent); } void NetworkHierarchyRootComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) { handler.Connect(m_networkHierarchyLeaveEvent); } void NetworkHierarchyRootComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child) { if (IsHierarchicalRoot()) { // Parent-child notifications are not reliable enough to avoid duplicate notifications, // so we will rebuild from scratch to avoid duplicate entries in @m_hierarchicalEntities. RebuildHierarchy(); } else if (NetworkHierarchyRootComponent* root = GetHierarchicalRoot()->FindComponent()) { root->RebuildHierarchy(); } } static AZStd::tuple GetHierarchyComponents(const AZ::Entity* entity) { NetworkHierarchyChildComponent* childComponent = nullptr; NetworkHierarchyRootComponent* rootComponent = nullptr; for (AZ::Component* component : entity->GetComponents()) { if (component->GetUnderlyingComponentType() == NetworkHierarchyChildComponent::TYPEINFO_Uuid()) { childComponent = static_cast(component); break; } if (component->GetUnderlyingComponentType() == NetworkHierarchyRootComponent::TYPEINFO_Uuid()) { rootComponent = static_cast(component); break; } } return AZStd::tie(rootComponent, childComponent); } void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent) { // If the parent is part of a hierarchy, it will detect this entity as a new child and rebuild hierarchy. // Thus, we only need to take care of a case when the parent is not part of a hierarchy, // in which case, this entity will be a new root of a new hierarchy. if (AZ::Entity* parentEntity = AZ::Interface::Get()->FindEntity(newParent)) { auto [rootComponent, childComponent] = GetHierarchyComponents(parentEntity); if (rootComponent == nullptr && childComponent == nullptr) { SetRootForEntity(nullptr, nullptr, GetEntity()); } else { m_hierarchicalEntities.clear(); } } else { // Detached from parent SetRootForEntity(nullptr, nullptr, GetEntity()); } } void NetworkHierarchyRootComponent::RebuildHierarchy() { AZStd::vector previousEntities; m_hierarchicalEntities.swap(previousEntities); m_hierarchicalEntities.reserve(bg_hierarchyEntityMaxLimit); InternalBuildHierarchyList(GetEntity()); bool hierarchyChanged = false; // Send out join and leave events. for (AZ::Entity* currentEntity : m_hierarchicalEntities) { const auto prevEntityIterator = AZStd::find(previousEntities.begin(), previousEntities.end(), currentEntity); if (prevEntityIterator != previousEntities.end()) { // This entity was here before the build of the hierarchy. previousEntities.erase(prevEntityIterator); } else { // This is a newly added entity to the network hierarchy. hierarchyChanged = true; SetRootForEntity(nullptr, GetEntity(), currentEntity); } } // These entities were removed since last rebuild. for (const AZ::Entity* previousEntity : previousEntities) { SetRootForEntity(GetEntity(), nullptr, previousEntity); } if (!previousEntities.empty()) { hierarchyChanged = true; } if (hierarchyChanged) { m_networkHierarchyChangedEvent.Signal(GetEntityId()); } } void NetworkHierarchyRootComponent::InternalBuildHierarchyList(AZ::Entity* underEntity) { AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface::Get(); AZStd::deque candidates; candidates.push_back(underEntity); while (!candidates.empty()) { AZ::Entity* candidate = candidates.front(); candidates.pop_front(); if (candidate) { auto [hierarchyRootComponent, hierarchyChildComponent] = GetHierarchyComponents(candidate); if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchyEnabled()) || (hierarchyRootComponent && hierarchyRootComponent->IsHierarchyEnabled())) { m_hierarchicalEntities.push_back(candidate); if (m_hierarchicalEntities.size() >= bg_hierarchyEntityMaxLimit) { AZLOG_WARN("Network hierarchy size exceeded, current limit is %d, root entity was %s", static_cast(bg_hierarchyEntityMaxLimit), GetEntity()->GetName().c_str()); return; } const AZStd::vector allChildren = candidate->GetTransform()->GetChildren(); for (const AZ::EntityId& newChildId : allChildren) { candidates.push_back(componentApplicationRequests->FindEntity(newChildId)); } } } } } void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* previousKnownRoot, AZ::Entity* newRoot, const AZ::Entity* childEntity) { auto [hierarchyRootComponent, hierarchyChildComponent] = GetHierarchyComponents(childEntity); if (hierarchyChildComponent) { hierarchyChildComponent->SetTopLevelHierarchyRootEntity(previousKnownRoot, newRoot); } else if (hierarchyRootComponent) { hierarchyRootComponent->SetTopLevelHierarchyRootEntity(previousKnownRoot, newRoot); } } void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot) { if (newHierarchyRoot) { if (m_rootEntity != newHierarchyRoot) { m_rootEntity = newHierarchyRoot; if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority) { NetworkHierarchyRootComponentController* controller = static_cast(GetController()); const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId()); controller->SetHierarchyRoot(netRootId); } GetNetBindComponent()->SetOwningConnectionId(m_rootEntity->FindComponent()->GetOwningConnectionId()); m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId()); } } else if ((previousHierarchyRoot && m_rootEntity == previousHierarchyRoot) || !previousHierarchyRoot) { m_rootEntity = nullptr; if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority) { NetworkHierarchyRootComponentController* controller = static_cast(GetController()); controller->SetHierarchyRoot(InvalidNetEntityId); } GetNetBindComponent()->SetOwningConnectionId(m_previousOwningConnectionId); m_networkHierarchyLeaveEvent.Signal(); // We lost the parent hierarchical entity, so as a root we need to re-build our own hierarchy. RebuildHierarchy(); } } void NetworkHierarchyRootComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId) { NetworkHierarchyRootComponentBase::SetOwningConnectionId(connectionId); if (IsHierarchicalChild() == false) { m_previousOwningConnectionId = connectionId; } } NetworkHierarchyRootComponentController::NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent) : NetworkHierarchyRootComponentControllerBase(parent) { } void NetworkHierarchyRootComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { } void NetworkHierarchyRootComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { } Multiplayer::MultiplayerController::InputPriorityOrder NetworkHierarchyRootComponentController::GetInputOrder() const { return Multiplayer::MultiplayerController::InputPriorityOrder::SubEntities; } void NetworkHierarchyRootComponentController::CreateInput(Multiplayer::NetworkInput& input, float deltaTime) { NetworkHierarchyRootComponent& component = GetParent(); if (!component.IsHierarchicalRoot()) { return; } INetworkEntityManager* networkEntityManager = AZ::Interface::Get(); AZ_Assert(networkEntityManager, "NetworkEntityManager must be created."); const AZStd::vector& entities = component.m_hierarchicalEntities; auto* networkInput = input.FindComponentInput(); networkInput->m_childInputs.clear(); networkInput->m_childInputs.reserve(entities.size()); for (AZ::Entity* child : entities) { if (child == component.GetEntity()) { continue; // Avoid infinite recursion } NetEntityId childNetEntitydId = networkEntityManager->GetNetEntityIdById(child->GetId()); AZ_Assert(childNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager"); ConstNetworkEntityHandle childEntityHandle = networkEntityManager->GetEntity(childNetEntitydId); NetBindComponent* netComp = childEntityHandle.GetNetBindComponent(); AZ_Assert(netComp, "No NetBindComponent, this should be impossible"); // Validate we still have a controller and we aren't in the middle of removing them if (netComp->HasController()) { NetworkInputChild subInput; subInput.Attach(childEntityHandle); subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId()); netComp->CreateInput(subInput.GetNetworkInput(), deltaTime); // make sure our input sub commands have the same time as the original subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId()); networkInput->m_childInputs.emplace_back(subInput); } } } void NetworkHierarchyRootComponentController::ProcessInput(Multiplayer::NetworkInput& input, float deltaTime) { if (auto* networkInput = input.FindComponentInput()) { INetworkEntityManager* networkEntityManager = AZ::Interface::Get(); AZ_Assert(networkEntityManager, "NetworkEntityManager must be created."); // Build a set of Net IDs for the children AZStd::unordered_set currentChildren; NetworkHierarchyRootComponent& component = GetParent(); for (AZ::Entity* child : component.m_hierarchicalEntities) { if (child == component.GetEntity()) // Skip the root entity { continue; } NetEntityId childNetEntitydId = networkEntityManager->GetNetEntityIdById(child->GetId()); AZ_Assert(childNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager"); currentChildren.insert(childNetEntitydId); } // Process the input for the child entities for (NetworkInputChild& subInput : networkInput->m_childInputs) { const ConstNetworkEntityHandle& inputOwnerHandle = subInput.GetOwner(); NetEntityId inputOwnerNetEntitydId = inputOwnerHandle.GetNetEntityId(); if (currentChildren.count(inputOwnerNetEntitydId) == 0) { // Skip the input for entities which are not a part of this hierarchy continue; } ConstNetworkEntityHandle localEntityHandle = networkEntityManager->GetEntity(inputOwnerNetEntitydId); if (localEntityHandle.Exists()) { auto* netComp = localEntityHandle.GetNetBindComponent(); AZ_Assert(netComp, "No NetBindComponent, this should be impossible"); // We do not rewind entity role changes, so make sure we are the correct role prior to processing if (netComp->HasController()) { subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId()); netComp->ProcessInput(subInput.GetNetworkInput(), deltaTime); } } } } } bool NetworkHierarchyRootComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer) { bool result = true; INetworkEntityManager* networkEntityManager = AZ::Interface::Get(); AZ_Assert(networkEntityManager, "NetworkEntityManager must be created."); for (AZ::Entity* child : m_hierarchicalEntities) { if (child == GetEntity()) { // Skip the root entity continue; } NetEntityId childNetEntitydId = networkEntityManager->GetNetEntityIdById(child->GetId()); AZ_Assert(childNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager"); ConstNetworkEntityHandle childEntityHandle = networkEntityManager->GetEntity(childNetEntitydId); NetBindComponent* netBindComponent = childEntityHandle.GetNetBindComponent(); AZ_Assert(netBindComponent, "No NetBindComponent, this should be impossible"); result = result && netBindComponent->SerializeEntityCorrection(serializer); } return result; } }