From a05d5f5d6dbc18ed85654b853fe9b815ebd70b8e Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Mon, 25 Oct 2021 14:35:49 -0700 Subject: [PATCH] Extended the Spawnable Entities Interface to allow entity aliases to be updated. Entity aliases can now be updated as a reaction to the spawnable being loaded or at any other time afterwards through the Spawnable Entities Interface. Currently these changes are applied to the spawnable that owns the entity aliases, but once the Spawnable Entities Interface makes use of AzFramework::Scene a copy of the entity aliases should be stored in the scene and be updated instead of the spawnable. This change also adds support for a load barrier, which acts the same as a regular barrier but also accounts for the spawnable being loaded and won't trigger the callback until has completed. The return values in from the processing functions in the Spawnable Entities Manager now have a clearer return value to indicate whether a request has completed or is being re-queued. Signed-off-by: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> --- .../AzFramework/Spawnable/Spawnable.cpp | 9 +- .../Spawnable/SpawnableEntitiesContainer.cpp | 24 +- .../Spawnable/SpawnableEntitiesContainer.h | 16 +- .../Spawnable/SpawnableEntitiesInterface.cpp | 8 +- .../Spawnable/SpawnableEntitiesInterface.h | 64 +- .../Spawnable/SpawnableEntitiesManager.cpp | 602 +++++++++++++----- .../Spawnable/SpawnableEntitiesManager.h | 90 ++- 7 files changed, 595 insertions(+), 218 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp index 2be76c28a6..92728a4575 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp @@ -46,8 +46,13 @@ namespace AzFramework AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); for (const EntityAlias& alias : *aliases) { - if ((alias.m_aliasType != Spawnable::EntityAliasType::Original && alias.m_aliasType != Spawnable::EntityAliasType::Disabled) && - !alias.m_spawnable.IsReady()) + if (!alias.m_queueLoad || + alias.m_aliasType == Spawnable::EntityAliasType::Original || + alias.m_aliasType == Spawnable::EntityAliasType::Disabled) + { + continue; + } + if (!alias.m_spawnable.IsReady() && !alias.m_spawnable.IsError()) { return false; } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp index b98ea275e4..912bf05058 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp @@ -26,7 +26,7 @@ namespace AzFramework return m_threadData != nullptr; } - uint64_t SpawnableEntitiesContainer::GetCurrentGeneration() const + uint32_t SpawnableEntitiesContainer::GetCurrentGeneration() const { return m_currentGeneration; } @@ -37,7 +37,7 @@ namespace AzFramework SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket); } - void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector entityIndices) + void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector entityIndices) { AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set."); SpawnableEntitiesInterface::Get()->SpawnEntities( @@ -78,15 +78,21 @@ namespace AzFramework } } - void SpawnableEntitiesContainer::Alert(AlertCallback callback) + void SpawnableEntitiesContainer::Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck) { AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set."); - SpawnableEntitiesInterface::Get()->Barrier( - m_threadData->m_spawnedEntitiesTicket, - [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id) - { - callback(generation); - }); + auto callbackWrapper = [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id) + { + callback(generation); + }; + if (spawnableCheck == CheckIfSpawnableIsLoaded::No) + { + SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper)); + } + else + { + SpawnableEntitiesInterface::Get()->LoadBarrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper)); + } } void SpawnableEntitiesContainer::Connect(AZ::Data::Asset spawnable) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.h index 6fa295e18c..1ec6e6a665 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.h @@ -36,6 +36,12 @@ namespace AzFramework public: using AlertCallback = AZStd::function; + enum class CheckIfSpawnableIsLoaded : bool + { + Yes, + No + }; + //! Constructs a new spawnables entity container that has not been connected. SpawnableEntitiesContainer() = default; //! Constructs a new spawnables entity container that connects to the provided spawnable. @@ -48,13 +54,13 @@ namespace AzFramework //! Returns a number that identifies the current generation of the container with. The completion callback can still receive //! calls from older generations as processing completes on those. The returned value can be used to help calls tell //! older versions apart from newer ones. - [[nodiscard]] uint64_t GetCurrentGeneration() const; + [[nodiscard]] uint32_t GetCurrentGeneration() const; //! Puts in a request to spawn entities using all entities in the provided spawnable as a template. void SpawnAllEntities(); //! Puts in a request to spawn entities using the entities found in the spawnable at the provided indices as a template. //! @param entityIndices A list of indices to the entities in the spawnable. - void SpawnEntities(AZStd::vector entityIndices); + void SpawnEntities(AZStd::vector entityIndices); //! Puts in a request to despawn all previous spawned entities. void DespawnAllEntities(); @@ -73,7 +79,11 @@ namespace AzFramework //! other than the calling thread including the main thread. Note that because the alert is queued it can still be called //! after the container has been deleted or can be called for a previously assigned spawnable. In the latter case check //! if the current generation matches the generation provided with the callback. - void Alert(AlertCallback callback); + //! @callback The function called when the alert triggers. This can be called from a different thread than the one that + //! the one that made the call to Alert. + //! @checkSpawnableIsLoaded If true the alert will also block until the spawnable has been loaded. If false then it will + //! be called after all previous calls have completed, but the spawnable may not be loaded at that point. + void Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck = CheckIfSpawnableIsLoaded::No); private: void Connect(AZ::Data::Asset spawnable); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp index 171d626b27..37091d8f0f 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp @@ -152,7 +152,7 @@ namespace AzFramework // SpawnableIndexEntityPair // - SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator) + SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator) : m_entity(entityIterator) , m_index(indexIterator) { @@ -168,7 +168,7 @@ namespace AzFramework return *m_entity; } - size_t SpawnableIndexEntityPair::GetIndex() const + uint32_t SpawnableIndexEntityPair::GetIndex() const { return *m_index; } @@ -177,7 +177,7 @@ namespace AzFramework // SpawnableIndexEntityIterator // - SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator) + SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator) : m_value(entityIterator, indexIterator) { } @@ -248,7 +248,7 @@ namespace AzFramework // SpawnableConstIndexEntityContainerView::SpawnableConstIndexEntityContainerView( - AZ::Entity** beginEntity, size_t* beginIndices, size_t length) + AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length) : m_begin(beginEntity, beginIndices) , m_end(beginEntity + length, beginIndices + length) { diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 74a17020df..dc9c7b4538 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -85,19 +85,19 @@ namespace AzFramework AZ::Entity* GetEntity(); const AZ::Entity* GetEntity() const; - size_t GetIndex() const; + uint32_t GetIndex() const; private: SpawnableIndexEntityPair() = default; SpawnableIndexEntityPair(const SpawnableIndexEntityPair&) = default; SpawnableIndexEntityPair(SpawnableIndexEntityPair&&) = default; - SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator); + SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator); SpawnableIndexEntityPair& operator=(const SpawnableIndexEntityPair&) = default; SpawnableIndexEntityPair& operator=(SpawnableIndexEntityPair&&) = default; AZ::Entity** m_entity { nullptr }; - size_t* m_index { nullptr }; + uint32_t* m_index { nullptr }; }; class SpawnableIndexEntityIterator @@ -110,7 +110,7 @@ namespace AzFramework using pointer = SpawnableIndexEntityPair*; using reference = SpawnableIndexEntityPair&; - SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator); + SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator); SpawnableIndexEntityIterator& operator++(); SpawnableIndexEntityIterator operator++(int); @@ -132,7 +132,7 @@ namespace AzFramework class SpawnableConstIndexEntityContainerView { public: - SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, size_t* beginIndices, size_t length); + SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length); const SpawnableIndexEntityIterator& begin(); const SpawnableIndexEntityIterator& end(); @@ -144,6 +144,16 @@ namespace AzFramework SpawnableIndexEntityIterator m_end; }; + //! Information used when updating the type of an entity alias. + struct EntityAliasTypeChange + { + //! The index of the alias in the spawnable. Note that due to optimizations done on the entity aliases the index of an alias + //! can change over time. + uint32_t m_aliasIndex; + //! The type to replace type stored in the spawnable at the index provided by m_aliasIndex. + Spawnable::EntityAliasType m_newAliasType; + }; + //! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that is used as a template. A ticket can //! be reused for multiple calls on the same spawnable and is safe to be used by multiple threads at the same time. Entities created //! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created @@ -178,6 +188,7 @@ namespace AzFramework using EntityDespawnCallback = AZStd::function; using RetrieveEntitySpawnTicketCallback = AZStd::function; using ReloadSpawnableCallback = AZStd::function; + using UpdateEntityAliasTypesCallback = AZStd::function; using ListEntitiesCallback = AZStd::function; using ListIndicesEntitiesCallback = AZStd::function; using ClaimEntitiesCallback = AZStd::function; @@ -247,6 +258,15 @@ namespace AzFramework SpawnablePriority m_priority { SpawnablePriority_Default }; }; + struct UpdateEntityAliasTypesOptionalArgs final + { + //! Callback that's called when entity aliases are updated. This can be triggered from a different thread than the one that + //! made the function call to update. + UpdateEntityAliasTypesCallback m_completionCallback; + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + }; + struct ListEntitiesOptionalArgs final { //! The priority at which this call will be executed. @@ -265,6 +285,14 @@ namespace AzFramework SpawnablePriority m_priority{ SpawnablePriority_Default }; }; + struct LoadBarrierOptionalArgs final + { + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + //! Also checks if the spawnables referenced in the entity aliases that are marked to be loaded are loaded. + bool m_checkAliasSpawnables{ true }; + }; + //! Interface definition to (de)spawn entities from a spawnable into the game world. //! //! While the callbacks of the individual calls are being processed they will block processing any other request. Callbacks can be @@ -298,7 +326,7 @@ namespace AzFramework //! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from. //! @param optionalArgs Optional additional arguments, see SpawnEntitiesOptionalArgs. virtual void SpawnEntities( - EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; + EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; //! Removes all entities in the provided list from the environment. //! @param ticket The ticket previously used to spawn entities with. //! @param optionalArgs Optional additional arguments, see DespawnAllEntitiesOptionalArgs. @@ -320,6 +348,16 @@ namespace AzFramework virtual void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) = 0; + //! Allows updating the entity alias on a spawnable. This allows the spawning behavior for all entities spawned from the used + //! spawnable to be changed and is not restricted to this ticket alone. + //! @param ticket Holds the information for the spawnable. + //! @param updateAliases An array of index and alias type values used to update the entity alias list. + //! @param optionalArgs Optional additional arguments, see UpdateEntityAliasTypesOptionalArgs. + virtual void UpdateEntityAliasTypes( + EntitySpawnTicket& ticket, + AZStd::vector updatedAliases, + UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) = 0; + //! List all entities that are spawned using this ticket. //! @param ticket Only the entities associated with this ticket will be listed. //! @param listCallback Required callback that will be called to list the entities on. @@ -351,31 +389,37 @@ namespace AzFramework //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. //! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs. virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0; + //! Blocks until the spawnable is loaded and all operations made on the provided ticket before the barrier call have completed. + //! @param ticket The ticket to monitor. + //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. + //! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs. + virtual void LoadBarrier( + EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) = 0; protected: [[nodiscard]] virtual AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) = 0; virtual void DestroyTicket(void* ticket) = 0; template - static T& GetTicketPayload(EntitySpawnTicket& ticket) + [[nodiscard]] static T& GetTicketPayload(EntitySpawnTicket& ticket) { return *reinterpret_cast(ticket.m_payload); } template - static const T& GetTicketPayload(const EntitySpawnTicket& ticket) + [[nodiscard]] static const T& GetTicketPayload(const EntitySpawnTicket& ticket) { return *reinterpret_cast(ticket.m_payload); } template - static T* GetTicketPayload(EntitySpawnTicket* ticket) + [[nodiscard]] static T* GetTicketPayload(EntitySpawnTicket* ticket) { return reinterpret_cast(ticket->m_payload); } template - static const T* GetTicketPayload(const EntitySpawnTicket* ticket) + [[nodiscard]] static const T* GetTicketPayload(const EntitySpawnTicket* ticket) { return reinterpret_cast(ticket->m_payload); } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index ef7351aabb..0ac80c3ed6 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -60,7 +60,7 @@ namespace AzFramework } void SpawnableEntitiesManager::SpawnEntities( - EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs) + EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized."); @@ -128,6 +128,20 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } + void SpawnableEntitiesManager::UpdateEntityAliasTypes( + EntitySpawnTicket& ticket, + AZStd::vector updatedAliases, + UpdateEntityAliasTypesOptionalArgs optionalArgs) + { + AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized."); + + UpdateEntityAliasTypesCommand queueEntry; + queueEntry.m_entityAliases = AZStd::move(updatedAliases); + queueEntry.m_ticketId = ticket.GetId(); + queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + } + void SpawnableEntitiesManager::ListEntities( EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs) { @@ -175,6 +189,19 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } + void SpawnableEntitiesManager::LoadBarrier( + EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs) + { + AZ_Assert(completionCallback, "Load barrier on spawnable entities called without a valid callback to use."); + AZ_Assert(ticket.IsValid(), "Ticket provided to LoadBarrier hasn't been initialized."); + + LoadBarrierCommand queueEntry; + queueEntry.m_ticketId = ticket.GetId(); + queueEntry.m_completionCallback = AZStd::move(completionCallback); + queueEntry.m_checkAliasSpawnables = optionalArgs.m_checkAliasSpawnables; + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + } + auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus { CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft; @@ -203,13 +230,13 @@ namespace AzFramework for (size_t i = 0; i < delayedSize; ++i) { Requests& request = queue.m_delayed.front(); - bool result = AZStd::visit( - [this](auto&& args) -> bool + CommandResult result = AZStd::visit( + [this](auto&& args) -> CommandResult { return ProcessRequest(args); }, request); - if (!result) + if (result == CommandResult::Requeue) { queue.m_delayed.emplace_back(AZStd::move(request)); } @@ -230,13 +257,13 @@ namespace AzFramework while (!pendingRequestQueue.empty()) { Requests& request = pendingRequestQueue.front(); - bool result = AZStd::visit( - [this](auto&& args) -> bool + CommandResult result = AZStd::visit( + [this](auto&& args) -> CommandResult { return ProcessRequest(args); }, request); - if (!result) + if (result == CommandResult::Requeue) { queue.m_delayed.emplace_back(AZStd::move(request)); } @@ -276,11 +303,81 @@ namespace AzFramework AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate, EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext) { - // If the same ID gets remapped more than once, preserve the original remapping instead of overwriting it. + if (!entityTemplate.GetComponents().empty()) + { + // If the same ID gets remapped more than once, preserve the original remapping instead of overwriting it. + constexpr bool allowDuplicateIds = false; + + return AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( + &entityTemplate, templateToCloneMap, &serializeContext); + } + else + { + return nullptr; + } + } + + AZ::Entity* SpawnableEntitiesManager::CloneSingleAliasedEntity( + const AZ::Entity& entityTemplate, + const Spawnable::EntityAlias& alias, + EntityIdMap& templateToCloneMap, + AZ::Entity* previouslySpawnedEntity, + AZ::SerializeContext& serializeContext) + { + using ResultType = AZStd::pair; + + AZ::Entity* clone = nullptr; + switch (alias.m_aliasType) + { + case Spawnable::EntityAliasType::Original: + // Behave as the original version. + clone = CloneSingleEntity(entityTemplate, templateToCloneMap, serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + return clone; + case Spawnable::EntityAliasType::Disabled: + // Do nothing. + return nullptr; + case Spawnable::EntityAliasType::Replace: + clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), templateToCloneMap, serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + return clone; + case Spawnable::EntityAliasType::Additional: + // The asset handler will have sorted and inserted a Spawnable::EntityAliasType::Original, so the just + // spawn the additional entity. + clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), templateToCloneMap, serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + return clone; + case Spawnable::EntityAliasType::Merge: + AZ_Assert(previouslySpawnedEntity != nullptr, "Merging components but there's no entity to add to yet."); + AZ_Assert( + previouslySpawnedEntity->GetId() == alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetId(), + "Entity ids for merging spawnables don't match."); + AppendComponents( + *previouslySpawnedEntity, alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetComponents(), templateToCloneMap, serializeContext); + return nullptr; + default: + AZ_Assert(false, "Unsupported spawnable entity alias type: %i", alias.m_aliasType); + return nullptr; + } + } + + void SpawnableEntitiesManager::AppendComponents( + AZ::Entity& target, + const AZ::Entity::ComponentArrayType& componentTemplates, + EntityIdMap& templateToCloneMap, + AZ::SerializeContext& serializeContext) + { + // Only components are added and entities are looked up so no duplicate entity ids should be encountered. constexpr bool allowDuplicateIds = false; - return AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( - &entityTemplate, templateToCloneMap, &serializeContext); + for (const AZ::Component* component : componentTemplates) + { + AZ::Component* clone = AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( + component, templateToCloneMap, &serializeContext); + AZ_Assert(clone, "Unable to clone component for entity '%s' (%zu).", target.GetName().c_str(), target.GetId()); + [[maybe_unused]] bool result = target.AddComponent(clone); + AZ_Assert(result, "Unable to add cloned component to entity '%s' (%zu).", target.GetName().c_str(), target.GetId()); + } } void SpawnableEntitiesManager::InitializeEntityIdMappings( @@ -316,161 +413,276 @@ namespace AzFramework } } - - bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) { - AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; - AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - - // 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 = entitiesToSpawn.size(); - - // Reserve buffers - spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); - spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); - - // Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below, - // any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly. - // We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference - // in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless - // of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to - // previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call. - InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); - - for (size_t i = 0; i < entitiesToSpawnSize; ++i) + if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst(); + aliases.HasLock() && aliases.AreAllSpawnablesReady()) { - // If this entity has previously been spawned, give it a new id in the reference map - RefreshEntityIdMapping(entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - AZ::Entity* clone = CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext); - AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + // Keep track how many entities there were in the array initially + size_t spawnedEntitiesInitialCount = spawnedEntities.size(); - spawnedEntities.emplace_back(clone); - spawnedEntityIndices.push_back(i); - } + // These are 'template' entities we'll be cloning from + const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); + uint32_t entitiesToSpawnSize = aznumeric_caster(entitiesToSpawn.size()); - // loadAll is true if every entity has been spawned only once - ticket.m_loadAll = (spawnedEntities.size() == entitiesToSpawnSize); - - // 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_ticketId, SpawnableEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); - } + // Reserve buffers + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); + spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); - // Add to the game context, now the entities are active - for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) - { - (*it)->SetSpawnTicketId(request.m_ticketId); - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - } - - // 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_ticketId, SpawnableConstEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); - } - - ticket.m_currentRequestId++; - return true; - } - else - { - return false; - } - } - - bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request) - { - Ticket& ticket = *request.m_ticket; - if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) - { - AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; - AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - AZ_Assert( - spawnedEntities.size() == spawnedEntityIndices.size(), - "The indices for the spawned entities has gone out of sync with the entities."); - - // Keep track of 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(); - - if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities) - { - // This map keeps track of ids from template (spawnable) to clone (instance) allowing patch ups of fields referring - // to entityIds outside of a given entity. - // We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below, + // Pre-generate the full set of entity-id-to-new-entity-id mappings, so that during the clone operation below, // any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly. - // By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so - // that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities - // (or SpawnAllEntities) call. - // However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false". + // We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference + // in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless + // of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to + // previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call. InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); - } - spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); - spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); - - for (size_t index : request.m_entityIndices) - { - if (index < entitiesToSpawn.size()) + auto aliasIt = aliases.begin(); + auto aliasEnd = aliases.end(); + if (aliasIt == aliasEnd) { - // If this entity has previously been spawned, give it a new id in the reference map - RefreshEntityIdMapping( - entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + for (uint32_t i = 0; i < entitiesToSpawnSize; ++i) + { + // If this entity has previously been spawned, give it a new id in the reference map + RefreshEntityIdMapping( + entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); - AZ::Entity* clone = - CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext); - AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - - spawnedEntities.push_back(clone); - spawnedEntityIndices.push_back(index); + spawnedEntities.emplace_back( + CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext)); + spawnedEntityIndices.push_back(i); + } } - } - ticket.m_loadAll = false; + else + { + for (uint32_t i = 0; i < entitiesToSpawnSize; ++i) + { + // If this entity has previously been spawned, give it a new id in the reference map + RefreshEntityIdMapping( + entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); - // 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_ticketId, SpawnableEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); - } + if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != i) + { + AZ::Entity* clone = + CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext); + spawnedEntities.emplace_back(clone); + spawnedEntityIndices.push_back(i); + } + else + { + // The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so can + // be safely executed in order without risking an invalid state. + AZ::Entity* previousEntity = nullptr; + do + { + AZ::Entity* clone = CloneSingleAliasedEntity( + *entitiesToSpawn[i], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity, + *request.m_serializeContext); + // Not all alias operations create a new instance. It's also possible for an empty entity to be left behind, + // in which case it's also filtered out as the entity component framework doesn't handle these gracefully. + if (clone) + { + if (!clone->GetComponents().empty()) + { + previousEntity = clone; + spawnedEntities.emplace_back(clone); + spawnedEntityIndices.push_back(i); + } + else + { + delete clone; + } + } + ++aliasIt; + } while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == i); + } + } + } - // Add to the game context, now the entities are active - for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) - { + // There were no initial entities then the ticket now holds exactly all entities. If there were already entities then + // a new set are not added so it no longer holds exactly the number of entities. + ticket.m_loadAll = spawnedEntitiesInitialCount == 0; + + auto newEntitiesBegin = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; + auto newEntitiesEnd = ticket.m_spawnedEntities.end(); + // 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_ticketId, SpawnableEntityContainerView(newEntitiesBegin, newEntitiesEnd)); + } + + // Add to the game context, now the entities are active + for (auto it = newEntitiesBegin; it != newEntitiesEnd; ++it) + { (*it)->SetSpawnTicketId(request.m_ticketId); - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - } + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + } - if (request.m_completionCallback) - { - request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); - } + // 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_ticketId, SpawnableConstEntityContainerView(newEntitiesBegin, newEntitiesEnd)); + } - ticket.m_currentRequestId++; - return true; - } - else - { - return false; + ticket.m_currentRequestId++; + return CommandResult::Executed; + } } + return CommandResult::Requeue; } - bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request) -> CommandResult + { + Ticket& ticket = *request.m_ticket; + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) + { + if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst(); + aliases.HasLock() && aliases.AreAllSpawnablesReady()) + { + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; + AZ_Assert( + spawnedEntities.size() == spawnedEntityIndices.size(), + "The indices for the spawned entities has gone out of sync with the entities."); + + // Keep track of 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(); + + if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities) + { + // This map keeps track of ids from template (spawnable) to clone (instance) allowing patch ups of fields referring + // to entityIds outside of a given entity. + // We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below, + // any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly. + // By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so + // that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities + // (or SpawnAllEntities) call. + // However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false". + InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + } + + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); + spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); + + auto aliasBegin = aliases.begin(); + auto aliasEnd = aliases.end(); + if (aliasBegin == aliasEnd) + { + for (uint32_t index : request.m_entityIndices) + { + if (index < entitiesToSpawn.size()) + { + // If this entity has previously been spawned, give it a new id in the reference map + RefreshEntityIdMapping( + entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + + AZ::Entity* clone = + CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + spawnedEntities.push_back(clone); + spawnedEntityIndices.push_back(index); + } + } + } + else + { + for (uint32_t index : request.m_entityIndices) + { + if (index < entitiesToSpawn.size()) + { + // If this entity has previously been spawned, give it a new id in the reference map + RefreshEntityIdMapping( + entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + + auto aliasIt = AZStd::lower_bound( + aliasBegin, aliasEnd, index, + [](const Spawnable::EntityAlias& lhs, uint32_t rhs) + { + return lhs.m_sourceIndex < rhs; + }); + + if (aliasIt == aliasEnd) + { + AZ::Entity* clone = + CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext); + spawnedEntities.emplace_back(clone); + spawnedEntityIndices.push_back(index); + } + else + { + // The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so + // can be safely executed in order without risking an invalid state. + AZ::Entity* previousEntity = nullptr; + do + { + AZ::Entity* clone = CloneSingleAliasedEntity( + *entitiesToSpawn[index], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity, + *request.m_serializeContext); + // Not all alias operations create a new instance. It's also possible for an empty entity to be left + // behind, in which case it's also filtered out as the entity component framework doesn't handle these + // gracefully. + if (clone) + { + if (!clone->GetComponents().empty()) + { + previousEntity = clone; + spawnedEntities.emplace_back(clone); + spawnedEntityIndices.push_back(index); + } + else + { + delete clone; + } + } + ++aliasIt; + } while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == 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_ticketId, + SpawnableEntityContainerView( + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); + } + + // Add to the game context, now the entities are active + for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) + { + (*it)->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + } + + if (request.m_completionCallback) + { + request.m_completionCallback( + request.m_ticketId, + SpawnableConstEntityContainerView( + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); + } + + ticket.m_currentRequestId++; + return CommandResult::Executed; + } + } + return CommandResult::Requeue; + } + + auto SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -495,15 +707,15 @@ namespace AzFramework } ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -529,15 +741,15 @@ namespace AzFramework } ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(), @@ -574,7 +786,7 @@ namespace AzFramework ticket.m_spawnedEntityIndices.clear(); size_t entitiesToSpawnSize = entities.size(); - for (size_t i = 0; i < entitiesToSpawnSize; ++i) + for (uint32_t i = 0; i < entitiesToSpawnSize; ++i) { // If this entity has previously been spawned, give it a new id in the reference map RefreshEntityIdMapping(entities[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); @@ -590,7 +802,7 @@ namespace AzFramework { size_t entitiesSize = entities.size(); - for (size_t index : ticket.m_spawnedEntityIndices) + for (uint32_t index : ticket.m_spawnedEntityIndices) { // It's possible for the new spawnable to have a different number of entities, so guard against this. // It's also possible that the entities have moved within the spawnable to a new index. This can't be @@ -616,15 +828,47 @@ namespace AzFramework ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(UpdateEntityAliasTypesCommand& request) -> CommandResult + { + Ticket& ticket = *request.m_ticket; + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) + { + if (Spawnable::EntityAliasVisitor aliases = ticket.m_spawnable->TryGetAliases(); aliases.HasLock()) + { + for (EntityAliasTypeChange& replacement : request.m_entityAliases) + { + aliases.UpdateAliasType(replacement.m_aliasIndex, replacement.m_newAliasType); + } + aliases.Optimize(); + + if (request.m_completionCallback) + { + request.m_completionCallback(request.m_ticketId); + } + + ticket.m_currentRequestId++; + return CommandResult::Executed; + } + else + { + AZ_Assert( + ticket.m_spawnable->IsPermanentlyLocked(), + "An request to UpdateEntityAliasTypes on the Spawnables Entities Manager was processed on a spawnable that's permanently " + "locked."); + } + } + return CommandResult::Requeue; + } + + auto SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -632,15 +876,15 @@ namespace AzFramework request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView( ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end())); ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -651,15 +895,15 @@ namespace AzFramework request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView( ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size())); ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -671,15 +915,15 @@ namespace AzFramework ticket.m_spawnedEntityIndices.clear(); ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -690,15 +934,39 @@ namespace AzFramework } ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(LoadBarrierCommand& request) -> CommandResult + { + Ticket& ticket = *request.m_ticket; + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) + { + if (request.m_checkAliasSpawnables) + { + if (Spawnable::EntityAliasConstVisitor visitor = ticket.m_spawnable->TryGetAliasesConst(); + !visitor.HasLock() || !visitor.AreAllSpawnablesReady()) + { + return CommandResult::Requeue; + } + } + + request.m_completionCallback(request.m_ticketId); + ticket.m_currentRequestId++; + return CommandResult::Executed; + } + else + { + return CommandResult::Requeue; + } + } + + auto SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request) -> CommandResult { if (request.m_requestId == request.m_ticket->m_currentRequestId) { @@ -714,11 +982,11 @@ namespace AzFramework } delete request.m_ticket; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index c3de5be003..09e9b1acfc 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -55,13 +55,18 @@ namespace AzFramework void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs = {}) override; void SpawnEntities( - EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; + EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override; void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) override; void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override; + void UpdateEntityAliasTypes( + EntitySpawnTicket& ticket, + AZStd::vector updatedAliases, + UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) override; + void ListEntities( EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override; void ListIndicesAndEntities( @@ -70,6 +75,8 @@ namespace AzFramework EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override; void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; + void LoadBarrier( + EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) override; // // The following function is thread safe but intended to be run from the main thread. @@ -78,7 +85,13 @@ namespace AzFramework CommandQueueStatus ProcessQueue(CommandQueuePriority priority); protected: - struct Ticket + enum class CommandResult : bool + { + Executed, + Requeue + }; + + struct Ticket final { AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0); static constexpr uint32_t Processing = AZStd::numeric_limits::max(); @@ -100,14 +113,14 @@ namespace AzFramework AZStd::unordered_set m_previouslySpawned; AZStd::vector m_spawnedEntities; - AZStd::vector m_spawnedEntityIndices; + AZStd::vector m_spawnedEntityIndices; AZ::Data::Asset m_spawnable; uint32_t m_nextRequestId{ 0 }; //!< Next id for this ticket. uint32_t m_currentRequestId { 0 }; //!< The id for the command that should be executed. bool m_loadAll{ true }; }; - struct SpawnAllEntitiesCommand + struct SpawnAllEntitiesCommand final { EntitySpawnCallback m_completionCallback; EntityPreInsertionCallback m_preInsertionCallback; @@ -116,9 +129,9 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct SpawnEntitiesCommand + struct SpawnEntitiesCommand final { - AZStd::vector m_entityIndices; + AZStd::vector m_entityIndices; EntitySpawnCallback m_completionCallback; EntityPreInsertionCallback m_preInsertionCallback; AZ::SerializeContext* m_serializeContext; @@ -127,7 +140,7 @@ namespace AzFramework uint32_t m_requestId; bool m_referencePreviouslySpawnedEntities; }; - struct DespawnAllEntitiesCommand + struct DespawnAllEntitiesCommand final { EntityDespawnCallback m_completionCallback; Ticket* m_ticket; @@ -142,7 +155,7 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ReloadSpawnableCommand + struct ReloadSpawnableCommand final { AZ::Data::Asset m_spawnable; ReloadSpawnableCallback m_completionCallback; @@ -151,35 +164,51 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ListEntitiesCommand + struct UpdateEntityAliasTypesCommand final + { + AZStd::vector m_entityAliases; + UpdateEntityAliasTypesCallback m_completionCallback; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; + }; + struct ListEntitiesCommand final { ListEntitiesCallback m_listCallback; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ListIndicesEntitiesCommand + struct ListIndicesEntitiesCommand final { ListIndicesEntitiesCallback m_listCallback; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ClaimEntitiesCommand + struct ClaimEntitiesCommand final { ClaimEntitiesCallback m_listCallback; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct BarrierCommand + struct BarrierCommand final { BarrierCallback m_completionCallback; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct DestroyTicketCommand + struct LoadBarrierCommand final + { + BarrierCallback m_completionCallback; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; + bool m_checkAliasSpawnables; + }; + struct DestroyTicketCommand final { Ticket* m_ticket; uint32_t m_requestId; @@ -191,10 +220,12 @@ namespace AzFramework DespawnAllEntitiesCommand, DespawnEntityCommand, ReloadSpawnableCommand, + UpdateEntityAliasTypesCommand, ListEntitiesCommand, ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, + LoadBarrierCommand, DestroyTicketCommand>; struct Queue @@ -213,17 +244,30 @@ namespace AzFramework AZ::Entity* CloneSingleEntity( const AZ::Entity& entityTemplate, EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext); + AZ::Entity* CloneSingleAliasedEntity( + const AZ::Entity& entityTemplate, + const Spawnable::EntityAlias& alias, + EntityIdMap& templateToCloneMap, + AZ::Entity* previouslySpawnedEntity, + AZ::SerializeContext& serializeContext); + void AppendComponents( + AZ::Entity& target, + const AZ::Entity::ComponentArrayType& componentTemplates, + EntityIdMap& templateToCloneMap, + AZ::SerializeContext& serializeContext); - bool ProcessRequest(SpawnAllEntitiesCommand& request); - bool ProcessRequest(SpawnEntitiesCommand& request); - bool ProcessRequest(DespawnAllEntitiesCommand& request); - bool ProcessRequest(DespawnEntityCommand& request); - bool ProcessRequest(ReloadSpawnableCommand& request); - bool ProcessRequest(ListEntitiesCommand& request); - bool ProcessRequest(ListIndicesEntitiesCommand& request); - bool ProcessRequest(ClaimEntitiesCommand& request); - bool ProcessRequest(BarrierCommand& request); - bool ProcessRequest(DestroyTicketCommand& request); + CommandResult ProcessRequest(SpawnAllEntitiesCommand& request); + CommandResult ProcessRequest(SpawnEntitiesCommand& request); + CommandResult ProcessRequest(DespawnAllEntitiesCommand& request); + CommandResult ProcessRequest(DespawnEntityCommand& request); + CommandResult ProcessRequest(ReloadSpawnableCommand& request); + CommandResult ProcessRequest(UpdateEntityAliasTypesCommand& request); + CommandResult ProcessRequest(ListEntitiesCommand& request); + CommandResult ProcessRequest(ListIndicesEntitiesCommand& request); + CommandResult ProcessRequest(ClaimEntitiesCommand& request); + CommandResult ProcessRequest(BarrierCommand& request); + CommandResult ProcessRequest(LoadBarrierCommand& request); + CommandResult ProcessRequest(DestroyTicketCommand& request); //! Generate a base set of original-to-new entity ID mappings to use during spawning. //! Since Entity references get fixed up on an entity-by-entity basis while spawning, it's important to have the complete