Files
o3de/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp
T
2021-09-15 13:28:15 -07:00

692 lines
31 KiB
C++

/*
* 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 <AzCore/Casting/numeric_cast.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/IdUtils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
#include <AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h>
namespace AzFramework
{
template<typename T>
void SpawnableEntitiesManager::QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request)
{
request.m_ticket = ticket;
Queue& queue = priority <= m_highPriorityThreshold ? m_highPriorityQueue : m_regularPriorityQueue;
{
AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex);
request.m_requestId = ticket->m_nextRequestId++;
queue.m_pendingRequest.push(AZStd::move(request));
}
}
SpawnableEntitiesManager::SpawnableEntitiesManager()
{
AZ::ComponentApplicationBus::BroadcastResult(m_defaultSerializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
AZ_Assert(
m_defaultSerializeContext, "Failed to retrieve serialization context during construction of the Spawnable Entities Manager.");
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
AZ::u64 value = aznumeric_caster(m_highPriorityThreshold);
settingsRegistry->Get(value, "/O3DE/AzFramework/Spawnables/HighPriorityThreshold");
m_highPriorityThreshold = aznumeric_cast<SpawnablePriority>(AZStd::clamp(value, 0llu, 255llu));
}
}
void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs)
{
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnAllEntities hasn't been initialized.");
SpawnAllEntitiesCommand queueEntry;
queueEntry.m_ticketId = ticket.GetId();
queueEntry.m_serializeContext =
optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext;
queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback);
queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback);
QueueRequest(&GetTicketPayload<Ticket>(ticket), optionalArgs.m_priority, AZStd::move(queueEntry));
}
void SpawnableEntitiesManager::SpawnEntities(
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs)
{
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized.");
SpawnEntitiesCommand queueEntry;
queueEntry.m_ticketId = ticket.GetId();
queueEntry.m_entityIndices = AZStd::move(entityIndices);
queueEntry.m_serializeContext =
optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext;
queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback);
queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback);
queueEntry.m_referencePreviouslySpawnedEntities = optionalArgs.m_referencePreviouslySpawnedEntities;
QueueRequest(&GetTicketPayload<Ticket>(ticket), optionalArgs.m_priority, AZStd::move(queueEntry));
}
void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs)
{
AZ_Assert(ticket.IsValid(), "Ticket provided to DespawnAllEntities hasn't been initialized.");
DespawnAllEntitiesCommand queueEntry;
queueEntry.m_ticketId = ticket.GetId();
queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback);
QueueRequest(&GetTicketPayload<Ticket>(ticket), optionalArgs.m_priority, AZStd::move(queueEntry));
}
void SpawnableEntitiesManager::ReloadSpawnable(
EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs)
{
AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized.");
ReloadSpawnableCommand queueEntry;
queueEntry.m_ticketId = ticket.GetId();
queueEntry.m_spawnable = AZStd::move(spawnable);
queueEntry.m_serializeContext =
optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext;
queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback);
QueueRequest(&GetTicketPayload<Ticket>(ticket), optionalArgs.m_priority, AZStd::move(queueEntry));
}
void SpawnableEntitiesManager::ListEntities(
EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs)
{
AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use.");
AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized.");
ListEntitiesCommand queueEntry;
queueEntry.m_ticketId = ticket.GetId();
queueEntry.m_listCallback = AZStd::move(listCallback);
QueueRequest(&GetTicketPayload<Ticket>(ticket), optionalArgs.m_priority, AZStd::move(queueEntry));
}
void SpawnableEntitiesManager::ListIndicesAndEntities(
EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs)
{
AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use.");
AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized.");
ListIndicesEntitiesCommand queueEntry;
queueEntry.m_ticketId = ticket.GetId();
queueEntry.m_listCallback = AZStd::move(listCallback);
QueueRequest(&GetTicketPayload<Ticket>(ticket), optionalArgs.m_priority, AZStd::move(queueEntry));
}
void SpawnableEntitiesManager::ClaimEntities(
EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs)
{
AZ_Assert(listCallback, "ClaimEntities called on spawnable entities without a valid callback to use.");
AZ_Assert(ticket.IsValid(), "Ticket provided to ClaimEntities hasn't been initialized.");
ClaimEntitiesCommand queueEntry;
queueEntry.m_ticketId = ticket.GetId();
queueEntry.m_listCallback = AZStd::move(listCallback);
QueueRequest(&GetTicketPayload<Ticket>(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*>(ticket), optionalArgs.m_priority, AZStd::move(queueEntry));
}
void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs)
{
AZ_Assert(completionCallback, "Barrier on spawnable entities called without a valid callback to use.");
AZ_Assert(ticket.IsValid(), "Ticket provided to Barrier hasn't been initialized.");
BarrierCommand queueEntry;
queueEntry.m_ticketId = ticket.GetId();
queueEntry.m_completionCallback = AZStd::move(completionCallback);
QueueRequest(&GetTicketPayload<Ticket>(ticket), optionalArgs.m_priority, AZStd::move(queueEntry));
}
auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus
{
CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft;
if ((priority & CommandQueuePriority::High) == CommandQueuePriority::High)
{
if (ProcessQueue(m_highPriorityQueue) == CommandQueueStatus::HasCommandsLeft)
{
result = CommandQueueStatus::HasCommandsLeft;
}
}
if ((priority & CommandQueuePriority::Regular) == CommandQueuePriority::Regular)
{
if (ProcessQueue(m_regularPriorityQueue) == CommandQueueStatus::HasCommandsLeft)
{
result = CommandQueueStatus::HasCommandsLeft;
}
}
return result;
}
auto SpawnableEntitiesManager::ProcessQueue(Queue& queue) -> CommandQueueStatus
{
// Process delayed requests first.
// Only process the requests that are currently in this queue, not the ones that could be re-added if they still can't complete.
size_t delayedSize = queue.m_delayed.size();
for (size_t i = 0; i < delayedSize; ++i)
{
Requests& request = queue.m_delayed.front();
bool result = AZStd::visit(
[this](auto&& args) -> bool
{
return ProcessRequest(args);
},
request);
if (!result)
{
queue.m_delayed.emplace_back(AZStd::move(request));
}
queue.m_delayed.pop_front();
}
// Process newly added requests.
while (true)
{
AZStd::queue<Requests> pendingRequestQueue;
{
AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex);
queue.m_pendingRequest.swap(pendingRequestQueue);
}
if (!pendingRequestQueue.empty())
{
while (!pendingRequestQueue.empty())
{
Requests& request = pendingRequestQueue.front();
bool result = AZStd::visit(
[this](auto&& args) -> bool
{
return ProcessRequest(args);
},
request);
if (!result)
{
queue.m_delayed.emplace_back(AZStd::move(request));
}
pendingRequestQueue.pop();
}
}
else
{
break;
}
};
return queue.m_delayed.empty() ? CommandQueueStatus::NoCommandsLeft : CommandQueueStatus::HasCommandsLeft;
}
AZStd::pair<uint64_t, void*> SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable)
{
static AZStd::atomic_uint64_t idCounter { 1 };
auto result = aznew Ticket();
result->m_spawnable = AZStd::move(spawnable);
return AZStd::make_pair<EntitySpawnTicket::Id, void*>(idCounter++, result);
}
void SpawnableEntitiesManager::DestroyTicket(void* ticket)
{
DestroyTicketCommand queueEntry;
queueEntry.m_ticket = reinterpret_cast<Ticket*>(ticket);
{
AZStd::scoped_lock queueLock(m_regularPriorityQueue.m_pendingRequestMutex);
queueEntry.m_requestId = reinterpret_cast<Ticket*>(ticket)->m_nextRequestId++;
m_regularPriorityQueue.m_pendingRequest.push(AZStd::move(queueEntry));
}
}
AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate,
EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext)
{
// If the same ID gets remapped more than once, preserve the original remapping instead of overwriting it.
constexpr bool allowDuplicateIds = false;
return AZ::IdUtils::Remapper<AZ::EntityId, allowDuplicateIds>::CloneObjectAndGenerateNewIdsAndFixRefs(
&entityTemplate, templateToCloneMap, &serializeContext);
}
void SpawnableEntitiesManager::InitializeEntityIdMappings(
const Spawnable::EntityList& entities, EntityIdMap& idMap, AZStd::unordered_set<AZ::EntityId>& previouslySpawned)
{
// Make sure we don't have any previous data lingering around.
idMap.clear();
previouslySpawned.clear();
idMap.reserve(entities.size());
previouslySpawned.reserve(entities.size());
for (auto& entity : entities)
{
idMap.emplace(entity->GetId(), AZ::Entity::MakeId());
}
}
void SpawnableEntitiesManager::RefreshEntityIdMapping(
const AZ::EntityId& entityId, EntityIdMap& idMap, AZStd::unordered_set<AZ::EntityId>& previouslySpawned)
{
if (previouslySpawned.contains(entityId))
{
// This entity has already been spawned at least once before, so we need to generate a new id for it and
// preserve the new id to fix up any future entity references to this entity.
idMap[entityId] = AZ::Entity::MakeId();
}
else
{
// This entity hasn't been spawned yet, so use the first id we've already generated for this entity and mark
// it as spawned so we know not to reuse this id next time.
previouslySpawned.emplace(entityId);
}
}
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request)
{
Ticket& ticket = *request.m_ticket;
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
{
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
// Keep track how many entities there were in the array initially
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
// These are 'template' entities we'll be cloning from
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
size_t entitiesToSpawnSize = entitiesToSpawn.size();
// Reserve buffers
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
// Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
// We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference
// in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless
// of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to
// previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call.
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
{
// If this entity has previously been spawned, give it a new id in the reference map
RefreshEntityIdMapping(entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
AZ::Entity* clone = CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
spawnedEntities.emplace_back(clone);
spawnedEntityIndices.push_back(i);
}
// loadAll is true if every entity has been spawned only once
ticket.m_loadAll = (spawnedEntities.size() == entitiesToSpawnSize);
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
if (request.m_preInsertionCallback)
{
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
}
// 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);
AZ::Interface<SpawnedEntityTicketMapperInterface>::Get()->AddSpawnedEntity((*it)->GetId(), &ticket);
}
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
if (request.m_completionCallback)
{
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
}
ticket.m_currentRequestId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request)
{
Ticket& ticket = *request.m_ticket;
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
{
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
AZ_Assert(
spawnedEntities.size() == spawnedEntityIndices.size(),
"The indices for the spawned entities has gone out of sync with the entities.");
// Keep track of how many entities there were in the array initially
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
// These are 'template' entities we'll be cloning from
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
size_t entitiesToSpawnSize = request.m_entityIndices.size();
if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities)
{
// This map keeps track of ids from template (spawnable) to clone (instance) allowing patch ups of fields referring
// to entityIds outside of a given entity.
// We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
// By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so
// that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities
// (or SpawnAllEntities) call.
// However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false".
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
}
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
for (size_t index : request.m_entityIndices)
{
if (index < entitiesToSpawn.size())
{
// If this entity has previously been spawned, give it a new id in the reference map
RefreshEntityIdMapping(
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
AZ::Entity* clone =
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
spawnedEntities.push_back(clone);
spawnedEntityIndices.push_back(index);
}
}
ticket.m_loadAll = false;
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
if (request.m_preInsertionCallback)
{
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
}
// Add to the game context, now the entities are active
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
{
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
AZ::Interface<SpawnedEntityTicketMapperInterface>::Get()->AddSpawnedEntity((*it)->GetId(), &ticket);
}
if (request.m_completionCallback)
{
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
}
ticket.m_currentRequestId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request)
{
Ticket& ticket = *request.m_ticket;
if (request.m_requestId == ticket.m_currentRequestId)
{
for (AZ::Entity* entity : ticket.m_spawnedEntities)
{
if (entity != nullptr)
{
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
}
}
ticket.m_spawnedEntities.clear();
ticket.m_spawnedEntityIndices.clear();
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;
AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(),
"Spawnable is being reloaded, but the provided spawnable has a different asset id. "
"This will likely result in unexpected entities being created.");
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
{
// Delete the original entities.
for (AZ::Entity* entity : ticket.m_spawnedEntities)
{
if (entity != nullptr)
{
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
}
}
// Rebuild the list of entities.
ticket.m_spawnedEntities.clear();
const Spawnable::EntityList& entities = request.m_spawnable->GetEntities();
// Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
// This map is intentionally cleared out and regenerated here to ensure that we're starting fresh with mappings that
// match the new set of template entities getting spawned.
InitializeEntityIdMappings(entities, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
if (ticket.m_loadAll)
{
// The new spawnable may have a different number of entities and since the intent of the user was
// to spawn every entity, simply start over.
ticket.m_spawnedEntityIndices.clear();
size_t entitiesToSpawnSize = entities.size();
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
{
// If this entity has previously been spawned, give it a new id in the reference map
RefreshEntityIdMapping(entities[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
AZ::Entity* clone = CloneSingleEntity(*entities[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
ticket.m_spawnedEntities.push_back(clone);
ticket.m_spawnedEntityIndices.push_back(i);
}
}
else
{
size_t entitiesSize = entities.size();
for (size_t index : ticket.m_spawnedEntityIndices)
{
// It's possible for the new spawnable to have a different number of entities, so guard against this.
// It's also possible that the entities have moved within the spawnable to a new index. This can't be
// detected and will result in the incorrect entities being spawned.
if (index < entitiesSize)
{
// If this entity has previously been spawned, give it a new id in the reference map
RefreshEntityIdMapping(entities[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
AZ::Entity* clone = CloneSingleEntity(*entities[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
ticket.m_spawnedEntities.push_back(clone);
}
}
}
ticket.m_spawnable = AZStd::move(request.m_spawnable);
if (request.m_completionCallback)
{
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
}
ticket.m_currentRequestId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request)
{
Ticket& ticket = *request.m_ticket;
if (request.m_requestId == ticket.m_currentRequestId)
{
request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView(
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
ticket.m_currentRequestId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request)
{
Ticket& ticket = *request.m_ticket;
if (request.m_requestId == ticket.m_currentRequestId)
{
AZ_Assert(
ticket.m_spawnedEntities.size() == ticket.m_spawnedEntityIndices.size(),
"Entities and indices on spawnable ticket have gone out of sync.");
request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView(
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size()));
ticket.m_currentRequestId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request)
{
Ticket& ticket = *request.m_ticket;
if (request.m_requestId == ticket.m_currentRequestId)
{
request.m_listCallback(request.m_ticketId, SpawnableEntityContainerView(
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
ticket.m_spawnedEntities.clear();
ticket.m_spawnedEntityIndices.clear();
ticket.m_currentRequestId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntityCommand& request)
{
Ticket& ticket = *request.m_ticket;
if (request.m_requestId == ticket.m_currentRequestId)
{
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
AZStd::vector<AZ::Entity*>::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;
if (request.m_requestId == ticket.m_currentRequestId)
{
if (request.m_completionCallback)
{
request.m_completionCallback(request.m_ticketId);
}
ticket.m_currentRequestId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request)
{
if (request.m_requestId == request.m_ticket->m_currentRequestId)
{
for (AZ::Entity* entity : request.m_ticket->m_spawnedEntities)
{
if (entity != nullptr)
{
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
}
}
delete request.m_ticket;
return true;
}
else
{
return false;
}
}
} // namespace AzFramework