SPEC-6590 Implemented network entities instantiation by network spawnable asset ID
This commit is contained in:
@@ -148,6 +148,12 @@ namespace AzFramework
|
|||||||
//! Blocks until all operations made on the provided ticket before the barrier call have completed.
|
//! Blocks until all operations made on the provided ticket before the barrier call have completed.
|
||||||
virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback) = 0;
|
virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback) = 0;
|
||||||
|
|
||||||
|
//! Register a handler for OnSpawned events.
|
||||||
|
virtual void AddOnSpawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) = 0;
|
||||||
|
|
||||||
|
//! Register a handler for OnDespawned events.
|
||||||
|
virtual void AddOnDespawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
[[nodiscard]] virtual void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
[[nodiscard]] virtual void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
||||||
virtual void DestroyTicket(void* ticket) = 0;
|
virtual void DestroyTicket(void* ticket) = 0;
|
||||||
|
|||||||
@@ -114,6 +114,16 @@ namespace AzFramework
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler)
|
||||||
|
{
|
||||||
|
handler.Connect(m_onSpawnedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnableEntitiesManager::AddOnDespawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler)
|
||||||
|
{
|
||||||
|
handler.Connect(m_onDespawnedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
auto SpawnableEntitiesManager::ProcessQueue() -> CommandQueueStatus
|
auto SpawnableEntitiesManager::ProcessQueue() -> CommandQueueStatus
|
||||||
{
|
{
|
||||||
AZStd::queue<Requests> pendingRequestQueue;
|
AZStd::queue<Requests> pendingRequestQueue;
|
||||||
@@ -223,6 +233,8 @@ namespace AzFramework
|
|||||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
|
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||||
|
|
||||||
ticket.m_currentTicketId++;
|
ticket.m_currentTicketId++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -257,6 +269,8 @@ namespace AzFramework
|
|||||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
|
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||||
|
|
||||||
ticket.m_currentTicketId++;
|
ticket.m_currentTicketId++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -289,6 +303,8 @@ namespace AzFramework
|
|||||||
request.m_completionCallback(*request.m_ticket);
|
request.m_completionCallback(*request.m_ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_onDespawnedEvent.Signal(ticket.m_spawnable);
|
||||||
|
|
||||||
ticket.m_currentTicketId++;
|
ticket.m_currentTicketId++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -315,6 +331,8 @@ namespace AzFramework
|
|||||||
&GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId());
|
&GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_onDespawnedEvent.Signal(ticket.m_spawnable);
|
||||||
|
|
||||||
// Rebuild the list of entities.
|
// Rebuild the list of entities.
|
||||||
ticket.m_spawnedEntities.clear();
|
ticket.m_spawnedEntities.clear();
|
||||||
@@ -350,6 +368,9 @@ namespace AzFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
ticket.m_currentTicketId++;
|
ticket.m_currentTicketId++;
|
||||||
|
|
||||||
|
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ namespace AzFramework
|
|||||||
|
|
||||||
void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback) override;
|
void Barrier(EntitySpawnTicket& spawnInfo, 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;
|
||||||
|
|
||||||
//
|
//
|
||||||
// The following function is thread safe but intended to be run from the main thread.
|
// The following function is thread safe but intended to be run from the main thread.
|
||||||
//
|
//
|
||||||
@@ -156,5 +159,8 @@ namespace AzFramework
|
|||||||
AZStd::deque<Requests> m_delayedQueue; //!< Requests that were processed before, but couldn't be completed.
|
AZStd::deque<Requests> m_delayedQueue; //!< Requests that were processed before, but couldn't be completed.
|
||||||
AZStd::queue<Requests> m_pendingRequestQueue;
|
AZStd::queue<Requests> m_pendingRequestQueue;
|
||||||
AZStd::mutex m_pendingRequestQueueMutex;
|
AZStd::mutex m_pendingRequestQueueMutex;
|
||||||
|
|
||||||
|
AZ::Event<AZ::Data::Asset<Spawnable>> m_onSpawnedEvent;
|
||||||
|
AZ::Event<AZ::Data::Asset<Spawnable>> m_onDespawnedEvent;
|
||||||
};
|
};
|
||||||
} // namespace AzFramework
|
} // namespace AzFramework
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include <AzFramework/Components/TransformComponent.h>
|
#include <AzFramework/Components/TransformComponent.h>
|
||||||
#include <AzFramework/Entity/EntityContextBus.h>
|
#include <AzFramework/Entity/EntityContextBus.h>
|
||||||
#include <AzFramework/Entity/GameEntityContextBus.h>
|
#include <AzFramework/Entity/GameEntityContextBus.h>
|
||||||
|
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||||
#include <Include/IMultiplayer.h>
|
#include <Include/IMultiplayer.h>
|
||||||
#include <Pipeline/NetworkSpawnableHolderComponent.h>
|
#include <Pipeline/NetworkSpawnableHolderComponent.h>
|
||||||
#include <Source/Components/NetBindComponent.h>
|
#include <Source/Components/NetBindComponent.h>
|
||||||
@@ -34,9 +35,14 @@ namespace Multiplayer
|
|||||||
: m_networkEntityAuthorityTracker(*this)
|
: m_networkEntityAuthorityTracker(*this)
|
||||||
, m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event"))
|
, m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event"))
|
||||||
, m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event"))
|
, m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event"))
|
||||||
|
, m_onSpawnedHandler([this](AZ::Data::Asset<AzFramework::Spawnable> spawnable) { this->OnSpawned(spawnable); })
|
||||||
|
, m_onDespawnedHandler([this](AZ::Data::Asset<AzFramework::Spawnable> spawnable) { this->OnDespawned(spawnable); })
|
||||||
{
|
{
|
||||||
AZ::Interface<INetworkEntityManager>::Register(this);
|
AZ::Interface<INetworkEntityManager>::Register(this);
|
||||||
AzFramework::RootSpawnableNotificationBus::Handler::BusConnect();
|
AzFramework::RootSpawnableNotificationBus::Handler::BusConnect();
|
||||||
|
|
||||||
|
AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler);
|
||||||
|
AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkEntityManager::~NetworkEntityManager()
|
NetworkEntityManager::~NetworkEntityManager()
|
||||||
@@ -365,58 +371,57 @@ namespace Multiplayer
|
|||||||
{
|
{
|
||||||
INetworkEntityManager::EntityList returnList;
|
INetworkEntityManager::EntityList returnList;
|
||||||
|
|
||||||
// TODO: Implement for non-root spawnables
|
|
||||||
auto spawnableAssetId = m_networkPrefabLibrary.GetAssetIdByName(prefabEntryId.m_prefabName);
|
auto spawnableAssetId = m_networkPrefabLibrary.GetAssetIdByName(prefabEntryId.m_prefabName);
|
||||||
if (spawnableAssetId == m_rootSpawnableAsset.GetId())
|
// Required for sync-instantiation. Todo: keep the reference in NetworkSpawnableLibrary
|
||||||
|
auto netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(spawnableAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
|
||||||
|
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(netSpawnableAsset);
|
||||||
|
|
||||||
|
AzFramework::Spawnable* netSpawnable = netSpawnableAsset.GetAs<AzFramework::Spawnable>();
|
||||||
|
if (!netSpawnable)
|
||||||
{
|
{
|
||||||
AzFramework::Spawnable* netSpawnable = m_rootSpawnableAsset.GetAs<AzFramework::Spawnable>();
|
return returnList;
|
||||||
if (!netSpawnable)
|
}
|
||||||
|
|
||||||
|
const uint32_t entityIndex = prefabEntryId.m_entityOffset;
|
||||||
|
|
||||||
|
if (entityIndex == PrefabEntityId::AllIndices)
|
||||||
|
{
|
||||||
|
return CreateEntitiesImmediate(*netSpawnable, netEntityRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AzFramework::Spawnable::EntityList& entities = netSpawnable->GetEntities();
|
||||||
|
size_t entitiesSize = entities.size();
|
||||||
|
if (entityIndex >= entitiesSize)
|
||||||
|
{
|
||||||
|
return returnList;
|
||||||
|
}
|
||||||
|
|
||||||
|
AZ::SerializeContext* serializeContext = nullptr;
|
||||||
|
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||||
|
|
||||||
|
AZ::Entity* clone = serializeContext->CloneObject(entities[entityIndex].get());
|
||||||
|
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||||
|
clone->SetId(AZ::Entity::MakeId());
|
||||||
|
|
||||||
|
NetBindComponent* netBindComponent = clone->FindComponent<NetBindComponent>();
|
||||||
|
if (netBindComponent)
|
||||||
|
{
|
||||||
|
netBindComponent->PreInit(clone, prefabEntryId, netEntityId, netEntityRole);
|
||||||
|
|
||||||
|
auto* transformComponent = clone->FindComponent<AzFramework::TransformComponent>();
|
||||||
|
if (transformComponent)
|
||||||
{
|
{
|
||||||
return returnList;
|
transformComponent->SetWorldTM(transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t entityIndex = prefabEntryId.m_entityOffset;
|
if (autoActivate == AutoActivate::DoNotActivate)
|
||||||
|
|
||||||
if (entityIndex == PrefabEntityId::AllIndices)
|
|
||||||
{
|
{
|
||||||
return CreateEntitiesImmediate(*netSpawnable, netEntityRole);
|
clone->SetRuntimeActiveByDefault(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AzFramework::Spawnable::EntityList& entities = netSpawnable->GetEntities();
|
AzFramework::GameEntityContextRequestBus::Broadcast(&AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone);
|
||||||
size_t entitiesSize = entities.size();
|
|
||||||
if (entityIndex >= entitiesSize)
|
|
||||||
{
|
|
||||||
return returnList;
|
|
||||||
}
|
|
||||||
|
|
||||||
AZ::SerializeContext* serializeContext = nullptr;
|
returnList.push_back(netBindComponent->GetEntityHandle());
|
||||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
|
||||||
|
|
||||||
AZ::Entity* clone = serializeContext->CloneObject(entities[entityIndex].get());
|
|
||||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
|
||||||
clone->SetId(AZ::Entity::MakeId());
|
|
||||||
|
|
||||||
NetBindComponent* netBindComponent = clone->FindComponent<NetBindComponent>();
|
|
||||||
if (netBindComponent)
|
|
||||||
{
|
|
||||||
netBindComponent->PreInit(clone, prefabEntryId, netEntityId, netEntityRole);
|
|
||||||
|
|
||||||
auto* transformComponent = clone->FindComponent<AzFramework::TransformComponent>();
|
|
||||||
if (transformComponent)
|
|
||||||
{
|
|
||||||
transformComponent->SetWorldTM(transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoActivate == AutoActivate::DoNotActivate)
|
|
||||||
{
|
|
||||||
clone->SetRuntimeActiveByDefault(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
AzFramework::GameEntityContextRequestBus::Broadcast(
|
|
||||||
&AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone);
|
|
||||||
|
|
||||||
returnList.push_back(netBindComponent->GetEntityHandle());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnList;
|
return returnList;
|
||||||
@@ -429,13 +434,37 @@ namespace Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NetworkEntityManager::OnRootSpawnableAssigned(
|
void NetworkEntityManager::OnRootSpawnableAssigned(
|
||||||
AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
|
[[maybe_unused]] AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
|
||||||
{
|
{
|
||||||
AzFramework::Spawnable* rootSpawnableData = rootSpawnable.GetAs<AzFramework::Spawnable>();
|
auto* multiplayer = AZ::Interface<IMultiplayer>::Get();
|
||||||
const auto& entityList = rootSpawnableData->GetEntities();
|
const auto agentType = multiplayer->GetAgentType();
|
||||||
|
|
||||||
|
if (agentType == MultiplayerAgentType::Client)
|
||||||
|
{
|
||||||
|
multiplayer->SendReadyForEntityUpdates(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
|
||||||
|
{
|
||||||
|
// TODO: Do we need to clear all entities here?
|
||||||
|
auto* multiplayer = AZ::Interface<IMultiplayer>::Get();
|
||||||
|
const auto agentType = multiplayer->GetAgentType();
|
||||||
|
|
||||||
|
if (agentType == MultiplayerAgentType::Client)
|
||||||
|
{
|
||||||
|
multiplayer->SendReadyForEntityUpdates(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkEntityManager::OnSpawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable)
|
||||||
|
{
|
||||||
|
AzFramework::Spawnable* spawnableData = spawnable.GetAs<AzFramework::Spawnable>();
|
||||||
|
const auto& entityList = spawnableData->GetEntities();
|
||||||
if (entityList.size() == 0)
|
if (entityList.size() == 0)
|
||||||
{
|
{
|
||||||
AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Root spawnable doesn't have any entities.");
|
AZ_Error("NetworkEntityManager", false, "OnSpawned: Spawnable %s doesn't have any entities.",
|
||||||
|
spawnable.GetHint().c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,7 +472,7 @@ namespace Multiplayer
|
|||||||
auto* spawnableHolder = rootEntity->FindComponent<NetworkSpawnableHolderComponent>();
|
auto* spawnableHolder = rootEntity->FindComponent<NetworkSpawnableHolderComponent>();
|
||||||
if (!spawnableHolder)
|
if (!spawnableHolder)
|
||||||
{
|
{
|
||||||
AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Root entity doesn't have NetworkSpawnableHolderComponent.");
|
// Root entity doesn't have NetworkSpawnableHolderComponent. It means there's no corresponding network spawnable.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,8 +494,6 @@ namespace Multiplayer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_rootSpawnableAsset = netSpawnableAsset;
|
|
||||||
|
|
||||||
auto* multiplayer = AZ::Interface<IMultiplayer>::Get();
|
auto* multiplayer = AZ::Interface<IMultiplayer>::Get();
|
||||||
|
|
||||||
const auto agentType = multiplayer->GetAgentType();
|
const auto agentType = multiplayer->GetAgentType();
|
||||||
@@ -477,17 +504,10 @@ namespace Multiplayer
|
|||||||
{
|
{
|
||||||
CreateEntitiesImmediate(*netSpawnable, NetEntityRole::Authority);
|
CreateEntitiesImmediate(*netSpawnable, NetEntityRole::Authority);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// If we don't spawn net entities immediately (i.e. it is a client),
|
|
||||||
// tell the server/host it can start sending updates that will instantiate entities.
|
|
||||||
multiplayer->SendReadyForEntityUpdates(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
|
void NetworkEntityManager::OnDespawned([[maybe_unused]]AZ::Data::Asset<AzFramework::Spawnable> spawnable)
|
||||||
{
|
{
|
||||||
// TODO: Do we need to clear all entities here?
|
// TODO: Remove entities instantiated from the spawnable
|
||||||
m_rootSpawnableAsset.Release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ namespace Multiplayer
|
|||||||
|
|
||||||
NetEntityId NextId();
|
NetEntityId NextId();
|
||||||
|
|
||||||
|
void OnSpawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable);
|
||||||
|
void OnDespawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable);
|
||||||
|
|
||||||
NetworkEntityTracker m_networkEntityTracker;
|
NetworkEntityTracker m_networkEntityTracker;
|
||||||
NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker;
|
NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker;
|
||||||
MultiplayerComponentRegistry m_multiplayerComponentRegistry;
|
MultiplayerComponentRegistry m_multiplayerComponentRegistry;
|
||||||
@@ -111,6 +114,8 @@ namespace Multiplayer
|
|||||||
DeferredRpcMessages m_localDeferredRpcMessages;
|
DeferredRpcMessages m_localDeferredRpcMessages;
|
||||||
|
|
||||||
NetworkSpawnableLibrary m_networkPrefabLibrary;
|
NetworkSpawnableLibrary m_networkPrefabLibrary;
|
||||||
AZ::Data::Asset<AzFramework::Spawnable> m_rootSpawnableAsset;
|
|
||||||
|
AZ::Event<AZ::Data::Asset<AzFramework::Spawnable>>::Handler m_onSpawnedHandler;
|
||||||
|
AZ::Event<AZ::Data::Asset<AzFramework::Spawnable>>::Handler m_onDespawnedHandler;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ namespace Multiplayer
|
|||||||
const AZ::Name name = AZ::Name(relativePath);
|
const AZ::Name name = AZ::Name(relativePath);
|
||||||
m_spawnables[name] = id;
|
m_spawnables[name] = id;
|
||||||
m_spawnablesReverseLookup[id] = name;
|
m_spawnablesReverseLookup[id] = name;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
|
void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
|
||||||
|
|||||||
@@ -104,9 +104,10 @@ namespace Multiplayer
|
|||||||
networkedEntityIds.push_back(sourceEntity->GetId());
|
networkedEntityIds.push_back(sourceEntity->GetId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefab))
|
|
||||||
|
if (networkedEntityIds.empty())
|
||||||
{
|
{
|
||||||
AZ_Error("NetworkPrefabProcessor", false, "Saving exported Prefab Instance within a Prefab Dom failed.");
|
// No networked entities in the prefab, no need to do anything in this processor.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user