Merge pull request #4154 from aws-lumberyard-dev/Prefab/DestroyGameEntitySupport

Enable destroying entities spawned using a spawnable
This commit is contained in:
AMZN-koppersr
2021-09-28 10:28:10 -07:00
committed by GitHub
10 changed files with 189 additions and 73 deletions
@@ -649,6 +649,16 @@ namespace AZ
m_stateEvent.Signal(oldState, m_state);
}
void Entity::SetSpawnTicketId(u32 spawnTicketId)
{
m_spawnTicketId = spawnTicketId;
}
u32 Entity::GetSpawnTicketId() const
{
return m_spawnTicketId;
}
void Entity::OnNameChanged() const
{
EBUS_EVENT_ID(GetId(), EntityBus, OnEntityNameChanged, m_name);
@@ -133,6 +133,14 @@ 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;
//! 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.
//! @param handler reference to the EntityStateEvent handler to attach to the entities state event.
@@ -410,6 +418,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;
@@ -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.
@@ -11,9 +11,10 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#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,28 @@ 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)
{
if (currentEntity->GetSpawnTicketId() > 0)
{
SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get();
AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found.");
spawnableEntitiesInterface->RetrieveEntitySpawnTicket(
currentEntity->GetSpawnTicketId(),
[spawnableEntitiesInterface, currentEntity](EntitySpawnTicket* entitySpawnTicket)
{
if (entitySpawnTicket != nullptr)
{
spawnableEntitiesInterface->DespawnEntity(currentEntity->GetId(), *entitySpawnTicket);
}
});
return;
}
}
if (currentEntity->GetState() == AZ::Entity::State::Active)
{
// Deactivate the entity, we'll destroy it as soon as it is safe.
@@ -90,11 +90,6 @@ namespace AzFramework
}
private:
//////////////////////////////////////////////////////////////////////////
// GameEntityContextRequestBus
void DestroyGameEntityOnlyInSliceMode(const AZ::EntityId&) override;
void DestroyGameEntityAndDescendantsOnlyInSliceMode(const AZ::EntityId&) override;
/////////////////////////////////////////////////////////////////////////
AzFramework::EntityVisibilityBoundsUnionSystem m_entityVisibilityBoundsUnionSystem;
};
@@ -283,8 +283,12 @@ namespace AzFramework
: 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;
rhs.m_id = 0;
AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex);
manager->m_entitySpawnTicketMap.insert_or_assign(rhs.m_id, this);
}
EntitySpawnTicket::EntitySpawnTicket(AZ::Data::Asset<Spawnable> spawnable)
@@ -294,6 +298,8 @@ namespace AzFramework
AZStd::pair<EntitySpawnTicket::Id, void*> 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()
@@ -304,6 +310,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;
}
}
@@ -312,17 +320,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;
}
@@ -154,7 +154,7 @@ namespace AzFramework
public:
friend class SpawnableEntitiesDefinition;
using Id = uint64_t;
using Id = uint32_t;
EntitySpawnTicket() = default;
EntitySpawnTicket(const EntitySpawnTicket&) = delete;
@@ -176,6 +176,7 @@ namespace AzFramework
using EntitySpawnCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
using EntityPreInsertionCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableEntityContainerView)>;
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
using RetrieveEntitySpawnTicketCallback = AZStd::function<void(EntitySpawnTicket*)>;
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstIndexEntityContainerView)>;
@@ -220,12 +221,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
@@ -291,9 +301,17 @@ namespace AzFramework
EntitySpawnTicket& ticket, AZStd::vector<size_t> 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 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.
@@ -361,6 +379,9 @@ namespace AzFramework
{
return reinterpret_cast<const T*>(ticket->m_payload);
}
AZStd::unordered_map<EntitySpawnTicket::Id, EntitySpawnTicket*> m_entitySpawnTicketMap;
AZStd::recursive_mutex m_entitySpawnTicketMapMutex;
};
using SpawnableEntitiesInterface = AZ::Interface<SpawnableEntitiesDefinition>;
@@ -85,6 +85,35 @@ namespace AzFramework
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::RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback)
{
if (entitySpawnTicketId == 0)
{
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_Assert(false, "The EntitySpawnTicket corresponding to id '%lu' cannot be found", entitySpawnTicketId);
return;
}
callback(entitySpawnTicketIterator->second);
}
void SpawnableEntitiesManager::ReloadSpawnable(
EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs)
{
@@ -223,12 +252,13 @@ namespace AzFramework
return queue.m_delayed.empty() ? CommandQueueStatus::NoCommandsLeft : CommandQueueStatus::HasCommandsLeft;
}
AZStd::pair<uint64_t, void*> SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable)
AZStd::pair<EntitySpawnTicket::Id, void*> SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset<Spawnable>&& 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<EntitySpawnTicket::Id, void*>(idCounter++, result);
}
@@ -339,6 +369,7 @@ 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)
{
(*it)->SetSpawnTicketId(request.m_ticketId);
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
}
@@ -420,6 +451,7 @@ 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)
{
(*it)->SetSpawnTicketId(request.m_ticketId);
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
}
@@ -447,8 +479,10 @@ 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::DestroyGameEntityAndDescendants, entity->GetId());
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
}
}
@@ -469,6 +503,40 @@ namespace AzFramework
}
}
bool SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request)
{
Ticket& ticket = *request.m_ticket;
if (request.m_requestId == ticket.m_currentRequestId)
{
AZStd::vector<AZ::Entity*>& spawnedEntities = request.m_ticket->m_spawnedEntities;
for (auto entityIterator = spawnedEntities.begin(); entityIterator != spawnedEntities.end(); ++entityIterator)
{
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());
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;
@@ -482,8 +550,10 @@ 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::DestroyGameEntityAndDescendants, entity->GetId());
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
}
}
@@ -636,8 +706,10 @@ 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::DestroyGameEntityAndDescendants, entity->GetId());
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
}
}
delete request.m_ticket;
@@ -57,6 +57,8 @@ namespace AzFramework
void SpawnEntities(
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override;
void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override;
void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override;
void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) override;
void ReloadSpawnable(
EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override;
@@ -132,6 +134,14 @@ namespace AzFramework
EntitySpawnTicket::Id m_ticketId;
uint32_t m_requestId;
};
struct DespawnEntityCommand
{
EntityDespawnCallback m_completionCallback;
Ticket* m_ticket;
AZ::EntityId m_entityId;
EntitySpawnTicket::Id m_ticketId;
uint32_t m_requestId;
};
struct ReloadSpawnableCommand
{
AZ::Data::Asset<Spawnable> m_spawnable;
@@ -176,8 +186,16 @@ namespace AzFramework
};
using Requests = AZStd::variant<
SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand,
ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>;
SpawnAllEntitiesCommand,
SpawnEntitiesCommand,
DespawnAllEntitiesCommand,
DespawnEntityCommand,
ReloadSpawnableCommand,
ListEntitiesCommand,
ListIndicesEntitiesCommand,
ClaimEntitiesCommand,
BarrierCommand,
DestroyTicketCommand>;
struct Queue
{
@@ -199,6 +217,7 @@ 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);
@@ -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> spawnable, ReloadSpawnableOptionalArgs optionalArgs));