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