SPEC-6590 Implemented network entities instantiation by network spawnable asset ID

This commit is contained in:
pereslav
2021-05-05 00:00:06 +01:00
parent dfb262c572
commit e414cc151d
7 changed files with 121 additions and 63 deletions
@@ -148,6 +148,12 @@ namespace AzFramework
//! Blocks until all operations made on the provided ticket before the barrier call have completed.
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:
[[nodiscard]] virtual void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 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
{
AZStd::queue<Requests> pendingRequestQueue;
@@ -223,6 +233,8 @@ namespace AzFramework
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
}
m_onSpawnedEvent.Signal(ticket.m_spawnable);
ticket.m_currentTicketId++;
return true;
}
@@ -257,6 +269,8 @@ namespace AzFramework
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
}
m_onSpawnedEvent.Signal(ticket.m_spawnable);
ticket.m_currentTicketId++;
return true;
}
@@ -289,6 +303,8 @@ namespace AzFramework
request.m_completionCallback(*request.m_ticket);
}
m_onDespawnedEvent.Signal(ticket.m_spawnable);
ticket.m_currentTicketId++;
return true;
}
@@ -315,6 +331,8 @@ namespace AzFramework
&GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId());
}
}
m_onDespawnedEvent.Signal(ticket.m_spawnable);
// Rebuild the list of entities.
ticket.m_spawnedEntities.clear();
@@ -350,6 +368,9 @@ namespace AzFramework
}
ticket.m_currentTicketId++;
m_onSpawnedEvent.Signal(ticket.m_spawnable);
return true;
}
else
@@ -60,6 +60,9 @@ namespace AzFramework
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.
//
@@ -156,5 +159,8 @@ namespace AzFramework
AZStd::deque<Requests> m_delayedQueue; //!< Requests that were processed before, but couldn't be completed.
AZStd::queue<Requests> m_pendingRequestQueue;
AZStd::mutex m_pendingRequestQueueMutex;
AZ::Event<AZ::Data::Asset<Spawnable>> m_onSpawnedEvent;
AZ::Event<AZ::Data::Asset<Spawnable>> m_onDespawnedEvent;
};
} // namespace AzFramework
@@ -21,6 +21,7 @@
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#include <Include/IMultiplayer.h>
#include <Pipeline/NetworkSpawnableHolderComponent.h>
#include <Source/Components/NetBindComponent.h>
@@ -34,9 +35,14 @@ namespace Multiplayer
: m_networkEntityAuthorityTracker(*this)
, m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities 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);
AzFramework::RootSpawnableNotificationBus::Handler::BusConnect();
AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler);
AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler);
}
NetworkEntityManager::~NetworkEntityManager()
@@ -365,58 +371,57 @@ namespace Multiplayer
{
INetworkEntityManager::EntityList returnList;
// TODO: Implement for non-root spawnables
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>();
if (!netSpawnable)
return returnList;
}
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 (entityIndex == PrefabEntityId::AllIndices)
if (autoActivate == AutoActivate::DoNotActivate)
{
return CreateEntitiesImmediate(*netSpawnable, netEntityRole);
clone->SetRuntimeActiveByDefault(false);
}
const AzFramework::Spawnable::EntityList& entities = netSpawnable->GetEntities();
size_t entitiesSize = entities.size();
if (entityIndex >= entitiesSize)
{
return returnList;
}
AzFramework::GameEntityContextRequestBus::Broadcast(&AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone);
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)
{
transformComponent->SetWorldTM(transform);
}
if (autoActivate == AutoActivate::DoNotActivate)
{
clone->SetRuntimeActiveByDefault(false);
}
AzFramework::GameEntityContextRequestBus::Broadcast(
&AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone);
returnList.push_back(netBindComponent->GetEntityHandle());
}
returnList.push_back(netBindComponent->GetEntityHandle());
}
return returnList;
@@ -429,13 +434,37 @@ namespace Multiplayer
}
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>();
const auto& entityList = rootSpawnableData->GetEntities();
auto* multiplayer = AZ::Interface<IMultiplayer>::Get();
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)
{
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;
}
@@ -443,7 +472,7 @@ namespace Multiplayer
auto* spawnableHolder = rootEntity->FindComponent<NetworkSpawnableHolderComponent>();
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;
}
@@ -465,8 +494,6 @@ namespace Multiplayer
return;
}
m_rootSpawnableAsset = netSpawnableAsset;
auto* multiplayer = AZ::Interface<IMultiplayer>::Get();
const auto agentType = multiplayer->GetAgentType();
@@ -477,17 +504,10 @@ namespace Multiplayer
{
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?
m_rootSpawnableAsset.Release();
// TODO: Remove entities instantiated from the spawnable
}
}
@@ -84,6 +84,9 @@ namespace Multiplayer
NetEntityId NextId();
void OnSpawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable);
void OnDespawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable);
NetworkEntityTracker m_networkEntityTracker;
NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker;
MultiplayerComponentRegistry m_multiplayerComponentRegistry;
@@ -111,6 +114,8 @@ namespace Multiplayer
DeferredRpcMessages m_localDeferredRpcMessages;
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);
m_spawnables[name] = id;
m_spawnablesReverseLookup[id] = name;
}
void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
@@ -104,9 +104,10 @@ namespace Multiplayer
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;
}