From e414cc151d4f941edfd4917b367eef22840f3bf8 Mon Sep 17 00:00:00 2001 From: pereslav Date: Wed, 5 May 2021 00:00:06 +0100 Subject: [PATCH] SPEC-6590 Implemented network entities instantiation by network spawnable asset ID --- .../Spawnable/SpawnableEntitiesInterface.h | 6 + .../Spawnable/SpawnableEntitiesManager.cpp | 21 +++ .../Spawnable/SpawnableEntitiesManager.h | 6 + .../NetworkEntity/NetworkEntityManager.cpp | 138 ++++++++++-------- .../NetworkEntity/NetworkEntityManager.h | 7 +- .../NetworkEntity/NetworkSpawnableLibrary.cpp | 1 - .../Pipeline/NetworkPrefabProcessor.cpp | 5 +- 7 files changed, 121 insertions(+), 63 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index ea19b3a0be..95d0b9e3a7 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -148,6 +148,12 @@ namespace AzFramework //! Blocks until all operations made on the provided ticket before the barrier call have completed. virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback) = 0; + //! Register a handler for OnSpawned events. + virtual void AddOnSpawnedHandler(AZ::Event>::Handler& handler) = 0; + + //! Register a handler for OnDespawned events. + virtual void AddOnDespawnedHandler(AZ::Event>::Handler& handler) = 0; + protected: [[nodiscard]] virtual void* 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 4e98616ef1..3ab004b516 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -114,6 +114,16 @@ namespace AzFramework } } + void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event>::Handler& handler) + { + handler.Connect(m_onSpawnedEvent); + } + + void SpawnableEntitiesManager::AddOnDespawnedHandler(AZ::Event>::Handler& handler) + { + handler.Connect(m_onDespawnedEvent); + } + auto SpawnableEntitiesManager::ProcessQueue() -> CommandQueueStatus { AZStd::queue pendingRequestQueue; @@ -223,6 +233,8 @@ namespace AzFramework ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end())); } + m_onSpawnedEvent.Signal(ticket.m_spawnable); + ticket.m_currentTicketId++; return true; } @@ -257,6 +269,8 @@ namespace AzFramework ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end())); } + m_onSpawnedEvent.Signal(ticket.m_spawnable); + ticket.m_currentTicketId++; return true; } @@ -289,6 +303,8 @@ namespace AzFramework request.m_completionCallback(*request.m_ticket); } + m_onDespawnedEvent.Signal(ticket.m_spawnable); + ticket.m_currentTicketId++; return true; } @@ -315,6 +331,8 @@ namespace AzFramework &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId()); } } + + m_onDespawnedEvent.Signal(ticket.m_spawnable); // Rebuild the list of entities. ticket.m_spawnedEntities.clear(); @@ -350,6 +368,9 @@ namespace AzFramework } ticket.m_currentTicketId++; + + m_onSpawnedEvent.Signal(ticket.m_spawnable); + return true; } else diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 29e9ed797b..c70b9ccaa6 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -60,6 +60,9 @@ namespace AzFramework void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback) override; + void AddOnSpawnedHandler(AZ::Event>::Handler& handler) override; + void AddOnDespawnedHandler(AZ::Event>::Handler& handler) override; + // // The following function is thread safe but intended to be run from the main thread. // @@ -156,5 +159,8 @@ namespace AzFramework AZStd::deque m_delayedQueue; //!< Requests that were processed before, but couldn't be completed. AZStd::queue m_pendingRequestQueue; AZStd::mutex m_pendingRequestQueueMutex; + + AZ::Event> m_onSpawnedEvent; + AZ::Event> m_onDespawnedEvent; }; } // namespace AzFramework diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index d1701bbb89..3764b92b25 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -34,9 +35,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) { this->OnSpawned(spawnable); }) + , m_onDespawnedHandler([this](AZ::Data::Asset spawnable) { this->OnDespawned(spawnable); }) { AZ::Interface::Register(this); AzFramework::RootSpawnableNotificationBus::Handler::BusConnect(); + + AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler); + AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler); } NetworkEntityManager::~NetworkEntityManager() @@ -365,58 +371,57 @@ namespace Multiplayer { INetworkEntityManager::EntityList returnList; - // TODO: Implement for non-root spawnables auto spawnableAssetId = m_networkPrefabLibrary.GetAssetIdByName(prefabEntryId.m_prefabName); - if (spawnableAssetId == m_rootSpawnableAsset.GetId()) + // Required for sync-instantiation. Todo: keep the reference in NetworkSpawnableLibrary + auto netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset(spawnableAssetId, AZ::Data::AssetLoadBehavior::PreLoad); + AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(netSpawnableAsset); + + AzFramework::Spawnable* netSpawnable = netSpawnableAsset.GetAs(); + if (!netSpawnable) { - AzFramework::Spawnable* netSpawnable = m_rootSpawnableAsset.GetAs(); - if (!netSpawnable) + return returnList; + } + + const uint32_t entityIndex = prefabEntryId.m_entityOffset; + + if (entityIndex == PrefabEntityId::AllIndices) + { + return CreateEntitiesImmediate(*netSpawnable, netEntityRole); + } + + const AzFramework::Spawnable::EntityList& entities = netSpawnable->GetEntities(); + size_t entitiesSize = entities.size(); + if (entityIndex >= entitiesSize) + { + return returnList; + } + + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); + + AZ::Entity* clone = serializeContext->CloneObject(entities[entityIndex].get()); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + clone->SetId(AZ::Entity::MakeId()); + + NetBindComponent* netBindComponent = clone->FindComponent(); + if (netBindComponent) + { + netBindComponent->PreInit(clone, prefabEntryId, netEntityId, netEntityRole); + + auto* transformComponent = clone->FindComponent(); + if (transformComponent) { - return returnList; + transformComponent->SetWorldTM(transform); } - const uint32_t entityIndex = prefabEntryId.m_entityOffset; - - if (entityIndex == PrefabEntityId::AllIndices) + if (autoActivate == AutoActivate::DoNotActivate) { - return CreateEntitiesImmediate(*netSpawnable, netEntityRole); + clone->SetRuntimeActiveByDefault(false); } - const AzFramework::Spawnable::EntityList& entities = netSpawnable->GetEntities(); - size_t entitiesSize = entities.size(); - if (entityIndex >= entitiesSize) - { - return returnList; - } + AzFramework::GameEntityContextRequestBus::Broadcast(&AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone); - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); - - AZ::Entity* clone = serializeContext->CloneObject(entities[entityIndex].get()); - AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - clone->SetId(AZ::Entity::MakeId()); - - NetBindComponent* netBindComponent = clone->FindComponent(); - if (netBindComponent) - { - netBindComponent->PreInit(clone, prefabEntryId, netEntityId, netEntityRole); - - auto* transformComponent = clone->FindComponent(); - if (transformComponent) - { - transformComponent->SetWorldTM(transform); - } - - if (autoActivate == AutoActivate::DoNotActivate) - { - clone->SetRuntimeActiveByDefault(false); - } - - AzFramework::GameEntityContextRequestBus::Broadcast( - &AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone); - - returnList.push_back(netBindComponent->GetEntityHandle()); - } + returnList.push_back(netBindComponent->GetEntityHandle()); } return returnList; @@ -429,13 +434,37 @@ namespace Multiplayer } void NetworkEntityManager::OnRootSpawnableAssigned( - AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) + [[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) { - AzFramework::Spawnable* rootSpawnableData = rootSpawnable.GetAs(); - const auto& entityList = rootSpawnableData->GetEntities(); + auto* multiplayer = AZ::Interface::Get(); + const auto agentType = multiplayer->GetAgentType(); + + if (agentType == MultiplayerAgentType::Client) + { + multiplayer->SendReadyForEntityUpdates(true); + } + } + + void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) + { + // TODO: Do we need to clear all entities here? + auto* multiplayer = AZ::Interface::Get(); + const auto agentType = multiplayer->GetAgentType(); + + if (agentType == MultiplayerAgentType::Client) + { + multiplayer->SendReadyForEntityUpdates(false); + } + } + + void NetworkEntityManager::OnSpawned(AZ::Data::Asset spawnable) + { + AzFramework::Spawnable* spawnableData = spawnable.GetAs(); + const auto& entityList = spawnableData->GetEntities(); if (entityList.size() == 0) { - AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Root spawnable doesn't have any entities."); + AZ_Error("NetworkEntityManager", false, "OnSpawned: Spawnable %s doesn't have any entities.", + spawnable.GetHint().c_str()); return; } @@ -443,7 +472,7 @@ namespace Multiplayer auto* spawnableHolder = rootEntity->FindComponent(); if (!spawnableHolder) { - AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Root entity doesn't have NetworkSpawnableHolderComponent."); + // Root entity doesn't have NetworkSpawnableHolderComponent. It means there's no corresponding network spawnable. return; } @@ -465,8 +494,6 @@ namespace Multiplayer return; } - m_rootSpawnableAsset = netSpawnableAsset; - auto* multiplayer = AZ::Interface::Get(); const auto agentType = multiplayer->GetAgentType(); @@ -477,17 +504,10 @@ namespace Multiplayer { CreateEntitiesImmediate(*netSpawnable, NetEntityRole::Authority); } - else - { - // If we don't spawn net entities immediately (i.e. it is a client), - // tell the server/host it can start sending updates that will instantiate entities. - multiplayer->SendReadyForEntityUpdates(true); - } } - void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) + void NetworkEntityManager::OnDespawned([[maybe_unused]]AZ::Data::Asset spawnable) { - // TODO: Do we need to clear all entities here? - m_rootSpawnableAsset.Release(); + // TODO: Remove entities instantiated from the spawnable } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 142730188b..4439503373 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -84,6 +84,9 @@ namespace Multiplayer NetEntityId NextId(); + void OnSpawned(AZ::Data::Asset spawnable); + void OnDespawned(AZ::Data::Asset spawnable); + NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; MultiplayerComponentRegistry m_multiplayerComponentRegistry; @@ -111,6 +114,8 @@ namespace Multiplayer DeferredRpcMessages m_localDeferredRpcMessages; NetworkSpawnableLibrary m_networkPrefabLibrary; - AZ::Data::Asset m_rootSpawnableAsset; + + AZ::Event>::Handler m_onSpawnedHandler; + AZ::Event>::Handler m_onDespawnedHandler; }; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp index ad2e18e222..ebf5d2609b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp @@ -46,7 +46,6 @@ namespace Multiplayer const AZ::Name name = AZ::Name(relativePath); m_spawnables[name] = id; m_spawnablesReverseLookup[id] = name; - } void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile) diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 2006272135..4962d16fb4 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -104,9 +104,10 @@ namespace Multiplayer networkedEntityIds.push_back(sourceEntity->GetId()); } } - if (!PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefab)) + + if (networkedEntityIds.empty()) { - AZ_Error("NetworkPrefabProcessor", false, "Saving exported Prefab Instance within a Prefab Dom failed."); + // No networked entities in the prefab, no need to do anything in this processor. return; }