Refactored spawning network entities to use SpawnableEntityManager instead of duplicating the code in NetworkEntityManager

This commit is contained in:
pereslav
2021-05-17 23:34:58 +01:00
parent a852da93a3
commit 970c87b487
15 changed files with 301 additions and 106 deletions
@@ -78,6 +78,12 @@ namespace Multiplayer
const AZ::Transform& transform
) = 0;
//! Configures new networked entity
//! @param netEntity the entity to setup
//! @param prefabEntryId the name of the spawnable the entity originated from
//! @param netEntityRole the net role the entity should be setup for
virtual void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) = 0;
//! Returns an ConstEntityPtr for the provided entityId.
//! @param netEntityId the netEntityId to get an ConstEntityPtr for
//! @return the requested ConstEntityPtr
@@ -0,0 +1,34 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Name/Name.h>
#include <AzCore/std/string/string.h>
namespace Multiplayer
{
//! @class INetworkSpawnableLibrary
//! @brief The interface for managing network spawnables.
class INetworkSpawnableLibrary
{
public:
AZ_RTTI(INetworkSpawnableLibrary, "{A3CF809C-6C1D-4B43-B2C4-3901B5DE1ABE}");
virtual ~INetworkSpawnableLibrary() = default;
virtual void BuildSpawnablesList() = 0;
virtual void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) = 0;
virtual AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) = 0;
virtual AZ::Data::AssetId GetAssetIdByName(AZ::Name name) = 0;
};
}
@@ -34,18 +34,15 @@ 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()
{
AzFramework::RootSpawnableNotificationBus::Handler::BusDisconnect();
AZ::Interface<INetworkEntityManager>::Unregister(this);
}
void NetworkEntityManager::Initialize(HostId hostId, AZStd::unique_ptr<IEntityDomain> entityDomain)
@@ -365,7 +362,7 @@ namespace Multiplayer
}
PrefabEntityId prefabEntityId;
prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetPrefabNameFromAssetId(spawnable.GetId());
prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetSpawnableNameFromAssetId(spawnable.GetId());
prefabEntityId.m_entityOffset = aznumeric_cast<uint32_t>(i);
const NetEntityId netEntityId = NextId();
@@ -493,57 +490,19 @@ namespace Multiplayer
}
}
void NetworkEntityManager::OnSpawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable)
void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole)
{
AzFramework::Spawnable* spawnableData = spawnable.GetAs<AzFramework::Spawnable>();
const auto& entityList = spawnableData->GetEntities();
if (entityList.size() == 0)
auto* netBindComponent = netEntity->FindComponent<NetBindComponent>();
if (netBindComponent)
{
AZ_Error("NetworkEntityManager", false, "OnSpawned: Spawnable %s doesn't have any entities.",
spawnable.GetHint().c_str());
return;
const NetEntityId netEntityId = NextId();
netBindComponent->PreInit(netEntity, prefabEntityId, netEntityId, netEntityRole);
}
const auto& rootEntity = entityList[0];
auto* spawnableHolder = rootEntity->FindComponent<NetworkSpawnableHolderComponent>();
if (!spawnableHolder)
else
{
// Root entity doesn't have NetworkSpawnableHolderComponent. It means there's no corresponding network spawnable.
return;
AZ_Error("NetworkEntityManager", false, "SetupNetEntity called for an entity with no NetBindComponent. Entity: %s",
netEntity->GetName().c_str());
}
AZ::Data::Asset<AzFramework::Spawnable> netSpawnableAsset = spawnableHolder->GetNetworkSpawnableAsset();
AzFramework::Spawnable* netSpawnable = netSpawnableAsset.GetAs<AzFramework::Spawnable>();
if (!netSpawnable)
{
// TODO: Temp sync load until JsonSerialization of loadBehavior is fixed.
netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(
netSpawnableAsset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(netSpawnableAsset);
netSpawnable = netSpawnableAsset.GetAs<AzFramework::Spawnable>();
}
if (!netSpawnable)
{
AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Net spawnable doesn't have any data.");
return;
}
auto* multiplayer = GetMultiplayer();
const auto agentType = multiplayer->GetAgentType();
const bool spawnImmediately =
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
if (spawnImmediately)
{
CreateEntitiesImmediate(*netSpawnable, NetEntityRole::Authority);
}
}
void NetworkEntityManager::OnDespawned([[maybe_unused]]AZ::Data::Asset<AzFramework::Spawnable> spawnable)
{
// TODO: Remove entities instantiated from the spawnable
}
}
@@ -62,6 +62,8 @@ namespace Multiplayer
const AZ::Transform& transform
) override;
void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override;
uint32_t GetEntityCount() const override;
NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) override;
void MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) override;
@@ -93,9 +95,6 @@ namespace Multiplayer
void RemoveEntities();
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;
@@ -123,8 +122,5 @@ namespace Multiplayer
DeferredRpcMessages m_localDeferredRpcMessages;
NetworkSpawnableLibrary m_networkPrefabLibrary;
AZ::Event<AZ::Data::Asset<AzFramework::Spawnable>>::Handler m_onSpawnedHandler;
AZ::Event<AZ::Data::Asset<AzFramework::Spawnable>>::Handler m_onDespawnedHandler;
};
}
@@ -14,20 +14,23 @@
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Interface/Interface.h>
namespace Multiplayer
{
NetworkSpawnableLibrary::NetworkSpawnableLibrary()
{
AZ::Interface<INetworkSpawnableLibrary>::Register(this);
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
}
NetworkSpawnableLibrary::~NetworkSpawnableLibrary()
{
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
AZ::Interface<INetworkSpawnableLibrary>::Unregister(this);
}
void NetworkSpawnableLibrary::BuildPrefabsList()
void NetworkSpawnableLibrary::BuildSpawnablesList()
{
auto enumerateCallback = [this](const AZ::Data::AssetId id, const AZ::Data::AssetInfo& info)
{
@@ -50,10 +53,10 @@ namespace Multiplayer
void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
{
BuildPrefabsList();
BuildSpawnablesList();
}
AZ::Name NetworkSpawnableLibrary::GetPrefabNameFromAssetId(AZ::Data::AssetId assetId)
AZ::Name NetworkSpawnableLibrary::GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId)
{
if (assetId.IsValid())
{
@@ -12,30 +12,31 @@
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/string/string.h>
#include <Multiplayer/INetworkSpawnableLibrary.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzCore/Name/Name.h>
namespace Multiplayer
{
/// Implementation of the network prefab library interface.
class NetworkSpawnableLibrary final
: private AzFramework::AssetCatalogEventBus::Handler
: public INetworkSpawnableLibrary
, private AzFramework::AssetCatalogEventBus::Handler
{
public:
AZ_RTTI(NetworkSpawnableLibrary, "{65E15F33-E893-49C2-A8E2-B6A8A6EF31E0}", INetworkSpawnableLibrary);
NetworkSpawnableLibrary();
~NetworkSpawnableLibrary();
void BuildPrefabsList();
void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id);
/// INetworkSpawnableLibrary overrides.
void BuildSpawnablesList() override;
void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) override;
AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) override;
AZ::Data::AssetId GetAssetIdByName(AZ::Name name) override;
/// AssetCatalogEventBus overrides.
void OnCatalogLoaded(const char* catalogFile) override;
AZ::Name GetPrefabNameFromAssetId(AZ::Data::AssetId assetId);
AZ::Data::AssetId GetAssetIdByName(AZ::Name name);
private:
AZStd::unordered_map<AZ::Name, AZ::Data::AssetId> m_spawnables;
AZStd::unordered_map<AZ::Data::AssetId, AZ::Name> m_spawnablesReverseLookup;
@@ -11,7 +11,12 @@
*/
#include <Source/Pipeline/NetBindMarkerComponent.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/INetworkSpawnableLibrary.h>
#include <AzCore/Component/TransformBus.h>
#include <AzFramework/Components/TransformComponent.h>
namespace Multiplayer
{
@@ -21,15 +26,91 @@ namespace Multiplayer
if (serializeContext)
{
serializeContext->Class<NetBindMarkerComponent, AZ::Component>()
->Version(1);
->Version(1)
->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex)
->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset);
}
}
AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset<AzFramework::Spawnable>& asset)
{
AzFramework::Spawnable* spawnable = asset.GetAs<AzFramework::Spawnable>();
if (!spawnable)
{
asset =
AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset);
spawnable = asset.GetAs<AzFramework::Spawnable>();
}
return spawnable;
}
void NetBindMarkerComponent::Activate()
{
const auto agentType = AZ::Interface<IMultiplayer>::Get()->GetAgentType();
const bool spawnImmediately =
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid())
{
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)
{
if (entities.size() == 1)
{
AZ::Entity* netEntity = *entities.begin();
auto* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
transformComponent->SetWorldTM(worldTm);
AZ::Name spawnableName = AZ::Interface<INetworkSpawnableLibrary>::Get()->GetSpawnableNameFromAssetId(spawnableAssetId);
PrefabEntityId prefabEntityId;
prefabEntityId.m_prefabName = spawnableName;
prefabEntityId.m_entityOffset = netEntityIndex;
AZ::Interface<INetworkEntityManager>::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority);
}
else
{
AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size());
}
};
m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset);
AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(m_netSpawnTicket, {m_netEntityIndex}, preInsertionCallback);
}
}
void NetBindMarkerComponent::Deactivate()
{
if(m_netSpawnTicket.IsValid())
{
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket);
}
}
size_t NetBindMarkerComponent::GetNetEntityIndex() const
{
return m_netEntityIndex;
}
void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex)
{
m_netEntityIndex = netEntityIndex;
}
void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset)
{
m_networkSpawnableAsset = networkSpawnableAsset;
}
AZ::Data::Asset<AzFramework::Spawnable> NetBindMarkerComponent::GetNetworkSpawnableAsset() const
{
return m_networkSpawnableAsset;
}
}
@@ -13,6 +13,9 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
namespace Multiplayer
{
@@ -34,6 +37,15 @@ namespace Multiplayer
void Deactivate() override;
//! @}
size_t GetNetEntityIndex() const;
void SetNetEntityIndex(size_t val);
void SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset);
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset() const;
private:
AZ::Data::Asset<AzFramework::Spawnable> m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad};
size_t m_netEntityIndex = 0;
AzFramework::EntitySpawnTicket m_netSpawnTicket;
};
} // namespace Multiplayer
@@ -114,29 +114,31 @@ namespace Multiplayer
AZStd::unique_ptr<Instance> networkInstance(aznew Instance());
for (auto entityId : networkedEntityIds)
AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset;
networkSpawnableAsset.Create(networkSpawnable->GetId());
networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
for (size_t entityIndex = 0; entityIndex < networkedEntityIds.size(); ++entityIndex)
{
AZ::EntityId entityId = networkedEntityIds[entityIndex];
AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release();
networkInstance->AddEntity(*netEntity);
AZ::Entity* breadcrumbEntity = aznew AZ::Entity(netEntity->GetName());
breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault());
breadcrumbEntity->CreateComponent<NetBindMarkerComponent>();
NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent<NetBindMarkerComponent>();
// Each spawnable has a root meta-data entity at position 0, so starting net indices from 1
netBindMarkerComponent->SetNetEntityIndex(entityIndex + 1);
netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset);
AzFramework::TransformComponent* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
breadcrumbEntity->CreateComponent<AzFramework::TransformComponent>(*transformComponent);
// TODO: Configure NetBindMarkerComponent to refer to the net entity
sourceInstance->AddEntity(*breadcrumbEntity);
}
// Add net spawnable asset holder
{
AZ::Data::AssetId assetId = networkSpawnable->GetId();
AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset;
networkSpawnableAsset.Create(assetId);
networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity();
if (containerEntityRef.has_value())
{
+1 -1
View File
@@ -15,7 +15,7 @@
#include <AzTest/AzTest.h>
#include <AzTest/GemTestEnvironment.h>
#include <QApplication>
#include <Source/Components/NetBindComponent.h>
#include <Multiplayer/NetBindComponent.h>
#include <Source/Pipeline/NetBindMarkerComponent.h>
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
#include <UnitTest/ToolsTestApplication.h>
@@ -16,7 +16,7 @@
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <Prefab/PrefabDomTypes.h>
#include <Prefab/Spawnable/PrefabProcessorContext.h>
#include <Source/Components/NetBindComponent.h>
#include <Multiplayer/NetBindComponent.h>
#include <Source/Pipeline/NetworkPrefabProcessor.h>
namespace UnitTest
@@ -15,6 +15,7 @@ set(FILES
Include/Multiplayer/IMultiplayer.h
Include/Multiplayer/IMultiplayerComponentInput.h
Include/Multiplayer/INetworkEntityManager.h
Include/Multiplayer/INetworkSpawnableLibrary.h
Include/Multiplayer/INetworkPlayerSpawner.h
Include/Multiplayer/INetworkTime.h
Include/Multiplayer/IReplicationWindow.h