From 619f31572e5b70dce5eea6abf7dfed5c19d34699 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Wed, 15 Sep 2021 13:26:55 -0700 Subject: [PATCH 1/7] Enable destroying game entities spawned with a spawnable Signed-off-by: srikappa-amzn --- .../AzFramework/Entity/GameEntityContextBus.h | 18 ----- .../Entity/GameEntityContextComponent.cpp | 50 ++++---------- .../Entity/GameEntityContextComponent.h | 5 -- .../Spawnable/SpawnableEntitiesInterface.h | 8 +++ .../Spawnable/SpawnableEntitiesManager.cpp | 67 +++++++++++++++---- .../Spawnable/SpawnableEntitiesManager.h | 14 +++- .../Spawnable/SpawnableSystemComponent.h | 2 + .../Spawnable/SpawnedEntityTicketMapper.cpp | 43 ++++++++++++ .../Spawnable/SpawnedEntityTicketMapper.h | 37 ++++++++++ .../SpawnedEntityTicketMapperInterface.h | 24 +++++++ .../AzFramework/azframework_files.cmake | 3 + 11 files changed, 194 insertions(+), 77 deletions(-) create mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp create mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h create mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextBus.h b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextBus.h index b5ca37df6e..f8e4dfa98b 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextBus.h +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextBus.h @@ -91,15 +91,6 @@ namespace AzFramework */ virtual void DestroyGameEntity(const AZ::EntityId& /*id*/) = 0; - /** - * Destroys an entity only in slice mode (when prefabs are disabled). This request is only added as a stop-gap solution - * to prevent the editor from crashing when prefabs are enabled and must only be called through the BehaviorContext binding - * for 'DestroyGameEntity'. No code should be written to directly call this method. This will be removed soon. - * - * @param id The ID of the entity to destroy. - */ - virtual void DestroyGameEntityOnlyInSliceMode(const AZ::EntityId& /*id*/) = 0; - /** * Destroys an entity and all of its descendants. * The entity and its descendants are immediately deactivated and will be @@ -108,15 +99,6 @@ namespace AzFramework */ virtual void DestroyGameEntityAndDescendants(const AZ::EntityId& /*id*/) = 0; - /** - * Destroys an entity and its descendants only in slice mode (when prefabs are disabled). This request is only added as a stop-gap - * solution to prevent the editor from crashing when prefabs are enabled and must only be called through the BehaviorContext - * binding for 'DestroyGameEntityAndDescendants'.No code should be written to directly call this method. This will be removed soon. - * - * @param id The ID of the entity to destroy. - */ - virtual void DestroyGameEntityAndDescendantsOnlyInSliceMode(const AZ::EntityId& /*id*/) = 0; - /** * Activates the game entity. * @param id The ID of the entity to activate. diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp index a97281b9c1..903bad91bf 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp @@ -11,9 +11,10 @@ #include #include #include +#include #include #include -#include +#include #include "GameEntityContextComponent.h" @@ -47,9 +48,9 @@ namespace AzFramework ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Event("CreateGameEntity", &GameEntityContextRequestBus::Events::CreateGameEntityForBehaviorContext) ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("DestroyGameEntity", &GameEntityContextRequestBus::Events::DestroyGameEntityOnlyInSliceMode) + ->Event("DestroyGameEntity", &GameEntityContextRequestBus::Events::DestroyGameEntity) ->Event( - "DestroyGameEntityAndDescendants", &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendantsOnlyInSliceMode) + "DestroyGameEntityAndDescendants", &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants) ->Event("ActivateGameEntity", &GameEntityContextRequestBus::Events::ActivateGameEntity) ->Event("DeactivateGameEntity", &GameEntityContextRequestBus::Events::DeactivateGameEntity) ->Attribute(AZ::ScriptCanvasAttributes::DeactivatesInputEntity, true) @@ -249,23 +250,6 @@ namespace AzFramework DestroyGameEntityInternal(id, false); } - void GameEntityContextComponent::DestroyGameEntityOnlyInSliceMode(const AZ::EntityId& id) - { - bool isPrefabSystemEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); - if (!isPrefabSystemEnabled) - { - DestroyGameEntityInternal(id, false); - } - else - { - AZ_Error( - "GameEntityContextComponent", false, - "Destroying a game entity is temporarily disabled until the Spawnable system can support this."); - } - } - //========================================================================= // GameEntityContextComponent::DestroyGameEntityAndDescendantsById //========================================================================= @@ -274,24 +258,6 @@ namespace AzFramework DestroyGameEntityInternal(id, true); } - - void GameEntityContextComponent::DestroyGameEntityAndDescendantsOnlyInSliceMode(const AZ::EntityId& id) - { - bool isPrefabSystemEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); - if (!isPrefabSystemEnabled) - { - DestroyGameEntityInternal(id, true); - } - else - { - AZ_Error( - "GameEntityContextComponent", false, - "Destroying a game entity and its descendants is temporarily disabled until the Spawnable system can support this."); - } - } - //========================================================================= // GameEntityContextComponent::DestroyGameEntityInternal //========================================================================= @@ -319,6 +285,14 @@ namespace AzFramework EBUS_EVENT_RESULT(currentEntity, AZ::ComponentApplicationBus, FindEntity, *entityIdIter); if (currentEntity) { + bool isPrefabSystemEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + if (isPrefabSystemEnabled) + { + AZ::Interface::Get()->RemoveSpawnedEntity(currentEntity->GetId()); + } + if (currentEntity->GetState() == AZ::Entity::State::Active) { // Deactivate the entity, we'll destroy it as soon as it is safe. diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.h b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.h index 831067d728..56a53f3c50 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.h @@ -90,11 +90,6 @@ namespace AzFramework } private: - ////////////////////////////////////////////////////////////////////////// - // GameEntityContextRequestBus - void DestroyGameEntityOnlyInSliceMode(const AZ::EntityId&) override; - void DestroyGameEntityAndDescendantsOnlyInSliceMode(const AZ::EntityId&) override; - ///////////////////////////////////////////////////////////////////////// AzFramework::EntityVisibilityBoundsUnionSystem m_entityVisibilityBoundsUnionSystem; }; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 4b09dcbc75..2ce4158229 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -235,6 +235,12 @@ namespace AzFramework SpawnablePriority m_priority{ SpawnablePriority_Default }; }; + struct ClaimEntityOptionalArgs 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. @@ -314,6 +320,8 @@ namespace AzFramework virtual void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) = 0; + virtual void ClaimEntity(AZ::EntityId entityId, void* ticket, ClaimEntityOptionalArgs optionalArgs = {}) = 0; + //! Blocks until all operations made on the provided ticket before the barrier call have completed. //! @param ticket The ticket to monitor. //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 5fa072a451..eae71b5ea0 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -17,17 +17,18 @@ #include #include #include +#include namespace AzFramework { template - void SpawnableEntitiesManager::QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request) + void SpawnableEntitiesManager::QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request) { - request.m_ticket = &GetTicketPayload(ticket); + request.m_ticket = ticket; Queue& queue = priority <= m_highPriorityThreshold ? m_highPriorityQueue : m_regularPriorityQueue; { AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); - request.m_requestId = GetTicketPayload(ticket).m_nextRequestId++; + request.m_requestId = ticket->m_nextRequestId++; queue.m_pendingRequest.push(AZStd::move(request)); } } @@ -56,7 +57,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); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::SpawnEntities( @@ -72,7 +73,7 @@ namespace AzFramework 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)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs) @@ -82,7 +83,7 @@ namespace AzFramework DespawnAllEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ReloadSpawnable( @@ -96,7 +97,7 @@ namespace AzFramework queueEntry.m_serializeContext = optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListEntities( @@ -108,7 +109,7 @@ namespace AzFramework ListEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListIndicesAndEntities( @@ -120,7 +121,7 @@ namespace AzFramework ListIndicesEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ClaimEntities( @@ -132,7 +133,18 @@ namespace AzFramework ClaimEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + } + + void SpawnableEntitiesManager::ClaimEntity( + AZ::EntityId entityId, void* ticket, + ClaimEntityOptionalArgs optionalArgs) + { + AZ_Assert(ticket == nullptr, "Ticket provided to ClaimEntity is invalid."); + + ClaimEntityCommand queueEntry; + queueEntry.m_entityId = entityId; + QueueRequest(reinterpret_cast(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs) @@ -143,7 +155,7 @@ namespace AzFramework BarrierCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(completionCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus @@ -340,6 +352,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + AZ::Interface::Get()->AddSpawnedEntity((*it)->GetId(), &ticket); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -421,6 +434,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + AZ::Interface::Get()->AddSpawnedEntity((*it)->GetId(), &ticket); } if (request.m_completionCallback) @@ -448,7 +462,7 @@ namespace AzFramework if (entity != nullptr) { GameEntityContextRequestBus::Broadcast( - &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId()); + &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } } @@ -483,7 +497,7 @@ namespace AzFramework if (entity != nullptr) { GameEntityContextRequestBus::Broadcast( - &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId()); + &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } } @@ -609,6 +623,31 @@ namespace AzFramework } } + bool SpawnableEntitiesManager::ProcessRequest(ClaimEntityCommand& request) + { + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) + { + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector::size_type entityIndex = 0; + for (entityIndex = 0; entityIndex < spawnedEntities.size(); entityIndex++) + { + if (spawnedEntities[entityIndex]->GetId() == request.m_entityId) + { + spawnedEntities.erase(spawnedEntities.begin() + entityIndex); + ticket.m_spawnedEntityIndices.erase(ticket.m_spawnedEntityIndices.begin() + entityIndex); + ticket.m_currentRequestId++; + return true; + } + } + return false; + } + else + { + return false; + } + } + bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) { Ticket& ticket = *request.m_ticket; @@ -637,7 +676,7 @@ namespace AzFramework if (entity != nullptr) { GameEntityContextRequestBus::Broadcast( - &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId()); + &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } } delete request.m_ticket; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 69985f8aa9..89181ea179 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -66,6 +66,8 @@ namespace AzFramework EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override; void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override; + void ClaimEntity( + AZ::EntityId entityId, void* ticket, ClaimEntityOptionalArgs optionalArgs = {}) override; void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; @@ -162,6 +164,13 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; + struct ClaimEntityCommand + { + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; + AZ::EntityId m_entityId; + }; struct BarrierCommand { BarrierCallback m_completionCallback; @@ -177,7 +186,7 @@ namespace AzFramework using Requests = AZStd::variant< SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand, - ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>; + ListIndicesEntitiesCommand, ClaimEntitiesCommand, ClaimEntityCommand, BarrierCommand, DestroyTicketCommand>; struct Queue { @@ -187,7 +196,7 @@ namespace AzFramework }; template - void QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request); + void QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request); AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) override; void DestroyTicket(void* ticket) override; @@ -203,6 +212,7 @@ namespace AzFramework bool ProcessRequest(ListEntitiesCommand& request); bool ProcessRequest(ListIndicesEntitiesCommand& request); bool ProcessRequest(ClaimEntitiesCommand& request); + bool ProcessRequest(ClaimEntityCommand& request); bool ProcessRequest(BarrierCommand& request); bool ProcessRequest(DestroyTicketCommand& request); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h index 5b5fb1b7ee..33e0a29704 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace AzFramework { @@ -90,6 +91,7 @@ namespace AzFramework void LoadRootSpawnableFromSettingsRegistry(); SpawnableAssetHandler m_assetHandler; + SpawnedEntityTicketMapper m_spawnedEntityTicketMapper; SpawnableEntitiesManager m_entitiesManager; SpawnableEntitiesContainer m_rootSpawnableContainer; AZ::SettingsRegistryInterface::NotifyEventHandler m_registryChangeHandler; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp new file mode 100644 index 0000000000..4179f3c111 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace AzFramework +{ + SpawnedEntityTicketMapper::SpawnedEntityTicketMapper() + { + AZ::Interface::Register(this); + } + + SpawnedEntityTicketMapper::~SpawnedEntityTicketMapper() + { + AZ::Interface::Unregister(this); + } + + void SpawnedEntityTicketMapper::RemoveSpawnedEntity(AZ::EntityId entityId) + { + auto spawnedGameEntitiesIterator = m_spawnedEntities.find(entityId); + if (spawnedGameEntitiesIterator != m_spawnedEntities.end()) + { + SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); + AZ_Assert(spawnableEntitiesInterface == nullptr, "SpawnableEntitiesInterface is not found."); + spawnableEntitiesInterface->ClaimEntity( + spawnedGameEntitiesIterator->first, spawnedGameEntitiesIterator->second); + m_spawnedEntities.erase(entityId); + } + } + + void SpawnedEntityTicketMapper::AddSpawnedEntity(AZ::EntityId entityId, void* ticket) + { + m_spawnedEntities.emplace(entityId, ticket); + } + +} + diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h new file mode 100644 index 0000000000..36582a05b2 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace AzFramework +{ + class SpawnedEntityTicketMapper + : public SpawnedEntityTicketMapperInterface + { + public: + AZ_RTTI(SpawnedEntityTicketMapper, "{5C803604-E949-44F4-94A4-F835E5794C77}", SpawnedEntityTicketMapperInterface); + + SpawnedEntityTicketMapper(); + ~SpawnedEntityTicketMapper(); + + //! Removes the entityId from the spawned entities map. + //! @param entityId The id of the entity to remove. + void RemoveSpawnedEntity(AZ::EntityId entityId) override; + + //! Adds the entityId,ticket pair to the spawned entities map. + //! @param entityId The id of the entity to add. + //! @param ticket The ticket pointer to add. + void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) override; + private: + AZStd::unordered_map m_spawnedEntities; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h new file mode 100644 index 0000000000..8f0bd96dbe --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AzFramework +{ + class SpawnedEntityTicketMapperInterface + { + public: + AZ_RTTI(SpawnedEntityTicketMapperInterface, "{D407E96B-635C-44F2-B089-084EBAB8B036}"); + + virtual void RemoveSpawnedEntity(AZ::EntityId entityId) = 0; + virtual void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) = 0; + }; +} diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index 87cf29ffec..b486596e2a 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -294,6 +294,9 @@ set(FILES Spawnable/SpawnableMonitor.cpp Spawnable/SpawnableSystemComponent.h Spawnable/SpawnableSystemComponent.cpp + Spawnable/SpawnedEntityTicketMapper.h + Spawnable/SpawnedEntityTicketMapper.cpp + Spawnable/SpawnedEntityTicketMapperInterface.h Terrain/TerrainDataRequestBus.h Terrain/TerrainDataRequestBus.cpp Thermal/ThermalInfo.h From 2cbd72e80132c5b0c8efce567a3317592ef0138c Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Wed, 15 Sep 2021 14:57:54 -0700 Subject: [PATCH 2/7] Improved comments and minor changes to accessing interfaces Signed-off-by: srikappa-amzn --- .../Entity/GameEntityContextComponent.cpp | 5 ++++- .../Spawnable/SpawnableEntitiesManager.cpp | 15 +++++++-------- .../Spawnable/SpawnableEntitiesManager.h | 4 +++- .../Spawnable/SpawnableSystemComponent.h | 2 -- .../Spawnable/SpawnedEntityTicketMapper.cpp | 5 +++-- .../Spawnable/SpawnedEntityTicketMapper.h | 3 ++- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp index 903bad91bf..694ed83765 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp @@ -290,7 +290,10 @@ namespace AzFramework isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); if (isPrefabSystemEnabled) { - AZ::Interface::Get()->RemoveSpawnedEntity(currentEntity->GetId()); + SpawnedEntityTicketMapperInterface* spawnedEntityTicketMapperInterface = + AZ::Interface::Get(); + AZ_Assert(spawnedEntityTicketMapperInterface != nullptr, "SpawnedEntityTicketMapperInterface is not found."); + spawnedEntityTicketMapperInterface->RemoveSpawnedEntity(currentEntity->GetId()); } if (currentEntity->GetState() == AZ::Entity::State::Active) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index eae71b5ea0..2c317bc33f 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -17,7 +17,6 @@ #include #include #include -#include namespace AzFramework { @@ -352,7 +351,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - AZ::Interface::Get()->AddSpawnedEntity((*it)->GetId(), &ticket); + m_spawnedEntityTicketMapper.AddSpawnedEntity((*it)->GetId(), &ticket); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -434,7 +433,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - AZ::Interface::Get()->AddSpawnedEntity((*it)->GetId(), &ticket); + m_spawnedEntityTicketMapper.AddSpawnedEntity((*it)->GetId(), &ticket); } if (request.m_completionCallback) @@ -625,18 +624,18 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(ClaimEntityCommand& request) { - Ticket& ticket = *request.m_ticket; - if (request.m_requestId == ticket.m_currentRequestId) + Ticket* ticket = request.m_ticket; + if (request.m_requestId == ticket->m_currentRequestId) { - AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntities = ticket->m_spawnedEntities; AZStd::vector::size_type entityIndex = 0; for (entityIndex = 0; entityIndex < spawnedEntities.size(); entityIndex++) { if (spawnedEntities[entityIndex]->GetId() == request.m_entityId) { spawnedEntities.erase(spawnedEntities.begin() + entityIndex); - ticket.m_spawnedEntityIndices.erase(ticket.m_spawnedEntityIndices.begin() + entityIndex); - ticket.m_currentRequestId++; + ticket->m_spawnedEntityIndices.erase(ticket->m_spawnedEntityIndices.begin() + entityIndex); + ticket->m_currentRequestId++; return true; } } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 89181ea179..e5a0760010 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -16,6 +16,7 @@ #include #include #include +#include namespace AZ { @@ -167,7 +168,6 @@ namespace AzFramework struct ClaimEntityCommand { Ticket* m_ticket; - EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; AZ::EntityId m_entityId; }; @@ -234,6 +234,8 @@ namespace AzFramework //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured //! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold". SpawnablePriority m_highPriorityThreshold { 64 }; + private: + SpawnedEntityTicketMapper m_spawnedEntityTicketMapper; }; AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h index 33e0a29704..5b5fb1b7ee 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h @@ -18,7 +18,6 @@ #include #include #include -#include namespace AzFramework { @@ -91,7 +90,6 @@ namespace AzFramework void LoadRootSpawnableFromSettingsRegistry(); SpawnableAssetHandler m_assetHandler; - SpawnedEntityTicketMapper m_spawnedEntityTicketMapper; SpawnableEntitiesManager m_entitiesManager; SpawnableEntitiesContainer m_rootSpawnableContainer; AZ::SettingsRegistryInterface::NotifyEventHandler m_registryChangeHandler; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp index 4179f3c111..3a6b40f5a9 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp @@ -13,6 +13,9 @@ namespace AzFramework { SpawnedEntityTicketMapper::SpawnedEntityTicketMapper() { + spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); + AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found."); + AZ::Interface::Register(this); } @@ -26,8 +29,6 @@ namespace AzFramework auto spawnedGameEntitiesIterator = m_spawnedEntities.find(entityId); if (spawnedGameEntitiesIterator != m_spawnedEntities.end()) { - SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); - AZ_Assert(spawnableEntitiesInterface == nullptr, "SpawnableEntitiesInterface is not found."); spawnableEntitiesInterface->ClaimEntity( spawnedGameEntitiesIterator->first, spawnedGameEntitiesIterator->second); m_spawnedEntities.erase(entityId); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h index 36582a05b2..45e701db4e 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h @@ -23,7 +23,7 @@ namespace AzFramework SpawnedEntityTicketMapper(); ~SpawnedEntityTicketMapper(); - //! Removes the entityId from the spawned entities map. + //! Removes the entityId from the spawned entities map if present. //! @param entityId The id of the entity to remove. void RemoveSpawnedEntity(AZ::EntityId entityId) override; @@ -32,6 +32,7 @@ namespace AzFramework //! @param ticket The ticket pointer to add. void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) override; private: + SpawnableEntitiesDefinition* spawnableEntitiesInterface = nullptr; AZStd::unordered_map m_spawnedEntities; }; } // namespace AzFramework From a4af34423a4b0e85ac4a6fc80408e9e06f120ba2 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Thu, 16 Sep 2021 00:03:00 -0700 Subject: [PATCH 3/7] Use iter_swap instead of erase Signed-off-by: srikappa-amzn --- .../Spawnable/SpawnableEntitiesManager.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 2c317bc33f..92d461819b 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -628,18 +628,16 @@ namespace AzFramework if (request.m_requestId == ticket->m_currentRequestId) { AZStd::vector& spawnedEntities = ticket->m_spawnedEntities; - AZStd::vector::size_type entityIndex = 0; - for (entityIndex = 0; entityIndex < spawnedEntities.size(); entityIndex++) + for (auto entityIterator = spawnedEntities.begin(); entityIterator != spawnedEntities.end(); ++entityIterator) { - if (spawnedEntities[entityIndex]->GetId() == request.m_entityId) + if ((*entityIterator)->GetId() == request.m_entityId) { - spawnedEntities.erase(spawnedEntities.begin() + entityIndex); - ticket->m_spawnedEntityIndices.erase(ticket->m_spawnedEntityIndices.begin() + entityIndex); - ticket->m_currentRequestId++; + AZStd::iter_swap(entityIterator, spawnedEntities.rbegin()); + spawnedEntities.pop_back(); return true; } } - return false; + return true; } else { From 3c882230edd196e2803806cff201c94b3c5ae147 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Thu, 23 Sep 2021 15:25:15 -0700 Subject: [PATCH 4/7] Store the spawn ticket id inside entity instead of maintaining a map of entity to ticket Signed-off-by: srikappa-amzn --- .../AzCore/AzCore/Component/Entity.cpp | 10 ++ .../AzCore/AzCore/Component/Entity.h | 5 + .../Entity/GameEntityContextComponent.cpp | 21 ++- .../Spawnable/SpawnableEntitiesInterface.cpp | 22 ++- .../Spawnable/SpawnableEntitiesInterface.h | 36 +++-- .../Spawnable/SpawnableEntitiesManager.cpp | 130 +++++++++++------- .../Spawnable/SpawnableEntitiesManager.h | 38 +++-- .../Spawnable/SpawnedEntityTicketMapper.cpp | 44 ------ .../Spawnable/SpawnedEntityTicketMapper.h | 38 ----- .../SpawnedEntityTicketMapperInterface.h | 24 ---- .../AzFramework/azframework_files.cmake | 3 - 11 files changed, 179 insertions(+), 192 deletions(-) delete mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp delete mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h delete mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.cpp b/Code/Framework/AzCore/AzCore/Component/Entity.cpp index 00c1895261..4f55c360cd 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.cpp +++ b/Code/Framework/AzCore/AzCore/Component/Entity.cpp @@ -655,6 +655,16 @@ namespace AZ m_stateEvent.Signal(oldState, m_state); } + void Entity::SetSpawnTicketId(u32 spawnTicketId) + { + m_spawnTicketId = AZStd::move(spawnTicketId); + } + + u32 Entity::GetSpawnTicketId() const + { + return m_spawnTicketId; + } + void Entity::OnNameChanged() const { EBUS_EVENT_ID(GetId(), EntityBus, OnEntityNameChanged, m_name); diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.h b/Code/Framework/AzCore/AzCore/Component/Entity.h index 7ec63a56ac..97cec50946 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.h +++ b/Code/Framework/AzCore/AzCore/Component/Entity.h @@ -133,6 +133,9 @@ namespace AZ //! @return The state of the entity. For example, the entity has been initialized, the entity is active, and so on. State GetState() const { return m_state; } + u32 GetSpawnTicketId() const; + void SetSpawnTicketId(u32); + //! Connects an entity state event handler to the entity. //! All state changes will be signaled through this event. //! @param handler reference to the EntityStateEvent handler to attach to the entities state event. @@ -411,6 +414,8 @@ namespace AZ //! A user-friendly name for the entity. This makes error messages easier to read. AZStd::string m_name; + u32 m_spawnTicketId = 0; + //! The state of the entity. State m_state; diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp index 694ed83765..f2571c7549 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include "GameEntityContextComponent.h" @@ -290,10 +290,21 @@ namespace AzFramework isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); if (isPrefabSystemEnabled) { - SpawnedEntityTicketMapperInterface* spawnedEntityTicketMapperInterface = - AZ::Interface::Get(); - AZ_Assert(spawnedEntityTicketMapperInterface != nullptr, "SpawnedEntityTicketMapperInterface is not found."); - spawnedEntityTicketMapperInterface->RemoveSpawnedEntity(currentEntity->GetId()); + if (currentEntity->GetSpawnTicketId() > 0) + { + SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); + AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found."); + spawnableEntitiesInterface->GetEntitySpawnTicket( + currentEntity->GetSpawnTicketId(), + [spawnableEntitiesInterface, currentEntity](EntitySpawnTicket* entitySpawnTicket) + { + if (entitySpawnTicket != nullptr) + { + spawnableEntitiesInterface->DespawnEntity(currentEntity->GetId(), *entitySpawnTicket); + } + }); + return; + } } if (currentEntity->GetState() == AZ::Entity::State::Active) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp index 617eb3a0b9..53bffd0d26 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp @@ -227,10 +227,16 @@ namespace AzFramework EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs) : m_payload(rhs.m_payload) - , m_id(rhs.m_id) { + auto manager = SpawnableEntitiesInterface::Get(); + AZ_Assert(manager, "SpawnableEntitiesInterface has no implementation."); rhs.m_payload = nullptr; + Id previousId = m_id; + m_id = rhs.m_id; rhs.m_id = 0; + AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); + manager->m_entitySpawnTicketMap.erase(previousId); + manager->m_entitySpawnTicketMap.insert_or_assign(rhs.m_id, this); } EntitySpawnTicket::EntitySpawnTicket(AZ::Data::Asset spawnable) @@ -240,6 +246,8 @@ namespace AzFramework AZStd::pair result = manager->CreateTicket(AZStd::move(spawnable)); m_id = result.first; m_payload = result.second; + AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); + manager->m_entitySpawnTicketMap.insert_or_assign(m_id, this); } EntitySpawnTicket::~EntitySpawnTicket() @@ -250,6 +258,8 @@ namespace AzFramework AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); manager->DestroyTicket(m_payload); m_payload = nullptr; + AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); + manager->m_entitySpawnTicketMap.erase(m_id); m_id = 0; } } @@ -258,17 +268,23 @@ namespace AzFramework { if (this != &rhs) { + auto manager = SpawnableEntitiesInterface::Get(); + AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); if (m_payload) { - auto manager = SpawnableEntitiesInterface::Get(); - AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); manager->DestroyTicket(m_payload); } + + Id previousId = m_id; m_id = rhs.m_id; rhs.m_id = 0; m_payload = rhs.m_payload; rhs.m_payload = nullptr; + + AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); + manager->m_entitySpawnTicketMap.erase(previousId); + manager->m_entitySpawnTicketMap.insert_or_assign(m_id, this); } return *this; } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 2ce4158229..91b2eafc54 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -140,7 +140,7 @@ namespace AzFramework public: friend class SpawnableEntitiesDefinition; - using Id = uint64_t; + using Id = uint32_t; EntitySpawnTicket() = default; EntitySpawnTicket(const EntitySpawnTicket&) = delete; @@ -162,6 +162,7 @@ namespace AzFramework using EntitySpawnCallback = AZStd::function; using EntityPreInsertionCallback = AZStd::function; using EntityDespawnCallback = AZStd::function; + using GetEntitySpawnTicketCallback = AZStd::function; using ReloadSpawnableCallback = AZStd::function; using ListEntitiesCallback = AZStd::function; using ListIndicesEntitiesCallback = AZStd::function; @@ -206,12 +207,21 @@ namespace AzFramework struct DespawnAllEntitiesOptionalArgs final { //! 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. + //! made the function call to despawn. EntityDespawnCallback m_completionCallback; //! The priority at which this call will be executed. SpawnablePriority m_priority { SpawnablePriority_Default }; }; + struct DespawnEntityOptionalArgs final + { + //! Callback that's called when despawning entity has completed. This can be triggered from a different thread than the one that + //! made the function call to despawn. + EntityDespawnCallback m_completionCallback; + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + }; + struct ReloadSpawnableOptionalArgs final { //! Callback that's called when respawning entities has completed. This can be triggered from a different thread than the one that @@ -235,12 +245,6 @@ namespace AzFramework SpawnablePriority m_priority{ SpawnablePriority_Default }; }; - struct ClaimEntityOptionalArgs 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. @@ -283,9 +287,18 @@ namespace AzFramework 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 DespawnAllEntitiesOptionalArgs. virtual void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) = 0; + //! Removes the entity with the provided id from the spawned list of entities. + //! @param entityId the id of entity to despawn. + //! @param ticket The ticket previously used to spawn entities with. + //! @param optionalArgs Optional additional arguments, see DespawnEntityOptionalArgs. + virtual void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) = 0; + //! Gets the EntitySpawnTicket associated with the entitySpawnTicketId. + //! @param entitySpawnTicketId the id of EntitySpawnTicket to get. + //! @param getEntitySpawnTicketCallback The callback to execute upon fetching the ticket. + virtual void GetEntitySpawnTicket( + EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) = 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. @@ -320,8 +333,6 @@ namespace AzFramework virtual void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) = 0; - virtual void ClaimEntity(AZ::EntityId entityId, void* ticket, ClaimEntityOptionalArgs optionalArgs = {}) = 0; - //! Blocks until all operations made on the provided ticket before the barrier call have completed. //! @param ticket The ticket to monitor. //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. @@ -355,6 +366,9 @@ namespace AzFramework { return reinterpret_cast(ticket->m_payload); } + + AZStd::unordered_map m_entitySpawnTicketMap; + AZStd::mutex m_entitySpawnTicketMapMutex; }; using SpawnableEntitiesInterface = AZ::Interface; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 92d461819b..c75a5fe834 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -21,13 +21,13 @@ namespace AzFramework { template - void SpawnableEntitiesManager::QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request) + void SpawnableEntitiesManager::QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request) { - request.m_ticket = ticket; + request.m_ticket = &GetTicketPayload(ticket); Queue& queue = priority <= m_highPriorityThreshold ? m_highPriorityQueue : m_regularPriorityQueue; { AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); - request.m_requestId = ticket->m_nextRequestId++; + request.m_requestId = GetTicketPayload(ticket).m_nextRequestId++; queue.m_pendingRequest.push(AZStd::move(request)); } } @@ -56,7 +56,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); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::SpawnEntities( @@ -72,7 +72,7 @@ namespace AzFramework queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); queueEntry.m_referencePreviouslySpawnedEntities = optionalArgs.m_referencePreviouslySpawnedEntities; - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs) @@ -82,7 +82,36 @@ namespace AzFramework DespawnAllEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + } + + void SpawnableEntitiesManager::DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs) + { + AZ_Assert(ticket.IsValid(), "Ticket provided to DespawnEntity hasn't been initialized."); + + DespawnEntityCommand queueEntry; + queueEntry.m_ticketId = ticket.GetId(); + queueEntry.m_entityId = entityId; + queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + } + + void SpawnableEntitiesManager::GetEntitySpawnTicket( + EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) + { + if (entitySpawnTicketId == 0) + { + AZ_Error("Spawnable", false, "Ticket id provided to GetEntitySpawnTicket is invalid."); + return; + } + + auto entitySpawnTicketIterator = m_entitySpawnTicketMap.find(entitySpawnTicketId); + if (entitySpawnTicketIterator == m_entitySpawnTicketMap.end()) + { + AZ_Error("Spawnable", false, "The EntitySpawnTicket corresponding to id '%lu' cannot be found", entitySpawnTicketId); + return; + } + getEntitySpawnTicketCallback(entitySpawnTicketIterator->second); } void SpawnableEntitiesManager::ReloadSpawnable( @@ -96,7 +125,7 @@ namespace AzFramework queueEntry.m_serializeContext = optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListEntities( @@ -108,7 +137,7 @@ namespace AzFramework ListEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListIndicesAndEntities( @@ -120,7 +149,7 @@ namespace AzFramework ListIndicesEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ClaimEntities( @@ -132,18 +161,7 @@ namespace AzFramework ClaimEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); - } - - void SpawnableEntitiesManager::ClaimEntity( - AZ::EntityId entityId, void* ticket, - ClaimEntityOptionalArgs optionalArgs) - { - AZ_Assert(ticket == nullptr, "Ticket provided to ClaimEntity is invalid."); - - ClaimEntityCommand queueEntry; - queueEntry.m_entityId = entityId; - QueueRequest(reinterpret_cast(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs) @@ -154,7 +172,7 @@ namespace AzFramework BarrierCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(completionCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus @@ -234,12 +252,13 @@ namespace AzFramework return queue.m_delayed.empty() ? CommandQueueStatus::NoCommandsLeft : CommandQueueStatus::HasCommandsLeft; } - AZStd::pair SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset&& spawnable) + AZStd::pair SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset&& spawnable) { - static AZStd::atomic_uint64_t idCounter { 1 }; + static AZStd::atomic_uint32_t idCounter { 1 }; auto result = aznew Ticket(); result->m_spawnable = AZStd::move(spawnable); + return AZStd::make_pair(idCounter++, result); } @@ -351,7 +370,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - m_spawnedEntityTicketMapper.AddSpawnedEntity((*it)->GetId(), &ticket); + (*it)->SetSpawnTicketId(request.m_ticketId); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -433,7 +452,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - m_spawnedEntityTicketMapper.AddSpawnedEntity((*it)->GetId(), &ticket); + (*it)->SetSpawnTicketId(request.m_ticketId); } if (request.m_completionCallback) @@ -460,6 +479,7 @@ namespace AzFramework { if (entity != nullptr) { + entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } @@ -482,6 +502,39 @@ namespace AzFramework } } + bool SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) + { + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) + { + AZStd::vector& spawnedEntities = request.m_ticket->m_spawnedEntities; + for (auto entityIterator = spawnedEntities.begin(); entityIterator != spawnedEntities.end(); ++entityIterator) + { + if (*entityIterator != nullptr && (*entityIterator)->GetId() == request.m_entityId) + { + (*entityIterator)->SetSpawnTicketId(0); + GameEntityContextRequestBus::Broadcast( + &GameEntityContextRequestBus::Events::DestroyGameEntity, (*entityIterator)->GetId()); + AZStd::iter_swap(entityIterator, spawnedEntities.rbegin()); + spawnedEntities.pop_back(); + break; + } + } + + if (request.m_completionCallback) + { + request.m_completionCallback(request.m_ticketId); + } + + ticket.m_currentRequestId++; + return true; + } + else + { + return false; + } + } + bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) { Ticket& ticket = *request.m_ticket; @@ -495,6 +548,7 @@ namespace AzFramework { if (entity != nullptr) { + entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } @@ -622,29 +676,6 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(ClaimEntityCommand& request) - { - Ticket* ticket = request.m_ticket; - if (request.m_requestId == ticket->m_currentRequestId) - { - AZStd::vector& spawnedEntities = ticket->m_spawnedEntities; - for (auto entityIterator = spawnedEntities.begin(); entityIterator != spawnedEntities.end(); ++entityIterator) - { - if ((*entityIterator)->GetId() == request.m_entityId) - { - AZStd::iter_swap(entityIterator, spawnedEntities.rbegin()); - spawnedEntities.pop_back(); - return true; - } - } - return true; - } - else - { - return false; - } - } - bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) { Ticket& ticket = *request.m_ticket; @@ -672,6 +703,7 @@ namespace AzFramework { if (entity != nullptr) { + entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index e5a0760010..5445360d64 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -16,7 +16,6 @@ #include #include #include -#include namespace AZ { @@ -58,6 +57,9 @@ namespace AzFramework void SpawnEntities( EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; + void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override; + void GetEntitySpawnTicket( + EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) override; void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override; @@ -67,8 +69,6 @@ namespace AzFramework EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override; void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override; - void ClaimEntity( - AZ::EntityId entityId, void* ticket, ClaimEntityOptionalArgs optionalArgs = {}) override; void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; @@ -135,6 +135,14 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; + struct DespawnEntityCommand + { + EntityDespawnCallback m_completionCallback; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; + AZ::EntityId m_entityId; + }; struct ReloadSpawnableCommand { AZ::Data::Asset m_spawnable; @@ -165,12 +173,6 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ClaimEntityCommand - { - Ticket* m_ticket; - uint32_t m_requestId; - AZ::EntityId m_entityId; - }; struct BarrierCommand { BarrierCallback m_completionCallback; @@ -185,8 +187,16 @@ namespace AzFramework }; using Requests = AZStd::variant< - SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand, - ListIndicesEntitiesCommand, ClaimEntitiesCommand, ClaimEntityCommand, BarrierCommand, DestroyTicketCommand>; + SpawnAllEntitiesCommand, + SpawnEntitiesCommand, + DespawnAllEntitiesCommand, + DespawnEntityCommand, + ReloadSpawnableCommand, + ListEntitiesCommand, + ListIndicesEntitiesCommand, + ClaimEntitiesCommand, + BarrierCommand, + DestroyTicketCommand>; struct Queue { @@ -196,7 +206,7 @@ namespace AzFramework }; template - void QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request); + void QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request); AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) override; void DestroyTicket(void* ticket) override; @@ -208,11 +218,11 @@ namespace AzFramework bool ProcessRequest(SpawnAllEntitiesCommand& request); bool ProcessRequest(SpawnEntitiesCommand& request); bool ProcessRequest(DespawnAllEntitiesCommand& request); + bool ProcessRequest(DespawnEntityCommand& request); bool ProcessRequest(ReloadSpawnableCommand& request); bool ProcessRequest(ListEntitiesCommand& request); bool ProcessRequest(ListIndicesEntitiesCommand& request); bool ProcessRequest(ClaimEntitiesCommand& request); - bool ProcessRequest(ClaimEntityCommand& request); bool ProcessRequest(BarrierCommand& request); bool ProcessRequest(DestroyTicketCommand& request); @@ -234,8 +244,6 @@ namespace AzFramework //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured //! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold". SpawnablePriority m_highPriorityThreshold { 64 }; - private: - SpawnedEntityTicketMapper m_spawnedEntityTicketMapper; }; AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp deleted file mode 100644 index 3a6b40f5a9..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -#include - -namespace AzFramework -{ - SpawnedEntityTicketMapper::SpawnedEntityTicketMapper() - { - spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); - AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found."); - - AZ::Interface::Register(this); - } - - SpawnedEntityTicketMapper::~SpawnedEntityTicketMapper() - { - AZ::Interface::Unregister(this); - } - - void SpawnedEntityTicketMapper::RemoveSpawnedEntity(AZ::EntityId entityId) - { - auto spawnedGameEntitiesIterator = m_spawnedEntities.find(entityId); - if (spawnedGameEntitiesIterator != m_spawnedEntities.end()) - { - spawnableEntitiesInterface->ClaimEntity( - spawnedGameEntitiesIterator->first, spawnedGameEntitiesIterator->second); - m_spawnedEntities.erase(entityId); - } - } - - void SpawnedEntityTicketMapper::AddSpawnedEntity(AZ::EntityId entityId, void* ticket) - { - m_spawnedEntities.emplace(entityId, ticket); - } - -} - diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h deleted file mode 100644 index 45e701db4e..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include -#include -#include - -namespace AzFramework -{ - class SpawnedEntityTicketMapper - : public SpawnedEntityTicketMapperInterface - { - public: - AZ_RTTI(SpawnedEntityTicketMapper, "{5C803604-E949-44F4-94A4-F835E5794C77}", SpawnedEntityTicketMapperInterface); - - SpawnedEntityTicketMapper(); - ~SpawnedEntityTicketMapper(); - - //! Removes the entityId from the spawned entities map if present. - //! @param entityId The id of the entity to remove. - void RemoveSpawnedEntity(AZ::EntityId entityId) override; - - //! Adds the entityId,ticket pair to the spawned entities map. - //! @param entityId The id of the entity to add. - //! @param ticket The ticket pointer to add. - void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) override; - private: - SpawnableEntitiesDefinition* spawnableEntitiesInterface = nullptr; - AZStd::unordered_map m_spawnedEntities; - }; -} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h deleted file mode 100644 index 8f0bd96dbe..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include -#include - -namespace AzFramework -{ - class SpawnedEntityTicketMapperInterface - { - public: - AZ_RTTI(SpawnedEntityTicketMapperInterface, "{D407E96B-635C-44F2-B089-084EBAB8B036}"); - - virtual void RemoveSpawnedEntity(AZ::EntityId entityId) = 0; - virtual void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) = 0; - }; -} diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index b486596e2a..87cf29ffec 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -294,9 +294,6 @@ set(FILES Spawnable/SpawnableMonitor.cpp Spawnable/SpawnableSystemComponent.h Spawnable/SpawnableSystemComponent.cpp - Spawnable/SpawnedEntityTicketMapper.h - Spawnable/SpawnedEntityTicketMapper.cpp - Spawnable/SpawnedEntityTicketMapperInterface.h Terrain/TerrainDataRequestBus.h Terrain/TerrainDataRequestBus.cpp Thermal/ThermalInfo.h From 28c056e4fdb9e11bbe22c3b186fd12b279370bc2 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Thu, 23 Sep 2021 18:26:39 -0700 Subject: [PATCH 5/7] Added comments and changed some function names Signed-off-by: srikappa-amzn --- .../AzCore/AzCore/Component/Entity.cpp | 2 +- .../Entity/GameEntityContextComponent.cpp | 2 +- .../Spawnable/SpawnableEntitiesInterface.cpp | 4 +--- .../Spawnable/SpawnableEntitiesInterface.h | 9 ++++----- .../Spawnable/SpawnableEntitiesManager.cpp | 18 +++++++++++------- .../Spawnable/SpawnableEntitiesManager.h | 5 ++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.cpp b/Code/Framework/AzCore/AzCore/Component/Entity.cpp index 4f55c360cd..4febd893b0 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.cpp +++ b/Code/Framework/AzCore/AzCore/Component/Entity.cpp @@ -657,7 +657,7 @@ namespace AZ void Entity::SetSpawnTicketId(u32 spawnTicketId) { - m_spawnTicketId = AZStd::move(spawnTicketId); + m_spawnTicketId = spawnTicketId; } u32 Entity::GetSpawnTicketId() const diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp index f2571c7549..aa7e03577a 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp @@ -294,7 +294,7 @@ namespace AzFramework { SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found."); - spawnableEntitiesInterface->GetEntitySpawnTicket( + spawnableEntitiesInterface->RetrieveEntitySpawnTicket( currentEntity->GetSpawnTicketId(), [spawnableEntitiesInterface, currentEntity](EntitySpawnTicket* entitySpawnTicket) { diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp index 53bffd0d26..d915680760 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp @@ -227,15 +227,13 @@ namespace AzFramework EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs) : m_payload(rhs.m_payload) + , m_id(rhs.m_id) { auto manager = SpawnableEntitiesInterface::Get(); AZ_Assert(manager, "SpawnableEntitiesInterface has no implementation."); rhs.m_payload = nullptr; - Id previousId = m_id; - m_id = rhs.m_id; rhs.m_id = 0; AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); - manager->m_entitySpawnTicketMap.erase(previousId); manager->m_entitySpawnTicketMap.insert_or_assign(rhs.m_id, this); } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 91b2eafc54..3eea87b511 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -162,7 +162,7 @@ namespace AzFramework using EntitySpawnCallback = AZStd::function; using EntityPreInsertionCallback = AZStd::function; using EntityDespawnCallback = AZStd::function; - using GetEntitySpawnTicketCallback = AZStd::function; + using RetrieveEntitySpawnTicketCallback = AZStd::function; using ReloadSpawnableCallback = AZStd::function; using ListEntitiesCallback = AZStd::function; using ListIndicesEntitiesCallback = AZStd::function; @@ -296,9 +296,8 @@ namespace AzFramework virtual void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) = 0; //! Gets the EntitySpawnTicket associated with the entitySpawnTicketId. //! @param entitySpawnTicketId the id of EntitySpawnTicket to get. - //! @param getEntitySpawnTicketCallback The callback to execute upon fetching the ticket. - virtual void GetEntitySpawnTicket( - EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) = 0; + //! @param callback The callback to execute upon retrieving the ticket. + virtual void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) = 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. @@ -368,7 +367,7 @@ namespace AzFramework } AZStd::unordered_map m_entitySpawnTicketMap; - AZStd::mutex m_entitySpawnTicketMapMutex; + AZStd::recursive_mutex m_entitySpawnTicketMapMutex; }; using SpawnableEntitiesInterface = AZ::Interface; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index c75a5fe834..ef7351aabb 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -96,22 +96,22 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::GetEntitySpawnTicket( - EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) + void SpawnableEntitiesManager::RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) { if (entitySpawnTicketId == 0) { - AZ_Error("Spawnable", false, "Ticket id provided to GetEntitySpawnTicket is invalid."); + AZ_Assert(false, "Ticket id provided to RetrieveEntitySpawnTicket is invalid."); return; } + AZStd::scoped_lock lock(m_entitySpawnTicketMapMutex); auto entitySpawnTicketIterator = m_entitySpawnTicketMap.find(entitySpawnTicketId); if (entitySpawnTicketIterator == m_entitySpawnTicketMap.end()) { - AZ_Error("Spawnable", false, "The EntitySpawnTicket corresponding to id '%lu' cannot be found", entitySpawnTicketId); + AZ_Assert(false, "The EntitySpawnTicket corresponding to id '%lu' cannot be found", entitySpawnTicketId); return; } - getEntitySpawnTicketCallback(entitySpawnTicketIterator->second); + callback(entitySpawnTicketIterator->second); } void SpawnableEntitiesManager::ReloadSpawnable( @@ -369,8 +369,8 @@ namespace AzFramework // Add to the game context, now the entities are active for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); (*it)->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -451,8 +451,8 @@ namespace AzFramework // Add to the game context, now the entities are active for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); (*it)->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); } if (request.m_completionCallback) @@ -479,6 +479,7 @@ namespace AzFramework { if (entity != nullptr) { + // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); @@ -512,6 +513,7 @@ namespace AzFramework { if (*entityIterator != nullptr && (*entityIterator)->GetId() == request.m_entityId) { + // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. (*entityIterator)->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, (*entityIterator)->GetId()); @@ -548,6 +550,7 @@ namespace AzFramework { if (entity != nullptr) { + // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); @@ -703,6 +706,7 @@ namespace AzFramework { if (entity != nullptr) { + // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 5445360d64..c3de5be003 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -58,8 +58,7 @@ namespace AzFramework EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override; - void GetEntitySpawnTicket( - EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) override; + void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) override; void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override; @@ -139,9 +138,9 @@ namespace AzFramework { EntityDespawnCallback m_completionCallback; Ticket* m_ticket; + AZ::EntityId m_entityId; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; - AZ::EntityId m_entityId; }; struct ReloadSpawnableCommand { From dcc325dfaa826b803d2d865fa0bf529dc5981471 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Fri, 24 Sep 2021 12:16:38 -0700 Subject: [PATCH 6/7] Added function comments for new entity functions Signed-off-by: srikappa-amzn --- Code/Framework/AzCore/AzCore/Component/Entity.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.h b/Code/Framework/AzCore/AzCore/Component/Entity.h index 97cec50946..3887b2e03e 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.h +++ b/Code/Framework/AzCore/AzCore/Component/Entity.h @@ -133,8 +133,13 @@ namespace AZ //! @return The state of the entity. For example, the entity has been initialized, the entity is active, and so on. State GetState() const { return m_state; } + //! Gets the ticket id used to spawn the entity. + //! @return the ticket id used to spawn the entity. If entity is not spawned, the id will be 0. u32 GetSpawnTicketId() const; - void SetSpawnTicketId(u32); + + //! Sets the ticket id used to spawn the entity. The ticket id in the entity will remain 0 unless it's set using this function. + //! @param spawnTicketId the ticket id used to spawn the entity. + void SetSpawnTicketId(u32 spawnTicketId); //! Connects an entity state event handler to the entity. //! All state changes will be signaled through this event. From 9078d1925aafc50c72e40f99da0f3f83b05f354c Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Tue, 28 Sep 2021 06:42:53 -0700 Subject: [PATCH 7/7] Mock newly added methods to SpawnableEntitiesInterface Signed-off-by: srikappa-amzn --- .../AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h index a5df7af4c3..a437545adf 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h @@ -40,6 +40,11 @@ namespace AzFramework MOCK_METHOD2(DespawnAllEntities, void(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs)); + MOCK_METHOD3(DespawnEntity, void(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs)); + + MOCK_METHOD2( + RetrieveEntitySpawnTicket, void(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback)); + MOCK_METHOD3( ReloadSpawnable, void(EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs));