From 1bf8c599e3789999e2c33da5a2f82d77e48ea9df Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Thu, 27 May 2021 09:56:11 -0700 Subject: [PATCH 1/5] External Serialize Context for spawning This change makes it possible to provide a Serialize Context for spawning entities from spawnables. This also removes the need for the Serialize Context to be retrieved multiple times per frame. --- .../Spawnable/SpawnableEntitiesInterface.h | 54 +++++--- .../Spawnable/SpawnableEntitiesManager.cpp | 126 +++++++++--------- .../Spawnable/SpawnableEntitiesManager.h | 33 ++--- .../SpawnableEntitiesManagerTests.cpp | 24 +++- 4 files changed, 139 insertions(+), 98 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 27f45064b6..cfa1f3b464 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -21,6 +21,7 @@ namespace AZ { class Entity; + class SerializeContext; } namespace AzFramework @@ -171,6 +172,34 @@ namespace AzFramework using ClaimEntitiesCallback = AZStd::function; using BarrierCallback = AZStd::function; + struct SpawnEntitiesOptionalArgs + { + //! Callback that's called after instances of entities have been created, but before they're spawned into the world. This + //! gives the opportunity to modify the entities if needed such as injecting additional components or modifying components. + EntityPreInsertionCallback m_preInsertionCallback; + //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that + //! made the function call. The returned list of entities contains all the newly created entities. + EntitySpawnCallback m_completionCallback; + //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Contetx will be used. + AZ::SerializeContext* m_serializeContext { nullptr }; + }; + + struct DespawnAllEntitiesOptionalArgs + { + //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that + //! made the function call. The returned list of entities contains all the newly created entities. + EntityDespawnCallback m_completionCallback; + }; + + struct ReloadSpawnableOptionalArgs + { + //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that + //! made the function call. The returned list of entities contains all the newly created entities. + ReloadSpawnableCallback m_completionCallback; + //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Contetx will be used. + AZ::SerializeContext* m_serializeContext { nullptr }; + }; + //! 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 @@ -197,40 +226,31 @@ namespace AzFramework //! Spawn instances of all entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. //! @param priority The priority at which this call will be executed. - //! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from - //! a different thread than the one that made the function call. The returned list of entities contains all the newly - //! created entities. + //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs virtual void SpawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback = {}, - EntitySpawnCallback completionCallback = {}) = 0; + EntitySpawnTicket& ticket, SpawnablePriority priority, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; //! Spawn instances of some entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. //! @param priority The priority at which this call will be executed. //! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from. - //! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from - //! a different thread than the one that made this function call. The returned list of entities contains all the newly - //! created entities. + //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs virtual void SpawnEntities( EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, - EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) = 0; + SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; //! Removes all entities in the provided list from the environment. //! @param ticket The ticket previously used to spawn entities with. //! @param priority The priority at which this call will be executed. - //! @param completionCallback Optional callback that's called when despawning entities has completed. This can be called from - //! a different thread than the one that made this function call. + //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs virtual void DespawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback = {}) = 0; - + EntitySpawnTicket& ticket, SpawnablePriority priority, DespawnAllEntitiesOptionalArgs optionalArgs = {}) = 0; //! Removes all entities in the provided list from the environment and reconstructs the entities from the provided spawnable. //! @param ticket Holds the information on the entities to reload. //! @param priority The priority at which this call will be executed. //! @param spawnable The spawnable that will replace the existing spawnable. Both need to have the same asset id. - //! @param completionCallback Optional callback that's called when the entities have been reloaded. This can be called from - //! a different thread than the one that made this function call. The returned list of entities contains all the replacement - //! entities. + //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs virtual void ReloadSpawnable( EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, - ReloadSpawnableCallback completionCallback = {}) = 0; + ReloadSpawnableOptionalArgs optionalArgs = {}) = 0; //! List all entities that are spawned using this ticket. //! @param ticket Only the entities associated with this ticket will be listed. diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 7b767d2a72..77106172f6 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -38,6 +38,10 @@ namespace AzFramework SpawnableEntitiesManager::SpawnableEntitiesManager() { + AZ::ComponentApplicationBus::BroadcastResult(m_defaultSerializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); + AZ_Assert( + m_defaultSerializeContext, "Failed to retrieve serialization context during construction of the Spawnable Entities Manager."); + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { AZ::u64 value = aznumeric_caster(m_highPriorityThreshold); @@ -47,53 +51,57 @@ namespace AzFramework } void SpawnableEntitiesManager::SpawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback, - EntitySpawnCallback completionCallback) + EntitySpawnTicket& ticket, SpawnablePriority priority, SpawnEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnAllEntities hasn't been initialized."); SpawnAllEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); - queueEntry.m_completionCallback = AZStd::move(completionCallback); - queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback); + queueEntry.m_serializeContext = + optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; + queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); + queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); QueueRequest(ticket, priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::SpawnEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, - EntityPreInsertionCallback preInsertionCallback, EntitySpawnCallback completionCallback) + EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized."); SpawnEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_entityIndices = AZStd::move(entityIndices); - queueEntry.m_completionCallback = AZStd::move(completionCallback); - queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback); + queueEntry.m_serializeContext = + optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; + queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); + queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); QueueRequest(ticket, priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::DespawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback) + EntitySpawnTicket& ticket, SpawnablePriority priority, DespawnAllEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to DespawnAllEntities hasn't been initialized."); DespawnAllEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); - queueEntry.m_completionCallback = AZStd::move(completionCallback); + queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); QueueRequest(ticket, priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ReloadSpawnable( EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, - ReloadSpawnableCallback completionCallback) + ReloadSpawnableOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized."); ReloadSpawnableCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_spawnable = AZStd::move(spawnable); - queueEntry.m_completionCallback = AZStd::move(completionCallback); + queueEntry.m_serializeContext = + optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; + queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); QueueRequest(ticket, priority, AZStd::move(queueEntry)); } @@ -174,58 +182,57 @@ namespace AzFramework auto SpawnableEntitiesManager::ProcessQueue(Queue& queue) -> CommandQueueStatus { - AZStd::queue pendingRequestQueue; + // Process delayed requests first. + // Only process the requests that are currently in this queue, not the ones that could be re-added if they still can't complete. + size_t delayedSize = queue.m_delayed.size(); + for (size_t i = 0; i < delayedSize; ++i) { - AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); - queue.m_pendingRequest.swap(pendingRequestQueue); + Requests& request = queue.m_delayed.front(); + bool result = AZStd::visit( + [this](auto&& args) -> bool + { + return ProcessRequest(args); + }, + request); + if (!result) + { + queue.m_delayed.emplace_back(AZStd::move(request)); + } + queue.m_delayed.pop_front(); } - if (!pendingRequestQueue.empty() || !queue.m_delayed.empty()) + // Process newly added requests. + while (true) { - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); - AZ_Assert(serializeContext, "Failed to retrieve serialization context."); - - // Only process the requests that are currently in this queue, not the ones that could be re-added if they still can't complete. - size_t delayedSize = queue.m_delayed.size(); - for (size_t i = 0; i < delayedSize; ++i) + AZStd::queue pendingRequestQueue; { - Requests& request = queue.m_delayed.front(); - bool result = AZStd::visit([this, serializeContext](auto&& args) -> bool - { - return ProcessRequest(args, *serializeContext); - }, request); - if (!result) - { - queue.m_delayed.emplace_back(AZStd::move(request)); - } - queue.m_delayed.pop_front(); + AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); + queue.m_pendingRequest.swap(pendingRequestQueue); } - do + if (!pendingRequestQueue.empty()) { while (!pendingRequestQueue.empty()) { Requests& request = pendingRequestQueue.front(); - bool result = AZStd::visit([this, serializeContext](auto&& args) -> bool + bool result = AZStd::visit( + [this](auto&& args) -> bool { - return ProcessRequest(args, *serializeContext); - }, request); + return ProcessRequest(args); + }, + request); if (!result) { queue.m_delayed.emplace_back(AZStd::move(request)); } pendingRequestQueue.pop(); } - - // Spawning entities can result in more entities being queued to spawn. Repeat spawning until the queue is - // empty to avoid a chain of entity spawning getting dragged out over multiple frames. - { - AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); - queue.m_pendingRequest.swap(pendingRequestQueue); - } - } while (!pendingRequestQueue.empty()); - } + } + else + { + break; + } + }; return queue.m_delayed.empty() ? CommandQueueStatus::NoCommandsLeft : CommandQueueStatus::HasCommandsLeft; } @@ -267,7 +274,7 @@ namespace AzFramework &entityTemplate, templateToCloneEntityIdMap, &serializeContext); } - bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) { Ticket& ticket = *request.m_ticket; if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) @@ -296,7 +303,7 @@ namespace AzFramework { const AZ::Entity& entityTemplate = *entitiesToSpawn[i]; - AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext); + AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, *request.m_serializeContext); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); @@ -347,7 +354,7 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request) { Ticket& ticket = *request.m_ticket; if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) @@ -371,7 +378,7 @@ namespace AzFramework { const AZ::Entity& entityTemplate = *entitiesToSpawn[index]; - AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); + AZ::Entity* clone = request.m_serializeContext->CloneObject(&entityTemplate); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); clone->SetId(AZ::Entity::MakeId()); @@ -413,8 +420,7 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request, - [[maybe_unused]] AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request) { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -447,7 +453,7 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request, AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) { Ticket& ticket = *request.m_ticket; AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(), @@ -488,7 +494,7 @@ namespace AzFramework { const AZ::Entity& entityTemplate = *entities[i]; - AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext); + AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, *request.m_serializeContext); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); @@ -502,7 +508,7 @@ namespace AzFramework for (size_t index : ticket.m_spawnedEntityIndices) { ticket.m_spawnedEntities.push_back( - index < entitiesSize ? SpawnSingleEntity(*entities[index], serializeContext) : nullptr); + index < entitiesSize ? SpawnSingleEntity(*entities[index], *request.m_serializeContext) : nullptr); } } ticket.m_spawnable = AZStd::move(request.m_spawnable); @@ -525,7 +531,7 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request) { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -541,7 +547,7 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request) { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -560,7 +566,7 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request) { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -580,7 +586,7 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -599,7 +605,7 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) + bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request) { if (request.m_requestId == request.m_ticket->m_currentRequestId) { diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index afffdab8b5..b40ec20aa3 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -57,19 +57,16 @@ namespace AzFramework // The following functions are thread safe // - void SpawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback = {}, - EntitySpawnCallback completionCallback = {}) override; + void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void SpawnEntities( EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, - EntityPreInsertionCallback preInsertionCallback = {}, - EntitySpawnCallback completionCallback = {}) override; + SpawnEntitiesOptionalArgs optionalArgs = {}) override; void DespawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback = {}) override; + EntitySpawnTicket& ticket, SpawnablePriority priority, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; void ReloadSpawnable( EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, - ReloadSpawnableCallback completionCallback = {}) override; + ReloadSpawnableOptionalArgs optionalArgs = {}) override; void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) override; void ListIndicesAndEntities( @@ -105,6 +102,7 @@ namespace AzFramework { EntitySpawnCallback m_completionCallback; EntityPreInsertionCallback m_preInsertionCallback; + AZ::SerializeContext* m_serializeContext; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; @@ -114,6 +112,7 @@ namespace AzFramework AZStd::vector m_entityIndices; EntitySpawnCallback m_completionCallback; EntityPreInsertionCallback m_preInsertionCallback; + AZ::SerializeContext* m_serializeContext; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; @@ -129,6 +128,7 @@ namespace AzFramework { AZ::Data::Asset m_spawnable; ReloadSpawnableCallback m_completionCallback; + AZ::SerializeContext* m_serializeContext; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; @@ -191,15 +191,15 @@ namespace AzFramework AZ::Entity* CloneSingleEntity(const AZ::Entity& entityTemplate, EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext); - bool ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext); - bool ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext); - bool ProcessRequest(DespawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext); - bool ProcessRequest(ReloadSpawnableCommand& request, AZ::SerializeContext& serializeContext); - bool ProcessRequest(ListEntitiesCommand& request, AZ::SerializeContext& serializeContext); - bool ProcessRequest(ListIndicesEntitiesCommand& request, AZ::SerializeContext& serializeContext); - bool ProcessRequest(ClaimEntitiesCommand& request, AZ::SerializeContext& serializeContext); - bool ProcessRequest(BarrierCommand& request, AZ::SerializeContext& serializeContext); - bool ProcessRequest(DestroyTicketCommand& request, AZ::SerializeContext& serializeContext); + bool ProcessRequest(SpawnAllEntitiesCommand& request); + bool ProcessRequest(SpawnEntitiesCommand& request); + bool ProcessRequest(DespawnAllEntitiesCommand& 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); Queue m_highPriorityQueue; Queue m_regularPriorityQueue; @@ -207,6 +207,7 @@ namespace AzFramework AZ::Event> m_onSpawnedEvent; AZ::Event> m_onDespawnedEvent; + AZ::SerializeContext* m_defaultSerializeContext { nullptr }; //! The threshold used to determine if a request goes in the regular (if bigger than the value) or high priority queue (if smaller //! or equal to this value). The starting value of 64 is chosen as it's between default values SpawnablePriority_High and //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured diff --git a/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp b/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp index 484b7f46d7..7a2e7f614b 100644 --- a/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp +++ b/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp @@ -104,7 +104,9 @@ namespace UnitTest { spawnedEntitiesCount += entities.size(); }; - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(callback)); + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(optionalArgs)); m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); EXPECT_EQ(NumEntities, spawnedEntitiesCount); @@ -305,8 +307,14 @@ namespace UnitTest defaultPriorityCallId = callCounter++; }; - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(defaultCallback)); - m_manager->SpawnAllEntities(highPriorityTicket, AzFramework::SpawnablePriority_High, {}, AZStd::move(highCallback)); + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(defaultCallback); + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(optionalArgs)); + + AzFramework::SpawnEntitiesOptionalArgs highPriortyOptionalArgs; + highPriortyOptionalArgs.m_completionCallback = AZStd::move(highCallback); + m_manager->SpawnAllEntities(highPriorityTicket, AzFramework::SpawnablePriority_High, AZStd::move(highPriortyOptionalArgs)); + m_manager->ProcessQueue( AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High | AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); @@ -333,8 +341,14 @@ namespace UnitTest defaultPriorityCallId = callCounter++; }; - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(defaultCallback)); - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_High, {}, AZStd::move(highCallback)); + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(defaultCallback); + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(optionalArgs)); + + AzFramework::SpawnEntitiesOptionalArgs highPriortyOptionalArgs; + highPriortyOptionalArgs.m_completionCallback = AZStd::move(highCallback); + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_High, AZStd::move(highPriortyOptionalArgs)); + m_manager->ProcessQueue( AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High | AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); From 8c541b5205dd7605965e9719df6bfcfc1470f52a Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Tue, 1 Jun 2021 16:11:55 -0700 Subject: [PATCH 2/5] Moved default values to object in the Spawnable Entities Interface --- .../Spawnable/SpawnableEntitiesContainer.cpp | 11 ++-- .../Spawnable/SpawnableEntitiesInterface.h | 53 +++++++++++++------ .../Spawnable/SpawnableEntitiesManager.cpp | 37 +++++++------ .../Spawnable/SpawnableEntitiesManager.h | 22 ++++---- .../SpawnableEntitiesManagerTests.cpp | 26 +++++---- .../Libraries/Spawning/SpawnNodeable.cpp | 6 ++- 6 files changed, 88 insertions(+), 67 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp index 808de74e71..a945ea4edc 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp @@ -38,20 +38,20 @@ namespace AzFramework void SpawnableEntitiesContainer::SpawnAllEntities() { AZ_Assert(m_threadData, "Calling SpawnAllEntities on a Spawnable container that's not set."); - SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default); + SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket); } void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector entityIndices) { AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set."); SpawnableEntitiesInterface::Get()->SpawnEntities( - m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default, AZStd::move(entityIndices)); + m_threadData->m_spawnedEntitiesTicket, AZStd::move(entityIndices)); } void SpawnableEntitiesContainer::DespawnAllEntities() { AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set."); - SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default); + SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_threadData->m_spawnedEntitiesTicket); } void SpawnableEntitiesContainer::Reset(AZ::Data::Asset spawnable) @@ -69,7 +69,6 @@ namespace AzFramework SpawnableEntitiesInterface::Get()->Barrier( m_threadData->m_spawnedEntitiesTicket, - SpawnablePriority_Default, [threadData = m_threadData](EntitySpawnTicket::Id) mutable { threadData.reset(); @@ -88,7 +87,6 @@ namespace AzFramework AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set."); SpawnableEntitiesInterface::Get()->Barrier( m_threadData->m_spawnedEntitiesTicket, - SpawnablePriority_Default, [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id) { callback(generation); @@ -115,7 +113,6 @@ namespace AzFramework AZ_Assert(m_threadData, "SpawnableEntitiesContainer is monitoring a spawnable, but doesn't have the associated data."); AZ_TracePrintf("Spawnables", "Reloading spawnable '%s'.\n", replacementAsset.GetHint().c_str()); - SpawnableEntitiesInterface::Get()->ReloadSpawnable( - m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default, AZStd::move(replacementAsset)); + SpawnableEntitiesInterface::Get()->ReloadSpawnable(m_threadData->m_spawnedEntitiesTicket, AZStd::move(replacementAsset)); } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index cfa1f3b464..5d7ce79641 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -172,7 +172,7 @@ namespace AzFramework using ClaimEntitiesCallback = AZStd::function; using BarrierCallback = AZStd::function; - struct SpawnEntitiesOptionalArgs + struct SpawnEntitiesOptionalArgs final { //! Callback that's called after instances of entities have been created, but before they're spawned into the world. This //! gives the opportunity to modify the entities if needed such as injecting additional components or modifying components. @@ -182,22 +182,46 @@ namespace AzFramework EntitySpawnCallback m_completionCallback; //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Contetx will be used. AZ::SerializeContext* m_serializeContext { nullptr }; + //! The priority at which this call will be executed. + SpawnablePriority m_priority { SpawnablePriority_Default }; }; - struct DespawnAllEntitiesOptionalArgs + struct DespawnAllEntitiesOptionalArgs final { //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that //! made the function call. The returned list of entities contains all the newly created entities. EntityDespawnCallback m_completionCallback; + //! The priority at which this call will be executed. + SpawnablePriority m_priority { SpawnablePriority_Default }; }; - struct ReloadSpawnableOptionalArgs + struct ReloadSpawnableOptionalArgs final { //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that //! made the function call. The returned list of entities contains all the newly created entities. ReloadSpawnableCallback m_completionCallback; //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Contetx will be used. AZ::SerializeContext* m_serializeContext { nullptr }; + //! 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. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + }; + + struct ClaimEntitiesOptionalArgs final + { + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + }; + + struct BarrierOptionalArgs final + { + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; }; //! Interface definition to (de)spawn entities from a spawnable into the game world. @@ -225,38 +249,34 @@ namespace AzFramework //! Spawn instances of all entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. - //! @param priority The priority at which this call will be executed. //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs - virtual void SpawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; + virtual void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; //! Spawn instances of some entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. //! @param priority The priority at which this call will be executed. //! @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 SpawnAllEntitiesOptionalArgs virtual void SpawnEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, 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 priority The priority at which this call will be executed. //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs - virtual void DespawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, DespawnAllEntitiesOptionalArgs optionalArgs = {}) = 0; + virtual void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) = 0; //! Removes all entities in the provided list from the environment and reconstructs the entities from the provided spawnable. //! @param ticket Holds the information on the entities to reload. //! @param priority The priority at which this call will be executed. //! @param spawnable The spawnable that will replace the existing spawnable. Both need to have the same asset id. //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs virtual void ReloadSpawnable( - EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, - ReloadSpawnableOptionalArgs optionalArgs = {}) = 0; + EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) = 0; //! List all entities that are spawned using this ticket. //! @param ticket Only the entities associated with this ticket will be listed. //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to list the entities on. - virtual void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) = 0; + virtual void ListEntities( + EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) = 0; //! List all entities that are spawned using this ticket with their spawnable index. //! Spawnables contain a flat list of entities, which are used as templates to spawn entities from. For every spawned entity //! the index of the entity in the spawnable that was used as a template is stored. This version of ListEntities will return @@ -267,20 +287,21 @@ namespace AzFramework //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to list the entities and indices on. virtual void ListIndicesAndEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) = 0; + EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) = 0; //! Claim all entities that are spawned using this ticket. Ownership of the entities is transferred from the ticket to the //! caller through the callback. After this call the ticket will have no entities associated with it. The caller of //! this function will need to manage the entities after this call. //! @param ticket Only the entities associated with this ticket will be released. //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to transfer the entities through. - virtual void ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) = 0; + virtual void ClaimEntities( + EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) = 0; //! Blocks until all operations made on the provided ticket before the barrier call have completed. //! @param ticket The ticket to monitor. //! @param priority The priority at which this call will be executed. //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. - virtual void Barrier(EntitySpawnTicket& ticket, SpawnablePriority priority, BarrierCallback completionCallback) = 0; + virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0; //! Register a handler for OnSpawned events. virtual void AddOnSpawnedHandler(AZ::Event>::Handler& handler) = 0; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 77106172f6..c11ce94bb2 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -50,8 +50,7 @@ namespace AzFramework } } - void SpawnableEntitiesManager::SpawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, SpawnEntitiesOptionalArgs optionalArgs) + void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, SpawnEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnAllEntities hasn't been initialized."); @@ -61,11 +60,11 @@ namespace AzFramework optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); - QueueRequest(ticket, priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::SpawnEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs) + EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized."); @@ -76,23 +75,21 @@ namespace AzFramework optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); - QueueRequest(ticket, priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::DespawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, DespawnAllEntitiesOptionalArgs optionalArgs) + void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to DespawnAllEntities hasn't been initialized."); DespawnAllEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(ticket, priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ReloadSpawnable( - EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, - ReloadSpawnableOptionalArgs optionalArgs) + EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized."); @@ -102,10 +99,11 @@ namespace AzFramework queueEntry.m_serializeContext = optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(ticket, priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) + void SpawnableEntitiesManager::ListEntities( + EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs) { AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use."); AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized."); @@ -113,11 +111,11 @@ namespace AzFramework ListEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListIndicesAndEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) + EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs) { AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use."); AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized."); @@ -125,10 +123,11 @@ namespace AzFramework ListIndicesEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) + void SpawnableEntitiesManager::ClaimEntities( + EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs) { AZ_Assert(listCallback, "ClaimEntities called on spawnable entities without a valid callback to use."); AZ_Assert(ticket.IsValid(), "Ticket provided to ClaimEntities hasn't been initialized."); @@ -136,10 +135,10 @@ namespace AzFramework ClaimEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, SpawnablePriority priority, BarrierCallback completionCallback) + void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs) { AZ_Assert(completionCallback, "Barrier on spawnable entities called without a valid callback to use."); AZ_Assert(ticket.IsValid(), "Ticket provided to Barrier hasn't been initialized."); @@ -147,7 +146,7 @@ namespace AzFramework BarrierCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(completionCallback); - QueueRequest(ticket, priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event>::Handler& handler) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index b40ec20aa3..e6db19557f 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -57,23 +57,21 @@ namespace AzFramework // The following functions are thread safe // - void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, SpawnEntitiesOptionalArgs optionalArgs = {}) override; + void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void SpawnEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, - SpawnEntitiesOptionalArgs optionalArgs = {}) override; - void DespawnAllEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; - + EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; + void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; void ReloadSpawnable( - EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, - ReloadSpawnableOptionalArgs optionalArgs = {}) override; + EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override; - void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) override; + void ListEntities( + EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override; void ListIndicesAndEntities( - EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) override; - void ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) override; + EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override; + void ClaimEntities( + EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override; - void Barrier(EntitySpawnTicket& spawnInfo, SpawnablePriority priority, BarrierCallback completionCallback) override; + void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; void AddOnSpawnedHandler(AZ::Event>::Handler& handler) override; void AddOnDespawnedHandler(AZ::Event>::Handler& handler) override; diff --git a/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp b/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp index 7a2e7f614b..8637684b3c 100644 --- a/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp +++ b/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp @@ -106,7 +106,7 @@ namespace UnitTest }; AzFramework::SpawnEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(callback); - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(optionalArgs)); + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); EXPECT_EQ(NumEntities, spawnedEntitiesCount); @@ -116,7 +116,7 @@ namespace UnitTest { { AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); - m_manager->SpawnAllEntities(ticket, AzFramework::SpawnablePriority_Default); + m_manager->SpawnAllEntities(ticket); } m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } @@ -130,7 +130,7 @@ namespace UnitTest { { AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); - m_manager->SpawnEntities(ticket, AzFramework::SpawnablePriority_Default, {}); + m_manager->SpawnEntities(ticket, {/* Deliberate empty list of indices. */}); } m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } @@ -144,7 +144,7 @@ namespace UnitTest { { AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); - m_manager->DespawnAllEntities(ticket, AzFramework::SpawnablePriority_Default); + m_manager->DespawnAllEntities(ticket); } m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } @@ -158,7 +158,7 @@ namespace UnitTest { { AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); - m_manager->ReloadSpawnable(ticket, AzFramework::SpawnablePriority_Default, *m_spawnableAsset); + m_manager->ReloadSpawnable(ticket, *m_spawnableAsset); } m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } @@ -185,7 +185,7 @@ namespace UnitTest spawnedEntitiesCount += entities.size(); }; - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default); + m_manager->SpawnAllEntities(*m_ticket); m_manager->ListEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); @@ -230,7 +230,7 @@ namespace UnitTest } }; - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default); + m_manager->SpawnAllEntities(*m_ticket); m_manager->ListIndicesAndEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); @@ -309,11 +309,13 @@ namespace UnitTest AzFramework::SpawnEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(defaultCallback); - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(optionalArgs)); + optionalArgs.m_priority = AzFramework::SpawnablePriority_Default; + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); AzFramework::SpawnEntitiesOptionalArgs highPriortyOptionalArgs; highPriortyOptionalArgs.m_completionCallback = AZStd::move(highCallback); - m_manager->SpawnAllEntities(highPriorityTicket, AzFramework::SpawnablePriority_High, AZStd::move(highPriortyOptionalArgs)); + highPriortyOptionalArgs.m_priority = AzFramework::SpawnablePriority_High; + m_manager->SpawnAllEntities(highPriorityTicket, AZStd::move(highPriortyOptionalArgs)); m_manager->ProcessQueue( AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High | @@ -343,11 +345,13 @@ namespace UnitTest AzFramework::SpawnEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(defaultCallback); - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(optionalArgs)); + optionalArgs.m_priority = AzFramework::SpawnablePriority_Default; + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); AzFramework::SpawnEntitiesOptionalArgs highPriortyOptionalArgs; highPriortyOptionalArgs.m_completionCallback = AZStd::move(highCallback); - m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_High, AZStd::move(highPriortyOptionalArgs)); + highPriortyOptionalArgs.m_priority = AzFramework::SpawnablePriority_High; + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(highPriortyOptionalArgs)); m_manager->ProcessQueue( AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High | diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp index ad53236108..39afa46a19 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp @@ -134,7 +134,9 @@ namespace ScriptCanvas::Nodeables::Spawning m_spawnBatchSizes.push_back(view.size()); }; - AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities( - m_spawnTicket, AzFramework::SpawnablePriority_Default, preSpawnCB, spawnCompleteCB); + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_preInsertionCallback = AZStd::move(preSpawnCB); + optionalArgs.m_completionCallback = AZStd::move(spawnCompleteCB); + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_spawnTicket, AZStd::move(optionalArgs)); } } From 1e7ac6094982b78a262da2989507fcbf0ab0d566 Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:49:25 -0700 Subject: [PATCH 3/5] Reintroduced spawning multiple instances of the same entity The following was changed: - The remapper in AZ::IdUtils now has an additional argument to tell it what to do when it encounters the same source entity id. The original behavior of ignoring the new entity id and returning the first occurrence is the default. The alternative behavior is to store the last known entity id and return that instead. - Split the optional arguments for SpawnAllEntities and SpawnEntities. - SpawnEntities now has an option to continue with the entity mapping from a previous spawn call or to start with a fresh mapping. The latter is the default as the former will come at a performance cost since the mapping table has to be reconstructed. - Entities spawned using SpawnEntities and ReloadEntities now also get the correct entity mapping applied. - Added several new unit tests to cover most of the new functionality. - Fixed some places where the older API version was still called. --- .../AzCore/AzCore/Serialization/IdUtils.h | 28 +- .../AzCore/AzCore/Serialization/IdUtils.inl | 16 +- .../Spawnable/SpawnableEntitiesInterface.h | 23 +- .../Spawnable/SpawnableEntitiesManager.cpp | 111 ++++---- .../Spawnable/SpawnableEntitiesManager.h | 14 +- .../SpawnableEntitiesManagerTests.cpp | 254 +++++++++++++++++- .../Pipeline/NetBindMarkerComponent.cpp | 6 +- .../Libraries/Spawning/SpawnNodeable.cpp | 2 +- 8 files changed, 362 insertions(+), 92 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Serialization/IdUtils.h b/Code/Framework/AzCore/AzCore/Serialization/IdUtils.h index a571379883..98959fe4bd 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/IdUtils.h +++ b/Code/Framework/AzCore/AzCore/Serialization/IdUtils.h @@ -28,7 +28,13 @@ namespace AZ { namespace IdUtils { - template + /** + * \param AllowDuplicates - If true allows the same id to be registered multiple time, + with the newer value overwriting the stored value. If false, duplicates are not allowed and + the first stored value is kept.The default is false. + */ + + template struct Remapper { /** @@ -138,14 +144,18 @@ namespace AZ * \param context - The serialize context for enumerating the @classPtr elements */ template - static void GenerateNewIdsAndFixRefs(T* object, MapType& newIdMap, AZ::SerializeContext* context = nullptr) + static void GenerateNewIdsAndFixRefs( + T* object, MapType& newIdMap, AZ::SerializeContext* context = nullptr) { if (!context) { AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext); if (!context) { - AZ_Error("Serialization", false, "No serialize context provided! Failed to get component application default serialize context! ComponentApp is not started or input serialize context should not be null!"); + AZ_Error( + "Serialization", false, + "No serialize context provided! Failed to get component application default serialize context! ComponentApp is " + "not started or input serialize context should not be null!"); return; } } @@ -156,8 +166,16 @@ namespace AZ { if (idGenerator) { - auto it = newIdMap.emplace(originalId, idGenerator()); - return it.first->second; + if constexpr(AllowDuplicates) + { + auto it = newIdMap.insert_or_assign(originalId, idGenerator()); + return it.first->second; + } + else + { + auto it = newIdMap.emplace(originalId, idGenerator()); + return it.first->second; + } } return originalId; } diff --git a/Code/Framework/AzCore/AzCore/Serialization/IdUtils.inl b/Code/Framework/AzCore/AzCore/Serialization/IdUtils.inl index 01617cf5aa..0dc87dfe18 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/IdUtils.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/IdUtils.inl @@ -30,8 +30,10 @@ namespace AZ bool m_isModifiedContainer; }; - template - unsigned int Remapper::RemapIds(void* classPtr, const AZ::Uuid& classUuid, const typename Remapper::IdMapper& mapper, AZ::SerializeContext* context, bool replaceId) + template + unsigned int Remapper::RemapIds( + void* classPtr, const AZ::Uuid& classUuid, const typename Remapper::IdMapper& mapper, + AZ::SerializeContext* context, bool replaceId) { if (!context) { @@ -152,16 +154,18 @@ namespace AZ return replaced; } - template - unsigned int Remapper::ReplaceIdsAndIdRefs(void* classPtr, const AZ::Uuid& classUuid, const IdMapper& mapper, AZ::SerializeContext* context /*= nullptr*/) + template + unsigned int Remapper::ReplaceIdsAndIdRefs(void* classPtr, const AZ::Uuid& classUuid, const IdMapper& mapper, AZ::SerializeContext* context /*= nullptr*/) { unsigned int replaced = RemapIds(classPtr, classUuid, mapper, context, true); replaced += RemapIds(classPtr, classUuid, mapper, context, false); return replaced; } - template - unsigned int Remapper::RemapIdsAndIdRefs(void* classPtr, const AZ::Uuid& classUuid, const typename Remapper::IdReplacer& mapper, AZ::SerializeContext* context) + template + unsigned int Remapper::RemapIdsAndIdRefs( + void* classPtr, const AZ::Uuid& classUuid, const typename Remapper::IdReplacer& mapper, + AZ::SerializeContext* context) { if (!context) { diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 5d7ce79641..2ad1db60a4 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -172,7 +172,7 @@ namespace AzFramework using ClaimEntitiesCallback = AZStd::function; using BarrierCallback = AZStd::function; - struct SpawnEntitiesOptionalArgs final + struct SpawnAllEntitiesOptionalArgs final { //! Callback that's called after instances of entities have been created, but before they're spawned into the world. This //! gives the opportunity to modify the entities if needed such as injecting additional components or modifying components. @@ -186,6 +186,25 @@ namespace AzFramework SpawnablePriority m_priority { SpawnablePriority_Default }; }; + struct SpawnEntitiesOptionalArgs final + { + //! Callback that's called after instances of entities have been created, but before they're spawned into the world. This + //! gives the opportunity to modify the entities if needed such as injecting additional components or modifying components. + EntityPreInsertionCallback m_preInsertionCallback; + //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that + //! made the function call. The returned list of entities contains all the newly created entities. + EntitySpawnCallback m_completionCallback; + //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Contetx will be used. + AZ::SerializeContext* m_serializeContext{ nullptr }; + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + //! Entity references are resolved by referring to the last entity spawned from a template entity in the spawnable. If this + //! is set to false entities from previous spawn calls are not taken into account. If set to true entity references may be + //! resolved to a previously spawned entity. A lookup table has to be constructed when true, which may negatively impact + //! performance, especially if a large number of entities are present on a ticket. + bool m_referencePreviouslySpawnedEntities{ false }; + }; + struct DespawnAllEntitiesOptionalArgs final { //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that @@ -250,7 +269,7 @@ namespace AzFramework //! Spawn instances of all entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs - virtual void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; + virtual void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs = {}) = 0; //! Spawn instances of some entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. //! @param priority The priority at which this call will be executed. diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index c11ce94bb2..a482c1d4f9 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -50,7 +50,7 @@ namespace AzFramework } } - void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, SpawnEntitiesOptionalArgs optionalArgs) + void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnAllEntities hasn't been initialized."); @@ -75,6 +75,7 @@ namespace AzFramework optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); + queueEntry.m_referencePreviouslySpawnedEntities = optionalArgs.m_referencePreviouslySpawnedEntities; QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } @@ -256,21 +257,11 @@ namespace AzFramework } } - AZ::Entity* SpawnableEntitiesManager::SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext) - { - AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate); - AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - clone->SetId(AZ::Entity::MakeId()); - - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, clone); - return clone; - } - AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate, - EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext) + EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext) { - return AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( - &entityTemplate, templateToCloneEntityIdMap, &serializeContext); + return AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( + &entityTemplate, templateToCloneMap, &serializeContext); } bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) @@ -297,13 +288,9 @@ namespace AzFramework spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); templateToCloneEntityIdMap.reserve(entitiesToSpawnSize); - // Mark all indices as spawned for (size_t i = 0; i < entitiesToSpawnSize; ++i) { - const AZ::Entity& entityTemplate = *entitiesToSpawn[i]; - - AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, *request.m_serializeContext); - + AZ::Entity* clone = CloneSingleEntity(*entitiesToSpawn[i], templateToCloneEntityIdMap, *request.m_serializeContext); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); spawnedEntities.emplace_back(clone); @@ -311,16 +298,8 @@ namespace AzFramework } // loadAll is true if every entity has been spawned only once - if (spawnedEntities.size() == entitiesToSpawnSize) - { - ticket.m_loadAll = true; - } - else - { - // Case where there were already spawns from a previous request - ticket.m_loadAll = false; - } - + 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) { @@ -329,11 +308,10 @@ namespace AzFramework } // Add to the game context, now the entities are active - AZStd::for_each(ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end(), - [](AZ::Entity* entity) + for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, entity); - }); + 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) @@ -360,14 +338,34 @@ namespace AzFramework { 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 how many entities there were in the array initially + // 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(); + // Reconstruct the template to entity mapping. + EntityIdMap templateToCloneEntityIdMap; + if (!request.m_referencePreviouslySpawnedEntities) + { + templateToCloneEntityIdMap.reserve(entitiesToSpawnSize); + } + else + { + templateToCloneEntityIdMap.reserve(spawnedEntitiesInitialCount + entitiesToSpawnSize); + SpawnableConstIndexEntityContainerView indexEntityView( + spawnedEntities.begin(), spawnedEntityIndices.begin(), spawnedEntities.size()); + for (auto& entry : indexEntityView) + { + templateToCloneEntityIdMap.insert_or_assign(entitiesToSpawn[entry.GetIndex()]->GetId(), entry.GetEntity()->GetId()); + } + } + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); @@ -375,15 +373,11 @@ namespace AzFramework { if (index < entitiesToSpawn.size()) { - const AZ::Entity& entityTemplate = *entitiesToSpawn[index]; - - AZ::Entity* clone = request.m_serializeContext->CloneObject(&entityTemplate); + AZ::Entity* clone = CloneSingleEntity(*entitiesToSpawn[index], templateToCloneEntityIdMap, *request.m_serializeContext); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - clone->SetId(AZ::Entity::MakeId()); spawnedEntities.push_back(clone); spawnedEntityIndices.push_back(index); - } } ticket.m_loadAll = false; @@ -396,11 +390,10 @@ namespace AzFramework } // Add to the game context, now the entities are active - AZStd::for_each(ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end(), - [](AZ::Entity* entity) + for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, entity); - }); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + } if (request.m_completionCallback) { @@ -475,39 +468,43 @@ namespace AzFramework // Rebuild the list of entities. ticket.m_spawnedEntities.clear(); const Spawnable::EntityList& entities = request.m_spawnable->GetEntities(); + + // Map keeps track of ids from template (spawnable) to clone (instance) + // Allowing patch ups of fields referring to entityIds outside of a given entity + EntityIdMap templateToCloneEntityIdMap; + if (ticket.m_loadAll) { // The new spawnable may have a different number of entities and since the intent of the user was - // to load every, simply start over. + // to spawn every entity, simply start over. ticket.m_spawnedEntityIndices.clear(); - size_t entitiesToSpawnSize = entities.size(); - - // Map keeps track of ids from template (spawnable) to clone (instance) - // Allowing patch ups of fields referring to entityIds outside of a given entity - EntityIdMap templateToCloneEntityIdMap; templateToCloneEntityIdMap.reserve(entitiesToSpawnSize); - // Mark all indices as spawned for (size_t i = 0; i < entitiesToSpawnSize; ++i) { - const AZ::Entity& entityTemplate = *entities[i]; - - AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, *request.m_serializeContext); - + AZ::Entity* clone = CloneSingleEntity(*entities[i], templateToCloneEntityIdMap, *request.m_serializeContext); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - ticket.m_spawnedEntities.emplace_back(clone); + ticket.m_spawnedEntities.push_back(clone); ticket.m_spawnedEntityIndices.push_back(i); } } else { size_t entitiesSize = entities.size(); + templateToCloneEntityIdMap.reserve(entitiesSize); for (size_t index : ticket.m_spawnedEntityIndices) { - ticket.m_spawnedEntities.push_back( - index < entitiesSize ? SpawnSingleEntity(*entities[index], *request.m_serializeContext) : nullptr); + // 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 + // detected and will result in the incorrect entities being spawned. + if (index < entitiesSize) + { + AZ::Entity* clone = CloneSingleEntity(*entities[index], templateToCloneEntityIdMap, *request.m_serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + ticket.m_spawnedEntities.push_back(clone); + } } } ticket.m_spawnable = AZStd::move(request.m_spawnable); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index e6db19557f..638559f3f1 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -37,7 +37,7 @@ namespace AzFramework AZ_CLASS_ALLOCATOR(SpawnableEntitiesManager, AZ::SystemAllocator, 0); using EntityIdMap = AZStd::unordered_map; - + enum class CommandQueueStatus : bool { HasCommandsLeft, @@ -57,7 +57,7 @@ namespace AzFramework // The following functions are thread safe // - void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnEntitiesOptionalArgs optionalArgs = {}) override; + void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs = {}) override; void SpawnEntities( EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; @@ -114,6 +114,7 @@ namespace AzFramework Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; + bool m_referencePreviouslySpawnedEntities; }; struct DespawnAllEntitiesCommand { @@ -183,12 +184,9 @@ namespace AzFramework CommandQueueStatus ProcessQueue(Queue& queue); - AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, - AZ::SerializeContext& serializeContext); - - AZ::Entity* CloneSingleEntity(const AZ::Entity& entityTemplate, - EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext); - + AZ::Entity* CloneSingleEntity( + const AZ::Entity& entityTemplate, EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext); + bool ProcessRequest(SpawnAllEntitiesCommand& request); bool ProcessRequest(SpawnEntitiesCommand& request); bool ProcessRequest(DespawnAllEntitiesCommand& request); diff --git a/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp b/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp index 8637684b3c..32ad7d8ea9 100644 --- a/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp +++ b/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace UnitTest @@ -81,6 +82,42 @@ namespace UnitTest } } + void CreateRecursiveHierarchy() + { + AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities(); + size_t numElements = entities.size(); + AZ::EntityId parent; + for (size_t i=0; i& entity = entities[i]; + auto component = entity->CreateComponent(); + if (i > 0) + { + component->SetParent(parent); + } + parent = entity->GetId(); + } + } + + void CreateSingleParent() + { + AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities(); + size_t numElements = entities.size(); + if (numElements > 0) + { + AZ::EntityId parent = entities[0]->GetId(); + for (size_t i = 0; i < numElements; ++i) + { + AZStd::unique_ptr& entity = entities[i]; + auto component = entity->CreateComponent(); + if (i > 0) + { + component->SetParent(parent); + } + } + } + } + protected: AZ::Data::Asset* m_spawnableAsset { nullptr }; AzFramework::SpawnableEntitiesManager* m_manager { nullptr }; @@ -104,7 +141,7 @@ namespace UnitTest { spawnedEntitiesCount += entities.size(); }; - AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(callback); m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); @@ -112,6 +149,37 @@ namespace UnitTest EXPECT_EQ(NumEntities, spawnedEntitiesCount); } + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_SetParentOnSpawnedEntities_LineageIsPreserved) + { + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + CreateRecursiveHierarchy(); + + auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + AZ::EntityId parentId; + bool isFirst = true; + for (const AZ::Entity* entity : entities) + { + if (!isFirst) + { + auto transform = entity->GetTransform(); + ASSERT_NE(nullptr, transform); + EXPECT_EQ(parentId, transform->GetParentId()); + } + else + { + isFirst = false; + } + parentId = entity->GetId(); + } + }; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash) { { @@ -126,6 +194,170 @@ namespace UnitTest // SpawnEntities // + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_Call_AllEntitiesSpawned) + { + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + + AZStd::vector indices = { 0, 2, 3, 1 }; + + size_t spawnedEntitiesCount = 0; + auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(NumEntities, spawnedEntitiesCount); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_SpawnTheSameEntity_AllEntitiesSpawned) + { + static constexpr size_t NumEntities = 1; + FillSpawnable(NumEntities); + + AZStd::vector indices = { 0, 0 }; + + size_t spawnedEntitiesCount = 0; + auto callback = + [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(NumEntities * 2, spawnedEntitiesCount); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_MultipleSpawns_AllEntitiesSpawned) + { + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + + AZStd::vector indices = { 0, 2, 3, 1 }; + + size_t spawnedEntitiesCount = 0; + auto callback = + [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnEntities(*m_ticket, indices, optionalArgs); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(NumEntities * 2, spawnedEntitiesCount); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_ReferencesAreRemappedForNewBatch_AllPointToLatestParent) + { + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + CreateSingleParent(); + + AZStd::vector indices = { 0, 1, 2, 3 }; + AZStd::vector parents; + + auto callback = [&parents](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + AZ::EntityId parent = (*entities.begin())->GetId(); + parents.push_back(parent); + auto it = entities.begin(); + ++it; // Skip the first as that is the parent. + for (; it != entities.end(); ++it) + { + AZ::TransformInterface* transform = (*it)->GetTransform(); + ASSERT_NE(nullptr, transform); + ASSERT_EQ(parent, transform->GetParentId()); + } + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + optionalArgs.m_referencePreviouslySpawnedEntities = false; + m_manager->SpawnEntities(*m_ticket, indices, optionalArgs); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_NE(parents[0], parents[1]); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_ReferencesAreRemappedForContinuedBatch_AllPointToLatestParent) + { + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + CreateSingleParent(); + + AZStd::vector indices = { 0, 1, 2, 3 }; + AZStd::vector parents; + + auto callback = + [&parents](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + AZ::EntityId parent = (*entities.begin())->GetId(); + parents.push_back(parent); + auto it = entities.begin(); + ++it; // Skip the first as that is the parent. + for (; it!=entities.end(); ++it) + { + AZ::TransformInterface* transform = (*it)->GetTransform(); + ASSERT_NE(nullptr, transform); + ASSERT_EQ(parent, transform->GetParentId()); + } + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + optionalArgs.m_referencePreviouslySpawnedEntities = true; + m_manager->SpawnEntities(*m_ticket, indices, optionalArgs); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_NE(parents[0], parents[1]); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_ReferencesAreRemappedAcrossBatches_AllPointToLatestParent) + { + FillSpawnable(4); + CreateSingleParent(); + + // Spawn a regular batch but with two parents and store the id of the last entity. This will the parent for the next batch. + AZ::EntityId parent; + auto getParent = [&parent](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + ASSERT_NE(entities.begin(), entities.end()); + parent = (*AZStd::prev(entities.end()))->GetId(); + }; + + AzFramework::SpawnEntitiesOptionalArgs optionalArgsFirstBatch; + optionalArgsFirstBatch.m_completionCallback = AZStd::move(getParent); + optionalArgsFirstBatch.m_referencePreviouslySpawnedEntities = true; + m_manager->SpawnEntities(*m_ticket, {0, 1, 2, 3, 0}, AZStd::move(optionalArgsFirstBatch)); + + // Next, spawn all the entities that have a reference to the parent that was just stored. + auto parentCheck = [&parent](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + for (auto& it : entities) + { + AZ::TransformInterface* transform = it->GetTransform(); + ASSERT_NE(nullptr, transform); + ASSERT_EQ(parent, transform->GetParentId()); + } + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgsSecondBatch; + optionalArgsSecondBatch.m_completionCallback = AZStd::move(parentCheck); + optionalArgsSecondBatch.m_referencePreviouslySpawnedEntities = true; + m_manager->SpawnEntities(*m_ticket, {1, 2, 3}, AZStd::move(optionalArgsSecondBatch)); + + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_DeleteTicketBeforeCall_NoCrash) { { @@ -186,7 +418,7 @@ namespace UnitTest }; m_manager->SpawnAllEntities(*m_ticket); - m_manager->ListEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + m_manager->ListEntities(*m_ticket, AZStd::move(callback)); m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); EXPECT_TRUE(allValidEntityIds); @@ -199,7 +431,7 @@ namespace UnitTest { AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); - m_manager->ListEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + m_manager->ListEntities(ticket, AZStd::move(callback)); } m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } @@ -231,7 +463,7 @@ namespace UnitTest }; m_manager->SpawnAllEntities(*m_ticket); - m_manager->ListIndicesAndEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + m_manager->ListIndicesAndEntities(*m_ticket, AZStd::move(callback)); m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); EXPECT_TRUE(allValidEntityIds); @@ -244,7 +476,7 @@ namespace UnitTest { AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); - m_manager->ListIndicesAndEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + m_manager->ListIndicesAndEntities(ticket, AZStd::move(callback)); } m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } @@ -260,7 +492,7 @@ namespace UnitTest { AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); - m_manager->ClaimEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + m_manager->ClaimEntities(ticket, AZStd::move(callback)); } m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } @@ -276,7 +508,7 @@ namespace UnitTest { AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); - m_manager->Barrier(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + m_manager->Barrier(ticket, AZStd::move(callback)); } m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } @@ -307,12 +539,12 @@ namespace UnitTest defaultPriorityCallId = callCounter++; }; - AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(defaultCallback); optionalArgs.m_priority = AzFramework::SpawnablePriority_Default; m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); - AzFramework::SpawnEntitiesOptionalArgs highPriortyOptionalArgs; + AzFramework::SpawnAllEntitiesOptionalArgs highPriortyOptionalArgs; highPriortyOptionalArgs.m_completionCallback = AZStd::move(highCallback); highPriortyOptionalArgs.m_priority = AzFramework::SpawnablePriority_High; m_manager->SpawnAllEntities(highPriorityTicket, AZStd::move(highPriortyOptionalArgs)); @@ -343,12 +575,12 @@ namespace UnitTest defaultPriorityCallId = callCounter++; }; - AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(defaultCallback); optionalArgs.m_priority = AzFramework::SpawnablePriority_Default; m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); - AzFramework::SpawnEntitiesOptionalArgs highPriortyOptionalArgs; + AzFramework::SpawnAllEntitiesOptionalArgs highPriortyOptionalArgs; highPriortyOptionalArgs.m_completionCallback = AZStd::move(highCallback); highPriortyOptionalArgs.m_priority = AzFramework::SpawnablePriority_High; m_manager->SpawnAllEntities(*m_ticket, AZStd::move(highPriortyOptionalArgs)); diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp index c93c09cfbc..bd1cf40da8 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp @@ -81,8 +81,10 @@ namespace Multiplayer }; m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset); + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_preInsertionCallback = AZStd::move(preInsertionCallback); AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities( - m_netSpawnTicket, AzFramework::SpawnablePriority_Default, { m_netEntityIndex }, preInsertionCallback); + m_netSpawnTicket, { m_netEntityIndex }, AZStd::move(optionalArgs)); } } @@ -90,7 +92,7 @@ namespace Multiplayer { if(m_netSpawnTicket.IsValid()) { - AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket, AzFramework::SpawnablePriority_Default); + AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket); } } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp index 39afa46a19..a7875c2615 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp @@ -134,7 +134,7 @@ namespace ScriptCanvas::Nodeables::Spawning m_spawnBatchSizes.push_back(view.size()); }; - AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_preInsertionCallback = AZStd::move(preSpawnCB); optionalArgs.m_completionCallback = AZStd::move(spawnCompleteCB); AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_spawnTicket, AZStd::move(optionalArgs)); From febf53671eacbe0f67a60feb5317ef93d70f43f5 Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Fri, 4 Jun 2021 11:09:44 -0700 Subject: [PATCH 4/5] Addressed PR feedback. --- .../AzCore/AzCore/Serialization/IdUtils.h | 2 +- .../Spawnable/SpawnableEntitiesInterface.h | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Serialization/IdUtils.h b/Code/Framework/AzCore/AzCore/Serialization/IdUtils.h index 98959fe4bd..15ce299000 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/IdUtils.h +++ b/Code/Framework/AzCore/AzCore/Serialization/IdUtils.h @@ -29,7 +29,7 @@ namespace AZ namespace IdUtils { /** - * \param AllowDuplicates - If true allows the same id to be registered multiple time, + * \param AllowDuplicates - If true allows the same id to be registered multiple times, with the newer value overwriting the stored value. If false, duplicates are not allowed and the first stored value is kept.The default is false. */ diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 2ad1db60a4..d2a5872fc8 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -177,8 +177,8 @@ namespace AzFramework //! Callback that's called after instances of entities have been created, but before they're spawned into the world. This //! gives the opportunity to modify the entities if needed such as injecting additional components or modifying components. EntityPreInsertionCallback m_preInsertionCallback; - //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that - //! made the function call. The returned list of entities contains all the newly created entities. + //! Callback that's called when spawning entities has completed. This can be triggered from a different thread than the one that + //! made the function call to spawn. The returned list of entities contains all the newly created entities. EntitySpawnCallback m_completionCallback; //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Contetx will be used. AZ::SerializeContext* m_serializeContext { nullptr }; @@ -191,8 +191,8 @@ namespace AzFramework //! Callback that's called after instances of entities have been created, but before they're spawned into the world. This //! gives the opportunity to modify the entities if needed such as injecting additional components or modifying components. EntityPreInsertionCallback m_preInsertionCallback; - //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that - //! made the function call. The returned list of entities contains all the newly created entities. + //! Callback that's called when spawning entities has completed. This can be triggered from a different thread than the one that + //! made the function call to spawn. The returned list of entities contains all the newly created entities. EntitySpawnCallback m_completionCallback; //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Contetx will be used. AZ::SerializeContext* m_serializeContext{ nullptr }; @@ -207,8 +207,8 @@ namespace AzFramework struct DespawnAllEntitiesOptionalArgs final { - //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that - //! made the function call. The returned list of entities contains all the newly created entities. + //! Callback that's called when despawning entities has completed. This can be triggered from a different thread than the one that + //! made the function call to despawn. The returned list of entities contains all the newly created entities. EntityDespawnCallback m_completionCallback; //! The priority at which this call will be executed. SpawnablePriority m_priority { SpawnablePriority_Default }; @@ -216,10 +216,10 @@ namespace AzFramework struct ReloadSpawnableOptionalArgs final { - //! Callback that's called when spawning entities has completed. This can be called from a different thread than the one that - //! made the function call. The returned list of entities contains all the newly created entities. + //! Callback that's called when respawning entities has completed. This can be triggered from a different thread than the one that + //! made the function call to respawn. The returned list of entities contains all the newly created entities. ReloadSpawnableCallback m_completionCallback; - //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Contetx will be used. + //! The Serialize Context used to clone entities with. If this is not provided the global Serialize Context will be used. AZ::SerializeContext* m_serializeContext { nullptr }; //! The priority at which this call will be executed. SpawnablePriority m_priority { SpawnablePriority_Default }; @@ -268,32 +268,32 @@ namespace AzFramework //! Spawn instances of all entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. - //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs + //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs. virtual void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs = {}) = 0; //! Spawn instances of some entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. //! @param priority The priority at which this call will be executed. //! @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 SpawnAllEntitiesOptionalArgs + //! @param optionalArgs Optional additional arguments, see SpawnEntitiesOptionalArgs. virtual void SpawnEntities( 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 priority The priority at which this call will be executed. - //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs + //! @param optionalArgs Optional additional arguments, see DespawnAllEntitiesOptionalArgs. virtual void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) = 0; //! Removes all entities in the provided list from the environment and reconstructs the entities from the provided spawnable. //! @param ticket Holds the information on the entities to reload. //! @param priority The priority at which this call will be executed. //! @param spawnable The spawnable that will replace the existing spawnable. Both need to have the same asset id. - //! @param optionalArgs Optional additional arguments, see SpawnAllEntitiesOptionalArgs + //! @param optionalArgs Optional additional arguments, see ReloadSpawnableOptionalArgs. virtual void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) = 0; //! List all entities that are spawned using this ticket. //! @param ticket Only the entities associated with this ticket will be listed. - //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to list the entities on. + //! @param optionalArgs Optional additional arguments, see ListEntitiesOptionalArgs. virtual void ListEntities( EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) = 0; //! List all entities that are spawned using this ticket with their spawnable index. @@ -303,23 +303,23 @@ namespace AzFramework //! the same index may appear multiple times as there are no restriction on how many instance of a specific entity can be //! created. //! @param ticket Only the entities associated with this ticket will be listed. - //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to list the entities and indices on. + //! @param optionalArgs Optional additional arguments, see ListEntitiesOptionalArgs. virtual void ListIndicesAndEntities( EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) = 0; //! Claim all entities that are spawned using this ticket. Ownership of the entities is transferred from the ticket to the //! caller through the callback. After this call the ticket will have no entities associated with it. The caller of //! this function will need to manage the entities after this call. //! @param ticket Only the entities associated with this ticket will be released. - //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to transfer the entities through. + //! @param optionalArgs Optional additional arguments, see ClaimEntitiesOptionalArgs. virtual void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) = 0; //! Blocks until all operations made on the provided ticket before the barrier call have completed. //! @param ticket The ticket to monitor. - //! @param priority The priority at which this call will be executed. //! @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; //! Register a handler for OnSpawned events. From 55a46806590d04c45b3203fa27b5216536677100 Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Fri, 4 Jun 2021 14:10:30 -0700 Subject: [PATCH 5/5] Fixed Multiplayer unit tests. The multiplayer unit tests created a SpawnableSystemComponent without an application to provide the Serialize Context. This caused an assert which failed the unit tests. Since the entity spawning system doesn't seem to be directly used the component was removed. --- Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp b/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp index 6ace4db592..7b09b65de4 100644 --- a/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp +++ b/Gems/Multiplayer/Code/Tests/MultiplayerSystemTests.cpp @@ -30,7 +30,6 @@ namespace UnitTest { SetupAllocator(); AZ::NameDictionary::Create(); - m_spawnableComponent = new AzFramework::SpawnableSystemComponent(); m_netComponent = new AzNetworking::NetworkingSystemComponent(); m_mpComponent = new Multiplayer::MultiplayerSystemComponent(); @@ -46,7 +45,6 @@ namespace UnitTest { delete m_mpComponent; delete m_netComponent; - delete m_spawnableComponent; AZ::NameDictionary::Destroy(); TeardownAllocator(); } @@ -76,7 +74,6 @@ namespace UnitTest AzNetworking::NetworkingSystemComponent* m_netComponent = nullptr; Multiplayer::MultiplayerSystemComponent* m_mpComponent = nullptr; - AzFramework::SpawnableSystemComponent* m_spawnableComponent = nullptr; }; TEST_F(MultiplayerSystemTests, TestInitEvent)