Merge pull request #973 from aws-lumberyard-dev/SpawnablePriorityQueue
Spawnable priority queue
This commit is contained in:
@@ -21,22 +21,6 @@ namespace AzFramework
|
||||
{
|
||||
}
|
||||
|
||||
Spawnable::Spawnable(Spawnable&& other)
|
||||
: m_entities(AZStd::move(other.m_entities))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Spawnable& Spawnable::operator=(Spawnable&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
m_entities = AZStd::move(other.m_entities);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const Spawnable::EntityList& Spawnable::GetEntities() const
|
||||
{
|
||||
return m_entities;
|
||||
|
||||
@@ -41,11 +41,11 @@ namespace AzFramework
|
||||
Spawnable() = default;
|
||||
explicit Spawnable(const AZ::Data::AssetId& id, AssetStatus status = AssetStatus::NotLoaded);
|
||||
Spawnable(const Spawnable& rhs) = delete;
|
||||
Spawnable(Spawnable&& other);
|
||||
Spawnable(Spawnable&& other) = delete;
|
||||
~Spawnable() override = default;
|
||||
|
||||
Spawnable& operator=(const Spawnable& rhs) = delete;
|
||||
Spawnable& operator=(Spawnable&& other);
|
||||
Spawnable& operator=(Spawnable&& other) = delete;
|
||||
|
||||
const EntityList& GetEntities() const;
|
||||
EntityList& GetEntities();
|
||||
|
||||
@@ -38,19 +38,20 @@ namespace AzFramework
|
||||
void SpawnableEntitiesContainer::SpawnAllEntities()
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling SpawnAllEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket);
|
||||
SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default);
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector<size_t> entityIndices)
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->SpawnEntities(m_threadData->m_spawnedEntitiesTicket, AZStd::move(entityIndices));
|
||||
SpawnableEntitiesInterface::Get()->SpawnEntities(
|
||||
m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default, AZStd::move(entityIndices));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::DespawnAllEntities()
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_threadData->m_spawnedEntitiesTicket);
|
||||
SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default);
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::Reset(AZ::Data::Asset<Spawnable> spawnable)
|
||||
@@ -66,8 +67,10 @@ namespace AzFramework
|
||||
m_monitor.Disconnect();
|
||||
m_monitor.m_threadData.reset();
|
||||
|
||||
SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket,
|
||||
[threadData = m_threadData](EntitySpawnTicket&) mutable
|
||||
SpawnableEntitiesInterface::Get()->Barrier(
|
||||
m_threadData->m_spawnedEntitiesTicket,
|
||||
SpawnablePriority_Default,
|
||||
[threadData = m_threadData](EntitySpawnTicket::Id) mutable
|
||||
{
|
||||
threadData.reset();
|
||||
});
|
||||
@@ -83,8 +86,10 @@ namespace AzFramework
|
||||
void SpawnableEntitiesContainer::Alert(AlertCallback callback)
|
||||
{
|
||||
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&)
|
||||
SpawnableEntitiesInterface::Get()->Barrier(
|
||||
m_threadData->m_spawnedEntitiesTicket,
|
||||
SpawnablePriority_Default,
|
||||
[generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id)
|
||||
{
|
||||
callback(generation);
|
||||
});
|
||||
@@ -110,6 +115,7 @@ namespace AzFramework
|
||||
AZ_Assert(m_threadData, "SpawnableEntitiesContainer is monitoring a spawnable, but doesn't have the associated data.");
|
||||
|
||||
AZ_TracePrintf("Spawnables", "Reloading spawnable '%s'.\n", replacementAsset.GetHint().c_str());
|
||||
SpawnableEntitiesInterface::Get()->ReloadSpawnable(m_threadData->m_spawnedEntitiesTicket, AZStd::move(replacementAsset));
|
||||
SpawnableEntitiesInterface::Get()->ReloadSpawnable(
|
||||
m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default, AZStd::move(replacementAsset));
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -239,7 +239,9 @@ namespace AzFramework
|
||||
{
|
||||
auto manager = SpawnableEntitiesInterface::Get();
|
||||
AZ_Assert(manager, "Attempting to create an entity spawn ticket while the SpawnableEntitiesInterface has no implementation.");
|
||||
m_payload = manager->CreateTicket(AZStd::move(spawnable));
|
||||
AZStd::pair<EntitySpawnTicket::Id, void*> result = manager->CreateTicket(AZStd::move(spawnable));
|
||||
m_id = result.first;
|
||||
m_payload = result.second;
|
||||
}
|
||||
|
||||
EntitySpawnTicket::~EntitySpawnTicket()
|
||||
@@ -250,6 +252,7 @@ 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;
|
||||
m_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,12 +266,20 @@ namespace AzFramework
|
||||
AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation.");
|
||||
manager->DestroyTicket(m_payload);
|
||||
}
|
||||
m_id = rhs.m_id;
|
||||
rhs.m_id = 0;
|
||||
|
||||
m_payload = rhs.m_payload;
|
||||
rhs.m_payload = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto EntitySpawnTicket::GetId() const -> Id
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
bool EntitySpawnTicket::IsValid() const
|
||||
{
|
||||
return m_payload != nullptr;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/RTTI/TypeSafeIntegral.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
|
||||
@@ -24,6 +25,14 @@ namespace AZ
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
AZ_TYPE_SAFE_INTEGRAL(SpawnablePriority, uint8_t);
|
||||
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_Highest { 0 };
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_High { 32 };
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_Default { 128 };
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_Low { 192 };
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_Lowest { 255 };
|
||||
|
||||
class SpawnableEntityContainerView
|
||||
{
|
||||
public:
|
||||
@@ -124,16 +133,18 @@ namespace AzFramework
|
||||
SpawnableIndexEntityIterator m_end;
|
||||
};
|
||||
|
||||
//! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that be used as a template. A ticket can
|
||||
//! be reused for multiple calls on the same spawnable and is safe to use by multiple threads at the same time. Entities created
|
||||
//! 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
|
||||
//! by a call so spawn entities. The life cycle of the spawned entities is tied to the ticket and all entities spawned using a
|
||||
//! by a call to spawn entities. The life cycle of the spawned entities is tied to the ticket and all entities spawned using a
|
||||
//! ticket will be despawned when it's deleted.
|
||||
class EntitySpawnTicket
|
||||
{
|
||||
public:
|
||||
friend class SpawnableEntitiesDefinition;
|
||||
|
||||
using Id = uint64_t;
|
||||
|
||||
EntitySpawnTicket() = default;
|
||||
EntitySpawnTicket(const EntitySpawnTicket&) = delete;
|
||||
EntitySpawnTicket(EntitySpawnTicket&& rhs);
|
||||
@@ -143,26 +154,37 @@ namespace AzFramework
|
||||
EntitySpawnTicket& operator=(const EntitySpawnTicket&) = delete;
|
||||
EntitySpawnTicket& operator=(EntitySpawnTicket&& rhs);
|
||||
|
||||
Id GetId() const;
|
||||
bool IsValid() const;
|
||||
|
||||
private:
|
||||
void* m_payload{ nullptr };
|
||||
Id m_id { 0 }; //!< An id that uniquely identifies a ticket.
|
||||
};
|
||||
|
||||
using EntitySpawnCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using EntityPreInsertionCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableEntityContainerView)>;
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket&)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstIndexEntityContainerView)>;
|
||||
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableEntityContainerView)>;
|
||||
using BarrierCallback = AZStd::function<void(EntitySpawnTicket&)>;
|
||||
using EntitySpawnCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using EntityPreInsertionCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableEntityContainerView)>;
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstIndexEntityContainerView)>;
|
||||
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableEntityContainerView)>;
|
||||
using BarrierCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
|
||||
//! 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
|
||||
//! issued from threads other than the one that issued the call, including the main thread.
|
||||
//!
|
||||
//! Calls on the same ticket are guaranteed to be executed in the order they are issued. Note that when issuing requests from
|
||||
//! multiple threads on the same ticket the order in which the requests are assigned to the ticket is not guaranteed.
|
||||
//!
|
||||
//! Most calls have a priority with values that range from 0 (highest priority) to 255 (lowest priority). The implementation of this
|
||||
//! interface may choose to use priority lanes which doesn't guarantee that higher priority requests happen before lower priority
|
||||
//! requests if they don't pass the priority lane threshold. Priority lanes and their thresholds are implementation specific and may
|
||||
//! differ between platforms. Note that if a call happened on a ticket with lower priority followed by a one with a higher priority
|
||||
//! the first lower priority call will still need to complete before the second higher priority call can be executed and the priority
|
||||
//! of the first call will not be updated.
|
||||
class SpawnableEntitiesDefinition
|
||||
{
|
||||
public:
|
||||
@@ -173,40 +195,48 @@ namespace AzFramework
|
||||
virtual ~SpawnableEntitiesDefinition() = default;
|
||||
|
||||
//! Spawn instances of all entities in the spawnable.
|
||||
//! @param spawnable The Spawnable asset that will be used to create entity instances from.
|
||||
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from
|
||||
//! a different thread than the one that made the function call. The returned list of entities contains all the newly
|
||||
//! created entities.
|
||||
virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
virtual void SpawnAllEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
EntitySpawnCallback completionCallback = {}) = 0;
|
||||
//! Spawn instances of some entities in the spawnable.
|
||||
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from.
|
||||
//! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from
|
||||
//! a different thread than the one that made this function call. The returned list of entities contains all the newly
|
||||
//! created entities.
|
||||
virtual void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
virtual void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector<size_t> entityIndices,
|
||||
EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) = 0;
|
||||
//! Removes all entities in the provided list from the environment.
|
||||
//! @param ticket The ticket previously used to spawn entities with.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param completionCallback Optional callback that's called when despawning entities has completed. This can be called from
|
||||
//! a different thread than the one that made this function call.
|
||||
virtual void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) = 0;
|
||||
virtual void DespawnAllEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback = {}) = 0;
|
||||
|
||||
//! Removes all entities in the provided list from the environment and reconstructs the entities from the provided spawnable.
|
||||
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
|
||||
//! @param ticket Holds the information on the entities to reload.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param spawnable The spawnable that will replace the existing spawnable. Both need to have the same asset id.
|
||||
//! @param completionCallback Optional callback that's called when the entities have been reloaded. This can be called from
|
||||
//! a different thread than the one that made this function call. The returned list of entities contains all the replacement
|
||||
//! entities.
|
||||
virtual void ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
|
||||
virtual void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset<Spawnable> spawnable,
|
||||
ReloadSpawnableCallback completionCallback = {}) = 0;
|
||||
|
||||
//! List all entities that are spawned using this ticket.
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param listCallback Required callback that will be called to list the entities on.
|
||||
virtual void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) = 0;
|
||||
virtual void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) = 0;
|
||||
//! List all entities that are spawned using this ticket with their spawnable index.
|
||||
//! Spawnables contain a flat list of entities, which are used as templates to spawn entities from. For every spawned entity
|
||||
//! the index of the entity in the spawnable that was used as a template is stored. This version of ListEntities will return
|
||||
@@ -214,17 +244,23 @@ namespace AzFramework
|
||||
//! the same index may appear multiple times as there are no restriction on how many instance of a specific entity can be
|
||||
//! created.
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param listCallback Required callback that will be called to list the entities and indices on.
|
||||
virtual void ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) = 0;
|
||||
virtual void ListIndicesAndEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) = 0;
|
||||
//! Claim all entities that are spawned using this ticket. Ownership of the entities is transferred from the ticket to the
|
||||
//! caller through the callback. After this call the ticket will have no entities associated with it. The caller of
|
||||
//! this function will need to manage the entities after this call.
|
||||
//! @param ticket Only the entities associated with this ticket will be released.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param listCallback Required callback that will be called to transfer the entities through.
|
||||
virtual void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) = 0;
|
||||
virtual void ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) = 0;
|
||||
|
||||
//! Blocks until all operations made on the provided ticket before the barrier call have completed.
|
||||
virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback) = 0;
|
||||
//! @param ticket The ticket to monitor.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param completionCallback Required callback that will be called as soon as the barrier has been reached.
|
||||
virtual void Barrier(EntitySpawnTicket& ticket, SpawnablePriority priority, BarrierCallback completionCallback) = 0;
|
||||
|
||||
//! Register a handler for OnSpawned events.
|
||||
virtual void AddOnSpawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) = 0;
|
||||
@@ -233,7 +269,7 @@ namespace AzFramework
|
||||
virtual void AddOnDespawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) = 0;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] virtual void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
||||
[[nodiscard]] virtual AZStd::pair<EntitySpawnTicket::Id, void*> CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
||||
virtual void DestroyTicket(void* ticket) = 0;
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#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>
|
||||
@@ -22,128 +24,122 @@
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback,
|
||||
template<typename T>
|
||||
void SpawnableEntitiesManager::QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request)
|
||||
{
|
||||
request.m_ticket = &GetTicketPayload<Ticket>(ticket);
|
||||
Queue& queue = priority <= m_highPriorityThreshold ? m_highPriorityQueue : m_regularPriorityQueue;
|
||||
{
|
||||
AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex);
|
||||
request.m_requestId = GetTicketPayload<Ticket>(ticket).m_nextRequestId++;
|
||||
queue.m_pendingRequest.push(AZStd::move(request));
|
||||
}
|
||||
}
|
||||
|
||||
SpawnableEntitiesManager::SpawnableEntitiesManager()
|
||||
{
|
||||
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, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback,
|
||||
EntitySpawnCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnAllEntities hasn't been initialized.");
|
||||
|
||||
SpawnAllEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector<size_t> entityIndices,
|
||||
EntityPreInsertionCallback preInsertionCallback, EntitySpawnCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized.");
|
||||
|
||||
SpawnEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_entityIndices = AZStd::move(entityIndices);
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback)
|
||||
void SpawnableEntitiesManager::DespawnAllEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to DespawnAllEntities hasn't been initialized.");
|
||||
|
||||
DespawnAllEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
|
||||
void SpawnableEntitiesManager::ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset<Spawnable> spawnable,
|
||||
ReloadSpawnableCallback completionCallback)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized.");
|
||||
|
||||
ReloadSpawnableCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_spawnable = AZStd::move(spawnable);
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback)
|
||||
void SpawnableEntitiesManager::ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback)
|
||||
{
|
||||
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_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_listCallback = AZStd::move(listCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback)
|
||||
void SpawnableEntitiesManager::ListIndicesAndEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback)
|
||||
{
|
||||
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_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_listCallback = AZStd::move(listCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback)
|
||||
void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback)
|
||||
{
|
||||
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_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_listCallback = AZStd::move(listCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback)
|
||||
void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, SpawnablePriority priority, BarrierCallback completionCallback)
|
||||
{
|
||||
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_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler)
|
||||
@@ -156,34 +152,54 @@ namespace AzFramework
|
||||
handler.Connect(m_onDespawnedEvent);
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessQueue() -> CommandQueueStatus
|
||||
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
|
||||
{
|
||||
AZStd::queue<Requests> pendingRequestQueue;
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
m_pendingRequestQueue.swap(pendingRequestQueue);
|
||||
AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex);
|
||||
queue.m_pendingRequest.swap(pendingRequestQueue);
|
||||
}
|
||||
|
||||
if (!pendingRequestQueue.empty() || !m_delayedQueue.empty())
|
||||
if (!pendingRequestQueue.empty() || !queue.m_delayed.empty())
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
AZ_Assert(serializeContext, "Failed to retrieve serialization context.");
|
||||
|
||||
// Only process the requests that are currently in this queue, not the ones that could be re-added if they still can't complete.
|
||||
size_t delayedSize = m_delayedQueue.size();
|
||||
size_t delayedSize = queue.m_delayed.size();
|
||||
for (size_t i = 0; i < delayedSize; ++i)
|
||||
{
|
||||
Requests& request = m_delayedQueue.front();
|
||||
Requests& request = queue.m_delayed.front();
|
||||
bool result = AZStd::visit([this, serializeContext](auto&& args) -> bool
|
||||
{
|
||||
return ProcessRequest(args, *serializeContext);
|
||||
}, request);
|
||||
if (!result)
|
||||
{
|
||||
m_delayedQueue.emplace_back(AZStd::move(request));
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
m_delayedQueue.pop_front();
|
||||
queue.m_delayed.pop_front();
|
||||
}
|
||||
|
||||
do
|
||||
@@ -197,7 +213,7 @@ namespace AzFramework
|
||||
}, request);
|
||||
if (!result)
|
||||
{
|
||||
m_delayedQueue.emplace_back(AZStd::move(request));
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
pendingRequestQueue.pop();
|
||||
}
|
||||
@@ -205,20 +221,22 @@ namespace AzFramework
|
||||
// Spawning entities can result in more entities being queued to spawn. Repeat spawning until the queue is
|
||||
// empty to avoid a chain of entity spawning getting dragged out over multiple frames.
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
m_pendingRequestQueue.swap(pendingRequestQueue);
|
||||
AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex);
|
||||
queue.m_pendingRequest.swap(pendingRequestQueue);
|
||||
}
|
||||
} while (!pendingRequestQueue.empty());
|
||||
}
|
||||
|
||||
return m_delayedQueue.empty() ? CommandQueueStatus::NoCommandLeft : CommandQueueStatus::HasCommandsLeft;
|
||||
return queue.m_delayed.empty() ? CommandQueueStatus::NoCommandsLeft : CommandQueueStatus::HasCommandsLeft;
|
||||
}
|
||||
|
||||
void* SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable)
|
||||
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 result;
|
||||
return AZStd::make_pair<EntitySpawnTicket::Id, void*>(idCounter++, result);
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::DestroyTicket(void* ticket)
|
||||
@@ -226,9 +244,9 @@ namespace AzFramework
|
||||
DestroyTicketCommand queueEntry;
|
||||
queueEntry.m_ticket = reinterpret_cast<Ticket*>(ticket);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = reinterpret_cast<Ticket*>(ticket)->m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,8 +269,8 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId)
|
||||
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;
|
||||
@@ -300,7 +318,7 @@ namespace AzFramework
|
||||
// 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_ticket, SpawnableEntityContainerView(
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
@@ -314,13 +332,13 @@ namespace AzFramework
|
||||
// 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_ticket, SpawnableConstEntityContainerView(
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -331,8 +349,8 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId)
|
||||
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;
|
||||
@@ -367,9 +385,7 @@ namespace AzFramework
|
||||
// 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_ticket,
|
||||
SpawnableEntityContainerView(
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
@@ -382,13 +398,13 @@ namespace AzFramework
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -400,8 +416,8 @@ namespace AzFramework
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request,
|
||||
[[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
for (AZ::Entity* entity : ticket.m_spawnedEntities)
|
||||
{
|
||||
@@ -417,12 +433,12 @@ namespace AzFramework
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket);
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
}
|
||||
|
||||
m_onDespawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -433,11 +449,11 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
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_ticketId == ticket.m_currentTicketId)
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
// Delete the original entities.
|
||||
for (AZ::Entity* entity : ticket.m_spawnedEntities)
|
||||
@@ -493,11 +509,11 @@ namespace AzFramework
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
|
||||
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
@@ -511,12 +527,12 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
request.m_listCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -527,17 +543,15 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
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_ticket,
|
||||
SpawnableConstIndexEntityContainerView(
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size()));
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -548,16 +562,16 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
request.m_listCallback(*request.m_ticket, SpawnableEntityContainerView(
|
||||
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_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -568,15 +582,15 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket);
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
}
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -587,7 +601,7 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
if (request.m_ticketId == request.m_ticket->m_currentTicketId)
|
||||
if (request.m_requestId == request.m_ticket->m_currentRequestId)
|
||||
{
|
||||
for (AZ::Entity* entity : request.m_ticket->m_spawnedEntities)
|
||||
{
|
||||
@@ -606,24 +620,4 @@ namespace AzFramework
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::IsEqualTicket(const EntitySpawnTicket* lhs, const EntitySpawnTicket* rhs)
|
||||
{
|
||||
return GetTicketPayload<Ticket>(lhs) == GetTicketPayload<Ticket>(rhs);
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::IsEqualTicket(const Ticket* lhs, const EntitySpawnTicket* rhs)
|
||||
{
|
||||
return lhs == GetTicketPayload<Ticket>(rhs);
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::IsEqualTicket(const EntitySpawnTicket* lhs, const Ticket* rhs)
|
||||
{
|
||||
return GetTicketPayload<Ticket>(lhs) == rhs;
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::IsEqualTicket(const Ticket* lhs, const Ticket* rhs)
|
||||
{
|
||||
return lhs = rhs;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -29,8 +29,6 @@ namespace AZ
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
|
||||
|
||||
class SpawnableEntitiesManager
|
||||
: public SpawnableEntitiesInterface::Registrar
|
||||
{
|
||||
@@ -38,31 +36,47 @@ namespace AzFramework
|
||||
AZ_RTTI(AzFramework::SpawnableEntitiesManager, "{6E14333F-128C-464C-94CA-A63B05A5E51C}");
|
||||
AZ_CLASS_ALLOCATOR(SpawnableEntitiesManager, AZ::SystemAllocator, 0);
|
||||
|
||||
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
|
||||
|
||||
enum class CommandQueueStatus : bool
|
||||
{
|
||||
HasCommandsLeft,
|
||||
NoCommandLeft
|
||||
NoCommandsLeft
|
||||
};
|
||||
|
||||
enum class CommandQueuePriority
|
||||
{
|
||||
High = 1 << 0,
|
||||
Regular = 1 << 1
|
||||
};
|
||||
|
||||
SpawnableEntitiesManager();
|
||||
~SpawnableEntitiesManager() override = default;
|
||||
|
||||
//
|
||||
// The following functions are thread safe
|
||||
//
|
||||
|
||||
void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override;
|
||||
void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
void SpawnAllEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
EntitySpawnCallback completionCallback = {}) override;
|
||||
void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) override;
|
||||
void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector<size_t> entityIndices,
|
||||
EntityPreInsertionCallback preInsertionCallback = {},
|
||||
EntitySpawnCallback completionCallback = {}) override;
|
||||
void DespawnAllEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback = {}) override;
|
||||
|
||||
void ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
|
||||
void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset<Spawnable> spawnable,
|
||||
ReloadSpawnableCallback completionCallback = {}) override;
|
||||
|
||||
void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) override;
|
||||
void ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) override;
|
||||
void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) override;
|
||||
void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) override;
|
||||
void ListIndicesAndEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) override;
|
||||
void ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) override;
|
||||
|
||||
void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback) override;
|
||||
void Barrier(EntitySpawnTicket& spawnInfo, SpawnablePriority priority, BarrierCallback completionCallback) override;
|
||||
|
||||
void AddOnSpawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) override;
|
||||
void AddOnDespawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) override;
|
||||
@@ -71,13 +85,9 @@ namespace AzFramework
|
||||
// The following function is thread safe but intended to be run from the main thread.
|
||||
//
|
||||
|
||||
CommandQueueStatus ProcessQueue();
|
||||
CommandQueueStatus ProcessQueue(CommandQueuePriority priority);
|
||||
|
||||
protected:
|
||||
void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) override;
|
||||
void DestroyTicket(void* ticket) override;
|
||||
|
||||
private:
|
||||
struct Ticket
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0);
|
||||
@@ -86,8 +96,8 @@ namespace AzFramework
|
||||
AZStd::vector<AZ::Entity*> m_spawnedEntities;
|
||||
AZStd::vector<size_t> m_spawnedEntityIndices;
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
uint32_t m_nextTicketId{ 0 }; //!< Next id for this ticket.
|
||||
uint32_t m_currentTicketId{ 0 }; //!< The id for the command that should be executed.
|
||||
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 };
|
||||
};
|
||||
|
||||
@@ -95,64 +105,86 @@ namespace AzFramework
|
||||
{
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct SpawnEntitiesCommand
|
||||
{
|
||||
AZStd::vector<size_t> m_entityIndices;
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct DespawnAllEntitiesCommand
|
||||
{
|
||||
EntityDespawnCallback m_completionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ReloadSpawnableCommand
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
ReloadSpawnableCallback m_completionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListEntitiesCommand
|
||||
{
|
||||
ListEntitiesCallback m_listCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListIndicesEntitiesCommand
|
||||
{
|
||||
ListIndicesEntitiesCallback m_listCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ClaimEntitiesCommand
|
||||
{
|
||||
ClaimEntitiesCallback m_listCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct BarrierCommand
|
||||
{
|
||||
BarrierCallback m_completionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct DestroyTicketCommand
|
||||
{
|
||||
Ticket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
|
||||
using Requests = AZStd::variant<
|
||||
SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand,
|
||||
ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>;
|
||||
|
||||
struct Queue
|
||||
{
|
||||
AZStd::deque<Requests> m_delayed; //!< Requests that were processed before, but couldn't be completed.
|
||||
AZStd::queue<Requests> m_pendingRequest; //!< Requests waiting to be processed for the first time.
|
||||
AZStd::mutex m_pendingRequestMutex;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request);
|
||||
AZStd::pair<EntitySpawnTicket::Id, void*> CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) override;
|
||||
void DestroyTicket(void* ticket) override;
|
||||
|
||||
CommandQueueStatus ProcessQueue(Queue& queue);
|
||||
|
||||
AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
|
||||
@@ -169,16 +201,18 @@ namespace AzFramework
|
||||
bool ProcessRequest(BarrierCommand& request, AZ::SerializeContext& serializeContext);
|
||||
bool ProcessRequest(DestroyTicketCommand& request, AZ::SerializeContext& serializeContext);
|
||||
|
||||
[[nodiscard]] static bool IsEqualTicket(const EntitySpawnTicket* lhs, const EntitySpawnTicket* rhs);
|
||||
[[nodiscard]] static bool IsEqualTicket(const Ticket* lhs, const EntitySpawnTicket* rhs);
|
||||
[[nodiscard]] static bool IsEqualTicket(const EntitySpawnTicket* lhs, const Ticket* rhs);
|
||||
[[nodiscard]] static bool IsEqualTicket(const Ticket* lhs, const Ticket* rhs);
|
||||
|
||||
AZStd::deque<Requests> m_delayedQueue; //!< Requests that were processed before, but couldn't be completed.
|
||||
AZStd::queue<Requests> m_pendingRequestQueue;
|
||||
AZStd::mutex m_pendingRequestQueueMutex;
|
||||
Queue m_highPriorityQueue;
|
||||
Queue m_regularPriorityQueue;
|
||||
|
||||
AZ::Event<AZ::Data::Asset<Spawnable>> m_onSpawnedEvent;
|
||||
AZ::Event<AZ::Data::Asset<Spawnable>> m_onDespawnedEvent;
|
||||
|
||||
//! The threshold used to determine if a request goes in the regular (if bigger than the value) or high priority queue (if smaller
|
||||
//! or equal to this value). The starting value of 64 is chosen as it's between default values SpawnablePriority_High and
|
||||
//! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured
|
||||
//! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold".
|
||||
SpawnablePriority m_highPriorityThreshold { 64 };
|
||||
};
|
||||
|
||||
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority);
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -48,10 +48,23 @@ namespace AzFramework
|
||||
|
||||
void SpawnableSystemComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
|
||||
{
|
||||
m_entitiesManager.ProcessQueue();
|
||||
m_entitiesManager.ProcessQueue(
|
||||
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
RootSpawnableNotificationBus::ExecuteQueuedEvents();
|
||||
}
|
||||
|
||||
int SpawnableSystemComponent::GetTickOrder()
|
||||
{
|
||||
return AZ::ComponentTickBus::TICK_GAME;
|
||||
}
|
||||
|
||||
void SpawnableSystemComponent::OnSystemTick()
|
||||
{
|
||||
// Handle only high priority spawning events such as those created from network. These need to happen even if the client
|
||||
// doesn't have focus to avoid time-out issues for instance.
|
||||
m_entitiesManager.ProcessQueue(SpawnableEntitiesManager::CommandQueuePriority::High);
|
||||
}
|
||||
|
||||
void SpawnableSystemComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
|
||||
{
|
||||
if (!m_catalogAvailable)
|
||||
@@ -168,7 +181,8 @@ namespace AzFramework
|
||||
SpawnableEntitiesManager::CommandQueueStatus queueStatus;
|
||||
do
|
||||
{
|
||||
queueStatus = m_entitiesManager.ProcessQueue();
|
||||
queueStatus = m_entitiesManager.ProcessQueue(
|
||||
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
} while (queueStatus == SpawnableEntitiesManager::CommandQueueStatus::HasCommandsLeft);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace AzFramework
|
||||
class SpawnableSystemComponent
|
||||
: public AZ::Component
|
||||
, public AZ::TickBus::Handler
|
||||
, public AZ::SystemTickBus::Handler
|
||||
, public AssetCatalogEventBus::Handler
|
||||
, public RootSpawnableInterface::Registrar
|
||||
, public RootSpawnableNotificationBus::Handler
|
||||
@@ -58,6 +59,13 @@ namespace AzFramework
|
||||
//
|
||||
|
||||
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
|
||||
int GetTickOrder() override;
|
||||
|
||||
//
|
||||
// SystemTickBus
|
||||
//
|
||||
|
||||
void OnSystemTick() override;
|
||||
|
||||
//
|
||||
// AssetCatalogEventBus
|
||||
|
||||
@@ -24,17 +24,6 @@
|
||||
|
||||
namespace AzToolsFramework::Prefab::SpawnableUtils
|
||||
{
|
||||
|
||||
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom)
|
||||
{
|
||||
AzFramework::Spawnable spawnable;
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> referencedAssets;
|
||||
[[maybe_unused]] bool result = CreateSpawnable(spawnable, prefabDom, referencedAssets);
|
||||
AZ_Assert(result,
|
||||
"Failed to Load Prefab Instance from given Prefab DOM while Spawnable creation.");
|
||||
return spawnable;
|
||||
}
|
||||
|
||||
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom)
|
||||
{
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> referencedAssets;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
namespace AzToolsFramework::Prefab::SpawnableUtils
|
||||
{
|
||||
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom);
|
||||
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom);
|
||||
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
|
||||
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@ namespace Benchmark
|
||||
{
|
||||
state.PauseTiming();
|
||||
|
||||
auto spawnable = ::AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(prefabDom);
|
||||
AzFramework::Spawnable spawnable;
|
||||
AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(spawnable, prefabDom);
|
||||
|
||||
state.ResumeTiming();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ namespace UnitTest
|
||||
|
||||
//Create Spawnable
|
||||
auto& prefabDom = m_prefabSystemComponent->FindTemplateDom(instance->GetTemplateId());
|
||||
auto spawnable = ::AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(prefabDom);
|
||||
AzFramework::Spawnable spawnable;
|
||||
AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(spawnable, prefabDom);
|
||||
|
||||
EXPECT_EQ(spawnable.GetEntities().size() - 1, normalEntityCount); // 1 for container entity
|
||||
const auto& spawnableEntities = spawnable.GetEntities();
|
||||
@@ -84,7 +85,8 @@ namespace UnitTest
|
||||
|
||||
//Create Spawnable
|
||||
auto& prefabDom = m_prefabSystemComponent->FindTemplateDom(thirdInstance->GetTemplateId());
|
||||
auto spawnable = ::AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(prefabDom);
|
||||
AzFramework::Spawnable spawnable;
|
||||
AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(spawnable, prefabDom);
|
||||
|
||||
EXPECT_EQ(spawnable.GetEntities().size() - 1, normalEntityCount); // 1 for container entity
|
||||
const auto& spawnableEntities = spawnable.GetEntities();
|
||||
|
||||
@@ -55,7 +55,11 @@ namespace UnitTest
|
||||
delete m_ticket;
|
||||
m_ticket = nullptr;
|
||||
// One more tick on the spawnable entities manager in order to delete the ticket fully.
|
||||
m_manager->ProcessQueue();
|
||||
while (m_manager->ProcessQueue(
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High |
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular) !=
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueueStatus::NoCommandsLeft)
|
||||
;
|
||||
|
||||
delete m_spawnableAsset;
|
||||
m_spawnableAsset = nullptr;
|
||||
@@ -85,6 +89,10 @@ namespace UnitTest
|
||||
TestApplication* m_application { nullptr };
|
||||
};
|
||||
|
||||
//
|
||||
// SpawnAllEntitities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_Call_AllEntitiesSpawned)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
@@ -92,16 +100,72 @@ namespace UnitTest
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
[&spawnedEntitiesCount](AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
[&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
m_manager->SpawnAllEntities(*m_ticket, {}, AZStd::move(callback));
|
||||
m_manager->ProcessQueue();
|
||||
m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(callback));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->SpawnAllEntities(ticket, AzFramework::SpawnablePriority_Default);
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// SpawnEntities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->SpawnEntities(ticket, AzFramework::SpawnablePriority_Default, {});
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// DespawnAllEntities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, DespawnAllEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->DespawnAllEntities(ticket, AzFramework::SpawnablePriority_Default);
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ReloadSpawnable
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ReloadSpawnable_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->ReloadSpawnable(ticket, AzFramework::SpawnablePriority_Default, *m_spawnableAsset);
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ListEntitities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListEntities_Call_AllEntitiesAreReported)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
@@ -110,7 +174,7 @@ namespace UnitTest
|
||||
bool allValidEntityIds = true;
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&allValidEntityIds, &spawnedEntitiesCount]
|
||||
(AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
for (auto&& entity : entities)
|
||||
{
|
||||
@@ -119,14 +183,30 @@ namespace UnitTest
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
|
||||
m_manager->SpawnAllEntities(*m_ticket);
|
||||
m_manager->ListEntities(*m_ticket, AZStd::move(callback));
|
||||
m_manager->ProcessQueue();
|
||||
m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default);
|
||||
m_manager->ListEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_TRUE(allValidEntityIds);
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView) {};
|
||||
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->ListEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback));
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ListIndicesAndEntities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListIndicesAndEntities_Call_AllEntitiesAreReportedAndIncrementByOne)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
@@ -135,7 +215,7 @@ namespace UnitTest
|
||||
bool allValidEntityIds = true;
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&allValidEntityIds, &spawnedEntitiesCount]
|
||||
(AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstIndexEntityContainerView entities)
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstIndexEntityContainerView entities)
|
||||
{
|
||||
for (auto&& indexEntityPair : entities)
|
||||
{
|
||||
@@ -148,11 +228,121 @@ namespace UnitTest
|
||||
}
|
||||
};
|
||||
|
||||
m_manager->SpawnAllEntities(*m_ticket);
|
||||
m_manager->ListIndicesAndEntities(*m_ticket, AZStd::move(callback));
|
||||
m_manager->ProcessQueue();
|
||||
m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default);
|
||||
m_manager->ListIndicesAndEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_TRUE(allValidEntityIds);
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListIndicesAndEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstIndexEntityContainerView) {};
|
||||
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->ListIndicesAndEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback));
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ClaimEntities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ClaimEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView) {};
|
||||
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->ClaimEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback));
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Barrier
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, Barrier_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id) {};
|
||||
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->Barrier(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback));
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Misc. - Priority tests
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, Priority_HighBeforeDefault_HigherPriorityCallHappensBeforeDefaultPriorityEvenWhenQueuedLater)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AzFramework::EntitySpawnTicket highPriorityTicket(*m_spawnableAsset);
|
||||
|
||||
size_t callCounter = 1;
|
||||
size_t highPriorityCallId = 0;
|
||||
size_t defaultPriorityCallId = 0;
|
||||
auto highCallback = [&callCounter, &highPriorityCallId]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView)
|
||||
{
|
||||
highPriorityCallId = callCounter++;
|
||||
};
|
||||
auto defaultCallback = [&callCounter, &defaultPriorityCallId]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView)
|
||||
{
|
||||
defaultPriorityCallId = callCounter++;
|
||||
};
|
||||
|
||||
m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(defaultCallback));
|
||||
m_manager->SpawnAllEntities(highPriorityTicket, AzFramework::SpawnablePriority_High, {}, AZStd::move(highCallback));
|
||||
m_manager->ProcessQueue(
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High |
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_LT(highPriorityCallId, defaultPriorityCallId);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, Priority_SameTicket_DefaultPriorityCallHappensBeforeHighPriority)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
size_t callCounter = 1;
|
||||
size_t highPriorityCallId = 0;
|
||||
size_t defaultPriorityCallId = 0;
|
||||
auto highCallback =
|
||||
[&callCounter, &highPriorityCallId](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView)
|
||||
{
|
||||
highPriorityCallId = callCounter++;
|
||||
};
|
||||
auto defaultCallback =
|
||||
[&callCounter, &defaultPriorityCallId](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView)
|
||||
{
|
||||
defaultPriorityCallId = callCounter++;
|
||||
};
|
||||
|
||||
m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(defaultCallback));
|
||||
m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_High, {}, AZStd::move(highCallback));
|
||||
m_manager->ProcessQueue(
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High |
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
// Run a second time as the high priority task will be pending at this point.
|
||||
m_manager->ProcessQueue(
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High |
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_LT(defaultPriorityCallId, highPriorityCallId);
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Multiplayer
|
||||
AZ::Transform worldTm = GetEntity()->FindComponent<AzFramework::TransformComponent>()->GetWorldTM();
|
||||
auto preInsertionCallback =
|
||||
[worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()]
|
||||
(AzFramework::EntitySpawnTicket&, AzFramework::SpawnableEntityContainerView entities)
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities)
|
||||
{
|
||||
if (entities.size() == 1)
|
||||
{
|
||||
@@ -81,7 +81,8 @@ namespace Multiplayer
|
||||
};
|
||||
|
||||
m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(m_netSpawnTicket, {m_netEntityIndex}, preInsertionCallback);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(
|
||||
m_netSpawnTicket, AzFramework::SpawnablePriority_Default, { m_netEntityIndex }, preInsertionCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ namespace Multiplayer
|
||||
{
|
||||
if(m_netSpawnTicket.IsValid())
|
||||
{
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket, AzFramework::SpawnablePriority_Default);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace ScriptCanvas::Nodeables::Spawning
|
||||
return;
|
||||
}
|
||||
|
||||
auto preSpawnCB = [this, translation, rotation, scale]([[maybe_unused]] AzFramework::EntitySpawnTicket& ticket,
|
||||
auto preSpawnCB = [this, translation, rotation, scale]([[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId,
|
||||
AzFramework::SpawnableEntityContainerView view)
|
||||
{
|
||||
AZ::Entity* rootEntity = *view.begin();
|
||||
@@ -122,7 +122,7 @@ namespace ScriptCanvas::Nodeables::Spawning
|
||||
}
|
||||
};
|
||||
|
||||
auto spawnCompleteCB = [this]([[maybe_unused]] AzFramework::EntitySpawnTicket& ticket,
|
||||
auto spawnCompleteCB = [this]([[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId,
|
||||
AzFramework::SpawnableConstEntityContainerView view)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_idBatchMutex);
|
||||
@@ -134,6 +134,7 @@ namespace ScriptCanvas::Nodeables::Spawning
|
||||
m_spawnBatchSizes.push_back(view.size());
|
||||
};
|
||||
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_spawnTicket, preSpawnCB, spawnCompleteCB);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(
|
||||
m_spawnTicket, AzFramework::SpawnablePriority_Default, preSpawnCB, spawnCompleteCB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"O3DE":
|
||||
{
|
||||
"AzFramework":
|
||||
{
|
||||
"Spawnables":
|
||||
{
|
||||
// Any requests with a priorty value equal or smaller than this will be considered a high priority request.
|
||||
// The range for this value is between 0 and 255.
|
||||
"HighPriorityThreshold" : 64
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user