You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
732 lines
30 KiB
C++
732 lines
30 KiB
C++
/*
|
|
* 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 <Multiplayer/IMultiplayer.h>
|
|
#include <Multiplayer/Components/NetBindComponent.h>
|
|
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
|
|
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
|
|
#include <Multiplayer/Components/NetworkTransformComponent.h>
|
|
#include <Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h>
|
|
#include <Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h>
|
|
#include <Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h>
|
|
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
|
|
#include <Source/NetworkEntity/NetworkEntityTracker.h>
|
|
#include <Source/NetworkEntity/EntityReplication/PropertyPublisher.h>
|
|
#include <Source/NetworkEntity/EntityReplication/PropertySubscriber.h>
|
|
|
|
#include <AzNetworking/ConnectionLayer/IConnection.h>
|
|
#include <AzNetworking/PacketLayer/IPacket.h>
|
|
#include <AzNetworking/Serialization/ISerializer.h>
|
|
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
|
|
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
|
|
|
|
#include <AzCore/Component/ComponentApplicationBus.h>
|
|
#include <AzCore/Console/IConsole.h>
|
|
#include <AzCore/Console/ILogger.h>
|
|
|
|
#include <AzFramework/Components/TransformComponent.h>
|
|
|
|
namespace Multiplayer
|
|
{
|
|
EntityReplicator::EntityReplicator
|
|
(
|
|
EntityReplicationManager& replicationManager,
|
|
AzNetworking::IConnection* connection,
|
|
NetEntityRole remoteNetworkRole,
|
|
const ConstNetworkEntityHandle& entityHandle
|
|
)
|
|
: m_replicationManager(replicationManager)
|
|
, m_connection(connection)
|
|
, m_entityHandle(entityHandle)
|
|
, m_remoteNetworkRole(remoteNetworkRole)
|
|
, m_onEntityDirtiedHandler([this]() { OnEntityDirtiedEvent(); })
|
|
, m_onSendRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
|
|
, m_onForwardRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
|
|
, m_onSendAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
|
|
, m_onForwardAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
|
|
, m_onEntityStopHandler([this](const ConstNetworkEntityHandle&) { OnEntityRemovedEvent(); })
|
|
, m_proxyRemovalEvent([this] { OnProxyRemovalTimedEvent(); }, AZ::Name("ProxyRemovalTimedEvent"))
|
|
{
|
|
if (auto localEnt = m_entityHandle.GetEntity())
|
|
{
|
|
m_netBindComponent = m_entityHandle.GetNetBindComponent();
|
|
m_boundLocalNetworkRole = m_netBindComponent->GetNetEntityRole();
|
|
}
|
|
}
|
|
|
|
EntityReplicator::~EntityReplicator()
|
|
{
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void EntityReplicator::SetPrefabEntityId(const PrefabEntityId& prefabEntityId)
|
|
{
|
|
m_prefabEntityId = prefabEntityId;
|
|
m_prefabEntityIdSet = true;
|
|
}
|
|
|
|
void EntityReplicator::Reset(NetEntityRole remoteNetworkRole)
|
|
{
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
|
|
m_remoteNetworkRole = remoteNetworkRole;
|
|
|
|
m_propertyPublisher = nullptr;
|
|
m_propertySubscriber = nullptr;
|
|
|
|
m_wasMigrated = false;
|
|
|
|
m_onSendRpcHandler.Disconnect();
|
|
m_onForwardRpcHandler.Disconnect();
|
|
m_onSendAutonomousRpcHandler.Disconnect();
|
|
m_onForwardAutonomousRpcHandler.Disconnect();
|
|
m_onEntityStopHandler.Disconnect();
|
|
}
|
|
|
|
void EntityReplicator::Initialize(const ConstNetworkEntityHandle& entityHandle)
|
|
{
|
|
AZ_Assert(entityHandle, "Empty handle passed to Initialize");
|
|
m_entityHandle = entityHandle;
|
|
if (auto localEntity = m_entityHandle.GetEntity())
|
|
{
|
|
m_netBindComponent = m_entityHandle.GetNetBindComponent();
|
|
AZ_Assert(m_netBindComponent, "No Multiplayer::NetBindComponent");
|
|
m_boundLocalNetworkRole = m_netBindComponent->GetNetEntityRole();
|
|
SetPrefabEntityId(m_netBindComponent->GetPrefabEntityId());
|
|
}
|
|
|
|
AZ_Assert
|
|
(
|
|
m_boundLocalNetworkRole != m_remoteNetworkRole,
|
|
"Invalid configuration detected, bound local role must differ from remote network role: %s",
|
|
GetEnumString(m_boundLocalNetworkRole)
|
|
);
|
|
|
|
if (RemoteManagerOwnsEntityLifetime())
|
|
{
|
|
// Make sure we don't have any outstanding entity migration timeouts since we now have a new replicator
|
|
GetNetworkEntityAuthorityTracker()->AddEntityAuthorityManager(entityHandle, m_replicationManager.GetRemoteHostId());
|
|
}
|
|
|
|
// We got re-added
|
|
m_proxyRemovalEvent.RemoveFromQueue();
|
|
|
|
if (CanSendUpdates())
|
|
{
|
|
m_replicationManager.AddReplicatorToPendingSend(*this);
|
|
m_propertyPublisher = AZStd::make_unique<PropertyPublisher>
|
|
(
|
|
GetRemoteNetworkRole(),
|
|
!RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False,
|
|
m_netBindComponent,
|
|
*m_connection
|
|
);
|
|
m_onEntityDirtiedHandler.Disconnect();
|
|
m_netBindComponent->AddEntityDirtiedEventHandler(m_onEntityDirtiedHandler);
|
|
}
|
|
else
|
|
{
|
|
m_propertyPublisher = nullptr;
|
|
}
|
|
|
|
if (m_remoteNetworkRole == NetEntityRole::Authority ||
|
|
m_remoteNetworkRole == NetEntityRole::Autonomous)
|
|
{
|
|
m_propertySubscriber = AZStd::make_unique<PropertySubscriber>(m_replicationManager, m_netBindComponent);
|
|
}
|
|
else
|
|
{
|
|
m_propertySubscriber = nullptr;
|
|
}
|
|
|
|
// Prepare event handlers
|
|
if (auto localEntity = m_entityHandle.GetEntity())
|
|
{
|
|
NetBindComponent* netBindComponent = m_entityHandle.GetNetBindComponent();
|
|
AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent");
|
|
m_onEntityStopHandler.Disconnect();
|
|
netBindComponent->AddEntityStopEventHandler(m_onEntityStopHandler);
|
|
AttachRPCHandlers();
|
|
}
|
|
|
|
AZ_Assert(m_remoteNetworkRole != NetEntityRole::InvalidRole, "Trying to add an entity replicator with the remote role as invalid");
|
|
AZ_Assert(m_boundLocalNetworkRole != NetEntityRole::InvalidRole, "Trying to add an entity replicator with the bound local role as invalid");
|
|
|
|
m_wasMigrated = false;
|
|
}
|
|
|
|
void EntityReplicator::AttachRPCHandlers()
|
|
{
|
|
// Make sure all handlers are detached first
|
|
m_onSendRpcHandler.Disconnect();
|
|
m_onSendAutonomousRpcHandler.Disconnect();
|
|
m_onForwardRpcHandler.Disconnect();
|
|
m_onForwardAutonomousRpcHandler.Disconnect();
|
|
|
|
if (auto localEntity = m_entityHandle.GetEntity())
|
|
{
|
|
NetBindComponent* netBindComponent = m_entityHandle.GetNetBindComponent();
|
|
AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent");
|
|
|
|
switch (GetBoundLocalNetworkRole())
|
|
{
|
|
case NetEntityRole::Authority:
|
|
if (GetRemoteNetworkRole() == NetEntityRole::Client || GetRemoteNetworkRole() == NetEntityRole::Autonomous)
|
|
{
|
|
m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
|
|
if (GetRemoteNetworkRole() == NetEntityRole::Autonomous)
|
|
{
|
|
m_onSendAutonomousRpcHandler.Connect(netBindComponent->GetSendAuthorityToAutonomousRpcEvent());
|
|
}
|
|
}
|
|
else if (GetRemoteNetworkRole() == NetEntityRole::Server)
|
|
{
|
|
m_onForwardRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
|
|
}
|
|
break;
|
|
case NetEntityRole::Server:
|
|
if (GetRemoteNetworkRole() == NetEntityRole::Authority)
|
|
{
|
|
m_onSendRpcHandler.Connect(netBindComponent->GetSendServerToAuthorityRpcEvent());
|
|
m_onForwardRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
|
|
m_onForwardAutonomousRpcHandler.Connect(netBindComponent->GetSendAuthorityToAutonomousRpcEvent());
|
|
}
|
|
else if (GetRemoteNetworkRole() == NetEntityRole::Client)
|
|
{
|
|
// Listen for these to forward the rpc along to the other Client replicators
|
|
m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
|
|
}
|
|
else if (GetRemoteNetworkRole() == NetEntityRole::Autonomous)
|
|
{
|
|
// NOTE: Autonomous is not connected to ServerProxy, it is always connected to an Authority
|
|
AZ_Assert(false, "Unexpected autonomous remote role")
|
|
}
|
|
break;
|
|
case NetEntityRole::Client:
|
|
// Nothing allowed, no Client to Server communication
|
|
break;
|
|
case NetEntityRole::Autonomous:
|
|
if (GetRemoteNetworkRole() == NetEntityRole::Authority)
|
|
{
|
|
m_onSendRpcHandler.Connect(netBindComponent->GetSendAutonomousToAuthorityRpcEvent());
|
|
}
|
|
break;
|
|
default:
|
|
AZ_Assert(false, "Unexpected network role");
|
|
}
|
|
}
|
|
}
|
|
|
|
void EntityReplicator::ActivateNetworkEntity()
|
|
{
|
|
ActivateNetworkEntityInternal();
|
|
}
|
|
|
|
void EntityReplicator::OnEntityActivated(const AZ::EntityId&)
|
|
{
|
|
ActivateNetworkEntityInternal();
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void EntityReplicator::OnEntityDestroyed(const AZ::EntityId&)
|
|
{
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void EntityReplicator::ActivateNetworkEntityInternal()
|
|
{
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
|
|
AZ::Entity* entity = GetEntityHandle().GetEntity();
|
|
AZ_Assert(entity, "Entity replicator entity unexpectedly missing");
|
|
|
|
if (entity->GetState() != AZ::Entity::State::Init)
|
|
{
|
|
AZLOG_WARN("Trying to activate an entity that is not in the Init state (%llu)", static_cast<AZ::u64>(GetEntityHandle().GetNetEntityId()));
|
|
}
|
|
|
|
entity->Activate();
|
|
|
|
m_replicationManager.m_orphanedEntityRpcs.DispatchOrphanedRpcs(*this);
|
|
}
|
|
|
|
bool EntityReplicator::CanSendUpdates()
|
|
{
|
|
bool ret(false);
|
|
if (auto localEnt = GetEntityHandle().GetEntity())
|
|
{
|
|
NetBindComponent* netBindComponent = m_netBindComponent;
|
|
AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent");
|
|
|
|
bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
|
|
bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client;
|
|
bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous;
|
|
if (isAuthority || isClient || isAutonomous)
|
|
{
|
|
ret = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool EntityReplicator::OwnsReplicatorLifetime() const
|
|
{
|
|
bool ret(false);
|
|
if (GetBoundLocalNetworkRole() == NetEntityRole::Authority // Authority always owns lifetime
|
|
|| (GetBoundLocalNetworkRole() == NetEntityRole::Server // Server also owns lifetime if the remote endpoint is a client of some form
|
|
&& (GetRemoteNetworkRole() == NetEntityRole::Client
|
|
|| GetRemoteNetworkRole() == NetEntityRole::Autonomous)))
|
|
{
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const
|
|
{
|
|
bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server)
|
|
&& (GetRemoteNetworkRole() == NetEntityRole::Authority);
|
|
bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client)
|
|
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
|
|
return isServer || isClient;
|
|
}
|
|
|
|
void EntityReplicator::MarkForRemoval()
|
|
{
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
|
|
if (RemoteManagerOwnsEntityLifetime())
|
|
{
|
|
GetNetworkEntityAuthorityTracker()->RemoveEntityAuthorityManager(m_entityHandle, m_replicationManager.GetRemoteHostId());
|
|
}
|
|
|
|
ClearPendingRemoval();
|
|
|
|
if (m_propertyPublisher)
|
|
{
|
|
m_propertyPublisher->SetDeleting();
|
|
m_replicationManager.AddReplicatorToPendingSend(*this);
|
|
m_onEntityDirtiedHandler.Disconnect();
|
|
}
|
|
else if (m_propertySubscriber)
|
|
{
|
|
m_propertySubscriber->SetDeleting();
|
|
}
|
|
|
|
m_replicationManager.AddReplicatorToPendingRemoval(*this);
|
|
|
|
m_onForwardRpcHandler.Disconnect();
|
|
m_onForwardAutonomousRpcHandler.Disconnect();
|
|
|
|
m_onEntityStopHandler.Disconnect();
|
|
}
|
|
|
|
bool EntityReplicator::IsMarkedForRemoval() const
|
|
{
|
|
bool ret(true);
|
|
if (m_propertyPublisher)
|
|
{
|
|
ret = m_propertyPublisher->IsDeleting();
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(m_propertySubscriber, "Expected to have at least a subscriber when deleting");
|
|
ret = m_propertySubscriber->IsDeleting();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void EntityReplicator::SetPendingRemoval(AZ::TimeMs pendingRemovalTimeMs)
|
|
{
|
|
AZ_Assert(m_propertyPublisher, "Only valid if we are publishing updates");
|
|
if (pendingRemovalTimeMs > AZ::Time::ZeroTimeMs)
|
|
{
|
|
if (!IsPendingRemoval())
|
|
{
|
|
m_proxyRemovalEvent.Enqueue(pendingRemovalTimeMs);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MarkForRemoval();
|
|
}
|
|
}
|
|
|
|
bool EntityReplicator::IsPendingRemoval() const
|
|
{
|
|
return m_proxyRemovalEvent.IsScheduled();
|
|
}
|
|
|
|
void EntityReplicator::ClearPendingRemoval()
|
|
{
|
|
m_proxyRemovalEvent.RemoveFromQueue();
|
|
}
|
|
|
|
bool EntityReplicator::IsDeletionAcknowledged() const
|
|
{
|
|
bool ret(true);
|
|
// we sent the delete message, make sure it gets there
|
|
if (m_propertyPublisher)
|
|
{
|
|
ret = m_propertyPublisher->IsDeleted();
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(m_propertySubscriber, "Expected to have at least a subscriber when deleting");
|
|
ret = m_propertySubscriber->IsDeleted();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
AZ::TimeMs EntityReplicator::GetResendTimeoutTimeMs() const
|
|
{
|
|
return m_replicationManager.GetResendTimeoutTimeMs();
|
|
}
|
|
|
|
bool EntityReplicator::IsReadyToActivate() const
|
|
{
|
|
const AZ::Entity* entity = m_entityHandle.GetEntity();
|
|
AZ_Assert(entity, "Entity replicator entity unexpectedly missing");
|
|
|
|
const NetworkHierarchyChildComponent* hierarchyChildComponent = entity->FindComponent<NetworkHierarchyChildComponent>();
|
|
const NetworkHierarchyRootComponent* hierarchyRootComponent = nullptr;
|
|
|
|
if (hierarchyChildComponent == nullptr)
|
|
{
|
|
// Child and root hierarchy components are mutually exclusive
|
|
hierarchyRootComponent = entity->FindComponent<NetworkHierarchyRootComponent>();
|
|
}
|
|
|
|
if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchicalChild())
|
|
|| (hierarchyRootComponent && hierarchyRootComponent->IsHierarchicalChild()))
|
|
{
|
|
// If hierarchy is enabled for the entity, check if the parent is available
|
|
if (const NetworkTransformComponent* networkTransform = entity->FindComponent<NetworkTransformComponent>())
|
|
{
|
|
const NetEntityId parentId = networkTransform->GetParentEntityId();
|
|
// For root entities attached to a level, a network parent won't be set.
|
|
// In this case, this entity is the root entity of the hierarchy and it will be activated first.
|
|
if (parentId != InvalidNetEntityId)
|
|
{
|
|
ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId);
|
|
|
|
const AZ::Entity* parentEntity = parentHandle.GetEntity();
|
|
if (parentEntity && parentEntity->GetState() == AZ::Entity::State::Active)
|
|
{
|
|
AZLOG
|
|
(
|
|
NET_HierarchyActivationInfo,
|
|
"Hierchical entity %s asking for activation - granted",
|
|
entity->GetName().c_str()
|
|
);
|
|
return true;
|
|
}
|
|
|
|
AZLOG
|
|
(
|
|
NET_HierarchyActivationInfo,
|
|
"Hierchical entity %s asking for activation - waiting on the parent %llu",
|
|
entity->GetName().c_str(),
|
|
aznumeric_cast<AZ::u64>(parentId)
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
NetworkEntityUpdateMessage EntityReplicator::GenerateUpdatePacket()
|
|
{
|
|
if (IsMarkedForRemoval() && OwnsReplicatorLifetime()) // TODO: clean this up
|
|
{
|
|
// If the remote replicator is not established, we need to take ownership of the entity
|
|
AZLOG
|
|
(
|
|
NET_RepDeletes,
|
|
"Sending delete replicator id %llu migrated %d to remote host %s",
|
|
aznumeric_cast<AZ::u64>(GetEntityHandle().GetNetEntityId()),
|
|
WasMigrated() ? 1 : 0,
|
|
m_replicationManager.GetRemoteHostId().GetString().c_str()
|
|
);
|
|
return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated());
|
|
}
|
|
|
|
NetBindComponent* netBindComponent = GetNetBindComponent();
|
|
const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished();
|
|
|
|
NetworkEntityUpdateMessage updateMessage(GetRemoteNetworkRole(), GetEntityHandle().GetNetEntityId());
|
|
if (sendSliceName)
|
|
{
|
|
updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId());
|
|
}
|
|
|
|
AzNetworking::NetworkInputSerializer inputSerializer(updateMessage.ModifyData().GetBuffer(), static_cast<uint32_t>(updateMessage.ModifyData().GetCapacity()));
|
|
m_propertyPublisher->UpdateSerialization(inputSerializer);
|
|
updateMessage.ModifyData().Resize(inputSerializer.GetSize());
|
|
|
|
return updateMessage;
|
|
}
|
|
|
|
void EntityReplicator::FinalizeSerialization(AzNetworking::PacketId sentId)
|
|
{
|
|
m_propertyPublisher->FinalizeSerialization(sentId);
|
|
}
|
|
|
|
void EntityReplicator::DeferRpcMessage(NetworkEntityRpcMessage& entityRpcMessage)
|
|
{
|
|
// Received rpc metrics, log rpc sent, number of bytes, and the componentId/rpcId for bandwidth metrics
|
|
MultiplayerStats& stats = GetMultiplayer()->GetStats();
|
|
stats.RecordRpcSent(GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str(),
|
|
entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize());
|
|
|
|
m_replicationManager.AddDeferredRpcMessage(entityRpcMessage);
|
|
}
|
|
|
|
void EntityReplicator::OnSendRpcEvent(NetworkEntityRpcMessage& entityRpcMessage)
|
|
{
|
|
if (IsMarkedForRemoval() && GetNetworkEntityAuthorityTracker()->DoesEntityHaveOwner(GetEntityHandle()))
|
|
{
|
|
// The remote end no longer owns this entity, so don't try and send to it (let another replicator send to it)
|
|
return;
|
|
}
|
|
if (m_isForwardingRpc)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (auto localEntity = m_entityHandle.GetEntity())
|
|
{
|
|
DeferRpcMessage(entityRpcMessage);
|
|
}
|
|
}
|
|
|
|
void EntityReplicator::OnEntityDirtiedEvent()
|
|
{
|
|
AZ_Assert(m_propertyPublisher, "Expected to have a publisher, did we forget to disconnect?");
|
|
m_propertyPublisher->GenerateRecord();
|
|
m_replicationManager.AddReplicatorToPendingSend(*this);
|
|
}
|
|
|
|
void EntityReplicator::OnEntityRemovedEvent()
|
|
{
|
|
m_netBindComponent = nullptr;
|
|
MarkForRemoval();
|
|
}
|
|
|
|
void EntityReplicator::OnProxyRemovalTimedEvent()
|
|
{
|
|
MarkForRemoval();
|
|
}
|
|
|
|
EntityReplicator::RpcValidationResult EntityReplicator::ValidateRpcMessage(const NetworkEntityRpcMessage& entityRpcMessage) const
|
|
{
|
|
RpcValidationResult result = RpcValidationResult::DropRpcAndDisconnect;
|
|
switch (entityRpcMessage.GetRpcDeliveryType())
|
|
{
|
|
case RpcDeliveryType::AuthorityToClient:
|
|
if (((GetBoundLocalNetworkRole() == NetEntityRole::Client) || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous))
|
|
&& (GetRemoteNetworkRole() == NetEntityRole::Authority))
|
|
{
|
|
// We are a local client, and we are connected to server, aka AuthorityToClient
|
|
result = RpcValidationResult::HandleRpc;
|
|
}
|
|
if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) && (GetRemoteNetworkRole() == NetEntityRole::Authority))
|
|
{
|
|
// We are on a server, and we received this message from another server, therefore we should forward this to any connected clients
|
|
result = RpcValidationResult::ForwardToClient;
|
|
}
|
|
break;
|
|
case RpcDeliveryType::AuthorityToAutonomous:
|
|
if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous) && (GetRemoteNetworkRole() == NetEntityRole::Authority))
|
|
{
|
|
// We are an autonomous client, and we are connected to server, aka AuthorityToAutonomous
|
|
result = RpcValidationResult::HandleRpc;
|
|
}
|
|
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server))
|
|
{
|
|
// We are on a server, and we received this message from another server, therefore we should forward this to our autonomous player
|
|
// This can occur if we've recently migrated
|
|
result = RpcValidationResult::ForwardToAutonomous;
|
|
}
|
|
break;
|
|
case RpcDeliveryType::AutonomousToAuthority:
|
|
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Autonomous))
|
|
{
|
|
if (IsMarkedForRemoval())
|
|
{
|
|
// we've likely migrated, forward if the message is reliable
|
|
if (entityRpcMessage.GetReliability() == ReliabilityType::Reliable)
|
|
{
|
|
// We only forward messages that should be reliable
|
|
result = RpcValidationResult::ForwardToAuthority;
|
|
}
|
|
else
|
|
{
|
|
// this isn't reliable, so we can just drop it
|
|
result = RpcValidationResult::DropRpc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We are on a server, and we got a message from the autonomous, aka AutonomousToAuthority, so handle
|
|
result = RpcValidationResult::HandleRpc;
|
|
}
|
|
}
|
|
break;
|
|
case RpcDeliveryType::ServerToAuthority:
|
|
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server))
|
|
{
|
|
// if we're marked for removal, then we should forward to whomever now owns this entity
|
|
if (IsMarkedForRemoval())
|
|
{
|
|
// we've likely migrated, forward if the message is reliable
|
|
if (entityRpcMessage.GetReliability() == ReliabilityType::Reliable)
|
|
{
|
|
// We only forward messages that should be reliable
|
|
result = RpcValidationResult::ForwardToAuthority;
|
|
}
|
|
else
|
|
{
|
|
// this isn't reliable, so we can just drop it
|
|
result = RpcValidationResult::DropRpc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We are the authority, and we got this message from a server proxy, aka ServerToAuthority, so handle
|
|
result = RpcValidationResult::HandleRpc;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (result == RpcValidationResult::DropRpcAndDisconnect)
|
|
{
|
|
bool isLocalServer = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) || (GetBoundLocalNetworkRole() == NetEntityRole::Server);
|
|
bool isRemoteServer = (GetRemoteNetworkRole() == NetEntityRole::Authority) || (GetRemoteNetworkRole() == NetEntityRole::Server);
|
|
if (isLocalServer && isRemoteServer)
|
|
{
|
|
// Demote this to just a drop message, we didn't want to handle the message, but we don't want to drop the connection
|
|
result = EntityReplicator::RpcValidationResult::DropRpc;
|
|
}
|
|
else
|
|
{
|
|
AZLOG_ERROR
|
|
(
|
|
"Dropping RPC and Connection EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
|
|
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
|
|
GetEnumString(GetBoundLocalNetworkRole()),
|
|
GetEnumString(GetRemoteNetworkRole()),
|
|
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
|
|
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
|
|
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
|
|
IsMarkedForRemoval() ? "true" : "false"
|
|
);
|
|
}
|
|
}
|
|
|
|
if (result == RpcValidationResult::DropRpc)
|
|
{
|
|
AZLOG
|
|
(
|
|
NET_Rpc,
|
|
"Dropping RPC EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
|
|
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
|
|
GetEnumString(GetBoundLocalNetworkRole()),
|
|
GetEnumString(GetRemoteNetworkRole()),
|
|
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
|
|
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
|
|
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
|
|
IsMarkedForRemoval() ? "true" : "false"
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool EntityReplicator::HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& entityRpcMessage)
|
|
{
|
|
// Received rpc metrics, log rpc received, time spent, number of bytes, and the componentId/rpcId for bandwidth metrics
|
|
MultiplayerStats& stats = GetMultiplayer()->GetStats();
|
|
stats.RecordRpcReceived(GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str(),
|
|
entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize());
|
|
|
|
if (!m_netBindComponent)
|
|
{
|
|
AZLOG_WARN
|
|
(
|
|
"Dropping RPC since entity deleted EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
|
|
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
|
|
GetEnumString(GetBoundLocalNetworkRole()),
|
|
GetEnumString(GetRemoteNetworkRole()),
|
|
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
|
|
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
|
|
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
|
|
IsMarkedForRemoval() ? "true" : "false"
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// When we forward a message, we'll likely hit the this entity replicator again (since it's already listening on the RPC events)
|
|
// Therefore, we need to ignore the re-entrant case.
|
|
class ScopedForwardingMessage
|
|
{
|
|
public:
|
|
ScopedForwardingMessage(EntityReplicator& replicator)
|
|
: m_replicator(replicator)
|
|
{
|
|
m_isForwardingCache = m_replicator.m_isForwardingRpc;
|
|
m_replicator.m_isForwardingRpc = true;
|
|
}
|
|
~ScopedForwardingMessage()
|
|
{
|
|
m_replicator.m_isForwardingRpc = m_isForwardingCache;
|
|
}
|
|
bool m_isForwardingCache = false;
|
|
EntityReplicator& m_replicator;
|
|
};
|
|
|
|
// First validate the message with local & remote roles
|
|
RpcValidationResult result = ValidateRpcMessage(entityRpcMessage);
|
|
|
|
switch (result)
|
|
{
|
|
case RpcValidationResult::HandleRpc:
|
|
return m_netBindComponent->HandleRpcMessage(invokingConnection, GetRemoteNetworkRole(), entityRpcMessage);
|
|
case RpcValidationResult::DropRpc:
|
|
return true;
|
|
case RpcValidationResult::DropRpcAndDisconnect:
|
|
return false;
|
|
case RpcValidationResult::ForwardToClient:
|
|
{
|
|
ScopedForwardingMessage forwarding(*this);
|
|
m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage);
|
|
}
|
|
return true;
|
|
case RpcValidationResult::ForwardToAutonomous:
|
|
{
|
|
ScopedForwardingMessage forwarding(*this);
|
|
m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage);
|
|
}
|
|
return true;
|
|
case RpcValidationResult::ForwardToAuthority:
|
|
{
|
|
ScopedForwardingMessage forwarding(*this);
|
|
m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage);
|
|
}
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
AZ_Assert(false, "Unhandled RpcValidationResult %d", result);
|
|
return false;
|
|
}
|
|
}
|