Extended the Spawnable Entities Interface to allow entity aliases to be updated.
Entity aliases can now be updated as a reaction to the spawnable being loaded or at any other time afterwards through the Spawnable Entities Interface. Currently these changes are applied to the spawnable that owns the entity aliases, but once the Spawnable Entities Interface makes use of AzFramework::Scene a copy of the entity aliases should be stored in the scene and be updated instead of the spawnable. This change also adds support for a load barrier, which acts the same as a regular barrier but also accounts for the spawnable being loaded and won't trigger the callback until has completed. The return values in from the processing functions in the Spawnable Entities Manager now have a clearer return value to indicate whether a request has completed or is being re-queued. Signed-off-by: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com>
This commit is contained in:
@@ -46,8 +46,13 @@ namespace AzFramework
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (const EntityAlias& alias : *aliases)
|
||||
{
|
||||
if ((alias.m_aliasType != Spawnable::EntityAliasType::Original && alias.m_aliasType != Spawnable::EntityAliasType::Disabled) &&
|
||||
!alias.m_spawnable.IsReady())
|
||||
if (!alias.m_queueLoad ||
|
||||
alias.m_aliasType == Spawnable::EntityAliasType::Original ||
|
||||
alias.m_aliasType == Spawnable::EntityAliasType::Disabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!alias.m_spawnable.IsReady() && !alias.m_spawnable.IsError())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace AzFramework
|
||||
return m_threadData != nullptr;
|
||||
}
|
||||
|
||||
uint64_t SpawnableEntitiesContainer::GetCurrentGeneration() const
|
||||
uint32_t SpawnableEntitiesContainer::GetCurrentGeneration() const
|
||||
{
|
||||
return m_currentGeneration;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace AzFramework
|
||||
SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket);
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector<size_t> entityIndices)
|
||||
void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector<uint32_t> entityIndices)
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->SpawnEntities(
|
||||
@@ -78,15 +78,21 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::Alert(AlertCallback callback)
|
||||
void SpawnableEntitiesContainer::Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck)
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->Barrier(
|
||||
m_threadData->m_spawnedEntitiesTicket,
|
||||
[generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id)
|
||||
{
|
||||
callback(generation);
|
||||
});
|
||||
auto callbackWrapper = [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id)
|
||||
{
|
||||
callback(generation);
|
||||
};
|
||||
if (spawnableCheck == CheckIfSpawnableIsLoaded::No)
|
||||
{
|
||||
SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper));
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnableEntitiesInterface::Get()->LoadBarrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper));
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::Connect(AZ::Data::Asset<Spawnable> spawnable)
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace AzFramework
|
||||
public:
|
||||
using AlertCallback = AZStd::function<void(uint32_t generation)>;
|
||||
|
||||
enum class CheckIfSpawnableIsLoaded : bool
|
||||
{
|
||||
Yes,
|
||||
No
|
||||
};
|
||||
|
||||
//! Constructs a new spawnables entity container that has not been connected.
|
||||
SpawnableEntitiesContainer() = default;
|
||||
//! Constructs a new spawnables entity container that connects to the provided spawnable.
|
||||
@@ -48,13 +54,13 @@ namespace AzFramework
|
||||
//! Returns a number that identifies the current generation of the container with. The completion callback can still receive
|
||||
//! calls from older generations as processing completes on those. The returned value can be used to help calls tell
|
||||
//! older versions apart from newer ones.
|
||||
[[nodiscard]] uint64_t GetCurrentGeneration() const;
|
||||
[[nodiscard]] uint32_t GetCurrentGeneration() const;
|
||||
|
||||
//! Puts in a request to spawn entities using all entities in the provided spawnable as a template.
|
||||
void SpawnAllEntities();
|
||||
//! Puts in a request to spawn entities using the entities found in the spawnable at the provided indices as a template.
|
||||
//! @param entityIndices A list of indices to the entities in the spawnable.
|
||||
void SpawnEntities(AZStd::vector<size_t> entityIndices);
|
||||
void SpawnEntities(AZStd::vector<uint32_t> entityIndices);
|
||||
//! Puts in a request to despawn all previous spawned entities.
|
||||
void DespawnAllEntities();
|
||||
|
||||
@@ -73,7 +79,11 @@ namespace AzFramework
|
||||
//! other than the calling thread including the main thread. Note that because the alert is queued it can still be called
|
||||
//! after the container has been deleted or can be called for a previously assigned spawnable. In the latter case check
|
||||
//! if the current generation matches the generation provided with the callback.
|
||||
void Alert(AlertCallback callback);
|
||||
//! @callback The function called when the alert triggers. This can be called from a different thread than the one that
|
||||
//! the one that made the call to Alert.
|
||||
//! @checkSpawnableIsLoaded If true the alert will also block until the spawnable has been loaded. If false then it will
|
||||
//! be called after all previous calls have completed, but the spawnable may not be loaded at that point.
|
||||
void Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck = CheckIfSpawnableIsLoaded::No);
|
||||
|
||||
private:
|
||||
void Connect(AZ::Data::Asset<Spawnable> spawnable);
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace AzFramework
|
||||
// SpawnableIndexEntityPair
|
||||
//
|
||||
|
||||
SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator)
|
||||
SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator)
|
||||
: m_entity(entityIterator)
|
||||
, m_index(indexIterator)
|
||||
{
|
||||
@@ -168,7 +168,7 @@ namespace AzFramework
|
||||
return *m_entity;
|
||||
}
|
||||
|
||||
size_t SpawnableIndexEntityPair::GetIndex() const
|
||||
uint32_t SpawnableIndexEntityPair::GetIndex() const
|
||||
{
|
||||
return *m_index;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace AzFramework
|
||||
// SpawnableIndexEntityIterator
|
||||
//
|
||||
|
||||
SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator)
|
||||
SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator)
|
||||
: m_value(entityIterator, indexIterator)
|
||||
{
|
||||
}
|
||||
@@ -248,7 +248,7 @@ namespace AzFramework
|
||||
//
|
||||
|
||||
SpawnableConstIndexEntityContainerView::SpawnableConstIndexEntityContainerView(
|
||||
AZ::Entity** beginEntity, size_t* beginIndices, size_t length)
|
||||
AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length)
|
||||
: m_begin(beginEntity, beginIndices)
|
||||
, m_end(beginEntity + length, beginIndices + length)
|
||||
{
|
||||
|
||||
@@ -85,19 +85,19 @@ namespace AzFramework
|
||||
|
||||
AZ::Entity* GetEntity();
|
||||
const AZ::Entity* GetEntity() const;
|
||||
size_t GetIndex() const;
|
||||
uint32_t GetIndex() const;
|
||||
|
||||
private:
|
||||
SpawnableIndexEntityPair() = default;
|
||||
SpawnableIndexEntityPair(const SpawnableIndexEntityPair&) = default;
|
||||
SpawnableIndexEntityPair(SpawnableIndexEntityPair&&) = default;
|
||||
SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator);
|
||||
SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator);
|
||||
|
||||
SpawnableIndexEntityPair& operator=(const SpawnableIndexEntityPair&) = default;
|
||||
SpawnableIndexEntityPair& operator=(SpawnableIndexEntityPair&&) = default;
|
||||
|
||||
AZ::Entity** m_entity { nullptr };
|
||||
size_t* m_index { nullptr };
|
||||
uint32_t* m_index { nullptr };
|
||||
};
|
||||
|
||||
class SpawnableIndexEntityIterator
|
||||
@@ -110,7 +110,7 @@ namespace AzFramework
|
||||
using pointer = SpawnableIndexEntityPair*;
|
||||
using reference = SpawnableIndexEntityPair&;
|
||||
|
||||
SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator);
|
||||
SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator);
|
||||
|
||||
SpawnableIndexEntityIterator& operator++();
|
||||
SpawnableIndexEntityIterator operator++(int);
|
||||
@@ -132,7 +132,7 @@ namespace AzFramework
|
||||
class SpawnableConstIndexEntityContainerView
|
||||
{
|
||||
public:
|
||||
SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, size_t* beginIndices, size_t length);
|
||||
SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length);
|
||||
|
||||
const SpawnableIndexEntityIterator& begin();
|
||||
const SpawnableIndexEntityIterator& end();
|
||||
@@ -144,6 +144,16 @@ namespace AzFramework
|
||||
SpawnableIndexEntityIterator m_end;
|
||||
};
|
||||
|
||||
//! Information used when updating the type of an entity alias.
|
||||
struct EntityAliasTypeChange
|
||||
{
|
||||
//! The index of the alias in the spawnable. Note that due to optimizations done on the entity aliases the index of an alias
|
||||
//! can change over time.
|
||||
uint32_t m_aliasIndex;
|
||||
//! The type to replace type stored in the spawnable at the index provided by m_aliasIndex.
|
||||
Spawnable::EntityAliasType m_newAliasType;
|
||||
};
|
||||
|
||||
//! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that is used as a template. A ticket can
|
||||
//! be reused for multiple calls on the same spawnable and is safe to be used by multiple threads at the same time. Entities created
|
||||
//! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created
|
||||
@@ -178,6 +188,7 @@ namespace AzFramework
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
using RetrieveEntitySpawnTicketCallback = AZStd::function<void(EntitySpawnTicket*)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using UpdateEntityAliasTypesCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstIndexEntityContainerView)>;
|
||||
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableEntityContainerView)>;
|
||||
@@ -247,6 +258,15 @@ namespace AzFramework
|
||||
SpawnablePriority m_priority { SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct UpdateEntityAliasTypesOptionalArgs final
|
||||
{
|
||||
//! Callback that's called when entity aliases are updated. This can be triggered from a different thread than the one that
|
||||
//! made the function call to update.
|
||||
UpdateEntityAliasTypesCallback m_completionCallback;
|
||||
//! The priority at which this call will be executed.
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct ListEntitiesOptionalArgs final
|
||||
{
|
||||
//! The priority at which this call will be executed.
|
||||
@@ -265,6 +285,14 @@ namespace AzFramework
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct LoadBarrierOptionalArgs final
|
||||
{
|
||||
//! The priority at which this call will be executed.
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
//! Also checks if the spawnables referenced in the entity aliases that are marked to be loaded are loaded.
|
||||
bool m_checkAliasSpawnables{ true };
|
||||
};
|
||||
|
||||
//! Interface definition to (de)spawn entities from a spawnable into the game world.
|
||||
//!
|
||||
//! While the callbacks of the individual calls are being processed they will block processing any other request. Callbacks can be
|
||||
@@ -298,7 +326,7 @@ namespace AzFramework
|
||||
//! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from.
|
||||
//! @param optionalArgs Optional additional arguments, see SpawnEntitiesOptionalArgs.
|
||||
virtual void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0;
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_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 optionalArgs Optional additional arguments, see DespawnAllEntitiesOptionalArgs.
|
||||
@@ -320,6 +348,16 @@ namespace AzFramework
|
||||
virtual void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
//! Allows updating the entity alias on a spawnable. This allows the spawning behavior for all entities spawned from the used
|
||||
//! spawnable to be changed and is not restricted to this ticket alone.
|
||||
//! @param ticket Holds the information for the spawnable.
|
||||
//! @param updateAliases An array of index and alias type values used to update the entity alias list.
|
||||
//! @param optionalArgs Optional additional arguments, see UpdateEntityAliasTypesOptionalArgs.
|
||||
virtual void UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
//! List all entities that are spawned using this ticket.
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param listCallback Required callback that will be called to list the entities on.
|
||||
@@ -351,31 +389,37 @@ namespace AzFramework
|
||||
//! @param completionCallback Required callback that will be called as soon as the barrier has been reached.
|
||||
//! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs.
|
||||
virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0;
|
||||
//! Blocks until the spawnable is loaded and all operations made on the provided ticket before the barrier call have completed.
|
||||
//! @param ticket The ticket to monitor.
|
||||
//! @param completionCallback Required callback that will be called as soon as the barrier has been reached.
|
||||
//! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs.
|
||||
virtual void LoadBarrier(
|
||||
EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] virtual AZStd::pair<EntitySpawnTicket::Id, void*> CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
||||
virtual void DestroyTicket(void* ticket) = 0;
|
||||
|
||||
template<typename T>
|
||||
static T& GetTicketPayload(EntitySpawnTicket& ticket)
|
||||
[[nodiscard]] static T& GetTicketPayload(EntitySpawnTicket& ticket)
|
||||
{
|
||||
return *reinterpret_cast<T*>(ticket.m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static const T& GetTicketPayload(const EntitySpawnTicket& ticket)
|
||||
[[nodiscard]] static const T& GetTicketPayload(const EntitySpawnTicket& ticket)
|
||||
{
|
||||
return *reinterpret_cast<const T*>(ticket.m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static T* GetTicketPayload(EntitySpawnTicket* ticket)
|
||||
[[nodiscard]] static T* GetTicketPayload(EntitySpawnTicket* ticket)
|
||||
{
|
||||
return reinterpret_cast<T*>(ticket->m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static const T* GetTicketPayload(const EntitySpawnTicket* ticket)
|
||||
[[nodiscard]] static const T* GetTicketPayload(const EntitySpawnTicket* ticket)
|
||||
{
|
||||
return reinterpret_cast<const T*>(ticket->m_payload);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs)
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized.");
|
||||
|
||||
@@ -128,6 +128,20 @@ namespace AzFramework
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized.");
|
||||
|
||||
UpdateEntityAliasTypesCommand queueEntry;
|
||||
queueEntry.m_entityAliases = AZStd::move(updatedAliases);
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback);
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ListEntities(
|
||||
EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs)
|
||||
{
|
||||
@@ -175,6 +189,19 @@ namespace AzFramework
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::LoadBarrier(
|
||||
EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(completionCallback, "Load barrier on spawnable entities called without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to LoadBarrier hasn't been initialized.");
|
||||
|
||||
LoadBarrierCommand queueEntry;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
queueEntry.m_checkAliasSpawnables = optionalArgs.m_checkAliasSpawnables;
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus
|
||||
{
|
||||
CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft;
|
||||
@@ -203,13 +230,13 @@ namespace AzFramework
|
||||
for (size_t i = 0; i < delayedSize; ++i)
|
||||
{
|
||||
Requests& request = queue.m_delayed.front();
|
||||
bool result = AZStd::visit(
|
||||
[this](auto&& args) -> bool
|
||||
CommandResult result = AZStd::visit(
|
||||
[this](auto&& args) -> CommandResult
|
||||
{
|
||||
return ProcessRequest(args);
|
||||
},
|
||||
request);
|
||||
if (!result)
|
||||
if (result == CommandResult::Requeue)
|
||||
{
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
@@ -230,13 +257,13 @@ namespace AzFramework
|
||||
while (!pendingRequestQueue.empty())
|
||||
{
|
||||
Requests& request = pendingRequestQueue.front();
|
||||
bool result = AZStd::visit(
|
||||
[this](auto&& args) -> bool
|
||||
CommandResult result = AZStd::visit(
|
||||
[this](auto&& args) -> CommandResult
|
||||
{
|
||||
return ProcessRequest(args);
|
||||
},
|
||||
request);
|
||||
if (!result)
|
||||
if (result == CommandResult::Requeue)
|
||||
{
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
@@ -276,11 +303,81 @@ namespace AzFramework
|
||||
AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate,
|
||||
EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
// If the same ID gets remapped more than once, preserve the original remapping instead of overwriting it.
|
||||
if (!entityTemplate.GetComponents().empty())
|
||||
{
|
||||
// If the same ID gets remapped more than once, preserve the original remapping instead of overwriting it.
|
||||
constexpr bool allowDuplicateIds = false;
|
||||
|
||||
return AZ::IdUtils::Remapper<AZ::EntityId, allowDuplicateIds>::CloneObjectAndGenerateNewIdsAndFixRefs(
|
||||
&entityTemplate, templateToCloneMap, &serializeContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Entity* SpawnableEntitiesManager::CloneSingleAliasedEntity(
|
||||
const AZ::Entity& entityTemplate,
|
||||
const Spawnable::EntityAlias& alias,
|
||||
EntityIdMap& templateToCloneMap,
|
||||
AZ::Entity* previouslySpawnedEntity,
|
||||
AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
using ResultType = AZStd::pair<AZ::Entity*, uint32_t>;
|
||||
|
||||
AZ::Entity* clone = nullptr;
|
||||
switch (alias.m_aliasType)
|
||||
{
|
||||
case Spawnable::EntityAliasType::Original:
|
||||
// Behave as the original version.
|
||||
clone = CloneSingleEntity(entityTemplate, templateToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Disabled:
|
||||
// Do nothing.
|
||||
return nullptr;
|
||||
case Spawnable::EntityAliasType::Replace:
|
||||
clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), templateToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Additional:
|
||||
// The asset handler will have sorted and inserted a Spawnable::EntityAliasType::Original, so the just
|
||||
// spawn the additional entity.
|
||||
clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), templateToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Merge:
|
||||
AZ_Assert(previouslySpawnedEntity != nullptr, "Merging components but there's no entity to add to yet.");
|
||||
AZ_Assert(
|
||||
previouslySpawnedEntity->GetId() == alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetId(),
|
||||
"Entity ids for merging spawnables don't match.");
|
||||
AppendComponents(
|
||||
*previouslySpawnedEntity, alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetComponents(), templateToCloneMap, serializeContext);
|
||||
return nullptr;
|
||||
default:
|
||||
AZ_Assert(false, "Unsupported spawnable entity alias type: %i", alias.m_aliasType);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::AppendComponents(
|
||||
AZ::Entity& target,
|
||||
const AZ::Entity::ComponentArrayType& componentTemplates,
|
||||
EntityIdMap& templateToCloneMap,
|
||||
AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
// Only components are added and entities are looked up so no duplicate entity ids should be encountered.
|
||||
constexpr bool allowDuplicateIds = false;
|
||||
|
||||
return AZ::IdUtils::Remapper<AZ::EntityId, allowDuplicateIds>::CloneObjectAndGenerateNewIdsAndFixRefs(
|
||||
&entityTemplate, templateToCloneMap, &serializeContext);
|
||||
for (const AZ::Component* component : componentTemplates)
|
||||
{
|
||||
AZ::Component* clone = AZ::IdUtils::Remapper<AZ::EntityId, allowDuplicateIds>::CloneObjectAndGenerateNewIdsAndFixRefs(
|
||||
component, templateToCloneMap, &serializeContext);
|
||||
AZ_Assert(clone, "Unable to clone component for entity '%s' (%zu).", target.GetName().c_str(), target.GetId());
|
||||
[[maybe_unused]] bool result = target.AddComponent(clone);
|
||||
AZ_Assert(result, "Unable to add cloned component to entity '%s' (%zu).", target.GetName().c_str(), target.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::InitializeEntityIdMappings(
|
||||
@@ -316,161 +413,276 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
AZStd::vector<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 (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst();
|
||||
aliases.HasLock() && aliases.AreAllSpawnablesReady())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<uint32_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
|
||||
AZ::Entity* clone = CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
// Keep track how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
// These are 'template' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
uint32_t entitiesToSpawnSize = aznumeric_caster(entitiesToSpawn.size());
|
||||
|
||||
// loadAll is true if every entity has been spawned only once
|
||||
ticket.m_loadAll = (spawnedEntities.size() == entitiesToSpawnSize);
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
// Reserve buffers
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
(*it)->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
|
||||
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request)
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
AZStd::vector<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,
|
||||
// Pre-generate the full set of entity-id-to-new-entity-id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so
|
||||
// that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities
|
||||
// (or SpawnAllEntities) call.
|
||||
// However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false".
|
||||
// We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference
|
||||
// in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless
|
||||
// of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to
|
||||
// previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call.
|
||||
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
}
|
||||
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
for (size_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
auto aliasIt = aliases.begin();
|
||||
auto aliasEnd = aliases.end();
|
||||
if (aliasIt == aliasEnd)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
AZ::Entity* clone =
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
|
||||
spawnedEntities.push_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
spawnedEntities.emplace_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
ticket.m_loadAll = false;
|
||||
else
|
||||
{
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != i)
|
||||
{
|
||||
AZ::Entity* clone =
|
||||
CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so can
|
||||
// be safely executed in order without risking an invalid state.
|
||||
AZ::Entity* previousEntity = nullptr;
|
||||
do
|
||||
{
|
||||
AZ::Entity* clone = CloneSingleAliasedEntity(
|
||||
*entitiesToSpawn[i], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity,
|
||||
*request.m_serializeContext);
|
||||
// Not all alias operations create a new instance. It's also possible for an empty entity to be left behind,
|
||||
// in which case it's also filtered out as the entity component framework doesn't handle these gracefully.
|
||||
if (clone)
|
||||
{
|
||||
if (!clone->GetComponents().empty())
|
||||
{
|
||||
previousEntity = clone;
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete clone;
|
||||
}
|
||||
}
|
||||
++aliasIt;
|
||||
} while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
// There were no initial entities then the ticket now holds exactly all entities. If there were already entities then
|
||||
// a new set are not added so it no longer holds exactly the number of entities.
|
||||
ticket.m_loadAll = spawnedEntitiesInitialCount == 0;
|
||||
|
||||
auto newEntitiesBegin = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount;
|
||||
auto newEntitiesEnd = ticket.m_spawnedEntities.end();
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(newEntitiesBegin, newEntitiesEnd));
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = newEntitiesBegin; it != newEntitiesEnd; ++it)
|
||||
{
|
||||
(*it)->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(newEntitiesBegin, newEntitiesEnd));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst();
|
||||
aliases.HasLock() && aliases.AreAllSpawnablesReady())
|
||||
{
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<uint32_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);
|
||||
|
||||
auto aliasBegin = aliases.begin();
|
||||
auto aliasEnd = aliases.end();
|
||||
if (aliasBegin == aliasEnd)
|
||||
{
|
||||
for (uint32_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
AZ::Entity* clone =
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
spawnedEntities.push_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
auto aliasIt = AZStd::lower_bound(
|
||||
aliasBegin, aliasEnd, index,
|
||||
[](const Spawnable::EntityAlias& lhs, uint32_t rhs)
|
||||
{
|
||||
return lhs.m_sourceIndex < rhs;
|
||||
});
|
||||
|
||||
if (aliasIt == aliasEnd)
|
||||
{
|
||||
AZ::Entity* clone =
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so
|
||||
// can be safely executed in order without risking an invalid state.
|
||||
AZ::Entity* previousEntity = nullptr;
|
||||
do
|
||||
{
|
||||
AZ::Entity* clone = CloneSingleAliasedEntity(
|
||||
*entitiesToSpawn[index], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity,
|
||||
*request.m_serializeContext);
|
||||
// Not all alias operations create a new instance. It's also possible for an empty entity to be left
|
||||
// behind, in which case it's also filtered out as the entity component framework doesn't handle these
|
||||
// gracefully.
|
||||
if (clone)
|
||||
{
|
||||
if (!clone->GetComponents().empty())
|
||||
{
|
||||
previousEntity = clone;
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete clone;
|
||||
}
|
||||
}
|
||||
++aliasIt;
|
||||
} while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ticket.m_loadAll = false;
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(
|
||||
request.m_ticketId,
|
||||
SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
(*it)->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(
|
||||
request.m_ticketId,
|
||||
SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -495,15 +707,15 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -529,15 +741,15 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(),
|
||||
@@ -574,7 +786,7 @@ namespace AzFramework
|
||||
ticket.m_spawnedEntityIndices.clear();
|
||||
size_t entitiesToSpawnSize = entities.size();
|
||||
|
||||
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(entities[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
@@ -590,7 +802,7 @@ namespace AzFramework
|
||||
{
|
||||
size_t entitiesSize = entities.size();
|
||||
|
||||
for (size_t index : ticket.m_spawnedEntityIndices)
|
||||
for (uint32_t index : ticket.m_spawnedEntityIndices)
|
||||
{
|
||||
// It's possible for the new spawnable to have a different number of entities, so guard against this.
|
||||
// It's also possible that the entities have moved within the spawnable to a new index. This can't be
|
||||
@@ -616,15 +828,47 @@ namespace AzFramework
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(UpdateEntityAliasTypesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (Spawnable::EntityAliasVisitor aliases = ticket.m_spawnable->TryGetAliases(); aliases.HasLock())
|
||||
{
|
||||
for (EntityAliasTypeChange& replacement : request.m_entityAliases)
|
||||
{
|
||||
aliases.UpdateAliasType(replacement.m_aliasIndex, replacement.m_newAliasType);
|
||||
}
|
||||
aliases.Optimize();
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(
|
||||
ticket.m_spawnable->IsPermanentlyLocked(),
|
||||
"An request to UpdateEntityAliasTypes on the Spawnables Entities Manager was processed on a spawnable that's permanently "
|
||||
"locked.");
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -632,15 +876,15 @@ namespace AzFramework
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -651,15 +895,15 @@ namespace AzFramework
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size()));
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -671,15 +915,15 @@ namespace AzFramework
|
||||
ticket.m_spawnedEntityIndices.clear();
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -690,15 +934,39 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(LoadBarrierCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (request.m_checkAliasSpawnables)
|
||||
{
|
||||
if (Spawnable::EntityAliasConstVisitor visitor = ticket.m_spawnable->TryGetAliasesConst();
|
||||
!visitor.HasLock() || !visitor.AreAllSpawnablesReady())
|
||||
{
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request) -> CommandResult
|
||||
{
|
||||
if (request.m_requestId == request.m_ticket->m_currentRequestId)
|
||||
{
|
||||
@@ -714,11 +982,11 @@ namespace AzFramework
|
||||
}
|
||||
delete request.m_ticket;
|
||||
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -55,13 +55,18 @@ namespace AzFramework
|
||||
|
||||
void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_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;
|
||||
|
||||
void UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
void ListEntities(
|
||||
EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void ListIndicesAndEntities(
|
||||
@@ -70,6 +75,8 @@ namespace AzFramework
|
||||
EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override;
|
||||
void LoadBarrier(
|
||||
EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
//
|
||||
// The following function is thread safe but intended to be run from the main thread.
|
||||
@@ -78,7 +85,13 @@ namespace AzFramework
|
||||
CommandQueueStatus ProcessQueue(CommandQueuePriority priority);
|
||||
|
||||
protected:
|
||||
struct Ticket
|
||||
enum class CommandResult : bool
|
||||
{
|
||||
Executed,
|
||||
Requeue
|
||||
};
|
||||
|
||||
struct Ticket final
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0);
|
||||
static constexpr uint32_t Processing = AZStd::numeric_limits<uint32_t>::max();
|
||||
@@ -100,14 +113,14 @@ namespace AzFramework
|
||||
AZStd::unordered_set<AZ::EntityId> m_previouslySpawned;
|
||||
|
||||
AZStd::vector<AZ::Entity*> m_spawnedEntities;
|
||||
AZStd::vector<size_t> m_spawnedEntityIndices;
|
||||
AZStd::vector<uint32_t> m_spawnedEntityIndices;
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
uint32_t m_nextRequestId{ 0 }; //!< Next id for this ticket.
|
||||
uint32_t m_currentRequestId { 0 }; //!< The id for the command that should be executed.
|
||||
bool m_loadAll{ true };
|
||||
};
|
||||
|
||||
struct SpawnAllEntitiesCommand
|
||||
struct SpawnAllEntitiesCommand final
|
||||
{
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
@@ -116,9 +129,9 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct SpawnEntitiesCommand
|
||||
struct SpawnEntitiesCommand final
|
||||
{
|
||||
AZStd::vector<size_t> m_entityIndices;
|
||||
AZStd::vector<uint32_t> m_entityIndices;
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
AZ::SerializeContext* m_serializeContext;
|
||||
@@ -127,7 +140,7 @@ namespace AzFramework
|
||||
uint32_t m_requestId;
|
||||
bool m_referencePreviouslySpawnedEntities;
|
||||
};
|
||||
struct DespawnAllEntitiesCommand
|
||||
struct DespawnAllEntitiesCommand final
|
||||
{
|
||||
EntityDespawnCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
@@ -142,7 +155,7 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ReloadSpawnableCommand
|
||||
struct ReloadSpawnableCommand final
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
ReloadSpawnableCallback m_completionCallback;
|
||||
@@ -151,35 +164,51 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListEntitiesCommand
|
||||
struct UpdateEntityAliasTypesCommand final
|
||||
{
|
||||
AZStd::vector<EntityAliasTypeChange> m_entityAliases;
|
||||
UpdateEntityAliasTypesCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListEntitiesCommand final
|
||||
{
|
||||
ListEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListIndicesEntitiesCommand
|
||||
struct ListIndicesEntitiesCommand final
|
||||
{
|
||||
ListIndicesEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ClaimEntitiesCommand
|
||||
struct ClaimEntitiesCommand final
|
||||
{
|
||||
ClaimEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct BarrierCommand
|
||||
struct BarrierCommand final
|
||||
{
|
||||
BarrierCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct DestroyTicketCommand
|
||||
struct LoadBarrierCommand final
|
||||
{
|
||||
BarrierCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
bool m_checkAliasSpawnables;
|
||||
};
|
||||
struct DestroyTicketCommand final
|
||||
{
|
||||
Ticket* m_ticket;
|
||||
uint32_t m_requestId;
|
||||
@@ -191,10 +220,12 @@ namespace AzFramework
|
||||
DespawnAllEntitiesCommand,
|
||||
DespawnEntityCommand,
|
||||
ReloadSpawnableCommand,
|
||||
UpdateEntityAliasTypesCommand,
|
||||
ListEntitiesCommand,
|
||||
ListIndicesEntitiesCommand,
|
||||
ClaimEntitiesCommand,
|
||||
BarrierCommand,
|
||||
LoadBarrierCommand,
|
||||
DestroyTicketCommand>;
|
||||
|
||||
struct Queue
|
||||
@@ -213,17 +244,30 @@ namespace AzFramework
|
||||
|
||||
AZ::Entity* CloneSingleEntity(
|
||||
const AZ::Entity& entityTemplate, EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext);
|
||||
AZ::Entity* CloneSingleAliasedEntity(
|
||||
const AZ::Entity& entityTemplate,
|
||||
const Spawnable::EntityAlias& alias,
|
||||
EntityIdMap& templateToCloneMap,
|
||||
AZ::Entity* previouslySpawnedEntity,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
void AppendComponents(
|
||||
AZ::Entity& target,
|
||||
const AZ::Entity::ComponentArrayType& componentTemplates,
|
||||
EntityIdMap& templateToCloneMap,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
|
||||
bool ProcessRequest(SpawnAllEntitiesCommand& request);
|
||||
bool ProcessRequest(SpawnEntitiesCommand& request);
|
||||
bool ProcessRequest(DespawnAllEntitiesCommand& request);
|
||||
bool ProcessRequest(DespawnEntityCommand& request);
|
||||
bool ProcessRequest(ReloadSpawnableCommand& request);
|
||||
bool ProcessRequest(ListEntitiesCommand& request);
|
||||
bool ProcessRequest(ListIndicesEntitiesCommand& request);
|
||||
bool ProcessRequest(ClaimEntitiesCommand& request);
|
||||
bool ProcessRequest(BarrierCommand& request);
|
||||
bool ProcessRequest(DestroyTicketCommand& request);
|
||||
CommandResult ProcessRequest(SpawnAllEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(SpawnEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(DespawnAllEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(DespawnEntityCommand& request);
|
||||
CommandResult ProcessRequest(ReloadSpawnableCommand& request);
|
||||
CommandResult ProcessRequest(UpdateEntityAliasTypesCommand& request);
|
||||
CommandResult ProcessRequest(ListEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(ListIndicesEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(ClaimEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(BarrierCommand& request);
|
||||
CommandResult ProcessRequest(LoadBarrierCommand& request);
|
||||
CommandResult ProcessRequest(DestroyTicketCommand& request);
|
||||
|
||||
//! Generate a base set of original-to-new entity ID mappings to use during spawning.
|
||||
//! Since Entity references get fixed up on an entity-by-entity basis while spawning, it's important to have the complete
|
||||
|
||||
Reference in New Issue
Block a user