From a0ada8fcd388d441f018fc1619845682a09f9c27 Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 9 Sep 2021 16:44:35 +0100 Subject: [PATCH 1/7] Reworked net entities instantiation in order to fix entity references e.g. parent-child relationship. Note: Only entities within network spawnable keep the references until the ticket system refactoring is done Signed-off-by: pereslav --- .../Code/Source/MultiplayerGem.cpp | 2 - .../NetworkEntity/NetworkEntityManager.cpp | 68 ++++++++++- .../NetworkEntity/NetworkEntityManager.h | 7 ++ .../NetworkEntity/NetworkEntityTracker.h | 1 + .../Pipeline/NetBindMarkerComponent.cpp | 115 ------------------ .../Source/Pipeline/NetBindMarkerComponent.h | 47 ------- .../Pipeline/NetworkPrefabProcessor.cpp | 24 +--- .../NetworkSpawnableHolderComponent.cpp | 2 +- .../NetworkSpawnableHolderComponent.h | 2 +- Gems/Multiplayer/Code/Tests/MainTools.cpp | 2 - Gems/Multiplayer/Code/multiplayer_files.cmake | 2 - 11 files changed, 78 insertions(+), 194 deletions(-) delete mode 100644 Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp delete mode 100644 Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h diff --git a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp index 1ad7be1a0a..8028e0c5d1 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -23,7 +22,6 @@ namespace Multiplayer AzNetworking::NetworkingSystemComponent::CreateDescriptor(), MultiplayerSystemComponent::CreateDescriptor(), NetBindComponent::CreateDescriptor(), - NetBindMarkerComponent::CreateDescriptor(), NetworkSpawnableHolderComponent::CreateDescriptor(), }); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 69d2726c65..88f0b221ec 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -467,6 +467,54 @@ namespace Multiplayer return netEntityId; } + AzFramework::EntitySpawnTicket NetworkEntityManager::RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable) + { + const AzFramework::Spawnable::EntityList& entityList = rootSpawnable->GetEntities(); + if (entityList.size() == 0) + { + AZ_Assert(false, "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()) + return {}; + } + + // The first entity in every spawnable is the root one + const AZ::Entity* rootEntity = (entityList.begin())->get(); + + const auto* holderComponent = rootEntity->FindComponent(); + if(!holderComponent) + { + // This spawnable doesn't have a corresponding network spawnable. + return {}; + } + + // Retrieve the corresponding network spawnable asset + AZ::Data::Asset netSpawnableAsset = holderComponent->GetNetworkSpawnableAsset(); + + // Prepare the parameters for the spawning process + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_priority = AzFramework::SpawnablePriority_High; + + // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene + optionalArgs.m_preInsertionCallback = [spawnableAssetId = netSpawnableAsset.GetId()](AzFramework::EntitySpawnTicket::Id, + AzFramework::SpawnableEntityContainerView entities) + { + for (uint32_t netEntityIndex = 0; netEntityIndex < entities.size(); netEntityIndex++) + { + AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); + AZ::Name spawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(spawnableAssetId); + + PrefabEntityId prefabEntityId; + prefabEntityId.m_prefabName = spawnableName; + prefabEntityId.m_entityOffset = netEntityIndex; + AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); + } + }; + + // Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities + AzFramework::EntitySpawnTicket ticket(netSpawnableAsset); + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(ticket, AZStd::move(optionalArgs)); + return ticket; + } + void NetworkEntityManager::OnRootSpawnableAssigned( [[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) { @@ -477,11 +525,15 @@ namespace Multiplayer { multiplayer->SendReadyForEntityUpdates(true); } + + if(ShouldSpawnNetEntities()) + { + m_rootNetSpawnableTicket = RequestNetSpawnableInstantiation(rootSpawnable); + } } void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) { - // TODO: Do we need to clear all entities here? auto* multiplayer = GetMultiplayer(); const auto agentType = multiplayer->GetAgentType(); @@ -489,6 +541,12 @@ namespace Multiplayer { multiplayer->SendReadyForEntityUpdates(false); } + + // Despawn any remaining net entities created locally + if (m_rootNetSpawnableTicket.IsValid()) + { + AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_rootNetSpawnableTicket); + } } void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) @@ -506,4 +564,12 @@ namespace Multiplayer netEntity->GetName().c_str()); } } + + bool NetworkEntityManager::ShouldSpawnNetEntities() const + { + const auto agentType = GetMultiplayer()->GetAgentType(); + const bool shouldSpawnNetEntities = + (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); + return shouldSpawnNetEntities; + } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 9ccc576447..a5956b448f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,11 @@ namespace Multiplayer private: void RemoveEntities(); NetEntityId NextId(); + bool ShouldSpawnNetEntities() const; + + // Note: This is an async function. + // The instantiated entities are not available immediately but will be constructed by the spawnable system + AzFramework::EntitySpawnTicket RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable); NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; @@ -120,5 +126,6 @@ namespace Multiplayer DeferredRpcMessages m_localDeferredRpcMessages; NetworkSpawnableLibrary m_networkPrefabLibrary; + AzFramework::EntitySpawnTicket m_rootNetSpawnableTicket; }; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h index 09238acaee..0e632dc7b4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h @@ -37,6 +37,7 @@ namespace Multiplayer NetworkEntityHandle Get(NetEntityId netEntityId); ConstNetworkEntityHandle Get(NetEntityId netEntityId) const; + //! Returns Net Entity ID for a given AZ Entity ID. NetEntityId Get(const AZ::EntityId& entityId) const; //! Returns true if the netEntityId exists. diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp deleted file mode 100644 index c8abac25cb..0000000000 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 - -namespace Multiplayer -{ - void NetBindMarkerComponent::Reflect(AZ::ReflectContext* context) - { - AZ::SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class() - ->Version(1) - ->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex) - ->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset); - } - } - - AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset& asset) - { - AzFramework::Spawnable* spawnable = asset.GetAs(); - if (!spawnable) - { - asset = - AZ::Data::AssetManager::Instance().GetAsset(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad); - AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset); - - spawnable = asset.GetAs(); - } - - return spawnable; - } - - - void NetBindMarkerComponent::Activate() - { - const auto agentType = AZ::Interface::Get()->GetAgentType(); - const bool spawnImmediately = - (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); - - if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid()) - { - AZ::Transform worldTm = GetEntity()->FindComponent()->GetWorldTM(); - auto preInsertionCallback = - [worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()] - (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) - { - if (entities.size() == 1) - { - AZ::Entity* netEntity = *entities.begin(); - - auto* transformComponent = netEntity->FindComponent(); - transformComponent->SetWorldTM(worldTm); - - AZ::Name spawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(spawnableAssetId); - PrefabEntityId prefabEntityId; - prefabEntityId.m_prefabName = spawnableName; - prefabEntityId.m_entityOffset = static_cast(netEntityIndex); - AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); - } - else - { - AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size()); - } - }; - - m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset); - AzFramework::SpawnEntitiesOptionalArgs optionalArgs; - optionalArgs.m_preInsertionCallback = AZStd::move(preInsertionCallback); - AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities( - m_netSpawnTicket, { m_netEntityIndex }, AZStd::move(optionalArgs)); - } - } - - void NetBindMarkerComponent::Deactivate() - { - if(m_netSpawnTicket.IsValid()) - { - AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket); - } - } - - size_t NetBindMarkerComponent::GetNetEntityIndex() const - { - return m_netEntityIndex; - } - - void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex) - { - m_netEntityIndex = netEntityIndex; - } - - void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset) - { - m_networkSpawnableAsset = networkSpawnableAsset; - } - - AZ::Data::Asset NetBindMarkerComponent::GetNetworkSpawnableAsset() const - { - return m_networkSpawnableAsset; - } - -} diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h deleted file mode 100644 index dce3252200..0000000000 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 -#include -#include -#include - -namespace Multiplayer -{ - //! @class NetBindMarkerComponent - //! @brief Component for tracking net entities in the original non-networked spawnable. - class NetBindMarkerComponent final : public AZ::Component - { - public: - AZ_COMPONENT(NetBindMarkerComponent, "{40612C1B-427D-45C6-A2F0-04E16DF5B718}"); - - static void Reflect(AZ::ReflectContext* context); - - NetBindMarkerComponent() = default; - ~NetBindMarkerComponent() override = default; - - //! AZ::Component overrides. - //! @{ - void Activate() override; - void Deactivate() override; - //! @} - - size_t GetNetEntityIndex() const; - void SetNetEntityIndex(size_t val); - - void SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset); - AZ::Data::Asset GetNetworkSpawnableAsset() const; - - private: - AZ::Data::Asset m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad}; - size_t m_netEntityIndex = 0; - AzFramework::EntitySpawnTicket m_netSpawnTicket; - }; -} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index acfec5eb38..e0990aa785 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -46,7 +45,7 @@ namespace Multiplayer { if (auto* serializeContext = azrtti_cast(context); serializeContext != nullptr) { - serializeContext->Class()->Version(1); + serializeContext->Class()->Version(2); } } @@ -137,8 +136,6 @@ namespace Multiplayer networkSpawnableAsset.Create(networkSpawnable->GetId()); networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); - size_t netEntitiesIndexCounter = 0; - for (auto* prefabEntity : prefabNetEntities) { Instance* instance = netEntityToInstanceMap[prefabEntity]; @@ -148,30 +145,11 @@ namespace Multiplayer AZ_Assert(netEntity, "Unable to detach entity %s [%s] from the source prefab instance", prefabEntity->GetName().c_str(), entityId.ToString().c_str()); - // Net entity will need a new ID to avoid IDs collision - netEntity->SetId(AZ::Entity::MakeId()); netEntity->InvalidateDependencies(); netEntity->EvaluateDependencies(); // Insert the entity into the target net spawnable netSpawnableEntities.emplace_back(netEntity); - - // Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable - AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName()); - breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault()); - - // Marker component is responsible to spawning entities based on the index. - NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent(); - netBindMarkerComponent->SetNetEntityIndex(netEntitiesIndexCounter); - netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset); - - // Copy the transform component from the original entity to have the correct transform and parent-child relationship - AzFramework::TransformComponent* transformComponent = netEntity->FindComponent(); - breadcrumbEntity->CreateComponent(*transformComponent); - - instance->AddEntity(*breadcrumbEntity); - - netEntitiesIndexCounter++; } // Add net spawnable asset holder to the prefab root diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp index c40cb3677b..e83f6fea84 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp @@ -39,7 +39,7 @@ namespace Multiplayer m_networkSpawnableAsset = networkSpawnableAsset; } - AZ::Data::Asset NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() + AZ::Data::Asset NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() const { return m_networkSpawnableAsset; } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h index d369bbefd3..f32e8d31ab 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h @@ -33,7 +33,7 @@ namespace Multiplayer //! @} void SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset); - AZ::Data::Asset GetNetworkSpawnableAsset(); + AZ::Data::Asset GetNetworkSpawnableAsset() const; private: AZ::Data::Asset m_networkSpawnableAsset{ AZ::Data::AssetLoadBehavior::PreLoad }; diff --git a/Gems/Multiplayer/Code/Tests/MainTools.cpp b/Gems/Multiplayer/Code/Tests/MainTools.cpp index 89b2492bc6..53dfad62d1 100644 --- a/Gems/Multiplayer/Code/Tests/MainTools.cpp +++ b/Gems/Multiplayer/Code/Tests/MainTools.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -30,7 +29,6 @@ namespace Multiplayer { AZStd::vector descriptors({ NetBindComponent::CreateDescriptor(), - NetBindMarkerComponent::CreateDescriptor(), NetworkSpawnableHolderComponent::CreateDescriptor() }); diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 0b2adb1530..bdc0129137 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -101,8 +101,6 @@ set(FILES Source/NetworkInput/NetworkInputMigrationVector.h Source/NetworkTime/NetworkTime.cpp Source/NetworkTime/NetworkTime.h - Source/Pipeline/NetBindMarkerComponent.cpp - Source/Pipeline/NetBindMarkerComponent.h Source/Pipeline/NetworkSpawnableHolderComponent.cpp Source/Pipeline/NetworkSpawnableHolderComponent.h Source/Physics/PhysicsUtils.cpp From 1f66e8a76054a7a136e13fcf1b2cd126c73c1d8a Mon Sep 17 00:00:00 2001 From: pereslav Date: Mon, 13 Sep 2021 13:35:35 +0100 Subject: [PATCH 2/7] PR feedback addressing Signed-off-by: pereslav --- .../NetworkEntity/NetworkEntityManager.cpp | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 88f0b221ec..4e0818323e 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -470,14 +470,21 @@ namespace Multiplayer AzFramework::EntitySpawnTicket NetworkEntityManager::RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable) { const AzFramework::Spawnable::EntityList& entityList = rootSpawnable->GetEntities(); - if (entityList.size() == 0) + if (entityList.empty()) { - AZ_Assert(false, "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()) + AZ_Error("NetworkEntityManager", false, + "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()); return {}; } // The first entity in every spawnable is the root one const AZ::Entity* rootEntity = (entityList.begin())->get(); + if(!rootEntity) + { + AZ_Error("NetworkEntityManager", false, + "RequestNetSpawnableInstantiation: Root entity is null in the spawnable %s", rootSpawnable.GetHint().c_str()); + return {}; + } const auto* holderComponent = rootEntity->FindComponent(); if(!holderComponent) @@ -493,17 +500,19 @@ namespace Multiplayer AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_priority = AzFramework::SpawnablePriority_High; + AZ::Name netSpawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); + // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene - optionalArgs.m_preInsertionCallback = [spawnableAssetId = netSpawnableAsset.GetId()](AzFramework::EntitySpawnTicket::Id, + optionalArgs.m_preInsertionCallback = [netSpawnableName = netSpawnableName](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) { - for (uint32_t netEntityIndex = 0; netEntityIndex < entities.size(); netEntityIndex++) + for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); + netEntityIndex < entitiesSize; netEntityIndex++) { AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); - AZ::Name spawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(spawnableAssetId); PrefabEntityId prefabEntityId; - prefabEntityId.m_prefabName = spawnableName; + prefabEntityId.m_prefabName = netSpawnableName; prefabEntityId.m_entityOffset = netEntityIndex; AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); } From 9b0e3f26b14e6573ad0877a63ef9e9d66879cefb Mon Sep 17 00:00:00 2001 From: pereslav Date: Mon, 13 Sep 2021 16:30:42 +0100 Subject: [PATCH 3/7] PR feedback addressing 2 Signed-off-by: pereslav --- .../Code/Source/NetworkEntity/NetworkEntityManager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 4e0818323e..793acc17ff 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -500,10 +500,11 @@ namespace Multiplayer AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_priority = AzFramework::SpawnablePriority_High; - AZ::Name netSpawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); + const AZ::Name netSpawnableName = + AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene - optionalArgs.m_preInsertionCallback = [netSpawnableName = netSpawnableName](AzFramework::EntitySpawnTicket::Id, + optionalArgs.m_preInsertionCallback = [netSpawnableName](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) { for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); From a3dfb8869691abf341dd27ac50dead1a890b6c52 Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 16 Sep 2021 17:48:53 +0100 Subject: [PATCH 4/7] Added support for instantiating network spawnables for any spawnables, not just root spawnable Signed-off-by: pereslav --- .../Spawnable/SpawnableEntitiesInterface.h | 9 +++ .../Spawnable/SpawnableEntitiesManager.cpp | 20 +++++ .../Spawnable/SpawnableEntitiesManager.h | 6 ++ .../Mocks/MockSpawnableEntitiesInterface.h | 2 + .../NetworkEntity/NetworkEntityManager.cpp | 79 ++++++++++++++----- .../NetworkEntity/NetworkEntityManager.h | 12 ++- 6 files changed, 105 insertions(+), 23 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 4b09dcbc75..254df8f1b0 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -320,6 +320,15 @@ namespace AzFramework //! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs. virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0; + //! Register a handler for OnSpawned events. + //! @param handler The handler to receive the event. + virtual void AddOnSpawnedHandler(AZ::Event, + const AZStd::vector&, const void*>::Handler& handler) = 0; + + //! Register a handler for OnDespawned events. + //! @param handler The handler to receive the event. + virtual void AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) = 0; + protected: [[nodiscard]] virtual AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) = 0; virtual void DestroyTicket(void* ticket) = 0; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 5fa072a451..f052f83224 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -146,6 +146,16 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } + void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event, const AZStd::vector&, const void*>::Handler& handler) + { + handler.Connect(m_onSpawnedEvent); + } + + void SpawnableEntitiesManager::AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) + { + handler.Connect(m_onDespawnedEvent); + } + auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus { CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft; @@ -349,6 +359,8 @@ namespace AzFramework ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } + m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); + ticket.m_currentRequestId++; return true; } @@ -429,6 +441,8 @@ namespace AzFramework ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } + m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); + ticket.m_currentRequestId++; return true; } @@ -460,6 +474,8 @@ namespace AzFramework request.m_completionCallback(request.m_ticketId); } + m_onDespawnedEvent.Signal(ticket.m_spawnable, &ticket); + ticket.m_currentRequestId++; return true; } @@ -487,6 +503,8 @@ namespace AzFramework } } + m_onDespawnedEvent.Signal(ticket.m_spawnable, &ticket); + // Rebuild the list of entities. ticket.m_spawnedEntities.clear(); const Spawnable::EntityList& entities = request.m_spawnable->GetEntities(); @@ -544,6 +562,8 @@ namespace AzFramework ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end())); } + m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); + ticket.m_currentRequestId++; return true; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 69985f8aa9..c78b1778a4 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -69,6 +69,9 @@ namespace AzFramework void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; + void AddOnSpawnedHandler(AZ::Event, const AZStd::vector&, const void*>::Handler& handler) override; + void AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) override; + // // The following function is thread safe but intended to be run from the main thread. // @@ -224,6 +227,9 @@ namespace AzFramework //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured //! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold". SpawnablePriority m_highPriorityThreshold { 64 }; + + AZ::Event, const AZStd::vector&, const void*> m_onSpawnedEvent; + AZ::Event, const void*> m_onDespawnedEvent; }; AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority); diff --git a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h index a5df7af4c3..89e79ecf9b 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h @@ -59,6 +59,8 @@ namespace AzFramework MOCK_METHOD1(CreateTicket, AZStd::pair(AZ::Data::Asset&& spawnable)); MOCK_METHOD1(DestroyTicket, void(void* ticket)); + MOCK_METHOD1(AddOnSpawnedHandler, void(AZ::Event, const AZStd::vector&, const void*>::Handler& handler)); + MOCK_METHOD1(AddOnDespawnedHandler, void(AZ::Event, const void*>::Handler& handler)); /** Installs some default result values for the above functions. * Note that you can always override these in scope of your test by adding additional ON_CALL / EXPECT_CALL diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 793acc17ff..62b57d9603 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -30,9 +30,14 @@ namespace Multiplayer : m_networkEntityAuthorityTracker(*this) , m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event")) , m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event")) + , m_onSpawnedHandler([this](AZ::Data::Asset spawnable, const AZStd::vector& entities, const void* spawnTicket) { this->OnSpawned(spawnable, entities, spawnTicket); }) + , m_onDespawnedHandler([this](AZ::Data::Asset spawnable, const void* spawnTicket) { this->OnDespawned(spawnable, spawnTicket); }) { AZ::Interface::Register(this); AzFramework::RootSpawnableNotificationBus::Handler::BusConnect(); + + AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler); + AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler); } NetworkEntityManager::~NetworkEntityManager() @@ -467,30 +472,39 @@ namespace Multiplayer return netEntityId; } - AzFramework::EntitySpawnTicket NetworkEntityManager::RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable) + AZStd::unique_ptr NetworkEntityManager::RequestNetSpawnableInstantiation( + const AZ::Data::Asset& rootSpawnable, const AZStd::vector& entities) { - const AzFramework::Spawnable::EntityList& entityList = rootSpawnable->GetEntities(); - if (entityList.empty()) + if (entities.empty()) { AZ_Error("NetworkEntityManager", false, "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()); - return {}; + return nullptr; } // The first entity in every spawnable is the root one - const AZ::Entity* rootEntity = (entityList.begin())->get(); + const AZ::Entity* rootEntity = *entities.begin(); if(!rootEntity) { AZ_Error("NetworkEntityManager", false, "RequestNetSpawnableInstantiation: Root entity is null in the spawnable %s", rootSpawnable.GetHint().c_str()); - return {}; + return nullptr; } const auto* holderComponent = rootEntity->FindComponent(); if(!holderComponent) { // This spawnable doesn't have a corresponding network spawnable. - return {}; + return nullptr; + } + + AzFramework::TransformComponent* rootEntityTransform = + rootEntity->FindComponent(); + if (!rootEntityTransform) + { + AZ_Error("NetworkEntityManager", false, + "RequestNetSpawnableInstantiation: Root entity has no transform in the spawnable %s", rootSpawnable.GetHint().c_str()); + return nullptr; } // Retrieve the corresponding network spawnable asset @@ -504,14 +518,26 @@ namespace Multiplayer AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene - optionalArgs.m_preInsertionCallback = [netSpawnableName](AzFramework::EntitySpawnTicket::Id, - AzFramework::SpawnableEntityContainerView entities) + optionalArgs.m_preInsertionCallback = [netSpawnableName, rootTransform = rootEntityTransform->GetWorldTM()] + (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) { + bool shouldUpdateTransform = (rootTransform.IsClose(AZ::Transform::Identity()) == false); + for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); netEntityIndex < entitiesSize; netEntityIndex++) { AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); + if(shouldUpdateTransform) + { + AzFramework::TransformComponent* netEntityTransform = + netEntity->FindComponent(); + + AZ::Transform worldTm = netEntityTransform->GetWorldTM(); + worldTm = rootTransform * worldTm; + netEntityTransform->SetWorldTM(worldTm); + } + PrefabEntityId prefabEntityId; prefabEntityId.m_prefabName = netSpawnableName; prefabEntityId.m_entityOffset = netEntityIndex; @@ -520,13 +546,13 @@ namespace Multiplayer }; // Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities - AzFramework::EntitySpawnTicket ticket(netSpawnableAsset); - AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(ticket, AZStd::move(optionalArgs)); + auto ticket = AZStd::make_unique(netSpawnableAsset); + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs)); return ticket; } - void NetworkEntityManager::OnRootSpawnableAssigned( - [[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) + void NetworkEntityManager::OnRootSpawnableAssigned(AZ::Data::Asset rootSpawnable, + [[maybe_unused]] uint32_t generation) { auto* multiplayer = GetMultiplayer(); const auto agentType = multiplayer->GetAgentType(); @@ -535,11 +561,6 @@ namespace Multiplayer { multiplayer->SendReadyForEntityUpdates(true); } - - if(ShouldSpawnNetEntities()) - { - m_rootNetSpawnableTicket = RequestNetSpawnableInstantiation(rootSpawnable); - } } void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) @@ -552,13 +573,29 @@ namespace Multiplayer multiplayer->SendReadyForEntityUpdates(false); } - // Despawn any remaining net entities created locally - if (m_rootNetSpawnableTicket.IsValid()) + m_netSpawnableTickets.clear(); + } + + void NetworkEntityManager::OnSpawned(AZ::Data::Asset spawnable, + const AZStd::vector& entities, const void* spawnTicket) + { + if (ShouldSpawnNetEntities()) { - AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_rootNetSpawnableTicket); + AZStd::unique_ptr ticket = RequestNetSpawnableInstantiation(spawnable, entities); + if(ticket) + { + AZ::Data::AssetId spawnableAssetId = spawnable.GetId(); + m_netSpawnableTickets[spawnTicket] = AZStd::move(ticket); + } } } + void NetworkEntityManager::OnDespawned([[maybe_unused]] AZ::Data::Asset spawnable, + const void* spawnTicket) + { + m_netSpawnableTickets.erase(spawnTicket); + } + void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) { auto* netBindComponent = netEntity->FindComponent(); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index a5956b448f..14efa4feeb 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -94,10 +94,14 @@ namespace Multiplayer void RemoveEntities(); NetEntityId NextId(); bool ShouldSpawnNetEntities() const; + void OnSpawned(AZ::Data::Asset spawnable, + const AZStd::vector& entities, const void* spawnTicket); + void OnDespawned(AZ::Data::Asset spawnable, const void* spawnTicket); // Note: This is an async function. // The instantiated entities are not available immediately but will be constructed by the spawnable system - AzFramework::EntitySpawnTicket RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable); + AZStd::unique_ptr RequestNetSpawnableInstantiation( + const AZ::Data::Asset& rootSpawnable, const AZStd::vector& entities); NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; @@ -126,6 +130,10 @@ namespace Multiplayer DeferredRpcMessages m_localDeferredRpcMessages; NetworkSpawnableLibrary m_networkPrefabLibrary; - AzFramework::EntitySpawnTicket m_rootNetSpawnableTicket; + + AZStd::unordered_map> m_netSpawnableTickets; + + AZ::Event, const AZStd::vector&, const void*>::Handler m_onSpawnedHandler; + AZ::Event, const void*>::Handler m_onDespawnedHandler; }; } From 454ae90afb9124d2f0b126ea51bb20cb305db78e Mon Sep 17 00:00:00 2001 From: pereslav Date: Fri, 17 Sep 2021 17:15:21 +0100 Subject: [PATCH 5/7] Added a few whitespaces Signed-off-by: pereslav --- .../Code/Source/NetworkEntity/NetworkEntityManager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 62b57d9603..61fd741dc6 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -484,7 +484,7 @@ namespace Multiplayer // The first entity in every spawnable is the root one const AZ::Entity* rootEntity = *entities.begin(); - if(!rootEntity) + if (!rootEntity) { AZ_Error("NetworkEntityManager", false, "RequestNetSpawnableInstantiation: Root entity is null in the spawnable %s", rootSpawnable.GetHint().c_str()); @@ -492,7 +492,7 @@ namespace Multiplayer } const auto* holderComponent = rootEntity->FindComponent(); - if(!holderComponent) + if (!holderComponent) { // This spawnable doesn't have a corresponding network spawnable. return nullptr; @@ -524,11 +524,11 @@ namespace Multiplayer bool shouldUpdateTransform = (rootTransform.IsClose(AZ::Transform::Identity()) == false); for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); - netEntityIndex < entitiesSize; netEntityIndex++) + netEntityIndex < entitiesSize; ++netEntityIndex) { AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); - if(shouldUpdateTransform) + if (shouldUpdateTransform) { AzFramework::TransformComponent* netEntityTransform = netEntity->FindComponent(); @@ -582,7 +582,7 @@ namespace Multiplayer if (ShouldSpawnNetEntities()) { AZStd::unique_ptr ticket = RequestNetSpawnableInstantiation(spawnable, entities); - if(ticket) + if (ticket) { AZ::Data::AssetId spawnableAssetId = spawnable.GetId(); m_netSpawnableTickets[spawnTicket] = AZStd::move(ticket); From 895974db57cc6472baf94d7254f7acca23384fc4 Mon Sep 17 00:00:00 2001 From: pereslav Date: Tue, 21 Sep 2021 14:50:06 +0100 Subject: [PATCH 6/7] Removed spawn/despawn events from the spawnable system. Used NetworkSpawnableHolderComponent instead Signed-off-by: pereslav --- .../Spawnable/SpawnableEntitiesInterface.h | 9 --- .../Spawnable/SpawnableEntitiesManager.cpp | 20 ----- .../Spawnable/SpawnableEntitiesManager.h | 6 -- .../Mocks/MockSpawnableEntitiesInterface.h | 2 - .../NetworkEntity/INetworkEntityManager.h | 10 +++ .../NetworkEntity/NetworkEntityManager.cpp | 78 +------------------ .../NetworkEntity/NetworkEntityManager.h | 18 +---- .../NetworkSpawnableHolderComponent.cpp | 24 ++++++ .../NetworkSpawnableHolderComponent.h | 2 + 9 files changed, 43 insertions(+), 126 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 254df8f1b0..4b09dcbc75 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -320,15 +320,6 @@ namespace AzFramework //! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs. virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0; - //! Register a handler for OnSpawned events. - //! @param handler The handler to receive the event. - virtual void AddOnSpawnedHandler(AZ::Event, - const AZStd::vector&, const void*>::Handler& handler) = 0; - - //! Register a handler for OnDespawned events. - //! @param handler The handler to receive the event. - virtual void AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) = 0; - protected: [[nodiscard]] virtual AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) = 0; virtual void DestroyTicket(void* ticket) = 0; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index f052f83224..5fa072a451 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -146,16 +146,6 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event, const AZStd::vector&, const void*>::Handler& handler) - { - handler.Connect(m_onSpawnedEvent); - } - - void SpawnableEntitiesManager::AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) - { - handler.Connect(m_onDespawnedEvent); - } - auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus { CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft; @@ -359,8 +349,6 @@ namespace AzFramework ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } - m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); - ticket.m_currentRequestId++; return true; } @@ -441,8 +429,6 @@ namespace AzFramework ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } - m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); - ticket.m_currentRequestId++; return true; } @@ -474,8 +460,6 @@ namespace AzFramework request.m_completionCallback(request.m_ticketId); } - m_onDespawnedEvent.Signal(ticket.m_spawnable, &ticket); - ticket.m_currentRequestId++; return true; } @@ -503,8 +487,6 @@ namespace AzFramework } } - m_onDespawnedEvent.Signal(ticket.m_spawnable, &ticket); - // Rebuild the list of entities. ticket.m_spawnedEntities.clear(); const Spawnable::EntityList& entities = request.m_spawnable->GetEntities(); @@ -562,8 +544,6 @@ namespace AzFramework ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end())); } - m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); - ticket.m_currentRequestId++; return true; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index c78b1778a4..69985f8aa9 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -69,9 +69,6 @@ namespace AzFramework void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; - void AddOnSpawnedHandler(AZ::Event, const AZStd::vector&, const void*>::Handler& handler) override; - void AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) override; - // // The following function is thread safe but intended to be run from the main thread. // @@ -227,9 +224,6 @@ namespace AzFramework //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured //! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold". SpawnablePriority m_highPriorityThreshold { 64 }; - - AZ::Event, const AZStd::vector&, const void*> m_onSpawnedEvent; - AZ::Event, const void*> m_onDespawnedEvent; }; AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority); diff --git a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h index 89e79ecf9b..a5df7af4c3 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h @@ -59,8 +59,6 @@ namespace AzFramework MOCK_METHOD1(CreateTicket, AZStd::pair(AZ::Data::Asset&& spawnable)); MOCK_METHOD1(DestroyTicket, void(void* ticket)); - MOCK_METHOD1(AddOnSpawnedHandler, void(AZ::Event, const AZStd::vector&, const void*>::Handler& handler)); - MOCK_METHOD1(AddOnDespawnedHandler, void(AZ::Event, const void*>::Handler& handler)); /** Installs some default result values for the above functions. * Note that you can always override these in scope of your test by adding additional ON_CALL / EXPECT_CALL diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 8a5fec869c..5bcf038eff 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace Multiplayer { @@ -75,6 +76,15 @@ namespace Multiplayer const AZ::Transform& transform ) = 0; + //! Requests a network spawnable to instantiate at a given transform + //! This is an async function. The instantiated entities are not available immediately but will be constructed by the spawnable system + //! The spawnable ticket has to be kept for the whole lifetime of the entities + //! @param netSpawnable the network spawnable to spawn + //! @param transform the transform where the spawnable should be spawned + //! @return the ticket for managing the spawned entities + [[nodiscard]] virtual AZStd::unique_ptr RequestNetSpawnableInstantiation( + const AZ::Data::Asset& netSpawnable, const AZ::Transform& transform) = 0; + //! Configures new networked entity //! @param netEntity the entity to setup //! @param prefabEntryId the name of the spawnable the entity originated from diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 61fd741dc6..f4f744494b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -30,14 +30,9 @@ namespace Multiplayer : m_networkEntityAuthorityTracker(*this) , m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event")) , m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event")) - , m_onSpawnedHandler([this](AZ::Data::Asset spawnable, const AZStd::vector& entities, const void* spawnTicket) { this->OnSpawned(spawnable, entities, spawnTicket); }) - , m_onDespawnedHandler([this](AZ::Data::Asset spawnable, const void* spawnTicket) { this->OnDespawned(spawnable, spawnTicket); }) { AZ::Interface::Register(this); AzFramework::RootSpawnableNotificationBus::Handler::BusConnect(); - - AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler); - AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler); } NetworkEntityManager::~NetworkEntityManager() @@ -473,52 +468,17 @@ namespace Multiplayer } AZStd::unique_ptr NetworkEntityManager::RequestNetSpawnableInstantiation( - const AZ::Data::Asset& rootSpawnable, const AZStd::vector& entities) + const AZ::Data::Asset& netSpawnable, const AZ::Transform& transform) { - if (entities.empty()) - { - AZ_Error("NetworkEntityManager", false, - "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()); - return nullptr; - } - - // The first entity in every spawnable is the root one - const AZ::Entity* rootEntity = *entities.begin(); - if (!rootEntity) - { - AZ_Error("NetworkEntityManager", false, - "RequestNetSpawnableInstantiation: Root entity is null in the spawnable %s", rootSpawnable.GetHint().c_str()); - return nullptr; - } - - const auto* holderComponent = rootEntity->FindComponent(); - if (!holderComponent) - { - // This spawnable doesn't have a corresponding network spawnable. - return nullptr; - } - - AzFramework::TransformComponent* rootEntityTransform = - rootEntity->FindComponent(); - if (!rootEntityTransform) - { - AZ_Error("NetworkEntityManager", false, - "RequestNetSpawnableInstantiation: Root entity has no transform in the spawnable %s", rootSpawnable.GetHint().c_str()); - return nullptr; - } - - // Retrieve the corresponding network spawnable asset - AZ::Data::Asset netSpawnableAsset = holderComponent->GetNetworkSpawnableAsset(); - // Prepare the parameters for the spawning process AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_priority = AzFramework::SpawnablePriority_High; const AZ::Name netSpawnableName = - AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); + AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnable.GetId()); // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene - optionalArgs.m_preInsertionCallback = [netSpawnableName, rootTransform = rootEntityTransform->GetWorldTM()] + optionalArgs.m_preInsertionCallback = [netSpawnableName, rootTransform = transform] (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) { bool shouldUpdateTransform = (rootTransform.IsClose(AZ::Transform::Identity()) == false); @@ -546,7 +506,7 @@ namespace Multiplayer }; // Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities - auto ticket = AZStd::make_unique(netSpawnableAsset); + auto ticket = AZStd::make_unique(netSpawnable); AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs)); return ticket; } @@ -572,28 +532,6 @@ namespace Multiplayer { multiplayer->SendReadyForEntityUpdates(false); } - - m_netSpawnableTickets.clear(); - } - - void NetworkEntityManager::OnSpawned(AZ::Data::Asset spawnable, - const AZStd::vector& entities, const void* spawnTicket) - { - if (ShouldSpawnNetEntities()) - { - AZStd::unique_ptr ticket = RequestNetSpawnableInstantiation(spawnable, entities); - if (ticket) - { - AZ::Data::AssetId spawnableAssetId = spawnable.GetId(); - m_netSpawnableTickets[spawnTicket] = AZStd::move(ticket); - } - } - } - - void NetworkEntityManager::OnDespawned([[maybe_unused]] AZ::Data::Asset spawnable, - const void* spawnTicket) - { - m_netSpawnableTickets.erase(spawnTicket); } void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) @@ -611,12 +549,4 @@ namespace Multiplayer netEntity->GetName().c_str()); } } - - bool NetworkEntityManager::ShouldSpawnNetEntities() const - { - const auto agentType = GetMultiplayer()->GetAgentType(); - const bool shouldSpawnNetEntities = - (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); - return shouldSpawnNetEntities; - } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 14efa4feeb..50e6beedad 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -61,6 +60,9 @@ namespace Multiplayer const AZ::Transform& transform ) override; + AZStd::unique_ptr RequestNetSpawnableInstantiation( + const AZ::Data::Asset& netSpawnable, const AZ::Transform& transform) override; + void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override; uint32_t GetEntityCount() const override; @@ -93,15 +95,6 @@ namespace Multiplayer private: void RemoveEntities(); NetEntityId NextId(); - bool ShouldSpawnNetEntities() const; - void OnSpawned(AZ::Data::Asset spawnable, - const AZStd::vector& entities, const void* spawnTicket); - void OnDespawned(AZ::Data::Asset spawnable, const void* spawnTicket); - - // Note: This is an async function. - // The instantiated entities are not available immediately but will be constructed by the spawnable system - AZStd::unique_ptr RequestNetSpawnableInstantiation( - const AZ::Data::Asset& rootSpawnable, const AZStd::vector& entities); NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; @@ -130,10 +123,5 @@ namespace Multiplayer DeferredRpcMessages m_localDeferredRpcMessages; NetworkSpawnableLibrary m_networkPrefabLibrary; - - AZStd::unordered_map> m_netSpawnableTickets; - - AZ::Event, const AZStd::vector&, const void*>::Handler m_onSpawnedHandler; - AZ::Event, const void*>::Handler m_onDespawnedHandler; }; } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp index e83f6fea84..d33c3274c2 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include namespace Multiplayer { @@ -28,10 +30,32 @@ namespace Multiplayer void NetworkSpawnableHolderComponent::Activate() { + const auto agentType = GetMultiplayer()->GetAgentType(); + const bool shouldSpawnNetEntities = + (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); + + if(shouldSpawnNetEntities) + { + AZ::Transform rootEntityTransform = AZ::Transform::CreateIdentity(); + + AzFramework::TransformComponent* rootEntityTransformComponent = + GetEntity()->FindComponent(); + if (rootEntityTransformComponent) + { + rootEntityTransform = rootEntityTransformComponent->GetWorldTM(); + } + + INetworkEntityManager* networkEntityManager = GetNetworkEntityManager(); + AZ_Assert(networkEntityManager != nullptr, + "Network Entity Manager must be initialized before NetworkSpawnableHolderComponent is activated"); + + m_netSpawnableTicket = networkEntityManager->RequestNetSpawnableInstantiation(m_networkSpawnableAsset, rootEntityTransform); + } } void NetworkSpawnableHolderComponent::Deactivate() { + m_netSpawnableTicket.reset(); } void NetworkSpawnableHolderComponent::SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset) diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h index f32e8d31ab..3836212c48 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace Multiplayer { @@ -37,5 +38,6 @@ namespace Multiplayer private: AZ::Data::Asset m_networkSpawnableAsset{ AZ::Data::AssetLoadBehavior::PreLoad }; + AZStd::unique_ptr m_netSpawnableTicket; }; } // namespace Multiplayer From dc0bb00c10ad370d9c80db157c334a47f5d7f25e Mon Sep 17 00:00:00 2001 From: pereslav Date: Tue, 21 Sep 2021 14:58:33 +0100 Subject: [PATCH 7/7] Added an error check in case of a requested spawnable is not networked Signed-off-by: pereslav --- .../Code/Source/NetworkEntity/NetworkEntityManager.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index f4f744494b..a66b750266 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -477,6 +477,14 @@ namespace Multiplayer const AZ::Name netSpawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnable.GetId()); + if (netSpawnableName.IsEmpty()) + { + AZ_Error("NetworkEntityManager", false, + "RequestNetSpawnableInstantiation: Requested spawnable %s doesn't exist in the NetworkSpawnableLibrary. Please make sure it is a network spawnable", + netSpawnable.GetHint().c_str()); + return nullptr; + } + // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene optionalArgs.m_preInsertionCallback = [netSpawnableName, rootTransform = transform] (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities)