diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 95d0b9e3a7..ac66288ff2 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -84,6 +84,7 @@ namespace AzFramework }; using EntitySpawnCallback = AZStd::function; + using EntityPreInsertionCallback = AZStd::function; using EntityDespawnCallback = AZStd::function; using ReloadSpawnableCallback = AZStd::function; using ListEntitiesCallback = AZStd::function; @@ -110,7 +111,8 @@ namespace AzFramework //! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from //! a different thread than the one that made the function call. The returned list of entities contains all the newly //! created entities. - virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback = {}) = 0; + virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, + EntitySpawnCallback completionCallback = {}) = 0; //! Spawn instances of some entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. //! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from. @@ -118,7 +120,7 @@ namespace AzFramework //! a different thread than the one that made this function call. The returned list of entities contains all the newly //! created entities. virtual void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, - EntitySpawnCallback completionCallback = {}) = 0; + EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) = 0; //! Removes all entities in the provided list from the environment. //! @param ticket The ticket previously used to spawn entities with. //! @param completionCallback Optional callback that's called when despawning entities has completed. This can be called from diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 3ab004b516..b18307ff7f 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -14,17 +14,20 @@ #include #include #include +#include #include #include #include namespace AzFramework { - void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback) + void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback, + EntitySpawnCallback completionCallback) { SpawnAllEntitiesCommand queueEntry; queueEntry.m_ticket = &ticket; queueEntry.m_completionCallback = AZStd::move(completionCallback); + queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback); { AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; @@ -32,13 +35,15 @@ namespace AzFramework } } - void SpawnableEntitiesManager::SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, - EntitySpawnCallback completionCallback) + void SpawnableEntitiesManager::SpawnEntities( + EntitySpawnTicket& ticket, AZStd::vector entityIndices, + EntityPreInsertionCallback preInsertionCallback, EntitySpawnCallback completionCallback) { SpawnEntitiesCommand queueEntry; queueEntry.m_ticket = &ticket; queueEntry.m_entityIndices = AZStd::move(entityIndices); queueEntry.m_completionCallback = AZStd::move(completionCallback); + queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback); { AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; @@ -205,6 +210,9 @@ namespace AzFramework AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); clone->SetId(AZ::Entity::MakeId()); + + // Need to inject a callback here + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, clone); return clone; } @@ -214,23 +222,79 @@ namespace AzFramework Ticket& ticket = GetTicketPayload(*request.m_ticket); if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId) { - size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size(); + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities(); - size_t entitiesSize = entities.size(); - ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize); - ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize); + // Keep track how many entities there were in the array initially + size_t spawnedEntitiesInitialCount = spawnedEntities.size(); - for(size_t i=0; iGetEntities(); + size_t entitiesToSpawnSize = entitiesToSpawn.size(); + + // Reserve buffers + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); + ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesToSpawnSize); + + // TEMP: To be replaced by IdUtils::Remapper + using EntityIdMap = AZStd::unordered_map; + EntityIdMap templateToCloneIdMap; + // \TEMP + + // Clone the entities from Spawnable + for (size_t i = 0; i < entitiesToSpawnSize; ++i) { - ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext)); - ticket.m_spawnedEntityIndices.push_back(i); + const AZ::Entity& entityTemplate = *entitiesToSpawn[i]; + + AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + clone->SetId(AZ::Entity::MakeId()); + + spawnedEntities.push_back(clone); + spawnedEntityIndices.push_back(i); + + // TEMP: To be replaced by IdUtils::Remapper + templateToCloneIdMap[entityTemplate.GetId()] = clone->GetId(); + + // Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here. + auto* transformComponent = clone->FindComponent(); + AZ::EntityId parentId = transformComponent->GetParentId(); + if (parentId.IsValid()) + { + auto it = templateToCloneIdMap.find(parentId); + if (it != templateToCloneIdMap.end()) + { + transformComponent->SetParentRelative(it->second); + } + else + { + AZ_Warning( + "SpawnableEntitiesManager", false, "Entity %s doesn't have the parent entity %s present in the spawnable", + clone->GetName().c_str(), parentId.ToString().data()); + } + } + // \TEMP } + // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. + if (request.m_preInsertionCallback) + { + request.m_preInsertionCallback(*request.m_ticket, SpawnableEntityContainerView( + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); + } + + // Add to the game context, now the entities are active + AZStd::for_each(ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end(), + [](AZ::Entity* entity) + { + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, entity); + }); + + // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. if (request.m_completionCallback) { request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end())); + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } m_onSpawnedEvent.Signal(ticket.m_spawnable); @@ -249,24 +313,56 @@ namespace AzFramework Ticket& ticket = GetTicketPayload(*request.m_ticket); if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId) { - size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size(); + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities(); - size_t entitiesSize = entities.size(); - ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize); - ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize); + // Keep track how many entities there were in the array initially + size_t spawnedEntitiesInitialCount = spawnedEntities.size(); + + // These are 'template' entities we'll be cloning from + const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); + size_t entitiesToSpawnSize = request.m_entityIndices.size(); + + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); + spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); for (size_t index : request.m_entityIndices) { - ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[index], serializeContext)); - ticket.m_spawnedEntityIndices.push_back(index); + if (index < entitiesToSpawn.size()) + { + const AZ::Entity& entityTemplate = *entitiesToSpawn[index]; + + AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + clone->SetId(AZ::Entity::MakeId()); + + spawnedEntities.push_back(clone); + spawnedEntityIndices.push_back(index); + + } } ticket.m_loadAll = false; + // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. + if (request.m_preInsertionCallback) + { + request.m_preInsertionCallback( + *request.m_ticket, + SpawnableEntityContainerView( + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); + } + + // Add to the game context, now the entities are active + AZStd::for_each(ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end(), + [](AZ::Entity* entity) + { + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, entity); + }); + if (request.m_completionCallback) { request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end())); + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } m_onSpawnedEvent.Signal(ticket.m_spawnable); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index c70b9ccaa6..54f48055fd 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -47,8 +47,8 @@ namespace AzFramework // The following functions are thread safe // - void SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback = {}) override; - void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, + void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override; + void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) override; @@ -90,6 +90,7 @@ namespace AzFramework struct SpawnAllEntitiesCommand { EntitySpawnCallback m_completionCallback; + EntityPreInsertionCallback m_preInsertionCallback; EntitySpawnTicket* m_ticket; uint32_t m_ticketId; }; @@ -97,6 +98,7 @@ namespace AzFramework { AZStd::vector m_entityIndices; EntitySpawnCallback m_completionCallback; + EntityPreInsertionCallback m_preInsertionCallback; EntitySpawnTicket* m_ticket; uint32_t m_ticketId; }; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/INetworkEntityManager.h index 17224e64cb..028c0310e1 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/INetworkEntityManager.h @@ -78,6 +78,12 @@ namespace Multiplayer 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 + //! @param netEntityRole the net role the entity should be setup for + virtual void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) = 0; + //! Returns an ConstEntityPtr for the provided entityId. //! @param netEntityId the netEntityId to get an ConstEntityPtr for //! @return the requested ConstEntityPtr diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkSpawnableLibrary.h b/Gems/Multiplayer/Code/Include/Multiplayer/INetworkSpawnableLibrary.h new file mode 100644 index 0000000000..422f7f4be3 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/INetworkSpawnableLibrary.h @@ -0,0 +1,34 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include + +namespace Multiplayer +{ + //! @class INetworkSpawnableLibrary + //! @brief The interface for managing network spawnables. + class INetworkSpawnableLibrary + { + public: + AZ_RTTI(INetworkSpawnableLibrary, "{A3CF809C-6C1D-4B43-B2C4-3901B5DE1ABE}"); + + virtual ~INetworkSpawnableLibrary() = default; + virtual void BuildSpawnablesList() = 0; + virtual void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) = 0; + virtual AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) = 0; + virtual AZ::Data::AssetId GetAssetIdByName(AZ::Name name) = 0; + }; +} diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 971353b0cb..fba5f477eb 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -34,18 +34,15 @@ 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() { AzFramework::RootSpawnableNotificationBus::Handler::BusDisconnect(); + AZ::Interface::Unregister(this); } void NetworkEntityManager::Initialize(HostId hostId, AZStd::unique_ptr entityDomain) @@ -365,7 +362,7 @@ namespace Multiplayer } PrefabEntityId prefabEntityId; - prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetPrefabNameFromAssetId(spawnable.GetId()); + prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetSpawnableNameFromAssetId(spawnable.GetId()); prefabEntityId.m_entityOffset = aznumeric_cast(i); const NetEntityId netEntityId = NextId(); @@ -493,57 +490,19 @@ namespace Multiplayer } } - void NetworkEntityManager::OnSpawned(AZ::Data::Asset spawnable) + void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) { - AzFramework::Spawnable* spawnableData = spawnable.GetAs(); - const auto& entityList = spawnableData->GetEntities(); - if (entityList.size() == 0) + auto* netBindComponent = netEntity->FindComponent(); + + if (netBindComponent) { - AZ_Error("NetworkEntityManager", false, "OnSpawned: Spawnable %s doesn't have any entities.", - spawnable.GetHint().c_str()); - return; + const NetEntityId netEntityId = NextId(); + netBindComponent->PreInit(netEntity, prefabEntityId, netEntityId, netEntityRole); } - - const auto& rootEntity = entityList[0]; - auto* spawnableHolder = rootEntity->FindComponent(); - if (!spawnableHolder) + else { - // Root entity doesn't have NetworkSpawnableHolderComponent. It means there's no corresponding network spawnable. - return; + AZ_Error("NetworkEntityManager", false, "SetupNetEntity called for an entity with no NetBindComponent. Entity: %s", + netEntity->GetName().c_str()); } - - AZ::Data::Asset netSpawnableAsset = spawnableHolder->GetNetworkSpawnableAsset(); - AzFramework::Spawnable* netSpawnable = netSpawnableAsset.GetAs(); - if (!netSpawnable) - { - // TODO: Temp sync load until JsonSerialization of loadBehavior is fixed. - netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset( - netSpawnableAsset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad); - AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(netSpawnableAsset); - - netSpawnable = netSpawnableAsset.GetAs(); - } - - if (!netSpawnable) - { - AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Net spawnable doesn't have any data."); - return; - } - - auto* multiplayer = GetMultiplayer(); - - const auto agentType = multiplayer->GetAgentType(); - const bool spawnImmediately = - (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); - - if (spawnImmediately) - { - CreateEntitiesImmediate(*netSpawnable, NetEntityRole::Authority); - } - } - - void NetworkEntityManager::OnDespawned([[maybe_unused]]AZ::Data::Asset spawnable) - { - // 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 e763e7ebca..482f2767e1 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -62,6 +62,8 @@ namespace Multiplayer const AZ::Transform& transform ) override; + void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override; + uint32_t GetEntityCount() const override; NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) override; void MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) override; @@ -93,9 +95,6 @@ namespace Multiplayer void RemoveEntities(); NetEntityId NextId(); - void OnSpawned(AZ::Data::Asset spawnable); - void OnDespawned(AZ::Data::Asset spawnable); - NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; MultiplayerComponentRegistry m_multiplayerComponentRegistry; @@ -123,8 +122,5 @@ namespace Multiplayer DeferredRpcMessages m_localDeferredRpcMessages; NetworkSpawnableLibrary m_networkPrefabLibrary; - - 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 ebf5d2609b..935744f807 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.cpp @@ -14,20 +14,23 @@ #include #include #include +#include namespace Multiplayer { NetworkSpawnableLibrary::NetworkSpawnableLibrary() { + AZ::Interface::Register(this); AzFramework::AssetCatalogEventBus::Handler::BusConnect(); } NetworkSpawnableLibrary::~NetworkSpawnableLibrary() { AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); + AZ::Interface::Unregister(this); } - void NetworkSpawnableLibrary::BuildPrefabsList() + void NetworkSpawnableLibrary::BuildSpawnablesList() { auto enumerateCallback = [this](const AZ::Data::AssetId id, const AZ::Data::AssetInfo& info) { @@ -50,10 +53,10 @@ namespace Multiplayer void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile) { - BuildPrefabsList(); + BuildSpawnablesList(); } - AZ::Name NetworkSpawnableLibrary::GetPrefabNameFromAssetId(AZ::Data::AssetId assetId) + AZ::Name NetworkSpawnableLibrary::GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) { if (assetId.IsValid()) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.h index a2c3d4ae56..6ebe2a7418 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkSpawnableLibrary.h @@ -12,30 +12,31 @@ #pragma once -#include -#include +#include #include -#include namespace Multiplayer { /// Implementation of the network prefab library interface. class NetworkSpawnableLibrary final - : private AzFramework::AssetCatalogEventBus::Handler + : public INetworkSpawnableLibrary + , private AzFramework::AssetCatalogEventBus::Handler { public: + AZ_RTTI(NetworkSpawnableLibrary, "{65E15F33-E893-49C2-A8E2-B6A8A6EF31E0}", INetworkSpawnableLibrary); + NetworkSpawnableLibrary(); ~NetworkSpawnableLibrary(); - void BuildPrefabsList(); - void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id); + /// INetworkSpawnableLibrary overrides. + void BuildSpawnablesList() override; + void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) override; + AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) override; + AZ::Data::AssetId GetAssetIdByName(AZ::Name name) override; /// AssetCatalogEventBus overrides. void OnCatalogLoaded(const char* catalogFile) override; - AZ::Name GetPrefabNameFromAssetId(AZ::Data::AssetId assetId); - AZ::Data::AssetId GetAssetIdByName(AZ::Name name); - private: AZStd::unordered_map m_spawnables; AZStd::unordered_map m_spawnablesReverseLookup; diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp index 84983b700e..1696e851a5 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp @@ -11,7 +11,12 @@ */ #include +#include #include +#include +#include +#include +#include namespace Multiplayer { @@ -21,15 +26,91 @@ namespace Multiplayer if (serializeContext) { serializeContext->Class() - ->Version(1); + ->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&, 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 = 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::SpawnableEntitiesInterface::Get()->SpawnEntities(m_netSpawnTicket, {m_netEntityIndex}, preInsertionCallback); + } } 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 index ebafead73c..5ab42ab3aa 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h @@ -13,6 +13,9 @@ #pragma once #include +#include +#include +#include namespace Multiplayer { @@ -34,6 +37,15 @@ namespace Multiplayer 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 0eed82b6ab..6c61e71ff7 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -114,29 +114,31 @@ namespace Multiplayer AZStd::unique_ptr networkInstance(aznew Instance()); - for (auto entityId : networkedEntityIds) + AZ::Data::Asset networkSpawnableAsset; + networkSpawnableAsset.Create(networkSpawnable->GetId()); + networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); + + for (size_t entityIndex = 0; entityIndex < networkedEntityIds.size(); ++entityIndex) { + AZ::EntityId entityId = networkedEntityIds[entityIndex]; AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release(); networkInstance->AddEntity(*netEntity); AZ::Entity* breadcrumbEntity = aznew AZ::Entity(netEntity->GetName()); breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault()); - breadcrumbEntity->CreateComponent(); + NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent(); + // Each spawnable has a root meta-data entity at position 0, so starting net indices from 1 + netBindMarkerComponent->SetNetEntityIndex(entityIndex + 1); + netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset); AzFramework::TransformComponent* transformComponent = netEntity->FindComponent(); breadcrumbEntity->CreateComponent(*transformComponent); - // TODO: Configure NetBindMarkerComponent to refer to the net entity sourceInstance->AddEntity(*breadcrumbEntity); } // Add net spawnable asset holder { - AZ::Data::AssetId assetId = networkSpawnable->GetId(); - AZ::Data::Asset networkSpawnableAsset; - networkSpawnableAsset.Create(assetId); - networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); - EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity(); if (containerEntityRef.has_value()) { diff --git a/Gems/Multiplayer/Code/Tests/MainTools.cpp b/Gems/Multiplayer/Code/Tests/MainTools.cpp index 65a1d921a9..56ad963dd9 100644 --- a/Gems/Multiplayer/Code/Tests/MainTools.cpp +++ b/Gems/Multiplayer/Code/Tests/MainTools.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp index df1da11725..dfc74a230c 100644 --- a/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp +++ b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include namespace UnitTest diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 26909cbfd3..4bb1b769cc 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -15,6 +15,7 @@ set(FILES Include/Multiplayer/IMultiplayer.h Include/Multiplayer/IMultiplayerComponentInput.h Include/Multiplayer/INetworkEntityManager.h + Include/Multiplayer/INetworkSpawnableLibrary.h Include/Multiplayer/INetworkPlayerSpawner.h Include/Multiplayer/INetworkTime.h Include/Multiplayer/IReplicationWindow.h