From 7e9b6116da19e2444ff42063c45ca8a1d6b93cfd Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Tue, 7 Sep 2021 21:04:24 -0700 Subject: [PATCH 01/15] Update component application tick to use the ITime interface and respect simulation t_scale settings Signed-off-by: kberg-amzn --- .../AzCore/Component/ComponentApplication.cpp | 20 ++++++--- .../AzCore/Component/ComponentApplication.h | 3 +- Code/Framework/AzCore/AzCore/Time/ITime.h | 41 ++++++++++++++++++- .../AzCore/Time/TimeSystemComponent.cpp | 19 +++++---- .../AzCore/AzCore/Time/TimeSystemComponent.h | 5 ++- 5 files changed, 71 insertions(+), 17 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index eda08401a2..e02485f8d9 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -612,6 +612,7 @@ namespace AZ AZ_Assert(m_systemEntity, "SystemEntity failed to initialize!"); AddRequiredSystemComponents(m_systemEntity.get()); + //m_currentTime = GetElapsedTimeUs(); m_isStarted = true; return m_systemEntity.get(); } @@ -652,7 +653,6 @@ namespace AZ ComponentApplicationBus::Handler::BusConnect(); - m_currentTime = AZStd::chrono::system_clock::now(); TickRequestBus::Handler::BusConnect(); #if defined(AZ_ENABLE_DEBUG_TOOLS) @@ -1368,14 +1368,18 @@ namespace AZ { AZ_PROFILE_SCOPE(System, "Component application simulation tick"); - AZStd::chrono::system_clock::time_point now = AZStd::chrono::system_clock::now(); + TimeUs now = GetElapsedTimeUs(); + if (m_currentTime == TimeUs{ 0 }) + { + m_currentTime = now; + } m_deltaTime = 0.0f; if (now >= m_currentTime) { - AZStd::chrono::duration delta = now - m_currentTime; - m_deltaTime = deltaOverride >= 0.f ? deltaOverride : delta.count(); + float delta = TimeUsToSeconds(now - m_currentTime); + m_deltaTime = deltaOverride >= 0.f ? deltaOverride : delta; } { @@ -1385,7 +1389,9 @@ namespace AZ m_currentTime = now; { AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:OnTick"); - EBUS_EVENT(TickBus, OnTick, m_deltaTime, ScriptTimePoint(now)); + auto epoch = AZStd::chrono::time_point(); + auto chronoNow = AZStd::chrono::microseconds(aznumeric_cast(now)); + EBUS_EVENT(TickBus, OnTick, m_deltaTime, ScriptTimePoint(epoch + chronoNow)); } } } @@ -1508,7 +1514,9 @@ namespace AZ //========================================================================= ScriptTimePoint ComponentApplication::GetTimeAtCurrentTick() { - return ScriptTimePoint(m_currentTime); + auto epoch = AZStd::chrono::time_point(); + auto chronoCurrent = AZStd::chrono::microseconds(aznumeric_cast(m_currentTime)); + return ScriptTimePoint(epoch + chronoCurrent); } //========================================================================= diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h index 3768a75d83..1f76f0f79e 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -368,7 +369,7 @@ namespace AZ } } - AZStd::chrono::system_clock::time_point m_currentTime{ AZStd::chrono::system_clock::time_point::max() }; + AZ::TimeUs m_currentTime{ 0 }; float m_deltaTime{ 0.0f }; AZStd::unique_ptr m_moduleManager; AZStd::unique_ptr m_settingsRegistry; diff --git a/Code/Framework/AzCore/AzCore/Time/ITime.h b/Code/Framework/AzCore/AzCore/Time/ITime.h index 845017bd14..96332cd3da 100644 --- a/Code/Framework/AzCore/AzCore/Time/ITime.h +++ b/Code/Framework/AzCore/AzCore/Time/ITime.h @@ -19,6 +19,10 @@ namespace AZ //! This is a strong typedef for representing a millisecond value since application start. AZ_TYPE_SAFE_INTEGRAL(TimeMs, int64_t); + //! This is a strong typedef for representing a microsecond value since application start. + //! Using int64_t as the underlying type, this is good to represent approximately 292,471 years + AZ_TYPE_SAFE_INTEGRAL(TimeUs, int64_t); + //! @class ITime //! @brief This is an AZ::Interface<> for managing time related operations. class ITime @@ -33,6 +37,10 @@ namespace AZ //! @return the number of milliseconds that have elapsed since application start virtual TimeMs GetElapsedTimeMs() const = 0; + //! Returns the number of microseconds since application start. + //! @return the number of microseconds that have elapsed since application start + virtual TimeUs GetElapsedTimeUs() const = 0; + AZ_DISABLE_COPY_MOVE(ITime); }; @@ -51,6 +59,37 @@ namespace AZ { return AZ::Interface::Get()->GetElapsedTimeMs(); } -} + + //! This is a simple convenience wrapper + inline TimeUs GetElapsedTimeUs() + { + return AZ::Interface::Get()->GetElapsedTimeUs(); + } + + //! Converts from milliseconds to microseconds + inline TimeUs TimeMsToUs(TimeMs value) + { + return static_cast(value * static_cast(1000)); + } + + //! Converts from microseconds to milliseconds + inline TimeMs TimeUsToMs(TimeUs value) + { + return static_cast(value * static_cast(1000)); + } + + //! Converts from milliseconds to seconds + inline float TimeMsToSeconds(TimeMs value) + { + return static_cast(value) / 1000.0f; + } + + //! Converts from microseconds to seconds + inline float TimeUsToSeconds(TimeUs value) + { + return static_cast(value) / 1000000.0f; + } +} // namespace AZ AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AZ::TimeMs); +AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AZ::TimeUs); diff --git a/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.cpp index a2e7733222..99145d377a 100644 --- a/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.cpp @@ -35,7 +35,7 @@ namespace AZ TimeSystemComponent::TimeSystemComponent() { - m_lastInvokedTimeMs = static_cast(AZStd::GetTimeNowMicroSecond() / 1000); + m_lastInvokedTimeUs = static_cast(AZStd::GetTimeNowMicroSecond()); AZ::Interface::Register(this); ITimeRequestBus::Handler::BusConnect(); } @@ -58,18 +58,23 @@ namespace AZ TimeMs TimeSystemComponent::GetElapsedTimeMs() const { - TimeMs currentTime = static_cast(AZStd::GetTimeNowMicroSecond() / 1000); - TimeMs deltaTime = currentTime - m_lastInvokedTimeMs; + return TimeUsToMs(GetElapsedTimeUs()); + } + + TimeUs TimeSystemComponent::GetElapsedTimeUs() const + { + TimeUs currentTime = static_cast(AZStd::GetTimeNowMicroSecond()); + TimeUs deltaTime = currentTime - m_lastInvokedTimeUs; if (t_scale != 1.0f) { float floatDelta = static_cast(deltaTime) * t_scale; - deltaTime = static_cast(static_cast(floatDelta)); + deltaTime = static_cast(static_cast(floatDelta)); } - m_accumulatedTimeMs += deltaTime; - m_lastInvokedTimeMs = currentTime; + m_accumulatedTimeUs += deltaTime; + m_lastInvokedTimeUs = currentTime; - return m_accumulatedTimeMs; + return m_accumulatedTimeUs; } } diff --git a/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.h b/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.h index adf576becb..3ab3dbc234 100644 --- a/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.h +++ b/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.h @@ -39,11 +39,12 @@ namespace AZ //! ITime overrides. //! @{ TimeMs GetElapsedTimeMs() const override; + TimeUs GetElapsedTimeUs() const override; //! @} private: - mutable TimeMs m_lastInvokedTimeMs = TimeMs{0}; - mutable TimeMs m_accumulatedTimeMs = TimeMs{0}; + mutable TimeUs m_lastInvokedTimeUs = TimeUs{0}; + mutable TimeUs m_accumulatedTimeUs = TimeUs{0}; }; } From 6e8449597576bef5fcd72f95fb0735aec293cb63 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Fri, 17 Sep 2021 16:48:16 -0700 Subject: [PATCH 02/15] Changes to make client and entity migration functional, needed in the event of a host quitting necessitating a host migration Signed-off-by: kberg-amzn --- Code/Framework/AzCore/AzCore/Math/Aabb.h | 1 + Code/Framework/AzCore/AzCore/Utils/Utils.h | 2 +- .../Framework/INetworkInterface.h | 12 +- .../Serialization/AzContainerSerializers.h | 15 ++ .../TcpTransport/TcpNetworkInterface.cpp | 19 +- .../TcpTransport/TcpNetworkInterface.h | 6 +- .../UdpTransport/UdpNetworkInterface.cpp | 19 +- .../UdpTransport/UdpNetworkInterface.h | 6 +- .../LocalPredictionPlayerInputComponent.h | 4 +- .../Multiplayer/Components/NetBindComponent.h | 8 - .../Multiplayer/EntityDomains/IEntityDomain.h | 8 + .../Code/Include/Multiplayer/IMultiplayer.h | 10 + .../Include/Multiplayer/MultiplayerTypes.h | 19 +- .../EntityReplicationManager.h | 218 ++++++++++++++++++ .../EntityReplication/EntityReplicator.h | 2 +- .../EntityReplication/EntityReplicator.inl | 0 .../NetworkEntity/INetworkEntityManager.h | 14 ++ .../LocalPredictionPlayerInputComponent.cpp | 4 +- .../Source/Components/NetBindComponent.cpp | 20 -- .../ClientToServerConnectionData.cpp | 2 + .../ClientToServerConnectionData.h | 2 +- .../ServerToClientConnectionData.h | 2 +- .../Editor/MultiplayerEditorConnection.cpp | 2 +- .../FullOwnershipEntityDomain.cpp | 11 + .../EntityDomains/FullOwnershipEntityDomain.h | 2 + .../Source/MultiplayerSystemComponent.cpp | 72 +++--- .../Code/Source/MultiplayerSystemComponent.h | 14 +- .../EntityReplicationManager.cpp | 21 +- .../EntityReplicationManager.h | 2 +- .../EntityReplication/EntityReplicator.cpp | 4 +- .../EntityReplication/PropertySubscriber.cpp | 2 +- .../NetworkEntity/NetworkEntityManager.cpp | 10 + .../NetworkEntity/NetworkEntityManager.h | 6 +- Gems/Multiplayer/Code/multiplayer_files.cmake | 6 +- 34 files changed, 424 insertions(+), 121 deletions(-) create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/NetworkEntity/EntityReplication/EntityReplicator.h (98%) rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/NetworkEntity/EntityReplication/EntityReplicator.inl (100%) 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 From ca7de715fdaced0bb040bb981f7d44501aa1e213 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Tue, 21 Sep 2021 11:56:19 -0700 Subject: [PATCH 03/15] minor code cleanup Signed-off-by: kberg-amzn --- .../Multiplayer/Components/NetworkCharacterComponent.h | 6 +----- .../Multiplayer/Components/NetworkRigidBodyComponent.h | 1 + .../Code/Source/Components/NetworkCharacterComponent.cpp | 6 +++++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h index 478c925299..9d111ea86d 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -31,14 +31,10 @@ namespace Multiplayer AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkCharacterComponent, s_networkCharacterComponentConcreteUuid, Multiplayer::NetworkCharacterComponentBase) static void Reflect(AZ::ReflectContext* context); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); NetworkCharacterComponent(); - static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) - { - incompatible.push_back(AZ_CRC_CE("NetworkRigidBodyService")); - } - // AZ::Component void OnInit() override {} void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h index 19379fc959..fa4c838816 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ + #pragma once #include diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp index b14fc8761f..f34771b2c6 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -19,7 +19,6 @@ namespace Multiplayer { - bool CollisionLayerBasedControllerFilter(const physx::PxController& controllerA, const physx::PxController& controllerB) { PHYSX_SCENE_READ_LOCK(controllerA.getActor()->getScene()); @@ -94,6 +93,11 @@ namespace Multiplayer NetworkCharacterComponentBase::Reflect(context); } + void NetworkCharacterComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("NetworkRigidBodyService")); + } + NetworkCharacterComponent::NetworkCharacterComponent() : m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) { From d28bcbe027bc7d0bc601cbca8be09dea1d71e004 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Tue, 21 Sep 2021 19:33:59 -0700 Subject: [PATCH 04/15] Reverts changes to component application and adds further client migration handling hookup Signed-off-by: kberg-amzn --- .../AzCore/Component/ComponentApplication.cpp | 92 ++++++++++--------- .../AzCore/Component/ComponentApplication.h | 12 +-- .../Code/Include/Multiplayer/IMultiplayer.h | 27 ++++-- .../Source/AutoGen/AutoComponent_Header.jinja | 2 + .../AutoGen/Multiplayer.AutoPackets.xml | 6 +- .../ServerToClientConnectionData.cpp | 58 ++++++------ .../Source/MultiplayerSystemComponent.cpp | 12 ++- .../Code/Source/MultiplayerSystemComponent.h | 3 + 8 files changed, 120 insertions(+), 92 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index b8f84cb0ea..54c3edfcdc 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -121,9 +121,9 @@ namespace AZ return environment ? environment->Get() : nullptr; } - ComponentApplication::EventLoggerDeleter::EventLoggerDeleter() noexcept= default; + ComponentApplication::EventLoggerDeleter::EventLoggerDeleter() noexcept = default; ComponentApplication::EventLoggerDeleter::EventLoggerDeleter(bool skipDelete) noexcept - : m_skipDelete{skipDelete} + : m_skipDelete{ skipDelete } {} void ComponentApplication::EventLoggerDeleter::operator()(AZ::Debug::LocalFileEventLogger* ptr) { @@ -332,27 +332,27 @@ namespace AZ ->Value("Stack trace always", Debug::AllocationRecords::RECORD_FULL); ec->Class("System memory settings", "Settings for managing application memory usage") ->ClassElement(Edit::ClassElements::EditorData, "") - ->Attribute(Edit::Attributes::AutoExpand, true) + ->Attribute(Edit::Attributes::AutoExpand, true) ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_grabAllMemory, "Allocate all memory at startup", "Allocate all system memory at startup if enabled, or allocate as needed if disabled") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_allocationRecords, "Record allocations", "Collect information on each allocation made for debugging purposes (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_allocationRecordsSaveNames, "Record allocations with name saving", "Saves names/filenames information on each allocation made, useful for tracking down leaks in dynamic modules (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_allocationRecordsAttemptDecodeImmediately, "Record allocations and attempt immediate decode", "Decode callstacks for each allocation when they occur, used for tracking allocations that fail decoding. Very expensive. (ignored in Release builds)") ->DataElement(Edit::UIHandlers::ComboBox, &Descriptor::m_recordingMode, "Stack recording mode", "Stack record mode. (Ignored in final builds)") ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_stackRecordLevels, "Stack entries to record", "Number of stack levels to record for each allocation (ignored in Release builds)") - ->Attribute(Edit::Attributes::Step, 1) - ->Attribute(Edit::Attributes::Max, 1024) + ->Attribute(Edit::Attributes::Step, 1) + ->Attribute(Edit::Attributes::Max, 1024) ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_autoIntegrityCheck, "Validate allocations", "Check allocations for integrity on each allocation/free (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_markUnallocatedMemory, "Mark freed memory", "Set memory to 0xcd when a block is freed for debugging (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_doNotUsePools, "Don't pool allocations", "Pipe pool allocations in system/tree heap (ignored in Release builds)") ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_pageSize, "Page size", "Memory page size in bytes (must be OS page size aligned)") - ->Attribute(Edit::Attributes::Step, 1024) + ->Attribute(Edit::Attributes::Step, 1024) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_poolPageSize, "Pool page size", "Memory pool page size in bytes (must be a multiple of page size)") - ->Attribute(Edit::Attributes::Max, &Descriptor::m_pageSize) - ->Attribute(Edit::Attributes::Step, 1024) + ->Attribute(Edit::Attributes::Max, &Descriptor::m_pageSize) + ->Attribute(Edit::Attributes::Step, 1024) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_memoryBlockAlignment, "Block alignment", "Memory block alignment in bytes (must be multiple of the page size)") - ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) + ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_memoryBlocksByteSize, "Block size", "Memory block size in bytes (must be multiple of the page size)") - ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) + ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedOS, "OS reserved memory", "System memory reserved for OS (used only when 'Allocate all memory at startup' is true)") ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedDebug, "Memory reserved for debugger", "System memory reserved for Debug allocator, like memory tracking (used only when 'Allocate all memory at startup' is true)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_enableDrilling, "Enable Driller", "Enable Drilling support for the application (ignored in Release builds)") @@ -419,11 +419,11 @@ namespace AZ } else { - azstrcpy(m_commandLineBuffer, AZ_ARRAY_SIZE(m_commandLineBuffer), "no_argv_supplied"); + azstrcpy(m_commandLineBuffer, AZ_ARRAY_SIZE(m_commandLineBuffer), "no_argv_supplied"); // use a "valid" value here. This is because Qt and potentially other third party libraries require // that ArgC be 'at least 1' and that (*argV)[0] be a valid pointer to a real null terminated string. - m_argC = 1; - m_argV = &m_commandLineBufferAddress; + m_argC = 1; + m_argV = &m_commandLineBufferAddress; } // Create the Event logger if it doesn't exist, otherwise reuse the one registered @@ -557,8 +557,8 @@ namespace AZ void ReportBadEngineRoot() { - AZStd::string errorMessage = {"Unable to determine a valid path to the engine.\n" - "Check parameters such as --project-path and --engine-path and make sure they are valid.\n"}; + AZStd::string errorMessage = { "Unable to determine a valid path to the engine.\n" + "Check parameters such as --project-path and --engine-path and make sure they are valid.\n" }; if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr) { AZ::SettingsRegistryInterface::FixedValueString filePathErrorStr; @@ -614,7 +614,6 @@ namespace AZ AZ_Assert(m_systemEntity, "SystemEntity failed to initialize!"); AddRequiredSystemComponents(m_systemEntity.get()); - //m_currentTime = GetElapsedTimeUs(); m_isStarted = true; return m_systemEntity.get(); } @@ -1372,38 +1371,44 @@ namespace AZ void ComponentApplication::Tick(float deltaOverride /*= -1.f*/) { - AZ_PROFILE_SCOPE(System, "Component application simulation tick"); - AZStd::chrono::system_clock::time_point now = AZStd::chrono::system_clock::now(); - m_deltaTime = 0.0f; - if (now >= m_currentTime) { - AZStd::chrono::duration delta = now - m_currentTime; - m_deltaTime = deltaOverride >= 0.f ? deltaOverride : delta.count(); - } - { - AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:ExecuteQueuedEvents"); - TickBus::ExecuteQueuedEvents(); - } - m_currentTime = now; - { - AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:OnTick"); - EBUS_EVENT(TickBus, OnTick, m_deltaTime, ScriptTimePoint(now)); - } + AZ_PROFILE_SCOPE(System, "Component application simulation tick"); - // If tick rate limiting is on, ensure (1 / g_simulation_tick_rate) ms has elapsed since the last frame, - // sleeping if there's still time remaining. - if (g_simulation_tick_rate > 0.f) - { - now = AZStd::chrono::system_clock::now(); + AZStd::chrono::system_clock::time_point now = AZStd::chrono::system_clock::now(); - // Work in microsecond durations here as that's the native measurement time for time_point - constexpr float microsecondsPerSecond = 1000.f * 1000.f; - const AZStd::chrono::microseconds timeBudgetPerTick(static_cast(microsecondsPerSecond / g_simulation_tick_rate)); - AZStd::chrono::microseconds timeUntilNextTick = m_currentTime + timeBudgetPerTick - now; + m_deltaTime = 0.0f; - if (timeUntilNextTick.count() > 0) + if (now >= m_currentTime) { - AZStd::this_thread::sleep_for(timeUntilNextTick); + AZStd::chrono::duration delta = now - m_currentTime; + m_deltaTime = deltaOverride >= 0.f ? deltaOverride : delta.count(); + } + + { + AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:ExecuteQueuedEvents"); + TickBus::ExecuteQueuedEvents(); + } + m_currentTime = now; + { + AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:OnTick"); + EBUS_EVENT(TickBus, OnTick, m_deltaTime, ScriptTimePoint(now)); + } + + // If tick rate limiting is on, ensure (1 / g_simulation_tick_rate) ms has elapsed since the last frame, + // sleeping if there's still time remaining. + if (g_simulation_tick_rate > 0.f) + { + now = AZStd::chrono::system_clock::now(); + + // Work in microsecond durations here as that's the native measurement time for time_point + constexpr float microsecondsPerSecond = 1000.f * 1000.f; + const AZStd::chrono::microseconds timeBudgetPerTick(static_cast(microsecondsPerSecond / g_simulation_tick_rate)); + AZStd::chrono::microseconds timeUntilNextTick = m_currentTime + timeBudgetPerTick - now; + + if (timeUntilNextTick.count() > 0) + { + AZStd::this_thread::sleep_for(timeUntilNextTick); + } } } } @@ -1555,5 +1560,4 @@ namespace AZ AZ::SettingsRegistryScriptUtils::ReflectSettingsRegistryToBehaviorContext(*behaviorContext); } } - } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h index 969903cdc1..892563fb08 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -5,13 +5,13 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ + #pragma once #include #include #include #include -#include #include #include #include @@ -377,14 +377,14 @@ namespace AZ EntityRemovedEvent m_entityRemovedEvent; EntityAddedEvent m_entityActivatedEvent; EntityRemovedEvent m_entityDeactivatedEvent; - AZ::IConsole* m_console{}; + AZ::IConsole* m_console{}; Descriptor m_descriptor; bool m_isStarted{ false }; bool m_isSystemAllocatorOwner{ false }; bool m_isOSAllocatorOwner{ false }; bool m_ownsConsole{}; - void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy. - IAllocatorAllocate* m_osAllocator{ nullptr }; + void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy. + IAllocatorAllocate* m_osAllocator{ nullptr }; EntitySetType m_entities; AZ::IO::FixedMaxPath m_exeDirectory; AZ::IO::FixedMaxPath m_engineRoot; @@ -405,11 +405,11 @@ namespace AZ // we create a buffer that can be written to (up to AZ_MAX_PATH_LEN) and then // pack it with a single param. char m_commandLineBuffer[AZ_MAX_PATH_LEN]; - char* m_commandLineBufferAddress{ m_commandLineBuffer }; + char* m_commandLineBufferAddress{ m_commandLineBuffer }; StartupParameters m_startupParameters; - char** m_argV{ nullptr }; + char** m_argV{ nullptr }; int m_argC{ 0 }; AZ::CommandLine m_commandLine; // < Stores parsed command line supplied to the constructor diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 11f5ac7e47..021907fb7a 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -45,6 +45,7 @@ namespace Multiplayer using ClientMigrationStartEvent = AZ::Event; using ClientMigrationEndEvent = AZ::Event<>; using ClientDisconnectedEvent = AZ::Event<>; + using NotifyClientMigrationEvent = AZ::Event; using ConnectionAcquiredEvent = AZ::Event; using SessionInitEvent = AZ::Event; using SessionShutdownEvent = AZ::Event; @@ -80,34 +81,38 @@ namespace Multiplayer //! @param state The state of this connection virtual void InitializeMultiplayer(MultiplayerAgentType state) = 0; - //! Starts hosting a server + //! Starts hosting a server. //! @param port The port to listen for connection on //! @param isDedicated Whether the server is dedicated or client hosted //! @return if the application successfully started hosting virtual bool StartHosting(uint16_t port, bool isDedicated = true) = 0; - //! Connects to the specified IP as a Client + //! Connects to the specified IP as a Client. //! @param remoteAddress The domain or IP to connect to //! @param port The port to connect to //! @result if a connection was successfully created virtual bool Connect(AZStd::string remoteAddress, uint16_t port) = 0; - // Disconnects all multiplayer connections, stops listening on the server and invokes handlers appropriate to network context + // Disconnects all multiplayer connections, stops listening on the server and invokes handlers appropriate to network context. //! @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 + //! 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 + //! 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 + //! 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; + //! Adds a NotifyClientMigrationEvent Handler which is invoked when a client migrates from one host to another. + //! @param handler The NotifyClientMigrationEvent Handler to add + virtual void AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler) = 0; + //! Adds a ConnectionAcquiredEvent Handler which is invoked when a new endpoint connects to the session. //! @param handler The ConnectionAcquiredEvent Handler to add virtual void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) = 0; @@ -120,7 +125,13 @@ namespace Multiplayer //! @param handler The SessionShutdownEvent handler to add virtual void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) = 0; - //! Sends a packet telling if entity update messages can be sent + //! Signals a NotifyClientMigrationEvent with the provided parameters. + //! @param hostId the host id of the host the client is migrating to + //! @param userIdentifier the user identifier the client will provide the new host to validate identity + //! @param lastClientInputId the last processed clientInputId by the current host + virtual void SendNotifyClientMigrationEvent(HostId hostId, uint64_t userIdentifier, ClientInputId lastClientInputId); + + //! Sends a packet telling if entity update messages can be sent. //! @param readyForEntityUpdates Ready for entity updates or not virtual void SendReadyForEntityUpdates(bool readyForEntityUpdates) = 0; @@ -132,7 +143,7 @@ namespace Multiplayer //! @return the current server time in milliseconds virtual AZ::TimeMs GetCurrentHostTimeMs() const = 0; - //! Returns the current blend factor for client side interpolation + //! Returns the current blend factor for client side interpolation. //! This value is only relevant on the client and is used to smooth between host frames //! @return the current blend factor virtual float GetCurrentBlendFactor() const = 0; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 41f0fa1b02..5cfeb250fa 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -403,6 +403,8 @@ namespace {{ Component.attrib['Namespace'] }} : public Multiplayer::MultiplayerController { public: + using ComponentType = {{ ComponentName }}; + {{ ControllerBaseName }}({{ ComponentName }}& owner); ~{{ ControllerBaseName }}() override = default; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index c553bc5351..d7602b1a3b 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -40,8 +40,8 @@ - - - + + + diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp index 5a9e8b4d4b..ad1bcb5130 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp @@ -7,7 +7,10 @@ */ #include +#include +#include #include +#include namespace Multiplayer { @@ -95,36 +98,31 @@ namespace Multiplayer [[maybe_unused]] AzNetworking::ConnectionId connectionId ) { - //Multiplayer::ServerAddrInfo serverAddr; - //if (gNovaGame->GetMultiplayerworkAgent().GetServerToServerNetwork().GetServerAddrInfoFromConnectionId(newConnectionId, serverAddr) == false) - //{ - // AZLOG_WARN("MigrateClient::Failed to find servershard address, userID:%d", static_cast(GetUserId())); - // return; - //} - // - //Multiplayer::GameTimePoint migratedClientGameTimePoint; - // - //if (m_ControlledEntity != nullptr) - //{ - // if (const PlayerNetworkInputComponent::Authority* pComponent = Multiplayer::FindController(m_ControlledEntity)) - // { - // migratedClientGameTimePoint = pComponent->GetLastInputId().GetServerGameTimePoint(); - // } - //} - // - // generate crypto-rand user identifier, send to both server and client so they can negotiate the autonomous entity to assume predictive control over after migration - //const uint64_t randomUserIdentifier = 0; - // - //// Tell the server a new client is about to join - //MultiplayerPackets::NotifyClientMigration notifyClientMigration(randomUserIdentifier); - //gNovaGame->GetMultiplayerworkAgent().GetServerToServerNetwork().SendReliablePacket(newConnectionId, notifyClientMigration); - // - //// Tell the client who to join - //MultiplayerPackets::ClientMigration clientMigration(randomUserIdentifier, serverAddr, migratedClientGameTimePoint); - //GetConnection()->SendReliablePacket(clientMigration); - // - //m_controlledEntity = nullptr; - //m_canSendUpdates = false; + AzNetworking::IpAddress serverAddress; + // serverAddress = GetHost(remoteHostId).GetAddress(); + + ClientInputId migratedClientInputId = ClientInputId{ 0 }; + if (m_controlledEntity != nullptr) + { + auto controller = m_controlledEntity.FindController(); + if (controller != nullptr) + { + migratedClientInputId = controller->GetLastInputId(); + } + } + + // Generate crypto-rand user identifier, send to both server and client so they can negotiate the autonomous entity to assume predictive control over after migration + const uint64_t randomUserIdentifier = AzNetworking::CryptoRand64(); + + // Tell the new host that a client is about to (re)join + GetMultiplayer()->SendNotifyClientMigrationEvent(remoteHostId, randomUserIdentifier, migratedClientInputId); + + // Tell the client who to join + MultiplayerPackets::ClientMigration clientMigration(serverAddress, randomUserIdentifier, migratedClientInputId); + GetConnection()->SendReliablePacket(clientMigration); + + m_controlledEntity = NetworkEntityHandle(); + m_canSendUpdates = false; } void ServerToClientConnectionData::OnGameplayStarted() diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index c28fb23cc4..dbc93834e8 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -617,7 +617,7 @@ namespace Multiplayer 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_clientMigrationStartEvent.Signal(ClientInputId{ 0 }); m_networkInterface->Connect(packet.GetRemoteServerAddress()); return true; } @@ -795,6 +795,11 @@ namespace Multiplayer handler.Connect(m_clientDisconnectedEvent); } + void MultiplayerSystemComponent::AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler) + { + handler.Connect(m_notifyClientMigrationEvent); + } + void MultiplayerSystemComponent::AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) { handler.Connect(m_connectionAcquiredEvent); @@ -810,6 +815,11 @@ namespace Multiplayer handler.Connect(m_shutdownEvent); } + void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(HostId hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) + { + m_notifyClientMigrationEvent.Signal(hostId, userIdentifier, lastClientInputId); + } + void MultiplayerSystemComponent::SendReadyForEntityUpdates(bool readyForEntityUpdates) { IConnectionSet& connectionSet = m_networkInterface->GetConnectionSet(); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 40d806a0e0..bdf6511409 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -111,9 +111,11 @@ namespace Multiplayer void AddClientMigrationStartEventHandler(ClientMigrationStartEvent::Handler& handler) override; void AddClientMigrationEndEventHandler(ClientMigrationEndEvent::Handler& handler) override; void AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler) override; + void AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler) override; void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; + void SendNotifyClientMigrationEvent(HostId hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; AZ::TimeMs GetCurrentHostTimeMs() const override; float GetCurrentBlendFactor() const override; @@ -154,6 +156,7 @@ namespace Multiplayer ClientDisconnectedEvent m_clientDisconnectedEvent; ClientMigrationStartEvent m_clientMigrationStartEvent; ClientMigrationEndEvent m_clientMigrationEndEvent; + NotifyClientMigrationEvent m_notifyClientMigrationEvent; AZStd::queue m_pendingConnectionTickets; From e8aeb9b10103edec556d79a4a33fdbe86e8cb265 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Tue, 21 Sep 2021 19:37:54 -0700 Subject: [PATCH 05/15] Format fixing Signed-off-by: kberg-amzn --- .../AzCore/Component/ComponentApplication.cpp | 30 +++++++++---------- .../AzCore/Component/ComponentApplication.h | 10 +++---- .../AutoGen/Multiplayer.AutoPackets.xml | 6 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index 54c3edfcdc..4a6e6d035d 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -121,9 +121,9 @@ namespace AZ return environment ? environment->Get() : nullptr; } - ComponentApplication::EventLoggerDeleter::EventLoggerDeleter() noexcept = default; + ComponentApplication::EventLoggerDeleter::EventLoggerDeleter() noexcept= default; ComponentApplication::EventLoggerDeleter::EventLoggerDeleter(bool skipDelete) noexcept - : m_skipDelete{ skipDelete } + : m_skipDelete{skipDelete} {} void ComponentApplication::EventLoggerDeleter::operator()(AZ::Debug::LocalFileEventLogger* ptr) { @@ -332,27 +332,27 @@ namespace AZ ->Value("Stack trace always", Debug::AllocationRecords::RECORD_FULL); ec->Class("System memory settings", "Settings for managing application memory usage") ->ClassElement(Edit::ClassElements::EditorData, "") - ->Attribute(Edit::Attributes::AutoExpand, true) + ->Attribute(Edit::Attributes::AutoExpand, true) ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_grabAllMemory, "Allocate all memory at startup", "Allocate all system memory at startup if enabled, or allocate as needed if disabled") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_allocationRecords, "Record allocations", "Collect information on each allocation made for debugging purposes (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_allocationRecordsSaveNames, "Record allocations with name saving", "Saves names/filenames information on each allocation made, useful for tracking down leaks in dynamic modules (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_allocationRecordsAttemptDecodeImmediately, "Record allocations and attempt immediate decode", "Decode callstacks for each allocation when they occur, used for tracking allocations that fail decoding. Very expensive. (ignored in Release builds)") ->DataElement(Edit::UIHandlers::ComboBox, &Descriptor::m_recordingMode, "Stack recording mode", "Stack record mode. (Ignored in final builds)") ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_stackRecordLevels, "Stack entries to record", "Number of stack levels to record for each allocation (ignored in Release builds)") - ->Attribute(Edit::Attributes::Step, 1) - ->Attribute(Edit::Attributes::Max, 1024) + ->Attribute(Edit::Attributes::Step, 1) + ->Attribute(Edit::Attributes::Max, 1024) ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_autoIntegrityCheck, "Validate allocations", "Check allocations for integrity on each allocation/free (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_markUnallocatedMemory, "Mark freed memory", "Set memory to 0xcd when a block is freed for debugging (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_doNotUsePools, "Don't pool allocations", "Pipe pool allocations in system/tree heap (ignored in Release builds)") ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_pageSize, "Page size", "Memory page size in bytes (must be OS page size aligned)") - ->Attribute(Edit::Attributes::Step, 1024) + ->Attribute(Edit::Attributes::Step, 1024) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_poolPageSize, "Pool page size", "Memory pool page size in bytes (must be a multiple of page size)") - ->Attribute(Edit::Attributes::Max, &Descriptor::m_pageSize) - ->Attribute(Edit::Attributes::Step, 1024) + ->Attribute(Edit::Attributes::Max, &Descriptor::m_pageSize) + ->Attribute(Edit::Attributes::Step, 1024) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_memoryBlockAlignment, "Block alignment", "Memory block alignment in bytes (must be multiple of the page size)") - ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) + ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_memoryBlocksByteSize, "Block size", "Memory block size in bytes (must be multiple of the page size)") - ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) + ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedOS, "OS reserved memory", "System memory reserved for OS (used only when 'Allocate all memory at startup' is true)") ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedDebug, "Memory reserved for debugger", "System memory reserved for Debug allocator, like memory tracking (used only when 'Allocate all memory at startup' is true)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_enableDrilling, "Enable Driller", "Enable Drilling support for the application (ignored in Release builds)") @@ -419,11 +419,11 @@ namespace AZ } else { - azstrcpy(m_commandLineBuffer, AZ_ARRAY_SIZE(m_commandLineBuffer), "no_argv_supplied"); + azstrcpy(m_commandLineBuffer, AZ_ARRAY_SIZE(m_commandLineBuffer), "no_argv_supplied"); // use a "valid" value here. This is because Qt and potentially other third party libraries require // that ArgC be 'at least 1' and that (*argV)[0] be a valid pointer to a real null terminated string. - m_argC = 1; - m_argV = &m_commandLineBufferAddress; + m_argC = 1; + m_argV = &m_commandLineBufferAddress; } // Create the Event logger if it doesn't exist, otherwise reuse the one registered @@ -557,8 +557,8 @@ namespace AZ void ReportBadEngineRoot() { - AZStd::string errorMessage = { "Unable to determine a valid path to the engine.\n" - "Check parameters such as --project-path and --engine-path and make sure they are valid.\n" }; + AZStd::string errorMessage = {"Unable to determine a valid path to the engine.\n" + "Check parameters such as --project-path and --engine-path and make sure they are valid.\n"}; if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr) { AZ::SettingsRegistryInterface::FixedValueString filePathErrorStr; diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h index 892563fb08..8e95ff5fa3 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -377,14 +377,14 @@ namespace AZ EntityRemovedEvent m_entityRemovedEvent; EntityAddedEvent m_entityActivatedEvent; EntityRemovedEvent m_entityDeactivatedEvent; - AZ::IConsole* m_console{}; + AZ::IConsole* m_console{}; Descriptor m_descriptor; bool m_isStarted{ false }; bool m_isSystemAllocatorOwner{ false }; bool m_isOSAllocatorOwner{ false }; bool m_ownsConsole{}; - void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy. - IAllocatorAllocate* m_osAllocator{ nullptr }; + void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy. + IAllocatorAllocate* m_osAllocator{ nullptr }; EntitySetType m_entities; AZ::IO::FixedMaxPath m_exeDirectory; AZ::IO::FixedMaxPath m_engineRoot; @@ -405,11 +405,11 @@ namespace AZ // we create a buffer that can be written to (up to AZ_MAX_PATH_LEN) and then // pack it with a single param. char m_commandLineBuffer[AZ_MAX_PATH_LEN]; - char* m_commandLineBufferAddress{ m_commandLineBuffer }; + char* m_commandLineBufferAddress{ m_commandLineBuffer }; StartupParameters m_startupParameters; - char** m_argV{ nullptr }; + char** m_argV{ nullptr }; int m_argC{ 0 }; AZ::CommandLine m_commandLine; // < Stores parsed command line supplied to the constructor diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index d7602b1a3b..ee0898b681 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -40,8 +40,8 @@ - - - + + + From aacb6a18db590d5310062fdf280a79448ae8855e Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Tue, 21 Sep 2021 19:41:23 -0700 Subject: [PATCH 06/15] Hook up the last client inputId to the migrate notification Signed-off-by: kberg-amzn --- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index dbc93834e8..df88af5296 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -611,13 +611,13 @@ namespace Multiplayer } // 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 + // The new server will use this to re-attach 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_clientMigrationStartEvent.Signal(ClientInputId{ 0 }); + m_clientMigrationStartEvent.Signal(packet.GetLastClientInputId()); m_networkInterface->Connect(packet.GetRemoteServerAddress()); return true; } From f837f0494b1e6159bf2bea1cc4e30922cb178753 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Thu, 23 Sep 2021 18:25:46 -0700 Subject: [PATCH 07/15] many bug fixes Signed-off-by: kberg-amzn --- Code/Framework/AzCore/AzCore/Time/ITime.h | 2 +- .../Visibility/EntityBoundsUnionBus.h | 5 +- .../EntityVisibilityBoundsUnionSystem.cpp | 18 +++++ .../EntityVisibilityBoundsUnionSystem.h | 1 + .../TcpTransport/TcpNetworkInterface.cpp | 4 +- .../AzNetworking/TcpTransport/TcpSocket.cpp | 18 ++--- .../UdpTransport/UdpNetworkInterface.cpp | 4 +- .../AzNetworking/UdpTransport/UdpSocket.cpp | 10 +-- .../Multiplayer/Components/NetBindComponent.h | 4 +- .../Code/Include/Multiplayer/IMultiplayer.h | 6 +- .../Include/Multiplayer/MultiplayerTypes.h | 7 +- .../EntityReplicationManager.h | 6 +- .../NetworkEntity/INetworkEntityManager.h | 4 +- .../AutoGen/Multiplayer.AutoPackets.xml | 1 - .../Source/Components/NetBindComponent.cpp | 2 +- .../Components/NetworkCharacterComponent.cpp | 2 +- .../ServerToClientConnectionData.cpp | 9 +-- .../ServerToClientConnectionData.h | 2 +- .../Source/MultiplayerSystemComponent.cpp | 22 +++--- .../Code/Source/MultiplayerSystemComponent.h | 4 +- .../EntityReplicationManager.cpp | 73 ++++++++++++------- .../EntityReplication/EntityReplicator.cpp | 4 +- .../NetworkEntityAuthorityTracker.cpp | 28 +++---- .../NetworkEntityAuthorityTracker.h | 6 +- .../NetworkEntity/NetworkEntityHandle.cpp | 13 +++- .../NetworkEntity/NetworkEntityManager.cpp | 6 +- .../NetworkEntity/NetworkEntityManager.h | 4 +- .../Code/Source/NetworkTime/NetworkTime.cpp | 2 +- 28 files changed, 154 insertions(+), 113 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Time/ITime.h b/Code/Framework/AzCore/AzCore/Time/ITime.h index 8146065146..a97ba2319a 100644 --- a/Code/Framework/AzCore/AzCore/Time/ITime.h +++ b/Code/Framework/AzCore/AzCore/Time/ITime.h @@ -83,7 +83,7 @@ namespace AZ //! Converts from microseconds to milliseconds inline TimeMs TimeUsToMs(TimeUs value) { - return static_cast(value * static_cast(1000)); + return static_cast(value / static_cast(1000)); } //! Converts from milliseconds to seconds diff --git a/Code/Framework/AzFramework/AzFramework/Visibility/EntityBoundsUnionBus.h b/Code/Framework/AzFramework/AzFramework/Visibility/EntityBoundsUnionBus.h index b6c26e3e9e..862dea38cc 100644 --- a/Code/Framework/AzFramework/AzFramework/Visibility/EntityBoundsUnionBus.h +++ b/Code/Framework/AzFramework/AzFramework/Visibility/EntityBoundsUnionBus.h @@ -28,9 +28,12 @@ namespace AzFramework //! @note This is used to drive event driven updates to the visibility system. virtual void RefreshEntityLocalBoundsUnion(AZ::EntityId entityId) = 0; - //! Returns the cached union of all component Aabbs. + //! Returns the cached union of all component Aabbs in local entity space. virtual AZ::Aabb GetEntityLocalBoundsUnion(AZ::EntityId entityId) const = 0; + //! Returns the cached union of all component Aabbs in world space. + virtual AZ::Aabb GetEntityWorldBoundsUnion(AZ::EntityId entityId) const = 0; + //! Writes the current changes made to all entities (transforms and bounds) to the visibility system. //! @note During normal operation this is called every frame in OnTick but can //! also be called explicitly (e.g. For testing purposes). diff --git a/Code/Framework/AzFramework/AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.cpp b/Code/Framework/AzFramework/AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.cpp index 826570fee5..1963411147 100644 --- a/Code/Framework/AzFramework/AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.cpp +++ b/Code/Framework/AzFramework/AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.cpp @@ -128,6 +128,24 @@ namespace AzFramework return AZ::Aabb::CreateNull(); } + AZ::Aabb EntityVisibilityBoundsUnionSystem::GetEntityWorldBoundsUnion(const AZ::EntityId entityId) const + { + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(entityId); + if (entity != nullptr) + { + // if the entity is not found in the mapping then return a null Aabb, this is to mimic + // as closely as possible the behavior of an individual GetLocalBounds call to an Entity that + // had been deleted (there would be no response, leaving the default value assigned) + if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entity); + instance_it != m_entityVisibilityBoundsUnionInstanceMapping.end()) + { + return instance_it->second.m_localEntityBoundsUnion.GetTranslated(entity->GetTransform()->GetWorldTranslation()); + } + } + + return AZ::Aabb::CreateNull(); + } + void EntityVisibilityBoundsUnionSystem::ProcessEntityBoundsUnionRequests() { AZ_PROFILE_FUNCTION(AzFramework); diff --git a/Code/Framework/AzFramework/AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.h b/Code/Framework/AzFramework/AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.h index 03109f0dd2..1a3531e506 100644 --- a/Code/Framework/AzFramework/AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.h +++ b/Code/Framework/AzFramework/AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.h @@ -31,6 +31,7 @@ namespace AzFramework // EntityBoundsUnionRequestBus overrides ... void RefreshEntityLocalBoundsUnion(AZ::EntityId entityId) override; AZ::Aabb GetEntityLocalBoundsUnion(AZ::EntityId entityId) const override; + AZ::Aabb GetEntityWorldBoundsUnion(AZ::EntityId entityId) const override; void ProcessEntityBoundsUnionRequests() override; void OnTransformUpdated(AZ::Entity* entity) override; diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp index f15e0c5d1a..1ccff7be50 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp @@ -23,14 +23,14 @@ namespace AzNetworking AZ_CVAR(bool, net_TcpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Tcp connections"); 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"); + AZ_CVAR(AZ::TimeMs, net_TcpDefaultTimeoutMs, 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) + , m_timeoutMs(net_TcpDefaultTimeoutMs) { ; } diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp index 272683febc..da0ae4ef08 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp @@ -55,16 +55,19 @@ namespace AzNetworking if (!SocketCreateInternal()) { + Close(); return false; } if (!BindSocketForListenInternal(port)) { + Close(); return false; } if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd))) { + Close(); return false; } @@ -75,18 +78,11 @@ namespace AzNetworking { Close(); - if (!SocketCreateInternal()) - { - return false; - } - - if (!BindSocketForConnectInternal(address)) - { - return false; - } - - if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd))) + if (!SocketCreateInternal() + || !BindSocketForConnectInternal(address) + || !(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd))) { + Close(); return false; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index b12d3ef845..b0e64f93e3 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -32,7 +32,7 @@ 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_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_UdpDefaultTimeoutMs, 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,7 +61,7 @@ namespace AzNetworking , m_connectionListener(connectionListener) , m_socket(net_UdpUseEncryption ? new DtlsSocket() : new UdpSocket()) , m_readerThread(readerThread) - , m_timeoutMs(net_UdpDefaultTimeoutTimeMs) + , m_timeoutMs(net_UdpDefaultTimeoutMs) { const AZ::CVarFixedString compressor = static_cast(net_UdpCompressor); const AZ::Name compressorName = AZ::Name(compressor); diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp index 44f6562c84..300b3527fa 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp @@ -79,17 +79,15 @@ namespace AzNetworking { const int32_t error = GetLastNetworkError(); AZLOG_ERROR("Failed to bind UDP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); + Close(); return false; } } - if (!SetSocketBufferSizes(m_socketFd, net_UdpSendBufferSize, net_UdpRecvBufferSize)) - { - return false; - } - - if (!SetSocketNonBlocking(m_socketFd)) + if (!SetSocketBufferSizes(m_socketFd, net_UdpSendBufferSize, net_UdpRecvBufferSize) + || !SetSocketNonBlocking(m_socketFd)) { + Close(); return false; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index d1f9e001b1..887aabb3fb 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -32,7 +32,7 @@ namespace Multiplayer using EntityStopEvent = AZ::Event; using EntityDirtiedEvent = AZ::Event<>; using EntitySyncRewindEvent = AZ::Event<>; - using EntityServerMigrationEvent = AZ::Event; + using EntityServerMigrationEvent = AZ::Event; using EntityPreRenderEvent = AZ::Event; using EntityCorrectionEvent = AZ::Event<>; @@ -113,7 +113,7 @@ namespace Multiplayer void MarkDirty(); void NotifyLocalChanges(); void NotifySyncRewindState(); - void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId); + void NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId); void NotifyPreRender(float deltaTime); void NotifyCorrection(); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 021907fb7a..ff4c9106e6 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -45,7 +45,7 @@ namespace Multiplayer using ClientMigrationStartEvent = AZ::Event; using ClientMigrationEndEvent = AZ::Event<>; using ClientDisconnectedEvent = AZ::Event<>; - using NotifyClientMigrationEvent = AZ::Event; + using NotifyClientMigrationEvent = AZ::Event; using ConnectionAcquiredEvent = AZ::Event; using SessionInitEvent = AZ::Event; using SessionShutdownEvent = AZ::Event; @@ -91,7 +91,7 @@ namespace Multiplayer //! @param remoteAddress The domain or IP to connect to //! @param port The port to connect to //! @result if a connection was successfully created - virtual bool Connect(AZStd::string remoteAddress, uint16_t port) = 0; + virtual bool Connect(const AZStd::string& remoteAddress, uint16_t port) = 0; // Disconnects all multiplayer connections, stops listening on the server and invokes handlers appropriate to network context. //! @param reason The reason for terminating connections @@ -129,7 +129,7 @@ namespace Multiplayer //! @param hostId the host id of the host the client is migrating to //! @param userIdentifier the user identifier the client will provide the new host to validate identity //! @param lastClientInputId the last processed clientInputId by the current host - virtual void SendNotifyClientMigrationEvent(HostId hostId, uint64_t userIdentifier, ClientInputId lastClientInputId); + virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) = 0; //! Sends a packet telling if entity update messages can be sent. //! @param readyForEntityUpdates Ready for entity updates or not diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h index 26cdf72e5f..96035083d8 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,8 +26,8 @@ namespace Multiplayer //! The default blend factor for ScopedAlterTime static constexpr float DefaultBlendFactor = 1.f; - AZ_TYPE_SAFE_INTEGRAL(HostId, uint32_t); - static constexpr HostId InvalidHostId = static_cast(-1); + using HostId = AzNetworking::IpAddress; + static const HostId InvalidHostId = HostId(); AZ_TYPE_SAFE_INTEGRAL(NetEntityId, uint32_t); static constexpr NetEntityId InvalidNetEntityId = static_cast(-1); @@ -152,7 +153,6 @@ namespace Multiplayer } } -AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::HostId); AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetEntityId); AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetComponentId); AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::PropertyIndex); @@ -162,7 +162,6 @@ AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::HostFrameId); namespace AZ { - AZ_TYPE_INFO_SPECIALIZE(Multiplayer::HostId, "{D04B3363-8E1B-4193-8B2B-D2140389C9D5}"); AZ_TYPE_INFO_SPECIALIZE(Multiplayer::NetEntityId, "{05E4C08B-3A1B-4390-8144-3767D8E56A81}"); AZ_TYPE_INFO_SPECIALIZE(Multiplayer::NetComponentId, "{8AF3B382-F187-4323-9014-B380638767E3}"); AZ_TYPE_INFO_SPECIALIZE(Multiplayer::PropertyIndex, "{F4460210-024D-4B3B-A10A-04B669C34230}"); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h index 118ff0e6af..dd84c9d952 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -56,8 +56,8 @@ namespace Multiplayer EntityReplicationManager(AzNetworking::IConnection& connection, AzNetworking::IConnectionListener& connectionListener, Mode mode); ~EntityReplicationManager() = default; - void SetRemoteHostId(HostId hostId); - HostId GetRemoteHostId() const; + void SetRemoteHostId(const HostId& hostId); + const HostId& GetRemoteHostId() const; void ActivatePendingEntities(); void SendUpdates(AZ::TimeMs hostTimeMs); @@ -127,7 +127,7 @@ namespace Multiplayer void MigrateEntityInternal(NetEntityId entityId); void OnEntityExitDomain(const ConstNetworkEntityHandle& entityHandle); - void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, HostId remoteHostId, AzNetworking::ConnectionId connectionId); + void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId); EntityReplicator* AddEntityReplicator(const ConstNetworkEntityHandle& entityHandle, NetEntityRole netEntityRole); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 51f432953e..ba2024bb8e 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -41,7 +41,7 @@ namespace Multiplayer //! 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; + virtual void Initialize(const 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 @@ -65,7 +65,7 @@ namespace Multiplayer //! Returns the HostId for this INetworkEntityManager instance. //! @return the HostId for this INetworkEntityManager instance - virtual HostId GetHostId() const = 0; + virtual const HostId& GetHostId() const = 0; //! Creates new entities of the given archetype //! @param prefabEntryId the name of the spawnable to spawn diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index ee0898b681..a782a7dcf2 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -13,7 +13,6 @@ - diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index 7d630ebe1d..524400d008 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -390,7 +390,7 @@ namespace Multiplayer m_syncRewindEvent.Signal(); } - void NetBindComponent::NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId) + void NetBindComponent::NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId) { m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp index f34771b2c6..5c46a30a9b 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -186,7 +186,7 @@ namespace Multiplayer // Ensure any entities that we might interact with are properly synchronized to their rewind state if (IsAuthority()) { - const AZ::Aabb entityStartBounds = AZ::Interface::Get()->GetEntityLocalBoundsUnion(GetEntity()->GetId()); + const AZ::Aabb entityStartBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(GetEntity()->GetId()); const AZ::Aabb entityFinalBounds = entityStartBounds.GetTranslated(velocity); AZ::Aabb entitySweptBounds = entityStartBounds; entitySweptBounds.AddAabb(entityFinalBounds); diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp index ad1bcb5130..218f8421d2 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp @@ -28,7 +28,7 @@ namespace Multiplayer ) : m_connection(connection) , m_controlledEntityRemovedHandler([this](const ConstNetworkEntityHandle&) { OnControlledEntityRemove(); }) - , m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, HostId remoteHostId, AzNetworking::ConnectionId connectionId) { OnControlledEntityMigration(entityHandle, remoteHostId, connectionId); }) + , m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId) { OnControlledEntityMigration(entityHandle, remoteHostId, connectionId); }) , m_controlledEntity(controlledEntity) , m_entityReplicationManager(*connection, connectionListener, EntityReplicationManager::Mode::LocalServerToRemoteClient) { @@ -94,13 +94,10 @@ namespace Multiplayer void ServerToClientConnectionData::OnControlledEntityMigration ( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, - [[maybe_unused]] HostId remoteHostId, + [[maybe_unused]] const HostId& remoteHostId, [[maybe_unused]] AzNetworking::ConnectionId connectionId ) { - AzNetworking::IpAddress serverAddress; - // serverAddress = GetHost(remoteHostId).GetAddress(); - ClientInputId migratedClientInputId = ClientInputId{ 0 }; if (m_controlledEntity != nullptr) { @@ -118,7 +115,7 @@ namespace Multiplayer GetMultiplayer()->SendNotifyClientMigrationEvent(remoteHostId, randomUserIdentifier, migratedClientInputId); // Tell the client who to join - MultiplayerPackets::ClientMigration clientMigration(serverAddress, randomUserIdentifier, migratedClientInputId); + MultiplayerPackets::ClientMigration clientMigration(remoteHostId, randomUserIdentifier, migratedClientInputId); GetConnection()->SendReliablePacket(clientMigration); m_controlledEntity = NetworkEntityHandle(); diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h index 78ee721d97..764497430f 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h @@ -42,7 +42,7 @@ namespace Multiplayer private: void OnControlledEntityRemove(); - void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, HostId remoteHostId, AzNetworking::ConnectionId connectionId); + void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId); void OnGameplayStarted(); EntityReplicationManager m_entityReplicationManager; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index df88af5296..eb4c2f0f0b 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -71,7 +71,7 @@ namespace Multiplayer AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); - AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic"); AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic"); @@ -92,8 +92,6 @@ namespace Multiplayer { serializeContext->Class() ->Version(1); - serializeContext->Class() - ->Version(1); serializeContext->Class() ->Version(1); serializeContext->Class() @@ -109,7 +107,6 @@ namespace Multiplayer } else if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { - behaviorContext->Class(); behaviorContext->Class(); behaviorContext->Class(); behaviorContext->Class(); @@ -208,7 +205,7 @@ namespace Multiplayer return m_networkInterface->Listen(port); } - bool MultiplayerSystemComponent::Connect(AZStd::string remoteAddress, uint16_t port) + bool MultiplayerSystemComponent::Connect(const AZStd::string& remoteAddress, uint16_t port) { InitializeMultiplayer(MultiplayerAgentType::Client); const IpAddress address(remoteAddress.c_str(), port, m_networkInterface->GetType()); @@ -468,7 +465,7 @@ namespace Multiplayer } reinterpret_cast(connection->GetUserData())->SetProviderTicket(packet.GetTicket().c_str()); - if (connection->SendReliablePacket(MultiplayerPackets::Accept(InvalidHostId, sv_map))) + if (connection->SendReliablePacket(MultiplayerPackets::Accept(sv_map))) { m_didHandshake = true; @@ -760,7 +757,11 @@ namespace Multiplayer 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()); + const AZ::CVarFixedString serverAddr = cl_serveraddr; + const uint16_t serverPort = cl_serverport; + const AzNetworking::ProtocolType serverProtocol = sv_protocol; + const AzNetworking::IpAddress hostId = AzNetworking::IpAddress(serverAddr.c_str(), serverPort, serverProtocol); + m_networkEntityManager.Initialize(hostId, AZStd::make_unique()); } } } @@ -815,7 +816,7 @@ namespace Multiplayer handler.Connect(m_shutdownEvent); } - void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(HostId hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) + void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) { m_notifyClientMigrationEvent.Signal(hostId, userIdentifier, lastClientInputId); } @@ -1012,7 +1013,10 @@ namespace Multiplayer void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { - AZ::Interface::Get()->StartHosting(sv_port, sv_isDedicated); + if (!AZ::Interface::Get()->StartHosting(sv_port, sv_isDedicated)) + { + AZLOG_ERROR("Failed to start listening on port %u, port is in use?", static_cast(sv_port)); + } } AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to"); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index bdf6511409..b49ad831c1 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -106,7 +106,7 @@ 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; + bool Connect(const 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; @@ -115,7 +115,7 @@ namespace Multiplayer void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; - void SendNotifyClientMigrationEvent(HostId hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; + void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; AZ::TimeMs GetCurrentHostTimeMs() const override; float GetCurrentBlendFactor() const override; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 938b3611c5..df6be37e5e 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -63,12 +63,12 @@ namespace Multiplayer } } - void EntityReplicationManager::SetRemoteHostId(HostId hostId) + void EntityReplicationManager::SetRemoteHostId(const HostId& hostId) { m_remoteHostId = hostId; } - HostId EntityReplicationManager::GetRemoteHostId() const + const HostId& EntityReplicationManager::GetRemoteHostId() const { return m_remoteHostId; } @@ -106,9 +106,9 @@ namespace Multiplayer AZLOG ( NET_ReplicationInfo, - "Sending from %u to %u, replicator count %u orphan count %u deferred reliable count %u deferred unreliable count %u", - aznumeric_cast(GetNetworkEntityManager()->GetHostId()), - aznumeric_cast(GetRemoteHostId()), + "Sending from %s to %s, replicator count %u orphan count %u deferred reliable count %u deferred unreliable count %u", + GetNetworkEntityManager()->GetHostId().GetString().c_str(), + GetRemoteHostId().GetString().c_str(), aznumeric_cast(m_entityReplicatorMap.size()), aznumeric_cast(m_orphanedEntityRpcs.Size()), aznumeric_cast(m_deferredRpcMessagesReliable.size()), @@ -250,7 +250,14 @@ namespace Multiplayer { EntityReplicatorList toSendList = GenerateEntityUpdateList(); - AZLOG(NET_ReplicationInfo, "Sending %zd updates from %d to %d", toSendList.size(), (uint8_t)GetNetworkEntityManager()->GetHostId(), (uint8_t)GetRemoteHostId()); + AZLOG + ( + NET_ReplicationInfo, + "Sending %zd updates from %s to %s", + toSendList.size(), + GetNetworkEntityManager()->GetHostId().GetString().c_str(), + GetRemoteHostId().GetString().c_str() + ); // prep a replication record for send, at this point, everything needs to be sent for (EntityReplicator* replicator : toSendList) @@ -357,7 +364,7 @@ namespace Multiplayer // Check if we changed our remote role - this can happen during server entity migration. After we migrate ownership to the new server, we hold onto our entity replicator until we are sure // the other side has received all the packets (and we haven't had to do resends). At this point, it is possible hear back from the remote side we migrated to on the old replicator prior to the timeout and cleanup on the old one const bool changedRemoteRole = (remoteNetworkRole != entityReplicator->GetRemoteNetworkRole()); - // check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client + // Check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client bool changedLocalRole(false); if (AZ::Entity* localEnt = entityReplicator->GetEntityHandle().GetEntity()) { @@ -377,19 +384,33 @@ namespace Multiplayer // Reset our replicator, we are establishing a new one entityReplicator->Reset(remoteNetworkRole); } - // else case is when an entity had left relevancy and come back (but it was still pending a removal) + // Else case is when an entity had left relevancy and come back (but it was still pending a removal) entityReplicator->Initialize(entityHandle); - AZLOG(NET_RepDeletes, "Reinited replicator for %u from remote manager id %d role %d", entityHandle.GetNetEntityId(), aznumeric_cast(GetRemoteHostId()), aznumeric_cast(remoteNetworkRole)); + AZLOG + ( + NET_RepDeletes, + "Reinited replicator for %u from remote host %s role %d", + entityHandle.GetNetEntityId(), + GetRemoteHostId().GetString().c_str(), + aznumeric_cast(remoteNetworkRole) + ); } else { - // haven't seen him before, let's add him + // Haven't seen him before, let's add him AZ_Assert(entityHandle.GetNetBindComponent(), "No NetBindComponent"); AZStd::unique_ptr newEntityReplicator = AZStd::make_unique(*this, &m_connection, remoteNetworkRole, entityHandle); newEntityReplicator->Initialize(entityHandle); entityReplicator = newEntityReplicator.get(); m_entityReplicatorMap.emplace(entityHandle.GetNetEntityId(), AZStd::move(newEntityReplicator)); - AZLOG(NET_RepDeletes, "Added replicator for %u from remote manager id %d role %d", entityHandle.GetNetEntityId(), aznumeric_cast(GetRemoteHostId()), aznumeric_cast(remoteNetworkRole)); + AZLOG + ( + NET_RepDeletes, + "Added replicator for %u from remote host %s role %d", + entityHandle.GetNetEntityId(), + GetRemoteHostId().GetString().c_str(), + aznumeric_cast(remoteNetworkRole) + ); } } else @@ -483,18 +504,18 @@ namespace Multiplayer { if (entityReplicator->IsMarkedForRemoval()) { - AZLOG(NET_RepDeletes, "Got a replicator delete message that is a duplicate id %u remote manager id %d", updateMessage.GetEntityId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "Got a replicator delete message that is a duplicate id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); } else if (entityReplicator->OwnsReplicatorLifetime()) { // This can occur if we migrate entities quickly - if this is a replicator from C to A, A migrates to B, B then migrates to C, and A's delete replicator has not arrived at C - AZLOG(NET_RepDeletes, "Got a replicator delete message for a replicator we own id %u remote manager id %d", updateMessage.GetEntityId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "Got a replicator delete message for a replicator we own id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); } else { shouldDeleteEntity = true; entityReplicator->MarkForRemoval(); - AZLOG(NET_RepDeletes, "Deleting replicater for entity id %u remote manager id %d", updateMessage.GetEntityId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "Deleting replicater for entity id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); } } else @@ -510,17 +531,17 @@ namespace Multiplayer { if (updateMessage.GetWasMigrated()) { - AZLOG(NET_RepDeletes, "Leaving id %u using timeout remote manager id %d", entity.GetNetEntityId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "Leaving id %u using timeout remote host %s", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); } else { - AZLOG(NET_RepDeletes, "Deleting entity id %u remote manager id %d", entity.GetNetEntityId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "Deleting entity id %u remote host %s", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); GetNetworkEntityManager()->MarkForRemoval(entity); } } else { - AZLOG(NET_RepDeletes, "Trying to delete entity id %u remote manager id %d, but it has been removed", entity.GetNetEntityId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "Trying to delete entity id %u remote host %s, but it has been removed", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); } } @@ -689,8 +710,8 @@ namespace Multiplayer AZLOG_WARN ( "Dropping Packet and LocalServerToRemoteClient connection, unexpected packet " - "LocalShard=%u EntityId=%u RemoteNetworkRole=%u BoundLocalNetworkRole=%u ActualNetworkRole=%u IsMarkedForRemoval=%s", - aznumeric_cast(GetNetworkEntityManager()->GetHostId()), + "LocalShard=%s EntityId=%u RemoteNetworkRole=%u BoundLocalNetworkRole=%u ActualNetworkRole=%u IsMarkedForRemoval=%s", + GetNetworkEntityManager()->GetHostId().GetString().c_str(), aznumeric_cast(entityReplicator->GetEntityHandle().GetNetEntityId()), aznumeric_cast(entityReplicator->GetRemoteNetworkRole()), aznumeric_cast(entityReplicator->GetBoundLocalNetworkRole()), @@ -741,13 +762,13 @@ namespace Multiplayer result = UpdateValidationResult::DropMessage; if (updateMessage.GetIsDelete()) { - AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %u, sequence %d latest sequence %d from remote manager id %d", - updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %u, sequence %d latest sequence %d from remote host %s", + updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str()); } else { - AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %u, sequence %d latest sequence %d from remote manager id %d", - updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %u, sequence %d latest sequence %d from remote host %s", + updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str()); } } } @@ -1126,7 +1147,7 @@ namespace Multiplayer AZ_Assert(didSucceed, "Failed to migrate entity from server"); m_sendMigrateEntityEvent.Signal(m_connection, message); - AZLOG(NET_RepDeletes, "Migration packet sent %u to remote manager id %d", netEntityId, aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "Migration packet sent %u to remote host %s", netEntityId, GetRemoteHostId().GetString().c_str()); // 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 AddEntityReplicator(entityHandle, NetEntityRole::Authority); @@ -1179,7 +1200,7 @@ namespace Multiplayer // Change the role on the replicator AddEntityReplicator(entityHandle, NetEntityRole::Server); - AZLOG(NET_RepDeletes, "Handle Migration %u new authority from remote manager id %d", entityHandle.GetNetEntityId(), aznumeric_cast(GetRemoteHostId())); + AZLOG(NET_RepDeletes, "Handle Migration %u new authority from remote host %s", entityHandle.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); return true; } @@ -1191,7 +1212,7 @@ namespace Multiplayer } } - void EntityReplicationManager::OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, HostId remoteHostId, [[maybe_unused]] AzNetworking::ConnectionId connectionId) + void EntityReplicationManager::OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, [[maybe_unused]] AzNetworking::ConnectionId connectionId) { if (remoteHostId == GetRemoteHostId()) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 95e2b6d12c..8329d7c9df 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -413,10 +413,10 @@ namespace Multiplayer AZLOG ( NET_RepDeletes, - "Sending delete replicator id %u migrated %d to remote manager id %d", + "Sending delete replicator id %u migrated %d to remote host %s", aznumeric_cast(GetEntityHandle().GetNetEntityId()), WasMigrated() ? 1 : 0, - aznumeric_cast(m_replicationManager.GetRemoteHostId()) + m_replicationManager.GetRemoteHostId().GetString().c_str() ); return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated(), m_propertyPublisher->IsRemoteReplicatorEstablished()); } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp index 947d303278..0a99f4b0d4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp @@ -24,7 +24,7 @@ namespace Multiplayer ; } - bool NetworkEntityAuthorityTracker::AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, HostId newOwner) + bool NetworkEntityAuthorityTracker::AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& newOwner) { bool ret = false; auto timeoutData = m_timeoutDataMap.find(entityHandle.GetNetEntityId()); @@ -33,10 +33,10 @@ namespace Multiplayer AZLOG ( NET_AuthTracker, - "AuthTracker: Removing timeout for networkEntityId %u from %u, new owner is %u", + "AuthTracker: Removing timeout for networkEntityId %u from %s, new owner is %s", aznumeric_cast(entityHandle.GetNetEntityId()), - aznumeric_cast(timeoutData->second.m_previousOwner), - aznumeric_cast(newOwner) + timeoutData->second.m_previousOwner.GetString().c_str(), + newOwner.GetString().c_str() ); m_timeoutDataMap.erase(timeoutData); ret = true; @@ -48,10 +48,10 @@ namespace Multiplayer AZLOG ( NET_AuthTracker, - "AuthTracker: Assigning networkEntityId %u from %u to %u", + "AuthTracker: Assigning networkEntityId %u from %s to %s", aznumeric_cast(entityHandle.GetNetEntityId()), - aznumeric_cast(iter->second.back()), - aznumeric_cast(newOwner) + iter->second.back().GetString().c_str(), + newOwner.GetString().c_str() ); } else @@ -59,9 +59,9 @@ namespace Multiplayer AZLOG ( NET_AuthTracker, - "AuthTracker: Assigning networkEntityId %u to %u", + "AuthTracker: Assigning networkEntityId %u to %s", aznumeric_cast(entityHandle.GetNetEntityId()), - aznumeric_cast(newOwner) + newOwner.GetString().c_str() ); } @@ -69,7 +69,7 @@ namespace Multiplayer return ret; } - void NetworkEntityAuthorityTracker::RemoveEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, HostId previousOwner) + void NetworkEntityAuthorityTracker::RemoveEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner) { auto mapIter = m_entityAuthorityMap.find(entityHandle.GetNetEntityId()); if (mapIter != m_entityAuthorityMap.end()) @@ -87,7 +87,7 @@ namespace Multiplayer } } - AZLOG(NET_AuthTracker, "AuthTracker: Removing networkEntityId %u from %u", aznumeric_cast(entityHandle.GetNetEntityId()), aznumeric_cast(previousOwner)); + AZLOG(NET_AuthTracker, "AuthTracker: Removing networkEntityId %u from %s", aznumeric_cast(entityHandle.GetNetEntityId()), previousOwner.GetString().c_str()); if (auto localEnt = entityHandle.GetEntity()) { if (authorityStack.empty()) @@ -167,7 +167,7 @@ namespace Multiplayer return InvalidHostId; } - NetworkEntityAuthorityTracker::TimeoutData::TimeoutData(ConstNetworkEntityHandle entityHandle, HostId previousOwner) + NetworkEntityAuthorityTracker::TimeoutData::TimeoutData(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner) : m_entityHandle(entityHandle) , m_previousOwner(previousOwner) { @@ -205,9 +205,9 @@ namespace Multiplayer { AZLOG_ERROR ( - "Timed out entity id %u during migration previous owner %u, removing it", + "Timed out entity id %u during migration previous owner %s, removing it", aznumeric_cast(entityHandle.GetNetEntityId()), - aznumeric_cast(timeoutData->second.m_previousOwner) + timeoutData->second.m_previousOwner.GetString().c_str() ); m_networkEntityManager.MarkForRemoval(entityHandle); } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h index d69edcf55b..0f4ff5665a 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h @@ -24,8 +24,8 @@ namespace Multiplayer NetworkEntityAuthorityTracker(INetworkEntityManager& networkEntityManager); bool DoesEntityHaveOwner(ConstNetworkEntityHandle entityHandle) const; - bool AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, HostId newOwner); - void RemoveEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, HostId previousOwner); + bool AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& newOwner); + void RemoveEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner); HostId GetEntityAuthorityManager(ConstNetworkEntityHandle entityHandle) const; private: @@ -37,7 +37,7 @@ namespace Multiplayer struct TimeoutData final { TimeoutData() = default; - TimeoutData(ConstNetworkEntityHandle entityHandle, HostId previousOwner); + TimeoutData(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner); ConstNetworkEntityHandle m_entityHandle; HostId m_previousOwner = InvalidHostId; }; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp index 6f5819eaa3..6f3bd719e0 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp @@ -29,9 +29,16 @@ namespace Multiplayer { AZ_Assert(networkEntityTracker, "NetworkEntityTracker is not valid"); NetBindComponent* netBindComponent = m_entity->template FindComponent(); - AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); - m_netBindComponent = netBindComponent; - m_netEntityId = netBindComponent->GetNetEntityId(); + if (netBindComponent != nullptr) + { + AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); + m_netBindComponent = netBindComponent; + m_netEntityId = netBindComponent->GetNetEntityId(); + } + else + { + *this = ConstNetworkEntityHandle(); + } } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index e9866085ae..1ed2c8f6a4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -41,7 +41,7 @@ namespace Multiplayer AZ::Interface::Unregister(this); } - void NetworkEntityManager::Initialize(HostId hostId, AZStd::unique_ptr entityDomain) + void NetworkEntityManager::Initialize(const HostId& hostId, AZStd::unique_ptr entityDomain) { m_hostId = hostId; m_entityDomain = AZStd::move(entityDomain); @@ -73,7 +73,7 @@ namespace Multiplayer return &m_multiplayerComponentRegistry; } - HostId NetworkEntityManager::GetHostId() const + const HostId& NetworkEntityManager::GetHostId() const { return m_hostId; } @@ -106,8 +106,6 @@ namespace Multiplayer if (net_DebugCheckNetworkEntityManager) { AZ_Assert(entityHandle.GetNetBindComponent(), "No NetBindComponent found on networked entity"); - [[maybe_unused]] const bool isClientOnlyEntity = false;// (ServerIdFromEntityId(it->first) == InvalidHostId); - AZ_Assert(entityHandle.GetNetBindComponent()->IsNetEntityRoleAuthority() || isClientOnlyEntity, "Trying to delete a proxy entity, this will lead to issues deserializing entity updates"); } m_removeList.push_back(entityHandle.GetNetEntityId()); m_removeEntitiesEvent.Enqueue(AZ::TimeMs{ 0 }); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 804946b882..d3f296b03d 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -33,13 +33,13 @@ namespace Multiplayer //! INetworkEntityManager overrides. //! @{ - void Initialize(HostId hostId, AZStd::unique_ptr entityDomain) override; + void Initialize(const HostId& hostId, AZStd::unique_ptr entityDomain) override; bool IsInitialized() const override; IEntityDomain* GetEntityDomain() const override; NetworkEntityTracker* GetNetworkEntityTracker() override; NetworkEntityAuthorityTracker* GetNetworkEntityAuthorityTracker() override; MultiplayerComponentRegistry* GetMultiplayerComponentRegistry() override; - HostId GetHostId() const override; + const HostId& GetHostId() const override; ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const override; NetEntityId GetNetEntityIdById(const AZ::EntityId& entityId) const override; diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index b37f485ff4..9e69d93e16 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -105,7 +105,7 @@ namespace Multiplayer if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) { AZ::Entity* entity = static_cast(visEntry->m_userData); - const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityLocalBoundsUnion(entity->GetId()); + const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityWorldBoundsUnion(entity->GetId()); const AZ::Vector3 currentCenter = currentBounds.GetCenter(); NetworkTransformComponent* networkTransform = entity->template FindComponent(); From efda2c91e984142dbddd4e8ba27c7c22c067080d Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Thu, 23 Sep 2021 18:28:11 -0700 Subject: [PATCH 08/15] Cleaning up tcp socket changes Signed-off-by: kberg-amzn --- .../AzNetworking/TcpTransport/TcpSocket.cpp | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp index da0ae4ef08..e469b70640 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp @@ -53,19 +53,9 @@ namespace AzNetworking { Close(); - if (!SocketCreateInternal()) - { - Close(); - return false; - } - - if (!BindSocketForListenInternal(port)) - { - Close(); - return false; - } - - if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd))) + if (!SocketCreateInternal() + || !BindSocketForListenInternal(port) + || !(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd))) { Close(); return false; From 95d7cc72123cd4802f232108da791f3d39784266 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Mon, 27 Sep 2021 12:14:41 -0700 Subject: [PATCH 09/15] Adding the NetworkConnectionComponent to hold user nonce and migration values for a backup host in case the current host crashes or abnormally disconnects Signed-off-by: kberg-amzn --- .../NetworkConnectionComponent.AutoComponent.xml | 14 ++++++++++++++ .../Source/NetworkEntity/NetworkEntityManager.cpp | 1 - Gems/Multiplayer/Code/multiplayer_files.cmake | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkConnectionComponent.AutoComponent.xml diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkConnectionComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkConnectionComponent.AutoComponent.xml new file mode 100644 index 0000000000..eff1b5e9cf --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkConnectionComponent.AutoComponent.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 1ed2c8f6a4..68c87d1c6d 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -374,7 +374,6 @@ namespace Multiplayer &AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone); returnList.push_back(netBindComponent->GetEntityHandle()); - } else { diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index c1d27402f5..51a5a1754d 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -58,6 +58,7 @@ set(FILES Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/MultiplayerEditor.AutoPackets.xml Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml + Source/AutoGen/NetworkConnectionComponent.AutoComponent.xml Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml From 02bc89cd9250626e129bc8cf80dc1601e51a31df Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Tue, 28 Sep 2021 19:25:04 -0700 Subject: [PATCH 10/15] Fixes to sending entity updates and entity rpcs within an environment set up for cross host entity migration Signed-off-by: kberg-amzn --- .../ConnectionData/IConnectionData.h | 3 +- .../EntityReplicationManager.h | 10 +- .../EntityReplication/EntityReplicator.h | 1 + .../NetworkEntity/NetworkEntityRpcMessage.h | 1 + .../NetworkEntityUpdateMessage.h | 1 + .../ReplicationWindows/IReplicationWindow.h | 8 + .../AutoGen/Multiplayer.AutoPackets.xml | 6 +- .../ClientToServerConnectionData.cpp | 4 +- .../ClientToServerConnectionData.h | 2 +- .../ServerToClientConnectionData.cpp | 4 +- .../ServerToClientConnectionData.h | 2 +- .../Source/MultiplayerSystemComponent.cpp | 7 +- .../EntityReplicationManager.cpp | 184 ++++++++---------- .../EntityReplicationManager.h | 8 +- .../EntityReplication/EntityReplicator.cpp | 6 +- .../EntityReplication/PropertyPublisher.cpp | 1 - .../NullReplicationWindow.cpp | 30 +++ .../NullReplicationWindow.h | 6 +- .../ServerToClientReplicationWindow.cpp | 26 ++- .../ServerToClientReplicationWindow.h | 6 +- Gems/Multiplayer/Code/multiplayer_files.cmake | 3 + 21 files changed, 185 insertions(+), 134 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/ConnectionData/IConnectionData.h b/Gems/Multiplayer/Code/Include/Multiplayer/ConnectionData/IConnectionData.h index 66a7a1175a..c3e251ea5e 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/ConnectionData/IConnectionData.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/ConnectionData/IConnectionData.h @@ -40,8 +40,7 @@ namespace Multiplayer virtual EntityReplicationManager& GetReplicationManager() = 0; //! Creates and manages sending updates to the remote endpoint. - //! @param hostTimeMs current server game time in milliseconds - virtual void Update(AZ::TimeMs hostTimeMs) = 0; + virtual void Update() = 0; //! Returns whether update messages can be sent to the connection. //! @return true if update messages can be sent diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h index dd84c9d952..01ae966fb5 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -60,7 +60,7 @@ namespace Multiplayer const HostId& GetRemoteHostId() const; void ActivatePendingEntities(); - void SendUpdates(AZ::TimeMs hostTimeMs); + void SendUpdates(); void Clear(bool forMigration); bool SetEntityRebasing(NetworkEntityHandle& entityHandle); @@ -81,7 +81,7 @@ namespace Multiplayer void AddDeferredRpcMessage(NetworkEntityRpcMessage& rpcMessage); - void AddAutonomousEntityReplicatorCreatedHandle(AZ::Event::Handler& handler); + void AddAutonomousEntityReplicatorCreatedHandler(AZ::Event::Handler& handler); void AddSendMigrateEntityEventHandler(SendMigrateEntityEvent::Handler& handler); bool HandleEntityMigration(AzNetworking::IConnection* invokingConnection, EntityMigrationMessage& message); @@ -120,10 +120,8 @@ namespace Multiplayer 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 SendEntityUpdateMessages(EntityReplicatorList& replicatorList); + void SendEntityRpcs(RpcMessages& rpcMessages, bool reliable); void MigrateEntityInternal(NetEntityId entityId); void OnEntityExitDomain(const ConstNetworkEntityHandle& entityHandle); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h index 7edefc15e0..d239682c93 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h @@ -66,6 +66,7 @@ namespace Multiplayer bool IsReadyToActivate() const; NetworkEntityUpdateMessage GenerateUpdatePacket(); + void FinalizeSerialization(AzNetworking::PacketId sentId); AZ::TimeMs GetResendTimeoutTimeMs() const; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h index 9720de81eb..3f1d8e346c 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h @@ -103,6 +103,7 @@ namespace Multiplayer // Non-serialized RPC metadata ReliabilityType m_isReliable = ReliabilityType::Reliable; }; + using NetworkEntityRpcVector = AZStd::fixed_vector; struct IRpcParamStruct { diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h index 3c400d5a13..90f622a8ae 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h @@ -118,4 +118,5 @@ namespace Multiplayer // This is to prevent blowing out stack memory if we declare an array of these EntityUpdateMessages AZStd::unique_ptr m_data; }; + using NetworkEntityUpdateVector = AZStd::fixed_vector; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/ReplicationWindows/IReplicationWindow.h b/Gems/Multiplayer/Code/Include/Multiplayer/ReplicationWindows/IReplicationWindow.h index e2e4c5abfe..018d90eef3 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/ReplicationWindows/IReplicationWindow.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/ReplicationWindows/IReplicationWindow.h @@ -10,10 +10,14 @@ #include #include +#include +#include #include namespace Multiplayer { + class EntityReplicator; + struct EntityReplicationData { EntityReplicationData() = default; @@ -21,6 +25,8 @@ namespace Multiplayer float m_priority = 0.0f; }; using ReplicationSet = AZStd::map; + using RpcMessages = AZStd::list; + using EntityReplicatorList = AZStd::deque; class IReplicationWindow { @@ -33,6 +39,8 @@ namespace Multiplayer virtual uint32_t GetMaxProxyEntityReplicatorSendCount() const = 0; virtual bool IsInWindow(const ConstNetworkEntityHandle& entityPtr, NetEntityRole& outNetworkRole) const = 0; virtual void UpdateWindow() = 0; + virtual AzNetworking::PacketId SendEntityUpdateMessages(NetworkEntityUpdateVector& entityUpdateVector) = 0; + virtual void SendEntityRpcs(NetworkEntityRpcVector& entityRpcVector, bool reliable) = 0; virtual void DebugDraw() const = 0; }; } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index a782a7dcf2..091043f034 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -15,7 +15,7 @@ - + @@ -31,11 +31,11 @@ - + - + diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp index a943406df3..392b020748 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp @@ -50,9 +50,9 @@ namespace Multiplayer return m_entityReplicationManager; } - void ClientToServerConnectionData::Update(AZ::TimeMs hostTimeMs) + void ClientToServerConnectionData::Update() { m_entityReplicationManager.ActivatePendingEntities(); - m_entityReplicationManager.SendUpdates(hostTimeMs); + m_entityReplicationManager.SendUpdates(); } } diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h index 9776cbabb9..77df604b49 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h @@ -30,7 +30,7 @@ namespace Multiplayer ConnectionDataType GetConnectionDataType() const override; AzNetworking::IConnection* GetConnection() const override; EntityReplicationManager& GetReplicationManager() override; - void Update(AZ::TimeMs hostTimeMs) override; + void Update() override; bool CanSendUpdates() const override; void SetCanSendUpdates(bool canSendUpdates) override; //! @} diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp index 218f8421d2..a9b8e03126 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp @@ -69,7 +69,7 @@ namespace Multiplayer return m_entityReplicationManager; } - void ServerToClientConnectionData::Update(AZ::TimeMs hostTimeMs) + void ServerToClientConnectionData::Update() { m_entityReplicationManager.ActivatePendingEntities(); @@ -79,7 +79,7 @@ namespace Multiplayer // potentially false if we just migrated the player, if that is the case, don't send any more updates if (netBindComponent != nullptr && (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority)) { - m_entityReplicationManager.SendUpdates(hostTimeMs); + m_entityReplicationManager.SendUpdates(); } } } diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h index 764497430f..8dcf08c480 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h @@ -30,7 +30,7 @@ namespace Multiplayer ConnectionDataType GetConnectionDataType() const override; AzNetworking::IConnection* GetConnection() const override; EntityReplicationManager& GetReplicationManager() override; - void Update(AZ::TimeMs hostTimeMs) override; + void Update() override; bool CanSendUpdates() const override; void SetCanSendUpdates(bool canSendUpdates) override; //! @} diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index c1a29f9539..cb72fe9252 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -307,7 +307,6 @@ namespace Multiplayer void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { const AZ::TimeMs deltaTimeMs = aznumeric_cast(static_cast(deltaTime * 1000.0f)); - const AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs(); const AZ::TimeMs serverRateMs = static_cast(sv_serverSendRateMs); const float serverRateSeconds = static_cast(serverRateMs) / 1000.0f; @@ -344,12 +343,12 @@ namespace Multiplayer // Send out the game state update to all connections { - auto sendNetworkUpdates = [hostTimeMs, &stats](IConnection& connection) + auto sendNetworkUpdates = [&stats](IConnection& connection) { if (connection.GetUserData() != nullptr) { IConnectionData* connectionData = reinterpret_cast(connection.GetUserData()); - connectionData->Update(hostTimeMs); + connectionData->Update(); if (connectionData->GetConnectionDataType() == ConnectionDataType::ServerToClient) { stats.m_clientConnectionCount++; @@ -671,7 +670,7 @@ namespace Multiplayer else { connection->SetUserData(new ClientToServerConnectionData(connection, *this, providerTicket)); - AZStd::unique_ptr window = AZStd::make_unique(); + AZStd::unique_ptr window = AZStd::make_unique(connection); reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index fff9a1cbd8..cdda18e43a 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -107,10 +106,34 @@ namespace Multiplayer } } - void EntityReplicationManager::SendUpdates(AZ::TimeMs hostTimeMs) + void EntityReplicationManager::SendUpdates() { m_frameTimeMs = AZ::GetElapsedTimeMs(); - SendEntityUpdates(hostTimeMs); + + { + EntityReplicatorList toSendList = GenerateEntityUpdateList(); + + AZLOG + ( + NET_ReplicationInfo, + "Sending %zd updates from %s to %s", + toSendList.size(), + GetNetworkEntityManager()->GetHostId().GetString().c_str(), + GetRemoteHostId().GetString().c_str() + ); + + // Prep a replication record for send, at this point, everything needs to be sent + for (EntityReplicator* replicator : toSendList) + { + replicator->GetPropertyPublisher()->PrepareSerialization(); + } + + // While our to send list is not empty, build up another packet to send + do + { + SendEntityUpdateMessages(toSendList); + } while (!toSendList.empty()); + } SendEntityRpcs(m_deferredRpcMessagesReliable, true); SendEntityRpcs(m_deferredRpcMessagesUnreliable, false); @@ -130,65 +153,6 @@ namespace Multiplayer ); } - void EntityReplicationManager::SendEntityUpdatesPacketHelper - ( - AZ::TimeMs hostTimeMs, - EntityReplicatorList& toSendList, - uint32_t maxPayloadSize, - AzNetworking::IConnection& connection - ) - { - uint32_t pendingPacketSize = 0; - EntityReplicatorList replicatorUpdatedList; - MultiplayerPackets::EntityUpdates entityUpdatePacket; - entityUpdatePacket.SetHostTimeMs(hostTimeMs); - entityUpdatePacket.SetHostFrameId(GetNetworkTime()->GetHostFrameId()); - // Serialize everything - while (!toSendList.empty()) - { - EntityReplicator* replicator = toSendList.front(); - NetworkEntityUpdateMessage updateMessage(replicator->GenerateUpdatePacket()); - - const uint32_t nextMessageSize = updateMessage.GetEstimatedSerializeSize(); - - // Check if we are over our limits - const bool payloadFull = (pendingPacketSize + nextMessageSize > maxPayloadSize); - const bool capacityReached = (entityUpdatePacket.GetEntityMessages().size() >= entityUpdatePacket.GetEntityMessages().capacity()); - const bool largeEntityDetected = (payloadFull && replicatorUpdatedList.empty()); - if (capacityReached || (payloadFull && !largeEntityDetected)) - { - break; - } - - pendingPacketSize += nextMessageSize; - entityUpdatePacket.ModifyEntityMessages().push_back(updateMessage); - replicatorUpdatedList.push_back(replicator); - toSendList.pop_front(); - - if (largeEntityDetected) - { - AZLOG_WARN("\n\n*******************************"); - AZLOG_WARN - ( - "Serializing extremely large entity (%u) - MaxPayload: %d NeededSize %d", - aznumeric_cast(replicator->GetEntityHandle().GetNetEntityId()), - maxPayloadSize, - nextMessageSize - ); - AZLOG_WARN("*******************************"); - break; - } - } - - const AzNetworking::PacketId sentId = connection.SendUnreliablePacket(entityUpdatePacket); - - // Update the sent things with the packet id - for (EntityReplicator* replicator : replicatorUpdatedList) - { - replicator->GetPropertyPublisher()->FinalizeSerialization(sentId); - } - } - EntityReplicationManager::EntityReplicatorList EntityReplicationManager::GenerateEntityUpdateList() { if (m_replicationWindow == nullptr) @@ -260,76 +224,92 @@ namespace Multiplayer return toSendList; } - void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs hostTimeMs) + void EntityReplicationManager::SendEntityUpdateMessages(EntityReplicatorList& replicatorList) { - EntityReplicatorList toSendList = GenerateEntityUpdateList(); - - AZLOG - ( - NET_ReplicationInfo, - "Sending %zd updates from %s to %s", - toSendList.size(), - GetNetworkEntityManager()->GetHostId().GetString().c_str(), - GetRemoteHostId().GetString().c_str() - ); - - // prep a replication record for send, at this point, everything needs to be sent - for (EntityReplicator* replicator : toSendList) + uint32_t pendingPacketSize = 0; + EntityReplicatorList replicatorUpdatedList; + NetworkEntityUpdateVector entityUpdates; + // Serialize everything + while (!replicatorList.empty()) { - replicator->GetPropertyPublisher()->PrepareSerialization(); + EntityReplicator* replicator = replicatorList.front(); + NetworkEntityUpdateMessage updateMessage(replicator->GenerateUpdatePacket()); + + const uint32_t nextMessageSize = updateMessage.GetEstimatedSerializeSize(); + + // Check if we are over our limits + const bool payloadFull = (pendingPacketSize + nextMessageSize > m_maxPayloadSize); + const bool capacityReached = (entityUpdates.size() >= entityUpdates.capacity()); + const bool largeEntityDetected = (payloadFull && replicatorUpdatedList.empty()); + if (capacityReached || (payloadFull && !largeEntityDetected)) + { + break; + } + + pendingPacketSize += nextMessageSize; + entityUpdates.push_back(updateMessage); + replicatorUpdatedList.push_back(replicator); + replicatorList.pop_front(); + + if (largeEntityDetected) + { + AZLOG_WARN + ( + "Serializing extremely large entity (%u) - MaxPayload: %d NeededSize %d", + aznumeric_cast(replicator->GetEntityHandle().GetNetEntityId()), + m_maxPayloadSize, + nextMessageSize + ); + break; + } } - // While our to send list is not empty, build up another packet to send - do + const AzNetworking::PacketId sentId = m_replicationWindow->SendEntityUpdateMessages(entityUpdates); + + // Update the sent things with the packet id + for (EntityReplicator* replicator : replicatorUpdatedList) { - SendEntityUpdatesPacketHelper(hostTimeMs, toSendList, m_maxPayloadSize, m_connection); - } while (!toSendList.empty()); + replicator->FinalizeSerialization(sentId); + } } - void EntityReplicationManager::SendEntityRpcs(RpcMessages& deferredRpcs, bool reliable) + void EntityReplicationManager::SendEntityRpcs(RpcMessages& rpcMessages, bool reliable) { - while (!deferredRpcs.empty()) + while (!rpcMessages.empty()) { - MultiplayerPackets::EntityRpcs entityRpcsPacket; + NetworkEntityRpcVector entityRpcs; uint32_t pendingPacketSize = 0; - while (!deferredRpcs.empty()) + while (!rpcMessages.empty()) { - NetworkEntityRpcMessage& message = deferredRpcs.front(); + NetworkEntityRpcMessage& message = rpcMessages.front(); const uint32_t nextRpcSize = message.GetEstimatedSerializeSize(); if ((pendingPacketSize + nextRpcSize) > m_maxPayloadSize) { // We're over our limit, break and send an Rpc packet - if (entityRpcsPacket.GetEntityRpcs().size() == 0) + if (entityRpcs.size() == 0) { AZLOG(NET_Replicator, "Encountered an RPC that is above our MTU, message will be segmented (object size %u, max allowed size %u)", nextRpcSize, m_maxPayloadSize); - entityRpcsPacket.ModifyEntityRpcs().push_back(message); - deferredRpcs.pop_front(); + entityRpcs.push_back(message); + rpcMessages.pop_front(); } break; } pendingPacketSize += nextRpcSize; - if (entityRpcsPacket.GetEntityRpcs().full()) + if (entityRpcs.full()) { // Packet was full, send what we've accumulated so far - AZLOG(NET_Replicator, "We've hit our RPC message limit (RPC count %u, packet size %u)", aznumeric_cast(entityRpcsPacket.GetEntityRpcs().size()), pendingPacketSize); + AZLOG(NET_Replicator, "We've hit our RPC message limit (RPC count %u, packet size %u)", aznumeric_cast(entityRpcs.size()), pendingPacketSize); break; } - entityRpcsPacket.ModifyEntityRpcs().push_back(message); - deferredRpcs.pop_front(); + entityRpcs.push_back(message); + rpcMessages.pop_front(); } - if (reliable) - { - m_connection.SendReliablePacket(entityRpcsPacket); - } - else - { - m_connection.SendUnreliablePacket(entityRpcsPacket); - } + m_replicationWindow->SendEntityRpcs(entityRpcs, reliable); } } @@ -474,7 +454,7 @@ namespace Multiplayer } // @nt: TODO - delete once dropped RPC problem fixed - void EntityReplicationManager::AddAutonomousEntityReplicatorCreatedHandle(AZ::Event::Handler& handler) + void EntityReplicationManager::AddAutonomousEntityReplicatorCreatedHandler(AZ::Event::Handler& handler) { handler.Connect(m_autonomousEntityReplicatorCreated); } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h index 9eec27be27..28a0963a57 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -79,7 +79,7 @@ namespace Multiplayer void AddDeferredRpcMessage(NetworkEntityRpcMessage& rpcMessage); - void AddAutonomousEntityReplicatorCreatedHandle(AZ::Event::Handler& handler); + void AddAutonomousEntityReplicatorCreatedHandler(AZ::Event::Handler& handler); bool HandleEntityMigration(AzNetworking::IConnection* invokingConnection, EntityMigrationMessage& message); bool HandleEntityDeleteMessage(EntityReplicator* entityReplicator, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage); @@ -117,10 +117,8 @@ namespace Multiplayer 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 SendEntityUpdateMessages(EntityReplicatorList& replicatorList); + void SendEntityRpcs(RpcMessages& rpcMessages, bool reliable); void MigrateEntityInternal(NetEntityId entityId); void OnEntityExitDomain(const ConstNetworkEntityHandle& entityHandle); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 3d6322eed5..e569389659 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -495,6 +494,11 @@ namespace Multiplayer 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 diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp index 43815da6c5..4247377629 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp @@ -336,7 +336,6 @@ namespace Multiplayer case PropertyPublisher::EntityReplicatorState::Deleting: { AZ_Assert(m_serializationPhase == PropertyPublisher::EntityReplicatorSerializationPhase::Prepared, "Unexpected serialization phase"); - FinalizeDeleteEntityRecord(sentId); } break; diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.cpp index 7c9ee2a667..e4e5a7bc53 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.cpp @@ -7,9 +7,16 @@ */ #include +#include namespace Multiplayer { + NullReplicationWindow::NullReplicationWindow(AzNetworking::IConnection* connection) + : m_connection(connection) + { + ; + } + bool NullReplicationWindow::ReplicationSetUpdateReady() { return true; @@ -36,6 +43,29 @@ namespace Multiplayer ; } + AzNetworking::PacketId NullReplicationWindow::SendEntityUpdateMessages(NetworkEntityUpdateVector& entityUpdateVector) + { + MultiplayerPackets::EntityUpdates entityUpdatePacket; + entityUpdatePacket.SetHostTimeMs(GetNetworkTime()->GetHostTimeMs()); + entityUpdatePacket.SetHostFrameId(GetNetworkTime()->GetHostFrameId()); + entityUpdatePacket.SetEntityMessages(entityUpdateVector); + return m_connection->SendUnreliablePacket(entityUpdatePacket); + } + + void NullReplicationWindow::SendEntityRpcs(NetworkEntityRpcVector& entityRpcVector, bool reliable) + { + MultiplayerPackets::EntityRpcs entityRpcsPacket; + entityRpcsPacket.SetEntityRpcs(entityRpcVector); + if (reliable) + { + m_connection->SendReliablePacket(entityRpcsPacket); + } + else + { + m_connection->SendUnreliablePacket(entityRpcsPacket); + } + } + void NullReplicationWindow::DebugDraw() const { // Nothing to draw diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h index 91788a6b15..39e373f164 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h @@ -9,6 +9,7 @@ #pragma once #include +#include namespace Multiplayer { @@ -16,7 +17,7 @@ namespace Multiplayer : public IReplicationWindow { public: - NullReplicationWindow() = default; + NullReplicationWindow(AzNetworking::IConnection* connection); //! IReplicationWindow interface //! @{ @@ -25,10 +26,13 @@ namespace Multiplayer uint32_t GetMaxProxyEntityReplicatorSendCount() const override; bool IsInWindow(const ConstNetworkEntityHandle& entityPtr, NetEntityRole& outNetworkRole) const override; void UpdateWindow() override; + AzNetworking::PacketId SendEntityUpdateMessages(NetworkEntityUpdateVector& entityUpdateVector) override; + void SendEntityRpcs(NetworkEntityRpcVector& entityRpcVector, bool reliable) override; void DebugDraw() const override; //! @} private: ReplicationSet m_emptySet; + AzNetworking::IConnection* m_connection = nullptr; }; } diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index 3677eb8b5c..740495b606 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -50,7 +51,7 @@ namespace Multiplayer return m_priority < rhs.m_priority; } - ServerToClientReplicationWindow::ServerToClientReplicationWindow(NetworkEntityHandle controlledEntity, const AzNetworking::IConnection* connection) + ServerToClientReplicationWindow::ServerToClientReplicationWindow(NetworkEntityHandle controlledEntity, AzNetworking::IConnection* connection) : m_controlledEntity(controlledEntity) , m_entityActivatedEventHandler([this](AZ::Entity* entity) { OnEntityActivated(entity); }) , m_entityDeactivatedEventHandler([this](AZ::Entity* entity) { OnEntityDeactivated(entity); }) @@ -179,6 +180,29 @@ namespace Multiplayer //} } + AzNetworking::PacketId ServerToClientReplicationWindow::SendEntityUpdateMessages(NetworkEntityUpdateVector& entityUpdateVector) + { + MultiplayerPackets::EntityUpdates entityUpdatePacket; + entityUpdatePacket.SetHostTimeMs(GetNetworkTime()->GetHostTimeMs()); + entityUpdatePacket.SetHostFrameId(GetNetworkTime()->GetHostFrameId()); + entityUpdatePacket.SetEntityMessages(entityUpdateVector); + return m_connection->SendUnreliablePacket(entityUpdatePacket); + } + + void ServerToClientReplicationWindow::SendEntityRpcs(NetworkEntityRpcVector& entityRpcVector, bool reliable) + { + MultiplayerPackets::EntityRpcs entityRpcsPacket; + entityRpcsPacket.SetEntityRpcs(entityRpcVector); + if (reliable) + { + m_connection->SendReliablePacket(entityRpcsPacket); + } + else + { + m_connection->SendUnreliablePacket(entityRpcsPacket); + } + } + void ServerToClientReplicationWindow::DebugDraw() const { //static const float BoundaryStripeHeight = 1.0f; diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h index bfaa351095..b034bde90c 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h @@ -38,7 +38,7 @@ namespace Multiplayer // we sort lowest priority first, so that we can easily keep the biggest N priorities using ReplicationCandidateQueue = AZStd::priority_queue; - ServerToClientReplicationWindow(NetworkEntityHandle controlledEntity, const AzNetworking::IConnection* connection); + ServerToClientReplicationWindow(NetworkEntityHandle controlledEntity, AzNetworking::IConnection* connection); //! IReplicationWindow interface //! @{ @@ -47,6 +47,8 @@ namespace Multiplayer uint32_t GetMaxProxyEntityReplicatorSendCount() const override; bool IsInWindow(const ConstNetworkEntityHandle& entityPtr, NetEntityRole& outNetworkRole) const override; void UpdateWindow() override; + AzNetworking::PacketId SendEntityUpdateMessages(NetworkEntityUpdateVector& entityUpdateVector) override; + void SendEntityRpcs(NetworkEntityRpcVector& entityRpcVector, bool reliable) override; void DebugDraw() const override; //! @} @@ -75,7 +77,7 @@ namespace Multiplayer //NetBindComponent* m_controlledNetBindComponent = nullptr; - const AzNetworking::IConnection* m_connection = nullptr; + AzNetworking::IConnection* m_connection = nullptr; // Cached values to detect a poor network connection uint32_t m_lastCheckedSentPackets = 0; diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index db76c76374..95fce35f8e 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -33,6 +33,9 @@ set(FILES Include/Multiplayer/MultiplayerConstants.h Include/Multiplayer/MultiplayerStats.h Include/Multiplayer/MultiplayerTypes.h + 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/NetworkEntity/IFilterEntityManager.h Include/Multiplayer/NetworkEntity/INetworkEntityManager.h From 8d993494f64bd39b87ac25f6e9028b9c0ede28fe Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Thu, 30 Sep 2021 22:53:51 -0700 Subject: [PATCH 11/15] Entity migrations now totally functional again, plus some fixes to network rigid bodies to make them work properly as they migrate around Signed-off-by: kberg-amzn --- .../Components/NetworkRigidBodyComponent.h | 5 +- .../Multiplayer/EntityDomains/IEntityDomain.h | 3 +- .../Code/Include/Multiplayer/IMultiplayer.h | 10 ++++ .../EntityReplicationManager.h | 8 ++- .../NetworkEntity/INetworkEntityManager.h | 3 + .../NetworkEntity/NetworkEntityHandle.h | 14 +---- ...etworkRigidBodyComponent.AutoComponent.xml | 4 +- .../Source/Components/NetBindComponent.cpp | 14 +++-- .../Components/NetworkRigidBodyComponent.cpp | 28 +++++++--- .../FullOwnershipEntityDomain.cpp | 4 +- .../EntityDomains/FullOwnershipEntityDomain.h | 5 +- .../Source/MultiplayerSystemComponent.cpp | 22 +++++++- .../Code/Source/MultiplayerSystemComponent.h | 3 + .../EntityReplicationManager.cpp | 19 +++++-- .../EntityReplication/EntityReplicator.cpp | 16 +++--- .../NetworkEntity/NetworkEntityHandle.cpp | 39 +++---------- .../NetworkEntity/NetworkEntityManager.cpp | 48 ++++++++++------ .../NetworkEntity/NetworkEntityManager.h | 5 +- .../NetworkEntity/NetworkEntityTracker.cpp | 15 ++++- .../NetworkEntity/NetworkEntityTracker.h | 22 +++++++- .../NetworkEntity/NetworkEntityTracker.inl | 10 ++++ .../Code/Source/NetworkTime/NetworkTime.cpp | 55 +++++++++++++------ .../ServerToClientReplicationWindow.cpp | 39 +++++++------ .../Code/Tests/ClientHierarchyTests.cpp | 2 +- .../Code/Tests/CommonHierarchySetup.h | 6 +- Gems/Multiplayer/Code/Tests/MockInterfaces.h | 31 ++++++++--- .../Code/Tests/ServerHierarchyTests.cpp | 2 +- 27 files changed, 277 insertions(+), 155 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h index 796016afee..72fd00cad9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h @@ -61,10 +61,11 @@ namespace Multiplayer { public: NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent); - void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; - void HandleSendApplyImpulse(AzNetworking::IConnection* invokingConnection, const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) override; + private: + void OnTransformUpdate(); + AZ::TransformChangedEvent::Handler m_transformChangedHandler; }; } // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h index 092cf9b854..fc41a9b4d0 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h @@ -39,8 +39,7 @@ namespace Multiplayer virtual void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) = 0; //! Return the set of netbound entities not included in this domain. - //! @param outEntitiesNotInDomain the set of known networked entities not included in this domain - virtual void RetrieveEntitiesNotInDomain(EntitiesNotInDomain& outEntitiesNotInDomain) const = 0; + virtual const EntitiesNotInDomain& RetrieveEntitiesNotInDomain() const = 0; //! Debug draw to visualize host entity domains. virtual void DebugDraw() const = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index b0507e8fc6..32af39a43b 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -46,6 +46,7 @@ namespace Multiplayer using ClientMigrationEndEvent = AZ::Event<>; using ClientDisconnectedEvent = AZ::Event<>; using NotifyClientMigrationEvent = AZ::Event; + using NotifyEntityMigrationEvent = AZ::Event; using ConnectionAcquiredEvent = AZ::Event; using SessionInitEvent = AZ::Event; using SessionShutdownEvent = AZ::Event; @@ -113,6 +114,10 @@ namespace Multiplayer //! @param handler The NotifyClientMigrationEvent Handler to add virtual void AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler) = 0; + //! Adds a NotifyEntityMigrationEvent Handler which is invoked when an entity migrates from one host to another. + //! @param handler The NotifyEntityMigrationEvent Handler to add + virtual void AddNotifyEntityMigrationEventHandler(NotifyEntityMigrationEvent::Handler& handler) = 0; + //! Adds a ConnectionAcquiredEvent Handler which is invoked when a new endpoint connects to the session. //! @param handler The ConnectionAcquiredEvent Handler to add virtual void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) = 0; @@ -131,6 +136,11 @@ namespace Multiplayer //! @param lastClientInputId the last processed clientInputId by the current host virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) = 0; + //! Signals a NotifyEntityMigrationEvent with the provided parameters. + //! @param entityHandle the network entity handle of the entity being migrated + //! @param remoteHostId the host id of the host the entity is migrating to + virtual void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) = 0; + //! Sends a packet telling if entity update messages can be sent. //! @param readyForEntityUpdates Ready for entity updates or not virtual void SendReadyForEntityUpdates(bool readyForEntityUpdates) = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h index 01ae966fb5..2e1f83ae38 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -71,8 +72,8 @@ namespace Multiplayer bool HasRemoteAuthority(const ConstNetworkEntityHandle& entityHandle) const; - void SetEntityDomain(AZStd::unique_ptr entityDomain); - IEntityDomain* GetEntityDomain(); + void SetRemoteEntityDomain(AZStd::unique_ptr entityDomain); + IEntityDomain* GetRemoteEntityDomain(); void SetReplicationWindow(AZStd::unique_ptr replicationWindow); IReplicationWindow* GetReplicationWindow(); @@ -125,7 +126,7 @@ namespace Multiplayer void MigrateEntityInternal(NetEntityId entityId); void OnEntityExitDomain(const ConstNetworkEntityHandle& entityHandle); - void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId); + void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId); EntityReplicator* AddEntityReplicator(const ConstNetworkEntityHandle& entityHandle, NetEntityRole netEntityRole); @@ -193,6 +194,7 @@ namespace Multiplayer AZ::Event m_autonomousEntityReplicatorCreated; EntityExitDomainEvent::Handler m_entityExitDomainEventHandler; SendMigrateEntityEvent m_sendMigrateEntityEvent; + NotifyEntityMigrationEvent::Handler m_notifyEntityMigrationHandler; AZ::ScheduledEvent m_clearRemovedReplicators; AZ::ScheduledEvent m_updateWindow; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 65bb1ad966..43915127ed 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -180,5 +180,8 @@ namespace Multiplayer //! Handle a local rpc message. //! @param entityRpcMessage the local rpc message to handle virtual void HandleLocalRpcMessage(NetworkEntityRpcMessage& message) = 0; + + //! Visualization of network entity manager state. + virtual void DebugDraw() const = 0; }; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h index 8deb3d92ce..3281bd40e9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h @@ -30,18 +30,8 @@ namespace Multiplayer //! Constructs a ConstNetworkEntityHandle given an entity, an entity tracker //! @param entity pointer to the entity to construct a ConstNetworkEntityHandle for //! @param entityTracker pointer to the entity tracker that tracks the entity - ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* entityTracker); - - //! Constructs a ConstNetworkEntityHandle given an entity, a networkEntityId, and an entity tracker - //! @param entity pointer to the entity to construct a ConstNetworkEntityHandle for - //! @param netEntityId the networkEntityId of the entity - //! @param entityTracker pointer to the entity tracker that tracks the entity - ConstNetworkEntityHandle(AZ::Entity* entity, NetEntityId netEntityId, const NetworkEntityTracker* entityTracker); - - //! Constructs a ConstNetworkEntityHandle given an entity, a networked entityId, an entity tracker, and a dirty version - //! @param netBindComponent pointer to the entities NetBindComponent - //! @param entityTracker pointer to the entity tracker that tracks the entity - ConstNetworkEntityHandle(NetBindComponent* netBindComponent, const NetworkEntityTracker* entityTracker); + //! can optionally be null in which case the entity tracker will be looked up + ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* entityTracker = nullptr); ConstNetworkEntityHandle(const ConstNetworkEntityHandle&) = default; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml index b6cdfca9c2..a85f8c833f 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml @@ -10,9 +10,11 @@ + + + - diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index 524400d008..4e183d22f1 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -58,7 +59,7 @@ namespace Multiplayer return false; } - NetBindComponent* netBindComponent = entity-> FindComponent(); + NetBindComponent* netBindComponent = GetNetworkEntityTracker()->GetNetBindComponent(entity); if (!netBindComponent) { AZ_Warning( "NetBindComponent", false, "NetBindComponent IsNetEntityRoleAuthority failed. Entity '%s' (id: %s) is missing a NetBindComponent, make sure this entity contains a component which derives from NetBindComponent.", entity->GetName().c_str(), id.ToString().c_str()) @@ -75,7 +76,7 @@ namespace Multiplayer return false; } - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = GetNetworkEntityTracker()->GetNetBindComponent(entity); if (!netBindComponent) { AZ_Warning("NetBindComponent", false, "NetBindComponent IsNetEntityRoleAutonomous failed. Entity '%s' (id: %s) is missing a NetBindComponent, make sure this entity contains a component which derives from NetBindComponent.", entity->GetName().c_str(), id.ToString().c_str()) @@ -92,7 +93,7 @@ namespace Multiplayer return false; } - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = GetNetworkEntityTracker()->GetNetBindComponent(entity); if (!netBindComponent) { AZ_Warning("NetBindComponent", false, "NetBindComponent IsNetEntityRoleClient failed. Entity '%s' (id: %s) is missing a NetBindComponent, make sure this entity contains a component which derives from NetBindComponent.", entity->GetName().c_str(), id.ToString().c_str()) @@ -109,7 +110,7 @@ namespace Multiplayer return false; } - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = GetNetworkEntityTracker()->GetNetBindComponent(entity); if (!netBindComponent) { AZ_Warning("NetBindComponent", false, "NetBindComponent IsNetEntityRoleServer failed. Entity '%s' (id: %s) is missing a NetBindComponent, make sure this entity contains a component which derives from NetBindComponent.", entity->GetName().c_str(), id.ToString().c_str()) @@ -171,6 +172,8 @@ namespace Multiplayer { GetNetworkEntityManager()->NotifyControllersDeactivated(m_netEntityHandle, EntityIsMigrating::False); } + + GetNetworkEntityTracker()->UnregisterNetBindComponent(this); } NetEntityRole NetBindComponent::GetNetEntityRole() const @@ -502,6 +505,9 @@ namespace Multiplayer m_netEntityId = netEntityId; m_netEntityRole = netEntityRole; m_prefabEntityId = prefabEntityId; + + GetNetworkEntityTracker()->RegisterNetBindComponent(entity, this); + m_netEntityHandle = GetNetworkEntityManager()->AddEntityToEntityMap(m_netEntityId, entity); for (AZ::Component* component : entity->GetComponents()) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp index 725ebc024c..7e25c8d34b 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp @@ -57,16 +57,13 @@ namespace Multiplayer NetworkRigidBodyRequestBus::Handler::BusConnect(GetEntityId()); GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler); - GetEntity()->FindComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); + GetEntity()->GetTransform()->BindTransformChangedEventHandler(m_transformChangedHandler); m_physicsRigidBodyComponent = Physics::RigidBodyRequestBus::FindFirstHandler(GetEntity()->GetId()); AZ_Assert(m_physicsRigidBodyComponent, "PhysX Rigid Body Component is required on entity %s", GetEntity()->GetName().c_str()); - - if (!HasController()) - { - m_physicsRigidBodyComponent->SetKinematic(true); - } + // By default we're kinematic, activating a controller will allow us to simulate + m_physicsRigidBodyComponent->SetKinematic(true); } void NetworkRigidBodyComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -121,18 +118,26 @@ namespace Multiplayer NetworkRigidBodyComponentController::NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent) : NetworkRigidBodyComponentControllerBase(parent) + , m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform&) { OnTransformUpdate(); }) { ; } void NetworkRigidBodyComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { - ; + GetParent().m_physicsRigidBodyComponent->SetKinematic(false); + if (IsAuthority()) + { + AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody(); + rigidBody->SetLinearVelocity(GetLinearVelocity()); + rigidBody->SetAngularVelocity(GetAngularVelocity()); + GetEntity()->GetTransform()->BindTransformChangedEventHandler(m_transformChangedHandler); + } } void NetworkRigidBodyComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { - ; + GetParent().m_physicsRigidBodyComponent->SetKinematic(true); } void NetworkRigidBodyComponentController::HandleSendApplyImpulse @@ -145,4 +150,11 @@ namespace Multiplayer AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody(); rigidBody->ApplyLinearImpulseAtWorldPoint(impulse, worldPoint); } + + void NetworkRigidBodyComponentController::OnTransformUpdate() + { + AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody(); + SetLinearVelocity(rigidBody->GetLinearVelocity()); + SetAngularVelocity(rigidBody->GetAngularVelocity()); + } } // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp index 5b28208cd9..9d53990fb4 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp @@ -31,9 +31,9 @@ namespace Multiplayer ; } - void FullOwnershipEntityDomain::RetrieveEntitiesNotInDomain([[maybe_unused]] EntitiesNotInDomain& outEntitiesNotInDomain) const + const IEntityDomain::EntitiesNotInDomain& FullOwnershipEntityDomain::RetrieveEntitiesNotInDomain() const { - ; + return m_entitiesNotInDomain; } void FullOwnershipEntityDomain::DebugDraw() const diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h index ddf09e31d5..ae80c16ab8 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h @@ -25,8 +25,11 @@ namespace Multiplayer 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; + const EntitiesNotInDomain& RetrieveEntitiesNotInDomain() const override; void DebugDraw() const override; //! @} + + private: + EntitiesNotInDomain m_entitiesNotInDomain; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index cb72fe9252..407a5b2140 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -85,6 +85,7 @@ namespace Multiplayer AZ_CVAR(float, cl_renderTickBlendBase, 0.15f, nullptr, AZ::ConsoleFunctorFlags::Null, "The base used for blending between network updates, 0.1 will be quite linear, 0.2 or 0.3 will " "slow down quicker and may be better suited to connections with highly variable latency"); + AZ_CVAR(bool, bg_multiplayerDebugDraw, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables debug draw for the multiplayer gem"); void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -390,6 +391,11 @@ namespace Multiplayer { m_networkInterface->GetConnectionSet().VisitConnections(visitor); } + + if (bg_multiplayerDebugDraw) + { + m_networkEntityManager.DebugDraw(); + } } int MultiplayerSystemComponent::GetTickOrder() @@ -802,6 +808,11 @@ namespace Multiplayer handler.Connect(m_notifyClientMigrationEvent); } + void MultiplayerSystemComponent::AddNotifyEntityMigrationEventHandler(NotifyEntityMigrationEvent::Handler& handler) + { + handler.Connect(m_notifyEntityMigrationEvent); + } + void MultiplayerSystemComponent::AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) { handler.Connect(m_connectionAcquiredEvent); @@ -822,6 +833,11 @@ namespace Multiplayer m_notifyClientMigrationEvent.Signal(hostId, userIdentifier, lastClientInputId); } + void MultiplayerSystemComponent::SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) + { + m_notifyEntityMigrationEvent.Signal(entityHandle, remoteHostId); + } + void MultiplayerSystemComponent::SendReadyForEntityUpdates(bool readyForEntityUpdates) { IConnectionSet& connectionSet = m_networkInterface->GetConnectionSet(); @@ -937,7 +953,7 @@ namespace Multiplayer // Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system AZStd::vector gatheredEntities; AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum, - [&gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData) + [this, &gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData) { gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) @@ -945,7 +961,7 @@ namespace Multiplayer if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) { AZ::Entity* entity = static_cast(visEntry->m_userData); - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = m_networkEntityManager.GetNetworkEntityTracker()->GetNetBindComponent(entity); if (netBindComponent != nullptr) { gatheredEntities.push_back(netBindComponent); @@ -965,7 +981,7 @@ namespace Multiplayer for (auto& iter : *(m_networkEntityManager.GetNetworkEntityTracker())) { AZ::Entity* entity = iter.second; - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = m_networkEntityManager.GetNetworkEntityTracker()->GetNetBindComponent(entity); if (netBindComponent != nullptr) { netBindComponent->NotifyPreRender(deltaTime); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index d24b143da0..262168b536 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -112,10 +112,12 @@ namespace Multiplayer void AddClientMigrationEndEventHandler(ClientMigrationEndEvent::Handler& handler) override; void AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler) override; void AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler) override; + void AddNotifyEntityMigrationEventHandler(NotifyEntityMigrationEvent::Handler& handler) override; void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; + void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; AZ::TimeMs GetCurrentHostTimeMs() const override; float GetCurrentBlendFactor() const override; @@ -159,6 +161,7 @@ namespace Multiplayer ClientMigrationStartEvent m_clientMigrationStartEvent; ClientMigrationEndEvent m_clientMigrationEndEvent; NotifyClientMigrationEvent m_notifyClientMigrationEvent; + NotifyEntityMigrationEvent m_notifyEntityMigrationEvent; 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 cdda18e43a..6f65d01e50 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -45,6 +45,7 @@ namespace Multiplayer , m_clearRemovedReplicators([this]() { ClearRemovedReplicators(); }, AZ::Name("EntityReplicationManager::ClearRemovedReplicators")) , m_updateWindow([this]() { UpdateWindow(); }, AZ::Name("EntityReplicationManager::UpdateWindow")) , m_entityExitDomainEventHandler([this](const ConstNetworkEntityHandle& entityHandle) { OnEntityExitDomain(entityHandle); }) + , m_notifyEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { OnPostEntityMigration(entityHandle, remoteHostId); }) { // Our max payload size is whatever is passed in, minus room for a udp packetheader m_maxPayloadSize = connection.GetConnectionMtu() - UdpPacketHeaderSerializeSize - ReplicationManagerPacketOverhead; @@ -60,6 +61,8 @@ namespace Multiplayer { networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler); } + + GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler); } void EntityReplicationManager::SetRemoteHostId(const HostId& hostId) @@ -355,8 +358,9 @@ namespace Multiplayer entityReplicator = GetEntityReplicator(entityHandle); if (entityReplicator) { - // Check if we changed our remote role - this can happen during server entity migration. After we migrate ownership to the new server, we hold onto our entity replicator until we are sure - // the other side has received all the packets (and we haven't had to do resends). At this point, it is possible hear back from the remote side we migrated to on the old replicator prior to the timeout and cleanup on the old one + // Check if we changed our remote role - this can happen during server entity migration. + // Retain our replicator after migration until we are sure the other side has received all the packets (and we haven't had to do resends). + // At this point, the remote host should inform us we've migrated prior to the timeout and cleanup of the old replicator const bool changedRemoteRole = (remoteNetworkRole != entityReplicator->GetRemoteNetworkRole()); // Check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client bool changedLocalRole(false); @@ -1068,12 +1072,12 @@ namespace Multiplayer return false; } - void EntityReplicationManager::SetEntityDomain(AZStd::unique_ptr entityDomain) + void EntityReplicationManager::SetRemoteEntityDomain(AZStd::unique_ptr entityDomain) { m_remoteEntityDomain = AZStd::move(entityDomain); } - IEntityDomain* EntityReplicationManager::GetEntityDomain() + IEntityDomain* EntityReplicationManager::GetRemoteEntityDomain() { return m_remoteEntityDomain.get(); } @@ -1143,6 +1147,9 @@ namespace Multiplayer m_sendMigrateEntityEvent.Signal(m_connection, message); AZLOG(NET_RepDeletes, "Migration packet sent %u to remote host %s", netEntityId, GetRemoteHostId().GetString().c_str()); + // Notify all other EntityReplicationManagers that this entity has migrated so they can adjust their own replicators given our new proxy status + GetMultiplayer()->SendNotifyEntityMigrationEvent(entityHandle, 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 AddEntityReplicator(entityHandle, NetEntityRole::Authority); } @@ -1206,11 +1213,11 @@ namespace Multiplayer } } - void EntityReplicationManager::OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, [[maybe_unused]] AzNetworking::ConnectionId connectionId) + void EntityReplicationManager::OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { if (remoteHostId == GetRemoteHostId()) { - // don't handle self sent messages + // Don't handle self sent messages return; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index e569389659..05001b5960 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -54,7 +54,7 @@ namespace Multiplayer { if (auto localEnt = m_entityHandle.GetEntity()) { - m_netBindComponent = localEnt->FindComponent(); + m_netBindComponent = m_entityHandle.GetNetBindComponent(); m_boundLocalNetworkRole = m_netBindComponent->GetNetEntityRole(); } } @@ -94,7 +94,7 @@ namespace Multiplayer m_entityHandle = entityHandle; if (auto localEntity = m_entityHandle.GetEntity()) { - m_netBindComponent = localEntity->FindComponent(); + m_netBindComponent = m_entityHandle.GetNetBindComponent(); AZ_Assert(m_netBindComponent, "No Multiplayer::NetBindComponent"); m_boundLocalNetworkRole = m_netBindComponent->GetNetEntityRole(); SetPrefabEntityId(m_netBindComponent->GetPrefabEntityId()); @@ -125,7 +125,8 @@ namespace Multiplayer !RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False, m_netBindComponent, *m_connection - ); + ); + m_onEntityDirtiedHandler.Disconnect(); m_netBindComponent->AddEntityDirtiedEventHandler(m_onEntityDirtiedHandler); } else @@ -146,8 +147,9 @@ namespace Multiplayer // Prepare event handlers if (auto localEntity = m_entityHandle.GetEntity()) { - NetBindComponent* netBindComponent = localEntity->FindComponent(); + NetBindComponent* netBindComponent = m_entityHandle.GetNetBindComponent(); AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); + m_onEntityStopHandler.Disconnect(); netBindComponent->AddEntityStopEventHandler(m_onEntityStopHandler); AttachRPCHandlers(); } @@ -168,7 +170,7 @@ namespace Multiplayer if (auto localEntity = m_entityHandle.GetEntity()) { - NetBindComponent* netBindComponent = localEntity->FindComponent(); + NetBindComponent* netBindComponent = m_entityHandle.GetNetBindComponent(); AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); switch (GetBoundLocalNetworkRole()) @@ -479,10 +481,10 @@ namespace Multiplayer } NetBindComponent* netBindComponent = GetNetBindComponent(); - const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished(); + //const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished(); NetworkEntityUpdateMessage updateMessage(GetRemoteNetworkRole(), GetEntityHandle().GetNetEntityId()); - if (sendSliceName) + //if (sendSliceName) { updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId()); } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp index 6f3bd719e0..ef338840f9 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp @@ -20,6 +20,11 @@ namespace Multiplayer : m_entity(entity) , m_networkEntityTracker(networkEntityTracker) { + if (m_networkEntityTracker == nullptr) + { + m_networkEntityTracker = GetNetworkEntityTracker(); + } + if (m_networkEntityTracker) { m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity); @@ -28,12 +33,10 @@ namespace Multiplayer if (entity) { AZ_Assert(networkEntityTracker, "NetworkEntityTracker is not valid"); - NetBindComponent* netBindComponent = m_entity->template FindComponent(); - if (netBindComponent != nullptr) + m_netBindComponent = networkEntityTracker->GetNetBindComponent(entity); + if (m_netBindComponent != nullptr) { - AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); - m_netBindComponent = netBindComponent; - m_netEntityId = netBindComponent->GetNetEntityId(); + m_netEntityId = m_netBindComponent->GetNetEntityId(); } else { @@ -42,30 +45,6 @@ namespace Multiplayer } } - ConstNetworkEntityHandle::ConstNetworkEntityHandle(AZ::Entity* entity, NetEntityId netEntityId, const NetworkEntityTracker* networkEntityTracker) - : m_entity(entity) - , m_netEntityId(netEntityId) - , m_networkEntityTracker(networkEntityTracker) - { - if (m_networkEntityTracker) - { - m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity); - } - } - - ConstNetworkEntityHandle::ConstNetworkEntityHandle(NetBindComponent* netBindComponent, const NetworkEntityTracker* networkEntityTracker) - : m_entity(netBindComponent->GetEntity()) - , m_netBindComponent(netBindComponent) - , m_networkEntityTracker(networkEntityTracker) - , m_netEntityId(netBindComponent->GetNetEntityId()) - { - if (m_networkEntityTracker) - { - m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity); - } - AZ_Assert(networkEntityTracker, "NetworkEntityTracker is not valid"); - } - bool ConstNetworkEntityHandle::Exists() const { if (!m_networkEntityTracker) @@ -151,7 +130,7 @@ namespace Multiplayer } if (m_netBindComponent == nullptr) { - m_netBindComponent = m_entity->template FindComponent(); + m_netBindComponent = m_networkEntityTracker->GetNetBindComponent(m_entity); } return m_netBindComponent; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 720f869633..93a816fdcf 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -46,6 +48,7 @@ namespace Multiplayer m_hostId = hostId; m_entityDomain = AZStd::move(entityDomain); m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true); + m_entityDomain->ActivateTracking(m_ownedEntities); } bool NetworkEntityManager::IsInitialized() const @@ -96,7 +99,7 @@ namespace Multiplayer NetworkEntityHandle NetworkEntityManager::AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) { m_networkEntityTracker.Add(netEntityId, entity); - return NetworkEntityHandle(entity, netEntityId, &m_networkEntityTracker); + return NetworkEntityHandle(entity, &m_networkEntityTracker); } void NetworkEntityManager::MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) @@ -212,6 +215,29 @@ namespace Multiplayer m_localDeferredRpcMessages.emplace_back(AZStd::move(message)); } + void NetworkEntityManager::DebugDraw() const + { + AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; + AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); + AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); + + for (NetworkEntityTracker::const_iterator it = m_networkEntityTracker.begin(); it != m_networkEntityTracker.end(); ++it) + { + AZ::Entity* entity = it->second; + NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); + if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) + { + const AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); + debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax()); + } + } + + if (m_entityDomain != nullptr) + { + m_entityDomain->DebugDraw(); + } + } + void NetworkEntityManager::DispatchLocalDeferredRpcMessages() { for (NetworkEntityRpcMessage& rpcMessage : m_localDeferredRpcMessages) @@ -219,7 +245,7 @@ namespace Multiplayer AZ::Entity* entity = m_networkEntityTracker.GetRaw(rpcMessage.GetEntityId()); if (entity != nullptr) { - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); AZ_Assert(netBindComponent != nullptr, "Attempting to send an RPC to an entity with no NetBindComponent"); netBindComponent->HandleRpcMessage(nullptr, NetEntityRole::Server, rpcMessage); } @@ -234,9 +260,8 @@ namespace Multiplayer return; } - m_entitiesNotInDomain.clear(); - m_entityDomain->RetrieveEntitiesNotInDomain(m_entitiesNotInDomain); - for (NetEntityId exitingId : m_entitiesNotInDomain) + const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain = m_entityDomain->RetrieveEntitiesNotInDomain(); + for (NetEntityId exitingId : entitiesNotInDomain) { OnEntityExitDomain(exitingId); } @@ -247,18 +272,6 @@ namespace Multiplayer bool safeToExit = true; NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(entityId); - // ClientAutonomous entities need special handling here. When we migrate a player's entity the player's client must tell the new server which - // entity they were controlling. If we tell them to migrate before they know which entity they control it results in them requesting a new entity - // from the new server, resulting in an orphaned PlayerChar. PlayerControllerComponentServerAuthority::PlayerClientHasControlledEntity() - // will tell us whether the client sent an RPC acknowledging that they now know which entity is theirs. - if (AZ::Entity* entity = entityHandle.GetEntity()) - { - //if (PlayerComponent::Authority* playerController = FindController(nonConstExitingEntityPtr)) - //{ - // safeToExit = playerController->PlayerClientHasControlledEntity(); - //} - } - // We also need special handling for the EntityHierarchyComponent as well, since related entities need to be migrated together //auto* hierarchyController = FindController(nonConstExitingEntityPtr); //if (hierarchyController) @@ -338,6 +351,7 @@ namespace Multiplayer originalToCloneIdMap[originalEntity->GetId()] = clone->GetId(); + // Can't use NetworkEntityTracker to do the lookup since the entity has not activated yet NetBindComponent* netBindComponent = clone->FindComponent(); if (netBindComponent != nullptr) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 7ff47f3f75..13b8f0e778 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -62,9 +62,7 @@ namespace Multiplayer AZStd::unique_ptr RequestNetSpawnableInstantiation( const AZ::Data::Asset& netSpawnable, const AZ::Transform& transform) override; - void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override; - uint32_t GetEntityCount() const override; NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) override; void MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) override; @@ -81,11 +79,13 @@ namespace Multiplayer void NotifyControllersActivated(const ConstNetworkEntityHandle& entityHandle, EntityIsMigrating entityIsMigrating) override; void NotifyControllersDeactivated(const ConstNetworkEntityHandle& entityHandle, EntityIsMigrating entityIsMigrating) override; void HandleLocalRpcMessage(NetworkEntityRpcMessage& message) override; + void DebugDraw() const override; //! @} void DispatchLocalDeferredRpcMessages(); void UpdateEntityDomain(); void OnEntityExitDomain(NetEntityId entityId); + //! RootSpawnableNotificationBus //! @{ void OnRootSpawnableAssigned(AZ::Data::Asset rootSpawnable, uint32_t generation) override; @@ -105,7 +105,6 @@ namespace Multiplayer AZStd::unique_ptr m_entityDomain; AZ::ScheduledEvent m_updateEntityDomainEvent; - IEntityDomain::EntitiesNotInDomain m_entitiesNotInDomain; OwnedEntitySet m_ownedEntities; EntityExitDomainEvent m_entityExitDomainEvent; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp index e31907461b..928f5b4f80 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -21,16 +22,26 @@ namespace Multiplayer m_netEntityIdMap[entity->GetId()] = netEntityId; } + void NetworkEntityTracker::RegisterNetBindComponent(AZ::Entity* entity, NetBindComponent* component) + { + m_netBindingMap[entity] = component; + } + + void NetworkEntityTracker::UnregisterNetBindComponent(NetBindComponent* component) + { + m_netBindingMap.erase(component->GetEntity()); + } + NetworkEntityHandle NetworkEntityTracker::Get(NetEntityId netEntityId) { AZ::Entity* entity = GetRaw(netEntityId); - return NetworkEntityHandle(entity, netEntityId, this); + return NetworkEntityHandle(entity, this); } ConstNetworkEntityHandle NetworkEntityTracker::Get(NetEntityId netEntityId) const { AZ::Entity* entity = GetRaw(netEntityId); - return ConstNetworkEntityHandle(entity, netEntityId, this); + return ConstNetworkEntityHandle(entity, this); } NetEntityId NetworkEntityTracker::Get(const AZ::EntityId& entityId) const diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h index 0e632dc7b4..a7f5b9e585 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h @@ -15,6 +15,8 @@ namespace Multiplayer { + class NetBindComponent; + //! @class NetworkEntityTracker //! @brief The responsibly of this class is to allow entity netEntityId's to be looked up. class NetworkEntityTracker @@ -23,16 +25,26 @@ namespace Multiplayer using EntityMap = AZStd::unordered_map; using NetEntityIdMap = AZStd::unordered_map; + using NetBindingMap = AZStd::unordered_map; using iterator = EntityMap::iterator; using const_iterator = EntityMap::const_iterator; NetworkEntityTracker() = default; - //! Adds a networked entity to the tracker + //! Adds a networked entity to the tracker. //! @param netEntityId the networkId of the entity to add //! @param entity pointer to the entity corresponding to the networkId void Add(NetEntityId netEntityId, AZ::Entity* entity); + //! Registers a new NetBindComponent with the NetworkEntityTracker. + //! @param entity pointer to the entity we are registering the NetBindComponent for + //! @param component pointer to the NetBindComponent being registered + void RegisterNetBindComponent(AZ::Entity* entity, NetBindComponent* component); + + //! Unregisters a NetBindComponent from the NetworkEntityTracker. + //! @param component pointer to the NetBindComponent being removed + void UnregisterNetBindComponent(NetBindComponent* component); + //! Returns an entity handle which can validate entity existence. NetworkEntityHandle Get(NetEntityId netEntityId); ConstNetworkEntityHandle Get(NetEntityId netEntityId) const; @@ -46,9 +58,14 @@ namespace Multiplayer //! Get a raw pointer of an entity. AZ::Entity *GetRaw(NetEntityId netEntityId) const; - //! Moves the given iterator out of the entity holder and returns the ptr + //! Moves the given iterator out of the entity holder and returns the ptr. AZ::Entity *Move(EntityMap::iterator iter); + //! Retrieves the NetBindComponent for the provided AZ::Entity, nullptr if the entity does not have netbinding. + //! @param entity pointer to the entity to retrieve the NetBindComponent for + //! @return pointer to the entities NetBindComponent, or nullptr if the entity doesn't exist or does not have netbinding + NetBindComponent* GetNetBindComponent(AZ::Entity* rawEntity) const; + //! Container overloads //!@{ iterator begin(); @@ -79,6 +96,7 @@ namespace Multiplayer EntityMap m_entityMap; NetEntityIdMap m_netEntityIdMap; + NetBindingMap m_netBindingMap; uint32_t m_deleteChangeDirty = 0; uint32_t m_addChangeDirty = 0; }; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.inl b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.inl index 44098336be..e9210e7a9f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.inl +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.inl @@ -10,6 +10,16 @@ namespace Multiplayer { + inline NetBindComponent* NetworkEntityTracker::GetNetBindComponent(AZ::Entity* rawEntity) const + { + auto found = m_netBindingMap.find(rawEntity); + if (found != m_netBindingMap.end()) + { + return found->second; + } + return nullptr; + } + inline NetworkEntityTracker::iterator NetworkEntityTracker::begin() { return m_entityMap.begin(); diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index 9e69d93e16..59dddf5652 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -13,10 +13,12 @@ #include #include #include +#include namespace Multiplayer { AZ_CVAR(float, sv_RewindVolumeExtrudeDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The amount to increase rewind volume checks to account for fast moving entities"); + AZ_CVAR(bool, bg_RewindDebugDraw, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If true enables debug draw of rewind operations"); NetworkTime::NetworkTime() { @@ -94,21 +96,46 @@ namespace Multiplayer // Since the vis system doesn't support rewound queries, first query with an expanded volume to catch any fast moving entities const AZ::Aabb expandedVolume = rewindVolume.GetExpanded(AZ::Vector3(sv_RewindVolumeExtrudeDistance)); - AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); - AZStd::vector gatheredEntities; - AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(expandedVolume, - [entityBoundsUnion, rewindVolume, &gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData) + AzFramework::DebugDisplayRequests* debugDisplay = nullptr; + if (bg_RewindDebugDraw) { - gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); + AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; + AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); + debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); + } + + if (debugDisplay) + { + debugDisplay->SetColor(AZ::Colors::Red); + debugDisplay->DrawWireBox(expandedVolume.GetMin(), expandedVolume.GetMax()); + } + + NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); + AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); + AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(expandedVolume, + [this, debugDisplay, networkEntityTracker, entityBoundsUnion, rewindVolume](const AzFramework::IVisibilityScene::NodeData& nodeData) + { + m_rewoundEntities.reserve(m_rewoundEntities.size() + nodeData.m_entries.size()); for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) { if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) { AZ::Entity* entity = static_cast(visEntry->m_userData); + NetworkEntityHandle entityHandle(entity, networkEntityTracker); + if (entityHandle.GetNetBindComponent() == nullptr) + { + // Not a net-bound entity, terminate processing of this entity + return; + } + const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityWorldBoundsUnion(entity->GetId()); const AZ::Vector3 currentCenter = currentBounds.GetCenter(); - NetworkTransformComponent* networkTransform = entity->template FindComponent(); + if (debugDisplay) + { + debugDisplay->SetColor(AZ::Colors::White); + debugDisplay->DrawWireBox(currentBounds.GetMin(), currentBounds.GetMax()); + } if (networkTransform != nullptr) { @@ -123,24 +150,20 @@ namespace Multiplayer } const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb + if (debugDisplay) + { + debugDisplay->SetColor(AZ::Colors::Grey); + debugDisplay->DrawWireBox(rewoundAabb.GetMin(), rewoundAabb.GetMax()); + } if (AZ::ShapeIntersection::Overlaps(rewoundAabb, rewindVolume)) // Validate the rewound aabb intersects our rewind volume { - // Due to component constraints, netBindComponent must exist if networkTransform exists - NetBindComponent* netBindComponent = entity->template FindComponent(); - gatheredEntities.push_back(netBindComponent); + m_rewoundEntities.push_back(entityHandle); } } } } }); - - NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); - for (NetBindComponent* netBindComponent : gatheredEntities) - { - netBindComponent->NotifySyncRewindState(); - m_rewoundEntities.push_back(NetworkEntityHandle(netBindComponent, networkEntityTracker)); - } } void NetworkTime::ClearRewoundEntities() diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index 740495b606..9d9cc74d2b 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -107,7 +107,7 @@ namespace Multiplayer void ServerToClientReplicationWindow::UpdateWindow() { - // clear the candidate queue, we're going to rebuild it + // Clear the candidate queue, we're going to rebuild it ReplicationCandidateQueue::container_type clearQueueContainer; clearQueueContainer.reserve(sv_MaxEntitiesToTrackReplication); // Move the clearQueueContainer into the ReplicationCandidateQueue to maintain the reserved memory @@ -118,7 +118,7 @@ namespace Multiplayer NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent(); if (!netBindComponent || !netBindComponent->HasController()) { - // if we don't have a controlled entity, or we no longer have control of the entity, don't run the update + // If we don't have a controlled entity, or we no longer have control of the entity, don't run the update return; } @@ -149,24 +149,25 @@ namespace Multiplayer for (AzFramework::VisibilityEntry* visEntry : gatheredEntries) { AZ::Entity* entity = static_cast(visEntry->m_userData); + NetworkEntityHandle entityHandle(entity, networkEntityTracker); + if (entityHandle.GetNetBindComponent() == nullptr) + { + // Entity does not have netbinding, skip this entity + continue; + } if (filterEntityManager && filterEntityManager->IsEntityFiltered(entity, m_controlledEntity, m_connection->GetConnectionId())) { continue; } - NetBindComponent* entryNetBindComponent = entity->template FindComponent(); - if (entryNetBindComponent != nullptr) - { - // We want to find the closest extent to the player and prioritize using that distance - const AZ::Vector3 supportNormal = controlledEntityPosition - visEntry->m_boundingVolume.GetCenter(); - const AZ::Vector3 closestPosition = visEntry->m_boundingVolume.GetSupport(supportNormal); - const float gatherDistanceSquared = controlledEntityPosition.GetDistanceSq(closestPosition); - const float priority = (gatherDistanceSquared > 0.0f) ? 1.0f / gatherDistanceSquared : 0.0f; - - NetworkEntityHandle entityHandle(entryNetBindComponent, networkEntityTracker); - AddEntityToReplicationSet(entityHandle, priority, gatherDistanceSquared); - } + // We want to find the closest extent to the player and prioritize using that distance + const AZ::Vector3 supportNormal = controlledEntityPosition - visEntry->m_boundingVolume.GetCenter(); + const AZ::Vector3 closestPosition = visEntry->m_boundingVolume.GetSupport(supportNormal); + const float gatherDistanceSquared = controlledEntityPosition.GetDistanceSq(closestPosition); + const float priority = (gatherDistanceSquared > 0.0f) ? 1.0f / gatherDistanceSquared : 0.0f; + + AddEntityToReplicationSet(entityHandle, priority, gatherDistanceSquared); } // Add in Autonomous Entities @@ -228,11 +229,10 @@ namespace Multiplayer void ServerToClientReplicationWindow::OnEntityActivated(AZ::Entity* entity) { - NetBindComponent* netBindComponent = entity->FindComponent(); + ConstNetworkEntityHandle entityHandle(entity); + NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); if (netBindComponent != nullptr) { - ConstNetworkEntityHandle entityHandle(netBindComponent, GetNetworkEntityTracker()); - if (netBindComponent->HasController()) { if (IFilterEntityManager* filter = GetMultiplayer()->GetFilterEntityManager()) @@ -261,10 +261,9 @@ namespace Multiplayer void ServerToClientReplicationWindow::OnEntityDeactivated(AZ::Entity* entity) { - NetBindComponent* netBindComponent = entity->FindComponent(); - if (netBindComponent != nullptr) + ConstNetworkEntityHandle entityHandle(entity); + if (entityHandle.GetNetBindComponent() != nullptr) { - ConstNetworkEntityHandle entityHandle(netBindComponent, GetNetworkEntityTracker()); m_replicationSet.erase(entityHandle); } } diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index 316f85c214..a58f23bae2 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h index 2deac1aa27..dba7fa5405 100644 --- a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -25,10 +25,10 @@ #include #include #include +#include +#include #include #include -#include -#include namespace Multiplayer { @@ -205,7 +205,7 @@ namespace Multiplayer NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) { m_networkEntityMap[netEntityId] = entity; - return NetworkEntityHandle(entity, netEntityId, m_networkEntityTracker.get()); + return NetworkEntityHandle(entity, m_networkEntityTracker.get()); } ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h index 42e034d234..44024f67ab 100644 --- a/Gems/Multiplayer/Code/Tests/MockInterfaces.h +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -19,42 +19,53 @@ namespace UnitTest class MockMultiplayer : public Multiplayer::IMultiplayer { public: - MOCK_CONST_METHOD0(GetCurrentBlendFactor, float ()); MOCK_CONST_METHOD0(GetAgentType, Multiplayer::MultiplayerAgentType()); MOCK_METHOD1(InitializeMultiplayer, void(Multiplayer::MultiplayerAgentType)); MOCK_METHOD2(StartHosting, bool(uint16_t, bool)); - MOCK_METHOD2(Connect, bool(AZStd::string, uint16_t)); + MOCK_METHOD2(Connect, bool(const AZStd::string&, uint16_t)); MOCK_METHOD1(Terminate, void(AzNetworking::DisconnectReason)); + MOCK_METHOD1(AddClientMigrationStartEventHandler, void(Multiplayer::ClientMigrationStartEvent::Handler&)); + MOCK_METHOD1(AddClientMigrationEndEventHandler, void(Multiplayer::ClientMigrationEndEvent::Handler&)); MOCK_METHOD1(AddClientDisconnectedHandler, void(AZ::Event<>::Handler&)); + MOCK_METHOD1(AddNotifyClientMigrationHandler, void(Multiplayer::NotifyClientMigrationEvent::Handler&)); + MOCK_METHOD1(AddNotifyEntityMigrationEventHandler, void(Multiplayer::NotifyEntityMigrationEvent::Handler&)); MOCK_METHOD1(AddConnectionAcquiredHandler, void(AZ::Event::Handler&)); MOCK_METHOD1(AddSessionInitHandler, void(AZ::Event::Handler&)); MOCK_METHOD1(AddSessionShutdownHandler, void(AZ::Event::Handler&)); + MOCK_METHOD3(SendNotifyClientMigrationEvent, void(const Multiplayer::HostId&, uint64_t, Multiplayer::ClientInputId)); + MOCK_METHOD2(SendNotifyEntityMigrationEvent, void(const Multiplayer::ConstNetworkEntityHandle&, const Multiplayer::HostId&)); MOCK_METHOD1(SendReadyForEntityUpdates, void(bool)); MOCK_CONST_METHOD0(GetCurrentHostTimeMs, AZ::TimeMs()); + MOCK_CONST_METHOD0(GetCurrentBlendFactor, float()); MOCK_METHOD0(GetNetworkTime, Multiplayer::INetworkTime* ()); MOCK_METHOD0(GetNetworkEntityManager, Multiplayer::INetworkEntityManager* ()); MOCK_METHOD1(SetFilterEntityManager, void(Multiplayer::IFilterEntityManager*)); MOCK_METHOD0(GetFilterEntityManager, Multiplayer::IFilterEntityManager* ()); + MOCK_METHOD1(SetShouldSpawnNetworkEntities, void(bool)); + MOCK_CONST_METHOD0(GetShouldSpawnNetworkEntities, bool()); }; class MockNetworkEntityManager : public Multiplayer::INetworkEntityManager { public: - MOCK_METHOD2(RequestNetSpawnableInstantiation, AZStd::unique_ptr (const AZ::Data::Asset&, const AZ::Transform&)); - MOCK_METHOD4( - CreateEntitiesImmediate, - EntityList (const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ::Transform&, Multiplayer::AutoActivate)); - MOCK_CONST_METHOD1(GetNetEntityIdById, Multiplayer::NetEntityId (const AZ::EntityId&)); + MOCK_METHOD2(Initialize, void(const Multiplayer::HostId&, AZStd::unique_ptr)); + MOCK_CONST_METHOD0(IsInitialized, bool()); + MOCK_CONST_METHOD0(GetEntityDomain, Multiplayer::IEntityDomain*()); MOCK_METHOD0(GetNetworkEntityTracker, Multiplayer::NetworkEntityTracker* ()); MOCK_METHOD0(GetNetworkEntityAuthorityTracker, Multiplayer::NetworkEntityAuthorityTracker* ()); MOCK_METHOD0(GetMultiplayerComponentRegistry, Multiplayer::MultiplayerComponentRegistry* ()); - MOCK_CONST_METHOD0(GetHostId, Multiplayer::HostId()); + MOCK_CONST_METHOD0(GetHostId, const Multiplayer::HostId&()); + MOCK_CONST_METHOD1(GetEntity, Multiplayer::ConstNetworkEntityHandle(Multiplayer::NetEntityId)); + MOCK_CONST_METHOD1(GetNetEntityIdById, Multiplayer::NetEntityId(const AZ::EntityId&)); MOCK_METHOD3(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ:: Transform&)); + MOCK_METHOD4( + CreateEntitiesImmediate, + EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ::Transform&, Multiplayer::AutoActivate)); MOCK_METHOD5(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityId, Multiplayer:: NetEntityRole, Multiplayer::AutoActivate, const AZ::Transform&)); + MOCK_METHOD2(RequestNetSpawnableInstantiation, AZStd::unique_ptr(const AZ::Data::Asset&, const AZ::Transform&)); MOCK_METHOD3(SetupNetEntity, void(AZ::Entity*, Multiplayer::PrefabEntityId, Multiplayer::NetEntityRole)); - MOCK_CONST_METHOD1(GetEntity, Multiplayer::ConstNetworkEntityHandle(Multiplayer::NetEntityId)); MOCK_CONST_METHOD0(GetEntityCount, uint32_t()); MOCK_METHOD2(AddEntityToEntityMap, Multiplayer::NetworkEntityHandle(Multiplayer::NetEntityId, AZ::Entity*)); MOCK_METHOD1(MarkForRemoval, void(const Multiplayer::ConstNetworkEntityHandle&)); @@ -73,6 +84,7 @@ namespace UnitTest MOCK_METHOD2(NotifyControllersActivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating)); MOCK_METHOD2(NotifyControllersDeactivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating)); MOCK_METHOD1(HandleLocalRpcMessage, void(Multiplayer::NetworkEntityRpcMessage&)); + MOCK_CONST_METHOD0(DebugDraw, void()); }; class MockConnectionListener : public AzNetworking::IConnectionListener @@ -88,6 +100,7 @@ namespace UnitTest class MockTime : public AZ::ITime { public: + MOCK_CONST_METHOD0(GetElapsedTimeUs, AZ::TimeUs()); MOCK_CONST_METHOD0(GetElapsedTimeMs, AZ::TimeMs()); }; diff --git a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp index 385a4b83ae..b8e892e71a 100644 --- a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include namespace Multiplayer { From bf136a567b538c8e2da7a29a821dbe7f9cd0f03d Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Fri, 1 Oct 2021 15:04:50 -0700 Subject: [PATCH 12/15] Some shutdown crash fixes, reverted a whitespace, and added some basic unit tests for time additions Signed-off-by: kberg-amzn --- Code/Framework/AzCore/AzCore/Math/Aabb.h | 2 - .../Framework/AzCore/Tests/Time/TimeTests.cpp | 56 +++++++++++++++++++ .../AzCore/Tests/azcoretests_files.cmake | 1 + .../Components/MultiplayerComponentRegistry.h | 3 + .../MultiplayerComponentRegistry.cpp | 5 ++ .../Source/MultiplayerSystemComponent.cpp | 2 + .../NetworkEntity/NetworkEntityManager.cpp | 15 +++++ .../NetworkEntity/NetworkEntityManager.h | 3 + 8 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 Code/Framework/AzCore/Tests/Time/TimeTests.cpp diff --git a/Code/Framework/AzCore/AzCore/Math/Aabb.h b/Code/Framework/AzCore/AzCore/Math/Aabb.h index f6fb695399..438d95622e 100644 --- a/Code/Framework/AzCore/AzCore/Math/Aabb.h +++ b/Code/Framework/AzCore/AzCore/Math/Aabb.h @@ -1,4 +1,3 @@ - /* * 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. @@ -6,7 +5,6 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - #pragma once #include diff --git a/Code/Framework/AzCore/Tests/Time/TimeTests.cpp b/Code/Framework/AzCore/Tests/Time/TimeTests.cpp new file mode 100644 index 0000000000..6727ef1501 --- /dev/null +++ b/Code/Framework/AzCore/Tests/Time/TimeTests.cpp @@ -0,0 +1,56 @@ +/* + * 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 +#include + +namespace UnitTest +{ + class TimeTests + : public AllocatorsFixture + { + public: + void SetUp() override + { + SetupAllocator(); + m_timeComponent = new AZ::TimeSystemComponent; + } + + void TearDown() override + { + delete m_timeComponent; + TeardownAllocator(); + } + + AZ::TimeSystemComponent* m_timeComponent = nullptr; + }; + + TEST_F(TimeTests, TestConversionUsToMs) + { + AZ::TimeUs timeUs = AZ::TimeUs{ 1000 }; + AZ::TimeMs timeMs = AZ::TimeUsToMs(timeUs); + EXPECT_EQ(timeMs, AZ::TimeMs{ 1 }); + } + + TEST_F(TimeTests, TestConversionMsToUs) + { + AZ::TimeMs timeMs = AZ::TimeMs{ 1000 }; + AZ::TimeUs timeUs = AZ::TimeMsToUs(timeMs); + EXPECT_EQ(timeUs, AZ::TimeUs{ 1000000 }); + } + + TEST_F(TimeTests, TestClocks) + { + AZ::TimeUs timeUs = AZ::GetElapsedTimeUs(); + AZ::TimeMs timeMs = AZ::GetElapsedTimeMs(); + + AZ::TimeMs timeUsToMs = AZ::TimeUsToMs(timeUs); + int64_t delta = static_cast(timeMs) - static_cast(timeUsToMs); + EXPECT_LT(abs(delta), 1); + } +} diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index c36d37d874..a111af0353 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -127,6 +127,7 @@ set(FILES Serialization/Json/UnorderedSetSerializerTests.cpp Serialization/Json/UnsupportedTypesSerializerTests.cpp Serialization/Json/UuidSerializerTests.cpp + Time/TimeTests.cpp Math/AabbTests.cpp Math/ColorTests.cpp Math/CrcTests.cpp diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponentRegistry.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponentRegistry.h index c4ded8cc1c..8cc507c0a2 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponentRegistry.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponentRegistry.h @@ -67,6 +67,9 @@ namespace Multiplayer //! @return reference to the requested component data, an empty container will be returned if the NetComponentId does not exist const ComponentData& GetMultiplayerComponentData(NetComponentId netComponentId) const; + //! This releases all owned memory, should only be called during multiplayer shutdown. + void Reset(); + private: NetComponentId m_nextNetComponentId = NetComponentId{ 0 }; AZStd::unordered_map m_componentData; diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponentRegistry.cpp b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponentRegistry.cpp index e0e180431c..d92f95f6d2 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponentRegistry.cpp +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponentRegistry.cpp @@ -57,4 +57,9 @@ namespace Multiplayer } return nullComponentData; } + + void MultiplayerComponentRegistry::Reset() + { + m_componentData.clear(); + } } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 877267adde..b7dde3b9e7 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -199,6 +199,8 @@ namespace Multiplayer AZ::Interface::Get()->DestroyNetworkInterface(AZ::Name(MpNetworkInterfaceName)); AzFramework::SessionNotificationBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); + + m_networkEntityManager.Reset(); } bool MultiplayerSystemComponent::StartHosting(uint16_t port, bool isDedicated) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 93a816fdcf..db7f7243cc 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -300,6 +300,21 @@ namespace Multiplayer } } + void NetworkEntityManager::Reset() + { + m_multiplayerComponentRegistry.Reset(); + m_removeList.clear(); + m_entityDomain = nullptr; + m_updateEntityDomainEvent.RemoveFromQueue(); + m_ownedEntities.clear(); + m_entityExitDomainEvent.DisconnectAllHandlers(); + m_onEntityMarkedDirty.DisconnectAllHandlers(); + m_onEntityNotifyChanges.DisconnectAllHandlers(); + m_controllersActivatedEvent.DisconnectAllHandlers(); + m_controllersDeactivatedEvent.DisconnectAllHandlers(); + m_localDeferredRpcMessages.clear(); + } + void NetworkEntityManager::RemoveEntities() { AZStd::vector removeList; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 13b8f0e778..133c35dce0 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -92,6 +92,9 @@ namespace Multiplayer void OnRootSpawnableReleased(uint32_t generation) override; //! @} + //! Used to release all memory prior to shutdown. + void Reset(); + private: void RemoveEntities(); NetEntityId NextId(); From 98e5a18e49bf232e76e47bf598c77f509c473a30 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Fri, 1 Oct 2021 16:06:11 -0700 Subject: [PATCH 13/15] Fixes for API changes from most recent integrate Signed-off-by: kberg-amzn --- .../AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp | 5 +++-- .../AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Code/Framework/AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp b/Code/Framework/AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp index 3031f66774..77632da572 100644 --- a/Code/Framework/AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp +++ b/Code/Framework/AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp @@ -149,8 +149,9 @@ namespace UnitTest EXPECT_EQ(testServer.m_serverNetworkInterface->GetConnectionSet().GetConnectionCount(), 1); EXPECT_EQ(testClient.m_clientNetworkInterface->GetConnectionSet().GetConnectionCount(), 1); - testClient.m_clientNetworkInterface->SetTimeoutEnabled(true); - EXPECT_TRUE(testClient.m_clientNetworkInterface->IsTimeoutEnabled()); + const AZ::TimeMs timeoutMs = AZ::TimeMs{ 100 }; + testClient.m_clientNetworkInterface->SetTimeoutMs(timeoutMs); + EXPECT_EQ(testClient.m_clientNetworkInterface->GetTimeoutMs(), timeoutMs); EXPECT_TRUE(testServer.m_serverNetworkInterface->StopListening()); } diff --git a/Code/Framework/AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp b/Code/Framework/AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp index 91db3b4549..65c2cfa2b5 100644 --- a/Code/Framework/AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp +++ b/Code/Framework/AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp @@ -279,8 +279,9 @@ namespace UnitTest EXPECT_EQ(testServer.m_serverNetworkInterface->GetConnectionSet().GetConnectionCount(), 1); EXPECT_EQ(testClient.m_clientNetworkInterface->GetConnectionSet().GetConnectionCount(), 1); - testClient.m_clientNetworkInterface->SetTimeoutEnabled(true); - EXPECT_TRUE(testClient.m_clientNetworkInterface->IsTimeoutEnabled()); + const AZ::TimeMs timeoutMs = AZ::TimeMs{ 100 }; + testClient.m_clientNetworkInterface->SetTimeoutMs(timeoutMs); + EXPECT_EQ(testClient.m_clientNetworkInterface->GetTimeoutMs(), timeoutMs); EXPECT_FALSE(dynamic_cast(testClient.m_clientNetworkInterface)->IsEncrypted()); From 66ab15a4b673b5f440a2d498648db9ceca7c758c Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Mon, 4 Oct 2021 09:39:35 -0700 Subject: [PATCH 14/15] Removing old EntityReplicationManager.h that got re-added, this has been moved to public includes Signed-off-by: kberg-amzn --- .../EntityReplicationManager.h | 213 ------------------ 1 file changed, 213 deletions(-) delete mode 100644 Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h deleted file mode 100644 index 28a0963a57..0000000000 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ /dev/null @@ -1,213 +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 -#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; - - //! @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 AddAutonomousEntityReplicatorCreatedHandler(AZ::Event::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 SendEntityUpdateMessages(EntityReplicatorList& replicatorList); - void SendEntityRpcs(RpcMessages& rpcMessages, 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); - virtual ~OrphanedEntityRpcs() = default; - 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; - - 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; - }; -} - From 9f209dceea9bf5f8e12971ca66247b7744ff8b2a Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Mon, 4 Oct 2021 09:43:43 -0700 Subject: [PATCH 15/15] Fix casing issue with client hierarchy tests Signed-off-by: kberg-amzn --- Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index a58f23bae2..d6dd068c3f 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include namespace Multiplayer {