Reworked net entities instantiation in order to fix entity references e.g. parent-child relationship. Note: Only entities within network spawnable keep the references until the ticket system refactoring is done

Signed-off-by: pereslav <pereslav@amazon.com>
This commit is contained in:
pereslav
2021-09-09 16:44:35 +01:00
parent 33299399af
commit a0ada8fcd3
11 changed files with 78 additions and 194 deletions
@@ -9,7 +9,6 @@
#include <Source/MultiplayerGem.h>
#include <Source/MultiplayerSystemComponent.h>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <Source/Pipeline/NetBindMarkerComponent.h>
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
@@ -23,7 +22,6 @@ namespace Multiplayer
AzNetworking::NetworkingSystemComponent::CreateDescriptor(),
MultiplayerSystemComponent::CreateDescriptor(),
NetBindComponent::CreateDescriptor(),
NetBindMarkerComponent::CreateDescriptor(),
NetworkSpawnableHolderComponent::CreateDescriptor(),
});
@@ -467,6 +467,54 @@ namespace Multiplayer
return netEntityId;
}
AzFramework::EntitySpawnTicket NetworkEntityManager::RequestNetSpawnableInstantiation(const AZ::Data::Asset<AzFramework::Spawnable>& rootSpawnable)
{
const AzFramework::Spawnable::EntityList& entityList = rootSpawnable->GetEntities();
if (entityList.size() == 0)
{
AZ_Assert(false, "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str())
return {};
}
// The first entity in every spawnable is the root one
const AZ::Entity* rootEntity = (entityList.begin())->get();
const auto* holderComponent = rootEntity->FindComponent<NetworkSpawnableHolderComponent>();
if(!holderComponent)
{
// This spawnable doesn't have a corresponding network spawnable.
return {};
}
// Retrieve the corresponding network spawnable asset
AZ::Data::Asset<AzFramework::Spawnable> netSpawnableAsset = holderComponent->GetNetworkSpawnableAsset();
// Prepare the parameters for the spawning process
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_priority = AzFramework::SpawnablePriority_High;
// Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene
optionalArgs.m_preInsertionCallback = [spawnableAssetId = netSpawnableAsset.GetId()](AzFramework::EntitySpawnTicket::Id,
AzFramework::SpawnableEntityContainerView entities)
{
for (uint32_t netEntityIndex = 0; netEntityIndex < entities.size(); netEntityIndex++)
{
AZ::Entity* netEntity = *(entities.begin() + netEntityIndex);
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);
}
};
// Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities
AzFramework::EntitySpawnTicket ticket(netSpawnableAsset);
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(ticket, AZStd::move(optionalArgs));
return ticket;
}
void NetworkEntityManager::OnRootSpawnableAssigned(
[[maybe_unused]] AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
{
@@ -477,11 +525,15 @@ namespace Multiplayer
{
multiplayer->SendReadyForEntityUpdates(true);
}
if(ShouldSpawnNetEntities())
{
m_rootNetSpawnableTicket = RequestNetSpawnableInstantiation(rootSpawnable);
}
}
void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
{
// TODO: Do we need to clear all entities here?
auto* multiplayer = GetMultiplayer();
const auto agentType = multiplayer->GetAgentType();
@@ -489,6 +541,12 @@ namespace Multiplayer
{
multiplayer->SendReadyForEntityUpdates(false);
}
// Despawn any remaining net entities created locally
if (m_rootNetSpawnableTicket.IsValid())
{
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_rootNetSpawnableTicket);
}
}
void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole)
@@ -506,4 +564,12 @@ namespace Multiplayer
netEntity->GetName().c_str());
}
}
bool NetworkEntityManager::ShouldSpawnNetEntities() const
{
const auto agentType = GetMultiplayer()->GetAgentType();
const bool shouldSpawnNetEntities =
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
return shouldSpawnNetEntities;
}
}
@@ -11,6 +11,7 @@
#include <AzCore/EBus/ScheduledEvent.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/NetworkSpawnableLibrary.h>
@@ -92,6 +93,11 @@ namespace Multiplayer
private:
void RemoveEntities();
NetEntityId NextId();
bool ShouldSpawnNetEntities() const;
// Note: This is an async function.
// The instantiated entities are not available immediately but will be constructed by the spawnable system
AzFramework::EntitySpawnTicket RequestNetSpawnableInstantiation(const AZ::Data::Asset<AzFramework::Spawnable>& rootSpawnable);
NetworkEntityTracker m_networkEntityTracker;
NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker;
@@ -120,5 +126,6 @@ namespace Multiplayer
DeferredRpcMessages m_localDeferredRpcMessages;
NetworkSpawnableLibrary m_networkPrefabLibrary;
AzFramework::EntitySpawnTicket m_rootNetSpawnableTicket;
};
}
@@ -37,6 +37,7 @@ namespace Multiplayer
NetworkEntityHandle Get(NetEntityId netEntityId);
ConstNetworkEntityHandle Get(NetEntityId netEntityId) const;
//! Returns Net Entity ID for a given AZ Entity ID.
NetEntityId Get(const AZ::EntityId& entityId) const;
//! Returns true if the netEntityId exists.
@@ -1,115 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#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
{
void NetBindMarkerComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetBindMarkerComponent, AZ::Component>()
->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::Id, 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 = static_cast<uint32_t>(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::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_preInsertionCallback = AZStd::move(preInsertionCallback);
AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(
m_netSpawnTicket, { m_netEntityIndex }, AZStd::move(optionalArgs));
}
}
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;
}
}
@@ -1,47 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
namespace Multiplayer
{
//! @class NetBindMarkerComponent
//! @brief Component for tracking net entities in the original non-networked spawnable.
class NetBindMarkerComponent final : public AZ::Component
{
public:
AZ_COMPONENT(NetBindMarkerComponent, "{40612C1B-427D-45C6-A2F0-04E16DF5B718}");
static void Reflect(AZ::ReflectContext* context);
NetBindMarkerComponent() = default;
~NetBindMarkerComponent() override = default;
//! AZ::Component overrides.
//! @{
void Activate() override;
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
@@ -8,7 +8,6 @@
#include <Multiplayer/IMultiplayerTools.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Pipeline/NetBindMarkerComponent.h>
#include <Pipeline/NetworkPrefabProcessor.h>
#include <Pipeline/NetworkSpawnableHolderComponent.h>
@@ -46,7 +45,7 @@ namespace Multiplayer
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<NetworkPrefabProcessor, PrefabProcessor>()->Version(1);
serializeContext->Class<NetworkPrefabProcessor, PrefabProcessor>()->Version(2);
}
}
@@ -137,8 +136,6 @@ namespace Multiplayer
networkSpawnableAsset.Create(networkSpawnable->GetId());
networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
size_t netEntitiesIndexCounter = 0;
for (auto* prefabEntity : prefabNetEntities)
{
Instance* instance = netEntityToInstanceMap[prefabEntity];
@@ -148,30 +145,11 @@ namespace Multiplayer
AZ_Assert(netEntity, "Unable to detach entity %s [%s] from the source prefab instance",
prefabEntity->GetName().c_str(), entityId.ToString().c_str());
// Net entity will need a new ID to avoid IDs collision
netEntity->SetId(AZ::Entity::MakeId());
netEntity->InvalidateDependencies();
netEntity->EvaluateDependencies();
// Insert the entity into the target net spawnable
netSpawnableEntities.emplace_back(netEntity);
// Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable
AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName());
breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault());
// Marker component is responsible to spawning entities based on the index.
NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent<NetBindMarkerComponent>();
netBindMarkerComponent->SetNetEntityIndex(netEntitiesIndexCounter);
netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset);
// Copy the transform component from the original entity to have the correct transform and parent-child relationship
AzFramework::TransformComponent* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
breadcrumbEntity->CreateComponent<AzFramework::TransformComponent>(*transformComponent);
instance->AddEntity(*breadcrumbEntity);
netEntitiesIndexCounter++;
}
// Add net spawnable asset holder to the prefab root
@@ -39,7 +39,7 @@ namespace Multiplayer
m_networkSpawnableAsset = networkSpawnableAsset;
}
AZ::Data::Asset<AzFramework::Spawnable> NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset()
AZ::Data::Asset<AzFramework::Spawnable> NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() const
{
return m_networkSpawnableAsset;
}
@@ -33,7 +33,7 @@ namespace Multiplayer
//! @}
void SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset);
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset();
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset() const;
private:
AZ::Data::Asset<AzFramework::Spawnable> m_networkSpawnableAsset{ AZ::Data::AssetLoadBehavior::PreLoad };
@@ -13,7 +13,6 @@
#include <AzTest/GemTestEnvironment.h>
#include <QApplication>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Source/Pipeline/NetBindMarkerComponent.h>
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
#include <UnitTest/ToolsTestApplication.h>
@@ -30,7 +29,6 @@ namespace Multiplayer
{
AZStd::vector<AZ::ComponentDescriptor*> descriptors({
NetBindComponent::CreateDescriptor(),
NetBindMarkerComponent::CreateDescriptor(),
NetworkSpawnableHolderComponent::CreateDescriptor()
});
@@ -101,8 +101,6 @@ set(FILES
Source/NetworkInput/NetworkInputMigrationVector.h
Source/NetworkTime/NetworkTime.cpp
Source/NetworkTime/NetworkTime.h
Source/Pipeline/NetBindMarkerComponent.cpp
Source/Pipeline/NetBindMarkerComponent.h
Source/Pipeline/NetworkSpawnableHolderComponent.cpp
Source/Pipeline/NetworkSpawnableHolderComponent.h
Source/Physics/PhysicsUtils.cpp