diff --git a/Code/Framework/AzCore/AzCore/Math/Aabb.h b/Code/Framework/AzCore/AzCore/Math/Aabb.h index 488808bc4c..f6fb695399 100644 --- a/Code/Framework/AzCore/AzCore/Math/Aabb.h +++ b/Code/Framework/AzCore/AzCore/Math/Aabb.h @@ -1,3 +1,4 @@ + /* * 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. diff --git a/Code/Framework/AzCore/AzCore/Utils/Utils.h b/Code/Framework/AzCore/AzCore/Utils/Utils.h index e72a9b3f9c..51711877ac 100644 --- a/Code/Framework/AzCore/AzCore/Utils/Utils.h +++ b/Code/Framework/AzCore/AzCore/Utils/Utils.h @@ -24,7 +24,7 @@ namespace AZ { //! Protects from allocating too much memory. The choice of a 1MB threshold is arbitrary. //! If you need to work with larger files, please use AZ::IO directly instead of these utility functions. - inline constexpr size_t DefaultMaxFileSize = 1024 * 1024; + inline constexpr size_t DefaultMaxFileSize = 5 * 1024 * 1024; //! Terminates the application without going through the shutdown procedure. //! This is used when due to abnormal circumstances the application can no diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h index 09aa62f7d7..3a37a44a02 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworkInterface.h @@ -103,13 +103,13 @@ namespace AzNetworking //! @return boolean true on success virtual bool Disconnect(ConnectionId connectionId, DisconnectReason reason) = 0; - //! Sets whether this connection interface can disconnect by virtue of a timeout - //! @param timeoutEnabled If this connection interface will automatically disconnect due to a timeout - virtual void SetTimeoutEnabled(bool timeoutEnabled) = 0; + //! Sets the timeout time in milliseconds, 0 ms means timeouts are disabled. + //! @param timeoutMs the number of milliseconds with no traffic before we timeout and close a connection + virtual void SetTimeoutMs(AZ::TimeMs timeoutMs) = 0; - //! Whether this connection interface will disconnect by virtue of a time out (does not account for cvars affecting all connections) - //! @return boolean true if this connection will not disconnect on timeout (does not account for cvars affecting all connections) - virtual bool IsTimeoutEnabled() const = 0; + //! Retrieves the timeout time in milliseconds for this network interface, 0 ms means timeouts are disabled. + //! @return the timeout time in milliseconds for this network interface, 0 ms means timeouts are disabled + virtual AZ::TimeMs GetTimeoutMs() const = 0; //! Const access to the metrics tracked by this network interface. //! @return const reference to the metrics tracked by this network interface diff --git a/Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h b/Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h index 59e0cc8235..e99144acbf 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h +++ b/Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h @@ -321,4 +321,19 @@ namespace AzNetworking return serializer.IsValid(); } }; + + template <> + struct SerializeObjectHelper + { + static bool SerializeObject(ISerializer& serializer, AZ::Aabb& value) + { + AZ::Vector3 minValue = value.GetMin(); + AZ::Vector3 maxValue = value.GetMax(); + serializer.Serialize(minValue, "minValue"); + serializer.Serialize(maxValue, "maxValue"); + value.SetMin(minValue); + value.SetMax(maxValue); + return serializer.IsValid(); + } + }; } diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp index 62335a9b39..f15e0c5d1a 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp @@ -22,14 +22,15 @@ namespace AzNetworking #endif AZ_CVAR(bool, net_TcpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Tcp connections"); - AZ_CVAR(AZ::TimeMs, net_TcpHearthbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency"); - AZ_CVAR(AZ::TimeMs, net_TcpTimeoutTimeMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Tcp connection"); + AZ_CVAR(AZ::TimeMs, net_TcpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency"); + AZ_CVAR(AZ::TimeMs, net_TcpDefaultTimeoutTimeMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Tcp connection"); TcpNetworkInterface::TcpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, TcpListenThread& listenThread) : m_name(name) , m_trustZone(trustZone) , m_connectionListener(connectionListener) , m_listenThread(listenThread) + , m_timeoutMs(net_TcpDefaultTimeoutTimeMs) { ; } @@ -97,7 +98,7 @@ namespace AzNetworking } AZLOG_INFO("Adding new socket %d", static_cast(tcpSocket->GetSocketFd())); - const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast(tcpSocket->GetSocketFd()), net_TcpHearthbeatTimeMs); + const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast(tcpSocket->GetSocketFd()), net_TcpHeartbeatTimeMs); connection->SetTimeoutId(newTimeoutId); connection->SendReliablePacket(CorePackets::InitiateConnectionPacket()); m_connectionListener.OnConnect(connection.get()); @@ -174,14 +175,14 @@ namespace AzNetworking return connection->Disconnect(reason, TerminationEndpoint::Local); } - void TcpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled) + void TcpNetworkInterface::SetTimeoutMs(AZ::TimeMs timeoutMs) { - m_timeoutEnabled = timeoutEnabled; + m_timeoutMs = timeoutMs; } - bool TcpNetworkInterface::IsTimeoutEnabled() const + AZ::TimeMs TcpNetworkInterface::GetTimeoutMs() const { - return m_timeoutEnabled; + return m_timeoutMs; } void TcpNetworkInterface::QueueNewConnection(const PendingConnection& pendingConnection) @@ -257,7 +258,7 @@ namespace AzNetworking return; } AZLOG(NET_TcpTraffic, "Adding new socket %d", static_cast(tcpSocket.GetSocketFd())); - const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast(tcpSocket.GetSocketFd()), net_TcpTimeoutTimeMs); + const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast(tcpSocket.GetSocketFd()), m_timeoutMs); AZStd::unique_ptr connection = AZStd::make_unique(connectionId, remoteAddress, *this, tcpSocket, timeoutId); AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Acceptor, "Invalid role for connection"); GetConnectionListener().OnConnect(connection.get()); @@ -316,7 +317,7 @@ namespace AzNetworking { tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket()); } - else if (net_TcpTimeoutConnections && m_networkInterface.IsTimeoutEnabled()) + else if (net_TcpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 })) { tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); return TimeoutResult::Delete; diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h index b9ea88974d..d8f5d1b62b 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h @@ -99,8 +99,8 @@ namespace AzNetworking bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; - void SetTimeoutEnabled(bool timeoutEnabled) override; - bool IsTimeoutEnabled() const override; + void SetTimeoutMs(AZ::TimeMs timeoutMs) override; + AZ::TimeMs GetTimeoutMs() const override; //! @} //! Queues a new incoming connection for this network interface. @@ -156,7 +156,7 @@ namespace AzNetworking AZ::Name m_name; TrustZone m_trustZone; uint16_t m_port = 0; - bool m_timeoutEnabled = true; + AZ::TimeMs m_timeoutMs = AZ::TimeMs{ 0 }; IConnectionListener& m_connectionListener; TcpConnectionSet m_connectionSet; TcpSocketManager m_tcpSocketManager; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index bf01ece458..b12d3ef845 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -31,8 +31,8 @@ namespace AzNetworking AZ_CVAR(bool, net_UdpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Udp connections"); AZ_CVAR(AZ::TimeMs, net_UdpPacketTimeSliceMs, AZ::TimeMs{ 8 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of milliseconds to allow for packet processing"); - AZ_CVAR(AZ::TimeMs, net_UdpHearthbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Udp connection heartbeat frequency"); - AZ_CVAR(AZ::TimeMs, net_UdpTimeoutTimeMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Udp connection"); + AZ_CVAR(AZ::TimeMs, net_UdpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Udp connection heartbeat frequency"); + AZ_CVAR(AZ::TimeMs, net_UdpDefaultTimeoutTimeMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Udp connection"); AZ_CVAR(AZ::TimeMs, net_MinPacketTimeoutMs, AZ::TimeMs{ 200 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Minimum time to wait before timing out an unacked packet"); AZ_CVAR(int32_t, net_MaxTimeoutsPerFrame, 1000, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of packet timeouts to allow to process in a single frame"); AZ_CVAR(float, net_RttFudgeScalar, 2.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Scalar value to multiply computed Rtt by to determine an optimal packet timeout threshold"); @@ -61,6 +61,7 @@ namespace AzNetworking , m_connectionListener(connectionListener) , m_socket(net_UdpUseEncryption ? new DtlsSocket() : new UdpSocket()) , m_readerThread(readerThread) + , m_timeoutMs(net_UdpDefaultTimeoutTimeMs) { const AZ::CVarFixedString compressor = static_cast(net_UdpCompressor); const AZ::Name compressorName = AZ::Name(compressor); @@ -138,7 +139,7 @@ namespace AzNetworking } const ConnectionId connectionId = m_connectionSet.GetNextConnectionId(); - const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast(connectionId), net_UdpHearthbeatTimeMs); + const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast(connectionId), m_timeoutMs); AZStd::unique_ptr connection = AZStd::make_unique(connectionId, remoteAddress, *this, ConnectionRole::Connector); UdpPacketEncodingBuffer dtlsData; @@ -403,14 +404,14 @@ namespace AzNetworking return connection->Disconnect(reason, TerminationEndpoint::Local); } - void UdpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled) + void UdpNetworkInterface::SetTimeoutMs(AZ::TimeMs timeoutMs) { - m_timeoutEnabled = timeoutEnabled; + m_timeoutMs = timeoutMs; } - bool UdpNetworkInterface::IsTimeoutEnabled() const + AZ::TimeMs UdpNetworkInterface::GetTimeoutMs() const { - return m_timeoutEnabled; + return m_timeoutMs; } bool UdpNetworkInterface::IsEncrypted() const @@ -681,7 +682,7 @@ namespace AzNetworking // How long should we sit in the timeout queue before heartbeating or disconnecting const ConnectionId connectionId = m_connectionSet.GetNextConnectionId(); - const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast(connectionId), net_UdpTimeoutTimeMs); + const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast(connectionId), m_timeoutMs); AZLOG(Debug_UdpConnect, "Accepted new Udp Connection"); AZStd::unique_ptr connection = AZStd::make_unique(connectionId, connectPacket.m_address, *this, ConnectionRole::Acceptor); @@ -745,7 +746,7 @@ namespace AzNetworking { udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket()); } - else if (net_UdpTimeoutConnections && m_networkInterface.IsTimeoutEnabled()) + else if (net_UdpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 })) { udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); return TimeoutResult::Delete; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h index 949914da91..8f827c74c4 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h @@ -104,8 +104,8 @@ namespace AzNetworking bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool StopListening() override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; - void SetTimeoutEnabled(bool timeoutEnabled) override; - bool IsTimeoutEnabled() const override; + void SetTimeoutMs(AZ::TimeMs timeoutMs) override; + AZ::TimeMs GetTimeoutMs() const override; //! @} //! Returns true if this is an encrypted socket, false if not. @@ -181,7 +181,7 @@ namespace AzNetworking TrustZone m_trustZone; uint16_t m_port = 0; bool m_allowIncomingConnections = false; - bool m_timeoutEnabled = true; + AZ::TimeMs m_timeoutMs = AZ::TimeMs{ 0 }; IConnectionListener& m_connectionListener; UdpConnectionSet m_connectionSet; TimeoutQueue m_connectionTimeoutQueue; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h index 397406f3b5..bcbea6542f 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h @@ -83,8 +83,8 @@ namespace Multiplayer AZ::ScheduledEvent m_autonomousUpdateEvent; // Drives autonomous input collection AZ::ScheduledEvent m_updateBankedTimeEvent; // Drives authority bank time updates - EntityMigrationStartEvent::Handler m_migrateStartHandler; - EntityMigrationEndEvent::Handler m_migrateEndHandler; + ClientMigrationStartEvent::Handler m_migrateStartHandler; + ClientMigrationEndEvent::Handler m_migrateEndHandler; double m_moveAccumulator = 0.0; double m_clientBankedTime = 0.0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 65bac09726..55ed70088d 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -32,8 +32,6 @@ namespace Multiplayer using EntityStopEvent = AZ::Event; using EntityDirtiedEvent = AZ::Event<>; using EntitySyncRewindEvent = AZ::Event<>; - using EntityMigrationStartEvent = AZ::Event; - using EntityMigrationEndEvent = AZ::Event<>; using EntityServerMigrationEvent = AZ::Event; using EntityPreRenderEvent = AZ::Event; using EntityCorrectionEvent = AZ::Event<>; @@ -115,8 +113,6 @@ namespace Multiplayer void MarkDirty(); void NotifyLocalChanges(); void NotifySyncRewindState(); - void NotifyMigrationStart(ClientInputId migratedInputId); - void NotifyMigrationEnd(); void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId); void NotifyPreRender(float deltaTime, float blendFactor); void NotifyCorrection(); @@ -124,8 +120,6 @@ namespace Multiplayer void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler); void AddEntityDirtiedEventHandler(EntityDirtiedEvent::Handler& eventHandler); void AddEntitySyncRewindEventHandler(EntitySyncRewindEvent::Handler& eventHandler); - void AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler); - void AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler); void AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler); void AddEntityPreRenderEventHandler(EntityPreRenderEvent::Handler& eventHandler); void AddEntityCorrectionEventHandler(EntityCorrectionEvent::Handler& handler); @@ -174,8 +168,6 @@ namespace Multiplayer EntityStopEvent m_entityStopEvent; EntityDirtiedEvent m_dirtiedEvent; EntitySyncRewindEvent m_syncRewindEvent; - EntityMigrationStartEvent m_entityMigrationStartEvent; - EntityMigrationEndEvent m_entityMigrationEndEvent; EntityServerMigrationEvent m_entityServerMigrationEvent; EntityPreRenderEvent m_entityPreRenderEvent; EntityCorrectionEvent m_entityCorrectionEvent; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h index 66e39419e7..092cf9b854 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h @@ -21,6 +21,14 @@ namespace Multiplayer virtual ~IEntityDomain() = default; + //! For domains that operate on a region of space, this sets the area the domain is responsible for. + //! @param aabb the aabb associated with this entity domain + virtual void SetAabb(const AZ::Aabb& aabb) = 0; + + //! Retrieves the aabb representing the domain area, an invalid aabb will be returned for non-spatial domains. + //! @return the aabb associated with this entity domain + virtual const AZ::Aabb& GetAabb() const = 0; + //! Returns whether or not an entity should be owned by an entity manager. //! @param entityHandle the handle of the netbound entity to check //! @return false if this entity should not belong to the entity manger, true if it could be owned by the entity manager diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 4f714068db..69d2eb4071 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -42,6 +42,8 @@ namespace Multiplayer AzNetworking::ByteBuffer<2048> m_userData; }; + using ClientMigrationStartEvent = AZ::Event; + using ClientMigrationEndEvent = AZ::Event<>; using ClientDisconnectedEvent = AZ::Event<>; using ConnectionAcquiredEvent = AZ::Event; using SessionInitEvent = AZ::Event; @@ -94,6 +96,14 @@ namespace Multiplayer //! @param reason The reason for terminating connections virtual void Terminate(AzNetworking::DisconnectReason reason) = 0; + //! Adds a ClientMigrationStartEvent Handler which is invoked at the start of a client migration + //! @param handler The ClientMigrationStartEvent Handler to add + virtual void AddClientMigrationStartEventHandler(ClientMigrationStartEvent::Handler& handler) = 0; + + //! Adds a ClientMigrationEndEvent Handler which is invoked when a client completes migration + //! @param handler The ClientMigrationEndEvent Handler to add + virtual void AddClientMigrationEndEventHandler(ClientMigrationEndEvent::Handler& handler) = 0; + //! Adds a ClientDisconnectedEvent Handler which is invoked on the client when a disconnection occurs //! @param handler The ClientDisconnectedEvent Handler to add virtual void AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler) = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h index 4ac20abc83..26cdf72e5f 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h @@ -105,9 +105,11 @@ namespace Multiplayer struct EntityMigrationMessage { - NetEntityId m_entityId; + NetEntityId m_netEntityId; PrefabEntityId m_prefabEntityId; AzNetworking::PacketEncodingBuffer m_propertyUpdateData; + bool operator!=(const EntityMigrationMessage& rhs) const; + bool Serialize(AzNetworking::ISerializer& serializer); }; inline PrefabEntityId::PrefabEntityId(AZ::Name name, uint32_t entityOffset) @@ -133,6 +135,21 @@ namespace Multiplayer serializer.Serialize(m_entityOffset, "entityOffset"); return serializer.IsValid(); } + + inline bool EntityMigrationMessage::operator!=(const EntityMigrationMessage& rhs) const + { + return m_netEntityId != rhs.m_netEntityId + || m_prefabEntityId != rhs.m_prefabEntityId + || m_propertyUpdateData != rhs.m_propertyUpdateData; + } + + inline bool EntityMigrationMessage::Serialize(AzNetworking::ISerializer& serializer) + { + serializer.Serialize(m_netEntityId, "netEntityId"); + serializer.Serialize(m_prefabEntityId, "prefabEntityId"); + serializer.Serialize(m_propertyUpdateData, "propertyUpdateData"); + return serializer.IsValid(); + } } AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::HostId); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h new file mode 100644 index 0000000000..118ff0e6af --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -0,0 +1,218 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AzNetworking +{ + class IConnection; + class IConnectionListener; +} + +namespace Multiplayer +{ + class IEntityDomain; + class EntityReplicator; + + using SendMigrateEntityEvent = AZ::Event; + + //! @class EntityReplicationManager + //! @brief Handles replication of relevant entities for one connection. + class EntityReplicationManager final + { + public: + using EntityReplicatorMap = AZStd::map>; + + enum class Mode + { + Invalid, + LocalServerToRemoteClient, + LocalServerToRemoteServer, + LocalClientToRemoteServer, + }; + + EntityReplicationManager(AzNetworking::IConnection& connection, AzNetworking::IConnectionListener& connectionListener, Mode mode); + ~EntityReplicationManager() = default; + + void SetRemoteHostId(HostId hostId); + HostId GetRemoteHostId() const; + + void ActivatePendingEntities(); + void SendUpdates(AZ::TimeMs hostTimeMs); + void Clear(bool forMigration); + + bool SetEntityRebasing(NetworkEntityHandle& entityHandle); + + void MigrateAllEntities(); + void MigrateEntity(NetEntityId netEntityId); + bool CanMigrateEntity(const ConstNetworkEntityHandle& entityHandle) const; + + bool HasRemoteAuthority(const ConstNetworkEntityHandle& entityHandle) const; + + void SetEntityDomain(AZStd::unique_ptr entityDomain); + IEntityDomain* GetEntityDomain(); + void SetReplicationWindow(AZStd::unique_ptr replicationWindow); + IReplicationWindow* GetReplicationWindow(); + + void GetEntityReplicatorIdList(AZStd::list& outList); + uint32_t GetEntityReplicatorCount(NetEntityRole localNetworkRole); + + void AddDeferredRpcMessage(NetworkEntityRpcMessage& rpcMessage); + + void AddAutonomousEntityReplicatorCreatedHandle(AZ::Event::Handler& handler); + void AddSendMigrateEntityEventHandler(SendMigrateEntityEvent::Handler& handler); + + bool HandleEntityMigration(AzNetworking::IConnection* invokingConnection, EntityMigrationMessage& message); + bool HandleEntityDeleteMessage(EntityReplicator* entityReplicator, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage); + bool HandleEntityUpdateMessage(AzNetworking::IConnection* invokingConnection, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage); + bool HandleEntityRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& message); + + AZ::TimeMs GetResendTimeoutTimeMs() const; + + void SetMaxRemoteEntitiesPendingCreationCount(uint32_t maxPendingEntities); + void SetEntityActivationTimeSliceMs(AZ::TimeMs timeSliceMs); + void SetEntityPendingRemovalMs(AZ::TimeMs entityPendingRemovalMs); + + AzNetworking::IConnection& GetConnection(); + AZ::TimeMs GetFrameTimeMs(); + + void AddReplicatorToPendingSend(const EntityReplicator& entityReplicator); + + bool IsUpdateModeToServerClient(); + + private: + AZ_DISABLE_COPY_MOVE(EntityReplicationManager); + + enum class UpdateValidationResult + { + HandleMessage, // Handle an entity update message + DropMessage, // Do not handle an entity update message, but don't disconnect (could be out of order/date and isn't relevant) + DropMessageAndDisconnect, // Do not handle the message, it is malformed and we should disconnect the connection + }; + + UpdateValidationResult ValidateUpdate(const NetworkEntityUpdateMessage& updateMessage, AzNetworking::PacketId packetId, EntityReplicator* entityReplicator); + + using RpcMessages = AZStd::list; + bool DispatchOrphanedRpc(NetworkEntityRpcMessage& message, EntityReplicator* entityReplicator); + + using EntityReplicatorList = AZStd::deque; + EntityReplicatorList GenerateEntityUpdateList(); + + void SendEntityUpdatesPacketHelper(AZ::TimeMs hostTimeMs, EntityReplicatorList& toSendList, uint32_t maxPayloadSize, AzNetworking::IConnection& connection); + + void SendEntityUpdates(AZ::TimeMs hostTimeMs); + void SendEntityRpcs(RpcMessages& deferredRpcs, bool reliable); + + void MigrateEntityInternal(NetEntityId entityId); + void OnEntityExitDomain(const ConstNetworkEntityHandle& entityHandle); + void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, HostId remoteHostId, AzNetworking::ConnectionId connectionId); + + EntityReplicator* AddEntityReplicator(const ConstNetworkEntityHandle& entityHandle, NetEntityRole netEntityRole); + + const EntityReplicator* GetEntityReplicator(NetEntityId entityId) const; + EntityReplicator* GetEntityReplicator(NetEntityId entityId); + EntityReplicator* GetEntityReplicator(const ConstNetworkEntityHandle& entityHandle); + + void UpdateWindow(); + + bool HandlePropertyChangeMessage + ( + AzNetworking::IConnection* invokingConnection, + EntityReplicator* entityReplicator, + AzNetworking::PacketId packetId, + NetEntityId netEntityId, + NetEntityRole netEntityRole, + AzNetworking::ISerializer& serializer, + const PrefabEntityId& prefabEntityId + ); + + void AddReplicatorToPendingRemoval(const EntityReplicator& replicator); + void ClearRemovedReplicators(); + + class OrphanedEntityRpcs + : public AzNetworking::ITimeoutHandler + { + public: + OrphanedEntityRpcs(EntityReplicationManager& replicationManager); + void Update(); + bool DispatchOrphanedRpcs(EntityReplicator& entityReplicator); + void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityRpcMessage); + AZStd::size_t Size() const { return m_entityRpcMap.size(); } + private: + AzNetworking::TimeoutResult HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) override; + struct OrphanedRpcs + { + OrphanedRpcs() = default; + OrphanedRpcs(OrphanedRpcs&& rhs) + { + m_rpcMessages.swap(rhs.m_rpcMessages); + m_timeoutId = rhs.m_timeoutId; + rhs.m_timeoutId = AzNetworking::TimeoutId{ 0 }; + } + RpcMessages m_rpcMessages; + AzNetworking::TimeoutId m_timeoutId = AzNetworking::TimeoutId{ 0 }; + }; + typedef AZStd::unordered_map EntityRpcMap; + EntityRpcMap m_entityRpcMap; + AzNetworking::TimeoutQueue m_timeoutQueue; + EntityReplicationManager& m_replicationManager; + }; + OrphanedEntityRpcs m_orphanedEntityRpcs; + EntityReplicatorMap m_entityReplicatorMap; + + //! The set of entities that we have sent creation messages for, but have not received confirmation back that the create has occurred + AZStd::unordered_set m_remoteEntitiesPendingCreation; + AZStd::deque m_entitiesPendingActivation; + AZStd::set m_replicatorsPendingRemoval; + AZStd::unordered_set m_replicatorsPendingSend; + + // Deferred RPC Sends + RpcMessages m_deferredRpcMessagesReliable; + RpcMessages m_deferredRpcMessagesUnreliable; + + AZ::Event m_autonomousEntityReplicatorCreated; + EntityExitDomainEvent::Handler m_entityExitDomainEventHandler; + SendMigrateEntityEvent m_sendMigrateEntityEvent; + + AZ::ScheduledEvent m_clearRemovedReplicators; + AZ::ScheduledEvent m_updateWindow; + + AzNetworking::IConnectionListener& m_connectionListener; + AzNetworking::IConnection& m_connection; + AZStd::unique_ptr m_replicationWindow; + AZStd::unique_ptr m_remoteEntityDomain; + + AZ::TimeMs m_entityActivationTimeSliceMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_entityPendingRemovalMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_frameTimeMs = AZ::TimeMs{ 0 }; + HostId m_remoteHostId = InvalidHostId; + uint32_t m_maxRemoteEntitiesPendingCreationCount = AZStd::numeric_limits::max(); + uint32_t m_maxPayloadSize = 0; + Mode m_updateMode = Mode::Invalid; + + friend class EntityReplicator; + }; +} + diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h similarity index 98% rename from Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h index ec4bd8c4f5..1f1c64c707 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h @@ -139,4 +139,4 @@ namespace Multiplayer }; } -#include +#include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.inl b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.inl similarity index 100% rename from Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.inl rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.inl diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 8a5fec869c..51f432953e 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -20,6 +20,7 @@ namespace Multiplayer class NetworkEntityAuthorityTracker; class NetworkEntityRpcMessage; class MultiplayerComponentRegistry; + class IEntityDomain; using EntityExitDomainEvent = AZ::Event; using ControllersActivatedEvent = AZ::Event; @@ -37,6 +38,19 @@ namespace Multiplayer virtual ~INetworkEntityManager() = default; + //! Configures the NetworkEntityManager to operate as an authoritative host. + //! @param hostId the hostId of this NetworkEntityManager + //! @param entityDomain the entity domain used to determine which entities this manager has authority over + virtual void Initialize(HostId hostId, AZStd::unique_ptr entityDomain) = 0; + + //! Returns whether or not the network entity manager has been initialized to host. + //! @return boolean true if this network entity manager has been intialized to host + virtual bool IsInitialized() const = 0; + + //! Returns the entity domain associated with this network entity manager, this will be nullptr on clients. + //! @return boolean the entity domain for this network entity manager + virtual IEntityDomain* GetEntityDomain() const = 0; + //! Returns the NetworkEntityTracker for this INetworkEntityManager instance. //! @return the NetworkEntityTracker for this INetworkEntityManager instance virtual NetworkEntityTracker* GetNetworkEntityTracker() = 0; diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 418bea79dd..41d7d7cb1c 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -123,8 +123,8 @@ namespace Multiplayer if (IsAutonomous()) { m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true); - GetParent().GetNetBindComponent()->AddEntityMigrationStartEventHandler(m_migrateStartHandler); - GetParent().GetNetBindComponent()->AddEntityMigrationEndEventHandler(m_migrateEndHandler); + GetMultiplayer()->AddClientMigrationStartEventHandler(m_migrateStartHandler); + GetMultiplayer()->AddClientMigrationEndEventHandler(m_migrateEndHandler); } } diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index d8e8a765ce..f42a8824f5 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -388,16 +388,6 @@ namespace Multiplayer m_syncRewindEvent.Signal(); } - void NetBindComponent::NotifyMigrationStart(ClientInputId migratedInputId) - { - m_entityMigrationStartEvent.Signal(migratedInputId); - } - - void NetBindComponent::NotifyMigrationEnd() - { - m_entityMigrationEndEvent.Signal(); - } - void NetBindComponent::NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId) { m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); @@ -428,16 +418,6 @@ namespace Multiplayer eventHandler.Connect(m_syncRewindEvent); } - void NetBindComponent::AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler) - { - eventHandler.Connect(m_entityMigrationStartEvent); - } - - void NetBindComponent::AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler) - { - eventHandler.Connect(m_entityMigrationEndEvent); - } - void NetBindComponent::AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler) { eventHandler.Connect(m_entityServerMigrationEvent); diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp index 7ac28fc078..a943406df3 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp @@ -13,6 +13,7 @@ namespace Multiplayer // This can be used to help mitigate client side performance when large numbers of entities are created off the network AZ_CVAR(uint32_t, cl_ClientMaxRemoteEntitiesPendingCreationCount, AZStd::numeric_limits::max(), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of entities that we have sent to the client, but have not had a confirmation back from the client"); AZ_CVAR(AZ::TimeMs, cl_ClientEntityReplicatorPendingRemovalTimeMs, AZ::TimeMs{ 10000 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "How long should wait prior to removing an entity for the client through a change in the replication window, entity deletes are still immediate"); + AZ_CVAR(AZ::TimeMs, cl_DefaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Max Ms to use to activate entities coming from the network, 0 means instantiate everything"); ClientToServerConnectionData::ClientToServerConnectionData ( @@ -26,6 +27,7 @@ namespace Multiplayer { m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(cl_ClientMaxRemoteEntitiesPendingCreationCount); m_entityReplicationManager.SetEntityPendingRemovalMs(cl_ClientEntityReplicatorPendingRemovalTimeMs); + m_entityReplicationManager.SetEntityActivationTimeSliceMs(cl_DefaultNetworkEntityActivationTimeSliceMs); } ClientToServerConnectionData::~ClientToServerConnectionData() diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h index e5f1d0edfc..9776cbabb9 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h @@ -9,7 +9,7 @@ #pragma once #include -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h index 9e9afc4413..78ee721d97 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h @@ -9,7 +9,7 @@ #pragma once #include -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index 3ca1af8b66..582cda3eea 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -32,7 +32,7 @@ namespace Multiplayer { m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( AZ::Name(MpEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); - m_networkEditorInterface->SetTimeoutEnabled(false); + m_networkEditorInterface->SetTimeoutMs(AZ::TimeMs{ 0 }); // Disable timeouts on this network interface if (editorsv_isDedicated) { uint16_t editorServerPort = DefaultServerEditorPort; diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp index b63ce65da7..5b28208cd9 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp @@ -10,6 +10,17 @@ namespace Multiplayer { + void FullOwnershipEntityDomain::SetAabb([[maybe_unused]] const AZ::Aabb& aabb) + { + ; // Do nothing, by definition we own everything + } + + const AZ::Aabb& FullOwnershipEntityDomain::GetAabb() const + { + static AZ::Aabb nullAabb = AZ::Aabb::CreateNull(); + return nullAabb; + } + bool FullOwnershipEntityDomain::IsInDomain([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) const { return true; diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h index 3fe164a31f..ddf09e31d5 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h @@ -21,6 +21,8 @@ namespace Multiplayer //! IEntityDomain overrides. //! @{ + void SetAabb(const AZ::Aabb& aabb) override; + const AZ::Aabb& GetAabb() const override; bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const override; void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) override; void RetrieveEntitiesNotInDomain(EntitiesNotInDomain& outEntitiesNotInDomain) const override; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 24f865194f..543107a3ed 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -79,8 +79,6 @@ namespace Multiplayer AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking"); AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server"); AZ_CVAR(bool, sv_isTransient, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether a dedicated server shuts down if all existing connections disconnect."); - AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, - "Max Ms to use to activate entities coming from the network, 0 means instantiate everything"); AZ_CVAR(AZ::TimeMs, sv_serverSendRateMs, AZ::TimeMs{ 50 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of milliseconds between each network update"); AZ_CVAR(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset, "prefabs/player.network.spawnable", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The default spawnable to use when a new player connects"); @@ -94,7 +92,6 @@ namespace Multiplayer { serializeContext->Class() ->Version(1); - serializeContext->Class() ->Version(1); serializeContext->Class() @@ -173,7 +170,12 @@ namespace Multiplayer AZ::ConsoleInvokedFrom invokedFrom ) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); }) { - ; + AZ::Interface::Register(this); + } + + MultiplayerSystemComponent::~MultiplayerSystemComponent() + { + AZ::Interface::Unregister(this); } void MultiplayerSystemComponent::Activate() @@ -185,7 +187,6 @@ namespace Multiplayer { m_consoleCommandHandler.Connect(AZ::Interface::Get()->GetConsoleCommandInvokedEvent()); } - AZ::Interface::Register(this); AZ::Interface::Register(this); //! Register our gems multiplayer components to assign NetComponentIds @@ -195,7 +196,6 @@ namespace Multiplayer void MultiplayerSystemComponent::Deactivate() { AZ::Interface::Unregister(this); - AZ::Interface::Unregister(this); m_consoleCommandHandler.Disconnect(); AZ::Interface::Get()->DestroyNetworkInterface(AZ::Name(MpNetworkInterfaceName)); AzFramework::SessionNotificationBus::Handler::BusDisconnect(); @@ -459,7 +459,7 @@ namespace Multiplayer AzFramework::PlayerConnectionConfig config; config.m_playerConnectionId = aznumeric_cast(connection->GetConnectionId()); config.m_playerSessionId = packet.GetTicket(); - if(!AZ::Interface::Get()->ValidatePlayerJoinSession(config)) + if (!AZ::Interface::Get()->ValidatePlayerJoinSession(config)) { auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); }; m_networkInterface->GetConnectionSet().VisitConnections(visitor); @@ -488,10 +488,8 @@ namespace Multiplayer ) { m_didHandshake = true; - AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); AZ::Interface::Get()->PerformCommand(commandString.c_str()); - AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); return true; @@ -606,7 +604,22 @@ namespace Multiplayer [[maybe_unused]] MultiplayerPackets::ClientMigration& packet ) { - return false; + if (GetAgentType() != MultiplayerAgentType::Client) + { + // Only clients are allowed to migrate from one server to another + return false; + } + + // Store the temporary user identifier so we can transmit it with our next Connect packet + // The new server will use this to reattach our set of autonomous entities + + // Disconnect our existing server connection + auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::ClientMigrated, TerminationEndpoint::Local); }; + m_networkInterface->GetConnectionSet().VisitConnections(visitor); + AZLOG_INFO("Migrating to new server shard"); + //m_clientMigrateStartEvent(packet.GetLastInputGameTimeMs()); + m_networkInterface->Connect(packet.GetRemoteServerAddress()); + return true; } ConnectResult MultiplayerSystemComponent::ValidateConnect @@ -640,7 +653,7 @@ namespace Multiplayer else { AZLOG_INFO("New incoming connection from remote address: %s", connection->GetRemoteAddress().GetString().c_str()); - m_connAcquiredEvent.Signal(datum); + m_connectionAcquiredEvent.Signal(datum); } // Hosts will spawn a new default player prefab for the user that just connected @@ -654,27 +667,14 @@ namespace Multiplayer } controlledEntity.Activate(); - if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so - { - connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); - } - + connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); AZStd::unique_ptr window = AZStd::make_unique(controlledEntity, connection); reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); } else { - if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so - { - connection->SetUserData(new ClientToServerConnectionData(connection, *this, providerTicket)); - } - else - { - reinterpret_cast(connection->GetUserData())->SetProviderTicket(providerTicket); - } - + connection->SetUserData(new ClientToServerConnectionData(connection, *this, providerTicket)); AZStd::unique_ptr window = AZStd::make_unique(); - reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetEntityActivationTimeSliceMs(cl_defaultNetworkEntityActivationTimeSliceMs); reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); } } @@ -757,9 +757,11 @@ namespace Multiplayer { m_initEvent.Signal(m_networkInterface); - //const AZ::Aabb worldBounds = AZ::Interface.Get()->GetWorldBounds(); - AZStd::unique_ptr newDomain = AZStd::make_unique(); - m_networkEntityManager.Initialize(InvalidHostId, AZStd::move(newDomain)); + if (!m_networkEntityManager.IsInitialized()) + { + // Set up a full ownership domain if we didn't construct a domain during the initialize event + m_networkEntityManager.Initialize(InvalidHostId, AZStd::make_unique()); + } } } m_agentType = multiplayerType; @@ -778,6 +780,16 @@ namespace Multiplayer AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType)); } + void MultiplayerSystemComponent::AddClientMigrationStartEventHandler(ClientMigrationStartEvent::Handler& handler) + { + handler.Connect(m_clientMigrationStartEvent); + } + + void MultiplayerSystemComponent::AddClientMigrationEndEventHandler(ClientMigrationEndEvent::Handler& handler) + { + handler.Connect(m_clientMigrationEndEvent); + } + void MultiplayerSystemComponent::AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler) { handler.Connect(m_clientDisconnectedEvent); @@ -785,7 +797,7 @@ namespace Multiplayer void MultiplayerSystemComponent::AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) { - handler.Connect(m_connAcquiredEvent); + handler.Connect(m_connectionAcquiredEvent); } void MultiplayerSystemComponent::AddSessionInitHandler(SessionInitEvent::Handler& handler) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index c467ed9ad9..40d806a0e0 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -55,7 +55,7 @@ namespace Multiplayer static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); MultiplayerSystemComponent(); - ~MultiplayerSystemComponent() override = default; + ~MultiplayerSystemComponent() override; //! AZ::Component overrides. //! @{ @@ -105,13 +105,15 @@ namespace Multiplayer //! @{ MultiplayerAgentType GetAgentType() const override; void InitializeMultiplayer(MultiplayerAgentType state) override; + bool StartHosting(uint16_t port, bool isDedicated = true) override; + bool Connect(AZStd::string remoteAddress, uint16_t port) override; + void Terminate(AzNetworking::DisconnectReason reason) override; + void AddClientMigrationStartEventHandler(ClientMigrationStartEvent::Handler& handler) override; + void AddClientMigrationEndEventHandler(ClientMigrationEndEvent::Handler& handler) override; void AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler) override; void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; - bool StartHosting(uint16_t port, bool isDedicated = true) override; - bool Connect(AZStd::string remoteAddress, uint16_t port) override; - void Terminate(AzNetworking::DisconnectReason reason) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; AZ::TimeMs GetCurrentHostTimeMs() const override; float GetCurrentBlendFactor() const override; @@ -148,8 +150,10 @@ namespace Multiplayer SessionInitEvent m_initEvent; SessionShutdownEvent m_shutdownEvent; - ConnectionAcquiredEvent m_connAcquiredEvent; + ConnectionAcquiredEvent m_connectionAcquiredEvent; ClientDisconnectedEvent m_clientDisconnectedEvent; + ClientMigrationStartEvent m_clientMigrationStartEvent; + ClientMigrationEndEvent m_clientMigrationEndEvent; AZStd::queue m_pendingConnectionTickets; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 482d3a1ee8..938b3611c5 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -6,8 +6,8 @@ * */ -#include -#include +#include +#include #include #include #include @@ -444,6 +444,11 @@ namespace Multiplayer handler.Connect(m_autonomousEntityReplicatorCreated); } + void EntityReplicationManager::AddSendMigrateEntityEventHandler(SendMigrateEntityEvent::Handler& handler) + { + handler.Connect(m_sendMigrateEntityEvent); + } + const EntityReplicator* EntityReplicationManager::GetEntityReplicator(NetEntityId netEntityId) const { auto it = m_entityReplicatorMap.find(netEntityId); @@ -1094,7 +1099,7 @@ namespace Multiplayer bool didSucceed = true; EntityMigrationMessage message; - message.m_entityId = replicator->GetEntityHandle().GetNetEntityId(); + message.m_netEntityId = replicator->GetEntityHandle().GetNetEntityId(); message.m_prefabEntityId = netBindComponent->GetPrefabEntityId(); if (localEnt->GetState() == AZ::Entity::State::Active) @@ -1119,8 +1124,8 @@ namespace Multiplayer message.m_propertyUpdateData.Resize(inputSerializer.GetSize()); } AZ_Assert(didSucceed, "Failed to migrate entity from server"); - // TODO: Move this to an event - //m_connection.SendReliablePacket(message); + + m_sendMigrateEntityEvent.Signal(m_connection, message); AZLOG(NET_RepDeletes, "Migration packet sent %u to remote manager id %d", netEntityId, aznumeric_cast(GetRemoteHostId())); // Immediately add a new replicator so that we catch RPC invocations, the remote side will make us a new one, and then remove us if needs be @@ -1130,7 +1135,7 @@ namespace Multiplayer bool EntityReplicationManager::HandleEntityMigration(AzNetworking::IConnection* invokingConnection, EntityMigrationMessage& message) { - EntityReplicator* replicator = GetEntityReplicator(message.m_entityId); + EntityReplicator* replicator = GetEntityReplicator(message.m_netEntityId); { if (message.m_propertyUpdateData.GetSize() > 0) { @@ -1140,7 +1145,7 @@ namespace Multiplayer invokingConnection, replicator, AzNetworking::InvalidPacketId, - message.m_entityId, + message.m_netEntityId, NetEntityRole::Server, outputSerializer, message.m_prefabEntityId @@ -1154,7 +1159,7 @@ namespace Multiplayer // The HandlePropertyChangeMessage will have made a replicator if we didn't have one already if (!replicator) { - replicator = GetEntityReplicator(message.m_entityId); + replicator = GetEntityReplicator(message.m_netEntityId); } AZ_Assert(replicator, "Do not have replicator after handling migration message"); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h index 731a1a7556..1920dc8881 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -8,7 +8,7 @@ #pragma once -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 14df1bb028..95e2b6d12c 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -6,8 +6,8 @@ * */ -#include -#include +#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp index 1541885620..c0e5c09c7b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp @@ -7,7 +7,7 @@ */ #include -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 69d2726c65..518c0c8c99 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -48,6 +48,16 @@ namespace Multiplayer m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true); } + bool NetworkEntityManager::IsInitialized() const + { + return m_entityDomain != nullptr; + } + + IEntityDomain* NetworkEntityManager::GetEntityDomain() const + { + return m_entityDomain.get(); + } + NetworkEntityTracker* NetworkEntityManager::GetNetworkEntityTracker() { return &m_networkEntityTracker; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 9ccc576447..804946b882 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -31,11 +31,11 @@ namespace Multiplayer NetworkEntityManager(); ~NetworkEntityManager(); - //! Only invoked for authoritative hosts - void Initialize(HostId hostId, AZStd::unique_ptr entityDomain); - //! INetworkEntityManager overrides. //! @{ + void Initialize(HostId hostId, AZStd::unique_ptr entityDomain) override; + bool IsInitialized() const override; + IEntityDomain* GetEntityDomain() const override; NetworkEntityTracker* GetNetworkEntityTracker() override; NetworkEntityAuthorityTracker* GetNetworkEntityAuthorityTracker() override; MultiplayerComponentRegistry* GetMultiplayerComponentRegistry() override; diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 0b2adb1530..c03773d5b3 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -28,6 +28,9 @@ set(FILES Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h Include/Multiplayer/NetworkEntity/NetworkEntityHandle.inl + Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h + Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h + Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.inl Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h Include/Multiplayer/NetworkInput/NetworkInput.h @@ -69,10 +72,7 @@ set(FILES Source/EntityDomains/FullOwnershipEntityDomain.cpp Source/EntityDomains/FullOwnershipEntityDomain.h Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp - Source/NetworkEntity/EntityReplication/EntityReplicationManager.h Source/NetworkEntity/EntityReplication/EntityReplicator.cpp - Source/NetworkEntity/EntityReplication/EntityReplicator.h - Source/NetworkEntity/EntityReplication/EntityReplicator.inl Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp Source/NetworkEntity/EntityReplication/PropertyPublisher.h Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp