From 5b734b9d4159c666a0a78d6d595c531e9f334367 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Wed, 3 Nov 2021 14:17:20 -0700 Subject: [PATCH 01/10] A number of fixes to timeout and disconnect handling Signed-off-by: kberg-amzn --- .../AzCore/EBus/ScheduledEventHandle.cpp | 2 +- .../AutoGen/CorePackets.AutoPackets.xml | 4 +- .../TcpTransport/TcpConnection.cpp | 11 +- .../AzNetworking/TcpTransport/TcpConnection.h | 13 +- .../TcpTransport/TcpConnection.inl | 10 -- .../TcpTransport/TcpNetworkInterface.cpp | 47 +------ .../TcpTransport/TcpNetworkInterface.h | 11 -- .../UdpTransport/UdpConnection.cpp | 7 +- .../AzNetworking/UdpTransport/UdpConnection.h | 14 +- .../UdpTransport/UdpNetworkInterface.cpp | 68 ++++----- .../UdpTransport/UdpNetworkInterface.h | 32 ++--- .../Source/MultiplayerSystemComponent.cpp | 1 - .../Code/Source/MultiplayerSystemComponent.h | 1 - .../NetworkEntityAuthorityTracker.cpp | 132 ++++++------------ .../NetworkEntityAuthorityTracker.h | 26 +--- .../NetworkEntity/NetworkEntityManager.cpp | 4 + 16 files changed, 104 insertions(+), 279 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp b/Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp index 37b4895b4a..08a5a7d4ad 100644 --- a/Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp +++ b/Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp @@ -50,7 +50,7 @@ namespace AZ } else { - AZLOG_WARN("ScheduledEventHandle event pointer doesn't match to the pointer of handle to the event."); + //AZLOG_WARN("ScheduledEventHandle event pointer doesn't match to the pointer of handle to the event."); } } return false; // Event has been deleted, so the handle class must be deleted after this function. diff --git a/Code/Framework/AzNetworking/AzNetworking/AutoGen/CorePackets.AutoPackets.xml b/Code/Framework/AzNetworking/AzNetworking/AutoGen/CorePackets.AutoPackets.xml index 8ce3e5ad86..ae025b67e3 100644 --- a/Code/Framework/AzNetworking/AzNetworking/AutoGen/CorePackets.AutoPackets.xml +++ b/Code/Framework/AzNetworking/AzNetworking/AutoGen/CorePackets.AutoPackets.xml @@ -13,7 +13,9 @@ - + + + diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp index 6d7358a425..7537232a27 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp @@ -27,13 +27,11 @@ namespace AzNetworking ConnectionId connectionId, const IpAddress& remoteAddress, TcpNetworkInterface& networkInterface, - TcpSocket& socket, - TimeoutId timeoutId + TcpSocket& socket ) : IConnection(connectionId, remoteAddress) , m_networkInterface(networkInterface) , m_socket(socket.CloneAndTakeOwnership()) - , m_timeoutId(timeoutId) , m_state(m_socket->IsOpen() ? ConnectionState::Connecting : ConnectionState::Disconnected) , m_connectionRole(ConnectionRole::Acceptor) , m_registeredSocketFd(InvalidSocketFd) @@ -163,13 +161,6 @@ namespace AzNetworking break; } - TimeoutQueue::TimeoutItem* timeoutItem = m_networkInterface.m_connectionTimeoutQueue.RetrieveItem(GetTimeoutId()); - if (timeoutItem == nullptr) - { - return true; - } - timeoutItem->UpdateTimeoutTime(startTimeMs); - NetworkOutputSerializer serializer(buffer.GetBuffer(), static_cast(buffer.GetSize())); if (m_state == ConnectionState::Connecting) { diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h index b769aea086..3d74f3f336 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h @@ -38,14 +38,12 @@ namespace AzNetworking //! @param remoteAddress IP address of the remote endpoint //! @param networkInterface TcpNetworkInterface that owns this connection instance //! @param socket TCP socket to take ownership of and use for sending and receiving data - //! @param timeoutId timeout identifier of this connection instance TcpConnection ( ConnectionId connectionId, const IpAddress& remoteAddress, TcpNetworkInterface& networkInterface, - TcpSocket& socket, - TimeoutId timeoutId + TcpSocket& socket ); //! Construct a new socket with optional encryption, used when initiating a new connection @@ -69,14 +67,6 @@ namespace AzNetworking //! @return the TcpSocket bound to this TcpConnection TcpSocket* GetTcpSocket() const; - //! Sets the timeout identifier for this TcpConnection. - //! @param timeoutId the timeout identifier to use for this TcpConnection - void SetTimeoutId(TimeoutId timeoutId); - - //! Returns the timeout identifier for this TcpConnection. - //! @return the timeout identifier for this TcpConnection - TimeoutId GetTimeoutId() const; - //! Returns true if this connection instance is in an open state, and is capable of actively sending and receiving packets. //! @return boolean true if this connection instance is in an open state bool IsOpen() const; @@ -142,7 +132,6 @@ namespace AzNetworking AZStd::unique_ptr m_socket; AZStd::unique_ptr m_compressor; - TimeoutId m_timeoutId; PacketId m_lastSentPacketId = InvalidPacketId; ConnectionState m_state = ConnectionState::Disconnected; ConnectionRole m_connectionRole = ConnectionRole::Connector; diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.inl b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.inl index ecd1e5e908..5b5f38774e 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.inl +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.inl @@ -15,16 +15,6 @@ namespace AzNetworking return m_socket.get(); } - inline void TcpConnection::SetTimeoutId(TimeoutId timeoutId) - { - m_timeoutId = timeoutId; - } - - inline TimeoutId TcpConnection::GetTimeoutId() const - { - return m_timeoutId; - } - inline bool TcpConnection::IsOpen() const { return m_socket->IsOpen(); diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp index 1ccff7be50..0278856ce9 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp @@ -21,16 +21,11 @@ namespace AzNetworking static const bool net_TcpUseEncryption = false; #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_TcpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency"); - 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_TcpDefaultTimeoutMs) { ; } @@ -98,8 +93,6 @@ namespace AzNetworking } AZLOG_INFO("Adding new socket %d", static_cast(tcpSocket->GetSocketFd())); - const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast(tcpSocket->GetSocketFd()), net_TcpHeartbeatTimeMs); - connection->SetTimeoutId(newTimeoutId); connection->SendReliablePacket(CorePackets::InitiateConnectionPacket()); m_connectionListener.OnConnect(connection.get()); m_connectionSet.AddConnection(AZStd::move(connection)); @@ -110,12 +103,6 @@ namespace AzNetworking { const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs(); - // Time out any stale connections - { - ConnectionTimeoutFunctor functor(*this); - m_connectionTimeoutQueue.UpdateTimeouts(functor); - } - AcceptNewConnections(); auto readCallback = [this, startTimeMs](SocketFd socketFd) { HandleConnectionRecv(socketFd, startTimeMs); }; @@ -258,8 +245,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()), m_timeoutMs); - AZStd::unique_ptr connection = AZStd::make_unique(connectionId, remoteAddress, *this, tcpSocket, timeoutId); + AZStd::unique_ptr connection = AZStd::make_unique(connectionId, remoteAddress, *this, tcpSocket); AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Acceptor, "Invalid role for connection"); GetConnectionListener().OnConnect(connection.get()); m_connectionSet.AddConnection(AZStd::move(connection)); @@ -286,7 +272,6 @@ namespace AzNetworking m_pendingRemoves.resize_no_construct(0); } - TcpNetworkInterface::PendingConnection::PendingConnection(SocketFd socketFd, uint32_t remoteIpAddress, uint16_t remotePort, uint16_t listenPort) : m_socketFd(socketFd) , m_remoteIpAddress(remoteIpAddress) @@ -295,34 +280,4 @@ namespace AzNetworking { ; } - - TcpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface) - : m_networkInterface(networkInterface) - { - ; - } - - TimeoutResult TcpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item) - { - const SocketFd socketFd = static_cast(item.m_userData); - TcpConnection* tcpConnection = m_networkInterface.m_connectionSet.GetConnection(socketFd); - - if (tcpConnection == nullptr) - { - // We've already deleted this connection - return TimeoutResult::Delete; - } - - if (tcpConnection->GetConnectionRole() == ConnectionRole::Connector) - { - tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket()); - } - else if (net_TcpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 })) - { - tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); - return TimeoutResult::Delete; - } - - return TimeoutResult::Refresh; - } } diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h index d8f5d1b62b..8d45e847a8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h @@ -137,16 +137,6 @@ namespace AzNetworking AZ_DISABLE_COPY_MOVE(TcpNetworkInterface); - struct ConnectionTimeoutFunctor final - : public ITimeoutHandler - { - ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface); - TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override; - private: - AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor); - TcpNetworkInterface& m_networkInterface; - }; - struct PendingRemove { SocketFd m_socketFd; @@ -162,7 +152,6 @@ namespace AzNetworking TcpSocketManager m_tcpSocketManager; AZ::ThreadSafeDeque m_pendingConnections; AZStd::vector m_pendingRemoves; - TimeoutQueue m_connectionTimeoutQueue; TcpListenThread& m_listenThread; friend class TcpConnection; // For access to private RequestDisconnect() method diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp index 7b451865c4..03d4e7bec9 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp @@ -79,7 +79,7 @@ namespace AzNetworking AZLOG(NET_Acks, "Unacked packet count exceeded, sending client heartbeat (curr %u : max %u)", m_unackedPacketCount, static_cast(net_UdpMaxUnackedPacketCount)); // This simply times out unreliable chunks that haven't completed within our timeout delay m_fragmentQueue.Update(); - SendUnreliablePacket(CorePackets::HeartbeatPacket()); + SendUnreliablePacket(CorePackets::HeartbeatPacket(false)); } } @@ -289,7 +289,10 @@ namespace AzNetworking { return PacketDispatchResult::Failure; } - // Do nothing, we've already processed our ack packets + if (packet.GetRequestResponse()) + { + SendUnreliablePacket(CorePackets::HeartbeatPacket(false)); + } return PacketDispatchResult::Success; } break; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h index 199a5a8347..c728016239 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h @@ -136,19 +136,19 @@ namespace AzNetworking AZ_DISABLE_COPY_MOVE(UdpConnection); UdpNetworkInterface& m_networkInterface; - UdpPacketTracker m_packetTracker; - UdpReliableQueue m_reliableQueue; - UdpFragmentQueue m_fragmentQueue; - ConnectionState m_state = ConnectionState::Disconnected; - ConnectionRole m_connectionRole = ConnectionRole::Connector; - DtlsEndpoint m_dtlsEndpoint; + UdpPacketTracker m_packetTracker; + UdpReliableQueue m_reliableQueue; + UdpFragmentQueue m_fragmentQueue; + ConnectionState m_state = ConnectionState::Disconnected; + ConnectionRole m_connectionRole = ConnectionRole::Connector; + DtlsEndpoint m_dtlsEndpoint; AZ::TimeMs m_lastSentPacketMs; uint32_t m_unackedPacketCount = 0; uint32_t m_connectionMtu = MaxUdpTransmissionUnit; TimeoutId m_timeoutId; - uint32_t m_timeoutCounter = 0; + int32_t m_timeoutCounter = 0; }; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index b0e64f93e3..67bb80a44c 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -31,7 +31,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(int32_t, net_UdpUnackedHeartbeats, 3, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of heartbeats to attempt to send to keep a connection alive before giving up"); 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"); @@ -139,7 +139,8 @@ namespace AzNetworking } const ConnectionId connectionId = m_connectionSet.GetNextConnectionId(); - const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast(connectionId), m_timeoutMs); + const AZ::TimeMs timeoutTimeMs = m_timeoutMs / static_cast(static_cast(net_UdpUnackedHeartbeats)); + const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast(connectionId), timeoutTimeMs); AZStd::unique_ptr connection = AZStd::make_unique(connectionId, remoteAddress, *this, ConnectionRole::Connector); UdpPacketEncodingBuffer dtlsData; @@ -277,6 +278,7 @@ namespace AzNetworking } timeoutItem->UpdateTimeoutTime(startTimeMs); + connection->m_timeoutCounter = 0; PacketDispatchResult handledPacket = PacketDispatchResult::Failure; if (header.GetPacketType() < aznumeric_cast(CorePackets::PacketType::MAX)) @@ -319,16 +321,10 @@ namespace AzNetworking const AZ::TimeMs receiveTimeMs = AZ::GetElapsedTimeMs() - startTimeMs; // Time out any stale client connections - { - ConnectionTimeoutFunctor functor(*this); - m_connectionTimeoutQueue.UpdateTimeouts(functor); - } + m_connectionTimeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) { return HandleConnectionTimeout(item); }); // Time out any packets that haven't been acked within our timeout window - { - PacketTimeoutFunctor functor(*this); - m_packetTimeoutQueue.UpdateTimeouts(functor, static_cast(net_MaxTimeoutsPerFrame)); - } + m_packetTimeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) { return HandlePacketTimeout(item); }, static_cast(net_MaxTimeoutsPerFrame)); // Delete any connections we've disconnected for (RemovedConnection& removedConnection : m_removedConnections) @@ -709,21 +705,14 @@ namespace AzNetworking { // Packets involved in handshake are InitiateConnection, ConnectionHandshake and FragmentedPackets of ConnectionHandshake return packetType == aznumeric_cast(CorePackets::PacketType::InitiateConnectionPacket) || - packetType == aznumeric_cast(CorePackets::PacketType::ConnectionHandshakePacket) || - (packetType == aznumeric_cast(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting()); + packetType == aznumeric_cast(CorePackets::PacketType::ConnectionHandshakePacket) || + (packetType == aznumeric_cast(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting()); } - - UdpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface) - : m_networkInterface(networkInterface) - { - ; - } - - TimeoutResult UdpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item) + TimeoutResult UdpNetworkInterface::HandleConnectionTimeout(TimeoutQueue::TimeoutItem& item) { const ConnectionId connectionId = ConnectionId(aznumeric_cast(item.m_userData)); - UdpConnection* udpConnection = static_cast(m_networkInterface.m_connectionSet.GetConnection(connectionId)); + UdpConnection* udpConnection = static_cast(m_connectionSet.GetConnection(connectionId)); if (udpConnection == nullptr) { @@ -731,22 +720,23 @@ namespace AzNetworking return TimeoutResult::Delete; } - if (udpConnection->GetConnectionState() == ConnectionState::Connecting) + if ((udpConnection->GetConnectionState() == ConnectionState::Connecting) + && udpConnection->GetDtlsEndpoint().IsConnecting()) { - if (udpConnection->GetDtlsEndpoint().IsConnecting()) - { - // DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here - UdpPacketEncodingBuffer dtlsData; - udpConnection->ProcessHandshakeData(dtlsData); - return TimeoutResult::Refresh; - } + // DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here + UdpPacketEncodingBuffer dtlsData; + udpConnection->ProcessHandshakeData(dtlsData); + return TimeoutResult::Refresh; } - if (udpConnection->GetConnectionRole() == ConnectionRole::Connector) + if ((udpConnection->GetConnectionRole() == ConnectionRole::Connector) + && (udpConnection->m_timeoutCounter < net_UdpUnackedHeartbeats)) { - udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket()); + // Set the request response flag to true since we want a response to keep the connection alive + udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket(true)); + ++udpConnection->m_timeoutCounter; } - else if (net_UdpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 })) + else if (net_UdpTimeoutConnections && (GetTimeoutMs() > AZ::TimeMs{ 0 })) { udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); return TimeoutResult::Delete; @@ -755,19 +745,13 @@ namespace AzNetworking return TimeoutResult::Refresh; } - UdpNetworkInterface::PacketTimeoutFunctor::PacketTimeoutFunctor(UdpNetworkInterface& networkInterface) - : m_networkInterface(networkInterface) - { - ; - } - - TimeoutResult UdpNetworkInterface::PacketTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item) + TimeoutResult UdpNetworkInterface::HandlePacketTimeout(TimeoutQueue::TimeoutItem& item) { ConnectionId connectionId; PacketId packetId; ReliabilityType reliability; DecodeTimeoutId(item.m_userData, connectionId, packetId, reliability); - UdpConnection* connection = static_cast(m_networkInterface.m_connectionSet.GetConnection(connectionId)); + UdpConnection* connection = static_cast(m_connectionSet.GetConnection(connectionId)); if (connection == nullptr) { @@ -782,16 +766,14 @@ namespace AzNetworking case PacketTimeoutResult::Acked: // Packet was already acked, just discard this timeout entry return TimeoutResult::Delete; - case PacketTimeoutResult::Pending: // Packet timed out before we received any info about it's sequence from the remote endpoint // The connection latency may have increased, and our Rtt metrics may still be adjusting.. // Just throw it back into the timeout queue return TimeoutResult::Refresh; - case PacketTimeoutResult::Lost: // Packet timed out and was not acked, so we consider it lost - m_networkInterface.m_connectionListener.OnPacketLost(connection, packetId); + m_connectionListener.OnPacketLost(connection, packetId); break; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h index 8f827c74c4..e6abeded0d 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h @@ -149,34 +149,24 @@ namespace AzNetworking //! @param endpoint whether the disconnection was initiated locally or remotely void RequestDisconnect(UdpConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint); - //! Internal helper to check if a packet's type is for connection handshake + //! Internal helper to check if a packet's type is for connection handshake. //! @param endpoint DTLS endpoint participating in the handshake //! @param packetType type of the packet //! @return if the packet is for handshake bool IsHandshakePacket(const DtlsEndpoint& endpoint, AzNetworking::PacketType packetType) const; + //! Internal helper to manage connection timeout behaviour. + //! @param item the timeout item corresponding to the timed out connection + //! @return whether to delete or persist the timeout item + TimeoutResult HandleConnectionTimeout(TimeoutQueue::TimeoutItem& item); + + //! Internal helper to manage packet timeout behaviour. + //! @param item the timeout item corresponding to the timed out packet + //! @return whether to delete or persist the timeout item + TimeoutResult HandlePacketTimeout(TimeoutQueue::TimeoutItem& item); + AZ_DISABLE_COPY_MOVE(UdpNetworkInterface); - struct ConnectionTimeoutFunctor final - : public ITimeoutHandler - { - ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface); - TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override; - private: - AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor); - UdpNetworkInterface& m_networkInterface; - }; - - struct PacketTimeoutFunctor final - : public ITimeoutHandler - { - PacketTimeoutFunctor(UdpNetworkInterface& networkInterface); - TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override; - private: - AZ_DISABLE_COPY_MOVE(PacketTimeoutFunctor); - UdpNetworkInterface& m_networkInterface; - }; - AZ::Name m_name; TrustZone m_trustZone; uint16_t m_port = 0; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index fdd7602f71..0bf5cea64b 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -1107,7 +1107,6 @@ namespace Multiplayer void MultiplayerSystemComponent::OnAutonomousEntityReplicatorCreated() { m_autonomousEntityReplicatorCreatedHandler.Disconnect(); - //m_networkEntityManager.GetNetworkEntityAuthorityTracker()->ResetTimeoutTime(AZ::TimeMs{ 2000 }); m_clientMigrationEndEvent.Signal(); } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 87d084d5bc..53707523bb 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -155,7 +155,6 @@ namespace Multiplayer AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); AzNetworking::INetworkInterface* m_networkInterface = nullptr; - AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; AZ::ConsoleCommandInvokedEvent::Handler m_consoleCommandHandler; AZ::ThreadSafeDeque m_cvarCommands; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp index b3f87ea9ab..7b6e8476e7 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -33,37 +34,21 @@ namespace Multiplayer AZLOG ( NET_AuthTracker, - "AuthTracker: Removing timeout for networkEntityId %llu from %s, new owner is %s", + "AuthTracker: Removing timeout for networkEntityId %llu, new owner is %s", aznumeric_cast(entityHandle.GetNetEntityId()), - timeoutData->second.m_previousOwner.GetString().c_str(), newOwner.GetString().c_str() ); m_timeoutDataMap.erase(timeoutData); ret = true; } - auto iter = m_entityAuthorityMap.find(entityHandle.GetNetEntityId()); - if (iter != m_entityAuthorityMap.end()) - { - AZLOG - ( - NET_AuthTracker, - "AuthTracker: Assigning networkEntityId %llu from %s to %s", - aznumeric_cast(entityHandle.GetNetEntityId()), - iter->second.back().GetString().c_str(), - newOwner.GetString().c_str() - ); - } - else - { - AZLOG - ( - NET_AuthTracker, - "AuthTracker: Assigning networkEntityId %llu to %s", - aznumeric_cast(entityHandle.GetNetEntityId()), - newOwner.GetString().c_str() - ); - } + AZLOG + ( + NET_AuthTracker, + "AuthTracker: Assigning networkEntityId %llu to %s", + aznumeric_cast(entityHandle.GetNetEntityId()), + newOwner.GetString().c_str() + ); m_entityAuthorityMap[entityHandle.GetNetEntityId()].push_back(newOwner); return ret; @@ -103,14 +88,41 @@ namespace Multiplayer { AZ_Assert ( - (m_timeoutDataMap.find(entityHandle.GetNetEntityId()) == m_timeoutDataMap.end()) || - (m_timeoutDataMap[entityHandle.GetNetEntityId()].m_previousOwner == previousOwner), + m_timeoutDataMap.find(entityHandle.GetNetEntityId()) == m_timeoutDataMap.end(), "Trying to add something twice to the timeout map, this is unexpected" ); - m_timeoutQueue.RegisterItem(aznumeric_cast(entityHandle.GetNetEntityId()), net_EntityMigrationTimeoutMs); - TimeoutData& timeoutData = m_timeoutDataMap[entityHandle.GetNetEntityId()]; - timeoutData.m_entityHandle = entityHandle; - timeoutData.m_previousOwner = previousOwner; + m_timeoutDataMap.insert(entityHandle.GetNetEntityId()); + AZ::Interface::Get()->AddCallback([this, netEntityId = entityHandle.GetNetEntityId(), previousOwner] + { + auto timeoutData = m_timeoutDataMap.find(netEntityId); + if (timeoutData != m_timeoutDataMap.end()) + { + m_timeoutDataMap.erase(timeoutData); + ConstNetworkEntityHandle entityHandle = m_networkEntityManager.GetEntity(netEntityId); + if (auto entity = entityHandle.GetEntity()) + { + NetEntityRole networkRole = NetEntityRole::InvalidRole; + NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); + if (netBindComponent != nullptr) + { + networkRole = netBindComponent->GetNetEntityRole(); + } + if (networkRole != NetEntityRole::Authority) + { + AZLOG_ERROR + ( + "Timed out entity id %llu during migration previous owner %s, removing it", + aznumeric_cast(entityHandle.GetNetEntityId()), + previousOwner.GetString().c_str() + ); + m_networkEntityManager.MarkForRemoval(entityHandle); + } + } + } + }, + AZ::Name("Entity authority removal functor"), + net_EntityMigrationTimeoutMs + ); } else { @@ -127,18 +139,6 @@ namespace Multiplayer } HostId NetworkEntityAuthorityTracker::GetEntityAuthorityManager(ConstNetworkEntityHandle entityHandle) const - { - HostId hostId = GetEntityAuthorityManagerInternal(entityHandle); - AZ_Assert(hostId != InvalidHostId, "Unable to determine manager for entity"); - return hostId; - } - - bool NetworkEntityAuthorityTracker::DoesEntityHaveOwner(ConstNetworkEntityHandle entityHandle) const - { - return InvalidHostId != GetEntityAuthorityManagerInternal(entityHandle); - } - - HostId NetworkEntityAuthorityTracker::GetEntityAuthorityManagerInternal(ConstNetworkEntityHandle entityHandle) const { if (auto localEnt = entityHandle.GetEntity()) { @@ -167,52 +167,8 @@ namespace Multiplayer return InvalidHostId; } - NetworkEntityAuthorityTracker::TimeoutData::TimeoutData(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner) - : m_entityHandle(entityHandle) - , m_previousOwner(previousOwner) + bool NetworkEntityAuthorityTracker::DoesEntityHaveOwner(ConstNetworkEntityHandle entityHandle) const { - ; - } - - NetworkEntityAuthorityTracker::NetworkEntityTimeoutFunctor::NetworkEntityTimeoutFunctor - ( - NetworkEntityAuthorityTracker& networkEntityAuthorityTracker, - INetworkEntityManager& networkEntityManager - ) - : m_networkEntityAuthorityTracker(networkEntityAuthorityTracker) - , m_networkEntityManager(networkEntityManager) - { - ; - } - - AzNetworking::TimeoutResult NetworkEntityAuthorityTracker::NetworkEntityTimeoutFunctor::HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) - { - const NetEntityId netEntityId = aznumeric_cast(item.m_userData); - auto timeoutData = m_networkEntityAuthorityTracker.m_timeoutDataMap.find(netEntityId); - if (timeoutData != m_networkEntityAuthorityTracker.m_timeoutDataMap.end()) - { - m_networkEntityAuthorityTracker.m_timeoutDataMap.erase(timeoutData); - ConstNetworkEntityHandle entityHandle = m_networkEntityManager.GetEntity(netEntityId); - if (auto entity = entityHandle.GetEntity()) - { - NetEntityRole networkRole = NetEntityRole::InvalidRole; - NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); - if (netBindComponent != nullptr) - { - networkRole = netBindComponent->GetNetEntityRole(); - } - if (networkRole != NetEntityRole::Authority) - { - AZLOG_ERROR - ( - "Timed out entity id %llu during migration previous owner %s, removing it", - aznumeric_cast(entityHandle.GetNetEntityId()), - timeoutData->second.m_previousOwner.GetString().c_str() - ); - m_networkEntityManager.MarkForRemoval(entityHandle); - } - } - } - return AzNetworking::TimeoutResult::Delete; + return InvalidHostId != GetEntityAuthorityManager(entityHandle); } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h index 0f4ff5665a..c2e330ab4d 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h @@ -29,37 +29,13 @@ namespace Multiplayer HostId GetEntityAuthorityManager(ConstNetworkEntityHandle entityHandle) const; private: - - HostId GetEntityAuthorityManagerInternal(ConstNetworkEntityHandle entityHandle) const; - NetworkEntityAuthorityTracker& operator= (const NetworkEntityAuthorityTracker&) = delete; - struct TimeoutData final - { - TimeoutData() = default; - TimeoutData(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner); - ConstNetworkEntityHandle m_entityHandle; - HostId m_previousOwner = InvalidHostId; - }; - - struct NetworkEntityTimeoutFunctor final - : public AzNetworking::ITimeoutHandler - { - NetworkEntityTimeoutFunctor(NetworkEntityAuthorityTracker& networkEntityAuthorityTracker, INetworkEntityManager& m_networkEntityManager); - AzNetworking::TimeoutResult HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) override; - private: - AZ_DISABLE_COPY_MOVE(NetworkEntityTimeoutFunctor); - NetworkEntityAuthorityTracker& m_networkEntityAuthorityTracker; - INetworkEntityManager& m_networkEntityManager; - }; - - using TimeoutDataMap = AZStd::unordered_map; + using TimeoutDataMap = AZStd::unordered_set; using EntityAuthorityMap = AZStd::unordered_map>; TimeoutDataMap m_timeoutDataMap; EntityAuthorityMap m_entityAuthorityMap; INetworkEntityManager& m_networkEntityManager; - AzNetworking::TimeoutQueue m_timeoutQueue; }; } - diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index c7582af83f..d973f0c80a 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -241,6 +241,10 @@ namespace Multiplayer { AZ::Entity* entity = it->second; NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); + if (netBindComponent == nullptr) + { + continue; + } AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); entityBounds.Expand(AZ::Vector3(0.01f)); if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) From fda7a6353e758eeef016ee2c75c130aff7bf1344 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Wed, 3 Nov 2021 14:20:56 -0700 Subject: [PATCH 02/10] Backing out some temporary debugging code Signed-off-by: kberg-amzn --- Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp | 2 +- .../Code/Source/NetworkEntity/NetworkEntityManager.cpp | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp b/Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp index 08a5a7d4ad..37b4895b4a 100644 --- a/Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp +++ b/Code/Framework/AzCore/AzCore/EBus/ScheduledEventHandle.cpp @@ -50,7 +50,7 @@ namespace AZ } else { - //AZLOG_WARN("ScheduledEventHandle event pointer doesn't match to the pointer of handle to the event."); + AZLOG_WARN("ScheduledEventHandle event pointer doesn't match to the pointer of handle to the event."); } } return false; // Event has been deleted, so the handle class must be deleted after this function. diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index d973f0c80a..c7582af83f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -241,10 +241,6 @@ namespace Multiplayer { AZ::Entity* entity = it->second; NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); - if (netBindComponent == nullptr) - { - continue; - } AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); entityBounds.Expand(AZ::Vector3(0.01f)); if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) From 70a1eb65d81079b28d84e15d5ead7f53758c1801 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Wed, 3 Nov 2021 14:26:41 -0700 Subject: [PATCH 03/10] Improving comments around heartbeat sends + bumping number of heartbeats for increased keep-alive robustness under high packet loss Signed-off-by: kberg-amzn --- .../AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp | 2 ++ .../AzNetworking/UdpTransport/UdpNetworkInterface.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp index 03d4e7bec9..452992f971 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp @@ -79,6 +79,7 @@ namespace AzNetworking AZLOG(NET_Acks, "Unacked packet count exceeded, sending client heartbeat (curr %u : max %u)", m_unackedPacketCount, static_cast(net_UdpMaxUnackedPacketCount)); // This simply times out unreliable chunks that haven't completed within our timeout delay m_fragmentQueue.Update(); + // This heartbeat is sent to minimize the time the remote endpoint spends waiting for ack vector replication, we don't require a response SendUnreliablePacket(CorePackets::HeartbeatPacket(false)); } } @@ -291,6 +292,7 @@ namespace AzNetworking } if (packet.GetRequestResponse()) { + // We're replying to a heartbeat request, we don't want a response SendUnreliablePacket(CorePackets::HeartbeatPacket(false)); } return PacketDispatchResult::Success; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index 67bb80a44c..280b749d9e 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -31,7 +31,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(int32_t, net_UdpUnackedHeartbeats, 3, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of heartbeats to attempt to send to keep a connection alive before giving up"); + AZ_CVAR(int32_t, net_UdpUnackedHeartbeats, 5, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of heartbeats to attempt to send to keep a connection alive before giving up"); 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"); From 8a3d055f8b7c4654dcb4285026f5b9d089d407d8 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Wed, 3 Nov 2021 19:34:10 -0700 Subject: [PATCH 04/10] Some cleanup around handling of migrations to simplify interfaces and add additional hooks for functionality Signed-off-by: kberg-amzn --- .../Multiplayer/EntityDomains/IEntityDomain.h | 12 +- .../NetworkEntity/INetworkEntityManager.h | 23 ++- .../Source/Components/NetBindComponent.cpp | 4 +- .../FullOwnershipEntityDomain.cpp | 9 +- .../EntityDomains/FullOwnershipEntityDomain.h | 6 +- .../Source/EntityDomains/NullEntityDomain.cpp | 41 +++++ .../Source/EntityDomains/NullEntityDomain.h | 31 ++++ .../Source/MultiplayerSystemComponent.cpp | 10 +- .../NetworkEntityAuthorityTracker.cpp | 21 +-- .../NetworkEntityAuthorityTracker.h | 3 + .../NetworkEntity/NetworkEntityManager.cpp | 140 +++++++++--------- .../NetworkEntity/NetworkEntityManager.h | 8 +- Gems/Multiplayer/Code/multiplayer_files.cmake | 2 + 13 files changed, 194 insertions(+), 116 deletions(-) create mode 100644 Gems/Multiplayer/Code/Source/EntityDomains/NullEntityDomain.cpp create mode 100644 Gems/Multiplayer/Code/Source/EntityDomains/NullEntityDomain.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h index fc41a9b4d0..4b1bbbdba8 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h @@ -17,8 +17,6 @@ namespace Multiplayer class IEntityDomain { public: - using EntitiesNotInDomain = AZStd::unordered_set; - virtual ~IEntityDomain() = default; //! For domains that operate on a region of space, this sets the area the domain is responsible for. @@ -34,12 +32,10 @@ namespace Multiplayer //! @return false if this entity should not belong to the entity manger, true if it could be owned by the entity manager virtual bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const = 0; - //! Enable Entity Domain Exit Tracking for entities on the host. - //! @param ownedEntitySet the set of entities to activate tracking for - virtual void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) = 0; - - //! Return the set of netbound entities not included in this domain. - virtual const EntitiesNotInDomain& RetrieveEntitiesNotInDomain() const = 0; + //! This method will be invoked whenever we unexpectedly lose the authoritative entity replicator for an entity. + //! This gives our entity domain a chance to determine whether or not it should assume authority in this instance. + //! @param entityHandle the network entity handle of the entity that has lost it's authoritative replicator + virtual void HandleLossOfAuthoritativeReplicator(const ConstNetworkEntityHandle& entityHandle) = 0; //! Debug draw to visualize host entity domains. virtual void DebugDraw() const = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 43915127ed..8c16176736 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -26,6 +26,7 @@ namespace Multiplayer using EntityExitDomainEvent = AZ::Event; using ControllersActivatedEvent = AZ::Event; using ControllersDeactivatedEvent = AZ::Event; + using NetEntityIdSet = AZStd::unordered_set; //! @class INetworkEntityManager //! @brief The interface for managing all networked entities. @@ -34,18 +35,17 @@ namespace Multiplayer public: AZ_RTTI(INetworkEntityManager, "{109759DE-9492-439C-A0B1-AE46E6FD029C}"); - using OwnedEntitySet = AZStd::unordered_set; using EntityList = AZStd::vector; virtual ~INetworkEntityManager() = default; - //! Configures the NetworkEntityManager to operate as an authoritative host. - //! @param hostId the hostId of this NetworkEntityManager + //! Configures the NetworkEntityManager. + //! @param hostId the hostId of this NetworkEntityManager (invalid for clients) //! @param entityDomain the entity domain used to determine which entities this manager has authority over 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 + //! Returns whether or not the network entity manager has been initialized. + //! @return boolean true if this network entity manager has been intialized virtual bool IsInitialized() const = 0; //! Returns the entity domain associated with this network entity manager, this will be nullptr on clients. @@ -181,6 +181,19 @@ namespace Multiplayer //! @param entityRpcMessage the local rpc message to handle virtual void HandleLocalRpcMessage(NetworkEntityRpcMessage& message) = 0; + //! Handles a set of entities transitioning between entity domains. + //! @param entitiesNotInDomain the set of entities that are no longer contained within our entity domain + virtual void HandleEntitiesExitDomain(const NetEntityIdSet& entitiesNotInDomain) = 0; + + //! Forcibly assumes authoritative control over the given entity. + //! This should only be used in the event of the unexpected loss of the previous authority, any other usage could corrupt the simulation. + //! @param entityHandle the entity to forcibly assume authoritative control over + virtual void ForceAssumeAuthority(const ConstNetworkEntityHandle& entityHandle) = 0; + + //! Overrides the default timeout time used during entity migrations. + //! @param timeoutTimeMs the timeout time to use in milliseconds + virtual void SetMigrateTimeoutTimeMs(AZ::TimeMs timeoutTimeMs) = 0; + //! Visualization of network entity manager state. virtual void DebugDraw() const = 0; }; diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index cc71000d33..ceb9412408 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -317,7 +317,7 @@ namespace Multiplayer return false; } - bool NetBindComponent::HandlePropertyChangeMessage([[maybe_unused]] AzNetworking::ISerializer& serializer, [[maybe_unused]] bool notifyChanges) + bool NetBindComponent::HandlePropertyChangeMessage(AzNetworking::ISerializer& serializer, bool notifyChanges) { const NetEntityRole netEntityRole = m_netEntityRole; ReplicationRecord replicationRecord(netEntityRole); @@ -492,7 +492,7 @@ namespace Multiplayer void NetBindComponent::FillTotalReplicationRecord(ReplicationRecord& replicationRecord) const { replicationRecord.Append(m_totalRecord); - // if we have any outstanding changes yet to be logged, grab those as well + // If we have any outstanding changes yet to be logged, grab those as well if (m_currentRecord.HasChanges()) { replicationRecord.Append(m_currentRecord); diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp index 9d53990fb4..59e2afc638 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp @@ -26,14 +26,9 @@ namespace Multiplayer return true; } - void FullOwnershipEntityDomain::ActivateTracking([[maybe_unused]] const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) + void FullOwnershipEntityDomain::HandleLossOfAuthoritativeReplicator([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) { - ; - } - - const IEntityDomain::EntitiesNotInDomain& FullOwnershipEntityDomain::RetrieveEntitiesNotInDomain() const - { - return m_entitiesNotInDomain; + AZ_Assert(false, "FullOwnershipEntityDomain has authoritative control over all entities, something unexpected has happened"); } void FullOwnershipEntityDomain::DebugDraw() const diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h index ae80c16ab8..203d9579ea 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h @@ -24,12 +24,8 @@ namespace Multiplayer 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; - const EntitiesNotInDomain& RetrieveEntitiesNotInDomain() const override; + void HandleLossOfAuthoritativeReplicator(const ConstNetworkEntityHandle& entityHandle) override; void DebugDraw() const override; //! @} - - private: - EntitiesNotInDomain m_entitiesNotInDomain; }; } diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/NullEntityDomain.cpp b/Gems/Multiplayer/Code/Source/EntityDomains/NullEntityDomain.cpp new file mode 100644 index 0000000000..21d6abcb5a --- /dev/null +++ b/Gems/Multiplayer/Code/Source/EntityDomains/NullEntityDomain.cpp @@ -0,0 +1,41 @@ +/* + * 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 +#include + +namespace Multiplayer +{ + void NullEntityDomain::SetAabb([[maybe_unused]] const AZ::Aabb& aabb) + { + ; // Do nothing, by definition we own everything + } + + const AZ::Aabb& NullEntityDomain::GetAabb() const + { + static AZ::Aabb nullAabb = AZ::Aabb::CreateNull(); + return nullAabb; + } + + bool NullEntityDomain::IsInDomain([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) const + { + return false; + } + + void NullEntityDomain::HandleLossOfAuthoritativeReplicator([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) + { + AZLOG_ERROR("Timed out entity id %llu during migration, marking for removal", aznumeric_cast(entityHandle.GetNetEntityId())); + GetNetworkEntityManager()->MarkForRemoval(entityHandle); + } + + void NullEntityDomain::DebugDraw() const + { + ; + } +} diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/NullEntityDomain.h b/Gems/Multiplayer/Code/Source/EntityDomains/NullEntityDomain.h new file mode 100644 index 0000000000..247d82b366 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/EntityDomains/NullEntityDomain.h @@ -0,0 +1,31 @@ +/* + * 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 + +namespace Multiplayer +{ + class NullEntityDomain + : public IEntityDomain + { + public: + NullEntityDomain() = default; + NullEntityDomain(const NullEntityDomain& rhs) = default; + + //! IEntityDomain overrides. + //! @{ + void SetAabb(const AZ::Aabb& aabb) override; + const AZ::Aabb& GetAabb() const override; + bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const override; + void HandleLossOfAuthoritativeReplicator(const ConstNetworkEntityHandle& entityHandle) override; + void DebugDraw() const override; + //! @} + }; +} diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 0bf5cea64b..1bc3404292 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -832,18 +833,21 @@ namespace Multiplayer if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer) { m_spawnNetboundEntities = true; - m_initEvent.Signal(m_networkInterface); - + m_initEvent.Signal(m_networkInterface); //< Note! This might initialize our network entity manager for us if (!m_networkEntityManager.IsInitialized()) { - // Set up a full ownership domain if we didn't construct a domain during the initialize event 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); + // Set up a full ownership domain if we didn't construct a domain during the initialize event m_networkEntityManager.Initialize(hostId, AZStd::make_unique()); } } + else if (multiplayerType == MultiplayerAgentType::Client) + { + m_networkEntityManager.Initialize(AzNetworking::IpAddress(), AZStd::make_unique()); + } } m_agentType = multiplayerType; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp index 7b6e8476e7..9f66bbee9e 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -17,14 +18,20 @@ namespace Multiplayer { - AZ_CVAR(AZ::TimeMs, net_EntityMigrationTimeoutMs, AZ::TimeMs{ 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time to wait for a new authority to attach to an entity before we delete the entity"); + AZ_CVAR(AZ::TimeMs, net_DefaultEntityMigrationTimeoutMs, AZ::TimeMs{ 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time to wait for a new authority to attach to an entity before we delete the entity"); NetworkEntityAuthorityTracker::NetworkEntityAuthorityTracker(INetworkEntityManager& networkEntityManager) : m_networkEntityManager(networkEntityManager) + , m_timeoutTimeMs(net_DefaultEntityMigrationTimeoutMs) { ; } + void NetworkEntityAuthorityTracker::SetTimeoutTimeMs(AZ::TimeMs timeoutTimeMs) + { + m_timeoutTimeMs = timeoutTimeMs; + } + bool NetworkEntityAuthorityTracker::AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& newOwner) { bool ret = false; @@ -92,7 +99,7 @@ namespace Multiplayer "Trying to add something twice to the timeout map, this is unexpected" ); m_timeoutDataMap.insert(entityHandle.GetNetEntityId()); - AZ::Interface::Get()->AddCallback([this, netEntityId = entityHandle.GetNetEntityId(), previousOwner] + AZ::Interface::Get()->AddCallback([this, netEntityId = entityHandle.GetNetEntityId()] { auto timeoutData = m_timeoutDataMap.find(netEntityId); if (timeoutData != m_timeoutDataMap.end()) @@ -109,19 +116,13 @@ namespace Multiplayer } if (networkRole != NetEntityRole::Authority) { - AZLOG_ERROR - ( - "Timed out entity id %llu during migration previous owner %s, removing it", - aznumeric_cast(entityHandle.GetNetEntityId()), - previousOwner.GetString().c_str() - ); - m_networkEntityManager.MarkForRemoval(entityHandle); + m_networkEntityManager.GetEntityDomain()->HandleLossOfAuthoritativeReplicator(entityHandle); } } } }, AZ::Name("Entity authority removal functor"), - net_EntityMigrationTimeoutMs + m_timeoutTimeMs ); } else diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h index c2e330ab4d..edb1b26ca8 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h @@ -23,6 +23,7 @@ namespace Multiplayer public: NetworkEntityAuthorityTracker(INetworkEntityManager& networkEntityManager); + void SetTimeoutTimeMs(AZ::TimeMs timeoutTimeMs); bool DoesEntityHaveOwner(ConstNetworkEntityHandle entityHandle) const; bool AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& newOwner); void RemoveEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner); @@ -37,5 +38,7 @@ namespace Multiplayer TimeoutDataMap m_timeoutDataMap; EntityAuthorityMap m_entityAuthorityMap; INetworkEntityManager& m_networkEntityManager; + + AZ::TimeMs m_timeoutTimeMs = AZ::TimeMs{ 0 }; }; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index c7582af83f..b178ebeb02 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -28,12 +28,10 @@ namespace Multiplayer { AZ_CVAR(bool, net_DebugCheckNetworkEntityManager, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables extra debug checks inside the NetworkEntityManager"); - AZ_CVAR(AZ::TimeMs, net_EntityDomainUpdateMs, AZ::TimeMs{ 500 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Frequency for updating the entity domain in ms"); NetworkEntityManager::NetworkEntityManager() : m_networkEntityAuthorityTracker(*this) , m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event")) - , m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event")) { AZ::Interface::Register(this); AzFramework::RootSpawnableNotificationBus::Handler::BusConnect(); @@ -63,8 +61,6 @@ namespace Multiplayer } m_entityDomain = AZStd::move(entityDomain); - m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true); - m_entityDomain->ActivateTracking(m_ownedEntities); } bool NetworkEntityManager::IsInitialized() const @@ -231,6 +227,74 @@ namespace Multiplayer m_localDeferredRpcMessages.emplace_back(AZStd::move(message)); } + void NetworkEntityManager::HandleEntitiesExitDomain(const NetEntityIdSet& entitiesNotInDomain) + { + for (NetEntityId exitingId : entitiesNotInDomain) + { + bool safeToExit = true; + NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(exitingId); + + // We need special handling for the NetworkHierarchy as well, since related entities need to be migrated together + NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController(); + NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController(); + + // Find the root entity + AZ::Entity* hierarchyRootEntity = nullptr; + if (hierarchyRootController) + { + hierarchyRootEntity = hierarchyRootController->GetParent().GetHierarchicalRoot(); + } + else if (hierarchyChildController) + { + hierarchyRootEntity = hierarchyChildController->GetParent().GetHierarchicalRoot(); + } + + if (hierarchyRootEntity) + { + NetEntityId rootNetId = GetNetEntityIdById(hierarchyRootEntity->GetId()); + ConstNetworkEntityHandle rootEntityHandle = GetEntity(rootNetId); + + // Check if the root entity is still tracked by this authority + if (rootEntityHandle.Exists() && rootEntityHandle.GetNetBindComponent()->HasController()) + { + safeToExit = false; + } + } + + // Validate that we aren't already planning to remove this entity + if (safeToExit) + { + for (auto remoteEntityId : m_removeList) + { + if (remoteEntityId == remoteEntityId) + { + safeToExit = false; + } + } + } + + if (safeToExit) + { + // Tell all the attached replicators for this entity that it's exited the domain + m_entityExitDomainEvent.Signal(entityHandle); + } + } + } + + void NetworkEntityManager::ForceAssumeAuthority(const ConstNetworkEntityHandle& entityHandle) + { + NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); + if (netBindComponent != nullptr) + { + netBindComponent->ConstructControllers(); + } + } + + void NetworkEntityManager::SetMigrateTimeoutTimeMs(AZ::TimeMs timeoutTimeMs) + { + m_networkEntityAuthorityTracker.SetTimeoutTimeMs(timeoutTimeMs); + } + void NetworkEntityManager::DebugDraw() const { AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; @@ -243,7 +307,7 @@ namespace Multiplayer NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); entityBounds.Expand(AZ::Vector3(0.01f)); - if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) + if ((netBindComponent != nullptr) && netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) { debugDisplay->SetColor(AZ::Colors::Black); debugDisplay->SetAlpha(0.5f); @@ -277,77 +341,11 @@ namespace Multiplayer m_localDeferredRpcMessages.clear(); } - void NetworkEntityManager::UpdateEntityDomain() - { - if (m_entityDomain == nullptr) - { - return; - } - - const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain = m_entityDomain->RetrieveEntitiesNotInDomain(); - for (NetEntityId exitingId : entitiesNotInDomain) - { - OnEntityExitDomain(exitingId); - } - } - - void NetworkEntityManager::OnEntityExitDomain(NetEntityId entityId) - { - bool safeToExit = true; - NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(entityId); - - // We also need special handling for the NetworkHierarchy as well, since related entities need to be migrated together - NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController(); - NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController(); - - // Find the root entity - AZ::Entity* hierarchyRootEntity = nullptr; - if (hierarchyRootController) - { - hierarchyRootEntity = hierarchyRootController->GetParent().GetHierarchicalRoot(); - } - else if (hierarchyChildController) - { - hierarchyRootEntity = hierarchyChildController->GetParent().GetHierarchicalRoot(); - } - - if (hierarchyRootEntity) - { - NetEntityId rootNetId = GetNetEntityIdById(hierarchyRootEntity->GetId()); - ConstNetworkEntityHandle rootEntityHandle = GetEntity(rootNetId); - - // Check if the root entity is still tracked by this authority - if (rootEntityHandle.Exists() && rootEntityHandle.GetNetBindComponent()->HasController()) - { - safeToExit = false; - } - } - - // Validate that we aren't already planning to remove this entity - if (safeToExit) - { - for (auto remoteEntityId : m_removeList) - { - if (remoteEntityId == remoteEntityId) - { - safeToExit = false; - } - } - } - - if (safeToExit) - { - m_entityExitDomainEvent.Signal(entityHandle); - } - } - 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(); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 133c35dce0..7c6fdd94f9 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -79,12 +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 HandleEntitiesExitDomain(const NetEntityIdSet& entitiesNotInDomain) override; + void ForceAssumeAuthority(const ConstNetworkEntityHandle& entityHandle) override; + void SetMigrateTimeoutTimeMs(AZ::TimeMs timeoutTimeMs) override; void DebugDraw() const override; //! @} void DispatchLocalDeferredRpcMessages(); - void UpdateEntityDomain(); - void OnEntityExitDomain(NetEntityId entityId); //! RootSpawnableNotificationBus //! @{ @@ -106,9 +107,6 @@ namespace Multiplayer AZ::ScheduledEvent m_removeEntitiesEvent; AZStd::vector m_removeList; AZStd::unique_ptr m_entityDomain; - AZ::ScheduledEvent m_updateEntityDomainEvent; - - OwnedEntitySet m_ownedEntities; EntityExitDomainEvent m_entityExitDomainEvent; AZ::Event<> m_onEntityMarkedDirty; diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index a799278203..d417304877 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -94,6 +94,8 @@ set(FILES Source/Editor/MultiplayerEditorConnection.h Source/EntityDomains/FullOwnershipEntityDomain.cpp Source/EntityDomains/FullOwnershipEntityDomain.h + Source/EntityDomains/NullEntityDomain.cpp + Source/EntityDomains/NullEntityDomain.h Source/MultiplayerStats.cpp Source/MultiplayerSystemComponent.cpp Source/MultiplayerSystemComponent.h From 7e65104155a539fb1999e09862928b95641f9071 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Wed, 3 Nov 2021 19:40:20 -0700 Subject: [PATCH 05/10] Addressing PR feedback Signed-off-by: kberg-amzn --- .../AzNetworking/UdpTransport/UdpConnection.h | 2 +- .../UdpTransport/UdpNetworkInterface.cpp | 2 +- .../NetworkEntityAuthorityTracker.cpp | 16 ++++++++-------- .../NetworkEntityAuthorityTracker.h | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h index c728016239..ddd75c9946 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h @@ -148,7 +148,7 @@ namespace AzNetworking uint32_t m_connectionMtu = MaxUdpTransmissionUnit; TimeoutId m_timeoutId; - int32_t m_timeoutCounter = 0; + uint32_t m_timeoutCounter = 0; }; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index 280b749d9e..b810c7a347 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -31,7 +31,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(int32_t, net_UdpUnackedHeartbeats, 5, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of heartbeats to attempt to send to keep a connection alive before giving up"); + AZ_CVAR(uint32_t, net_UdpUnackedHeartbeats, 5, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of heartbeats to attempt to send to keep a connection alive before giving up"); 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"); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp index 9f66bbee9e..b401b85a27 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp @@ -35,8 +35,8 @@ namespace Multiplayer bool NetworkEntityAuthorityTracker::AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& newOwner) { bool ret = false; - auto timeoutData = m_timeoutDataMap.find(entityHandle.GetNetEntityId()); - if (timeoutData != m_timeoutDataMap.end()) + auto timeoutData = m_timedOutNetEntityIds.find(entityHandle.GetNetEntityId()); + if (timeoutData != m_timedOutNetEntityIds.end()) { AZLOG ( @@ -45,7 +45,7 @@ namespace Multiplayer aznumeric_cast(entityHandle.GetNetEntityId()), newOwner.GetString().c_str() ); - m_timeoutDataMap.erase(timeoutData); + m_timedOutNetEntityIds.erase(timeoutData); ret = true; } @@ -95,16 +95,16 @@ namespace Multiplayer { AZ_Assert ( - m_timeoutDataMap.find(entityHandle.GetNetEntityId()) == m_timeoutDataMap.end(), + m_timedOutNetEntityIds.find(entityHandle.GetNetEntityId()) == m_timedOutNetEntityIds.end(), "Trying to add something twice to the timeout map, this is unexpected" ); - m_timeoutDataMap.insert(entityHandle.GetNetEntityId()); + m_timedOutNetEntityIds.insert(entityHandle.GetNetEntityId()); AZ::Interface::Get()->AddCallback([this, netEntityId = entityHandle.GetNetEntityId()] { - auto timeoutData = m_timeoutDataMap.find(netEntityId); - if (timeoutData != m_timeoutDataMap.end()) + auto timeoutData = m_timedOutNetEntityIds.find(netEntityId); + if (timeoutData != m_timedOutNetEntityIds.end()) { - m_timeoutDataMap.erase(timeoutData); + m_timedOutNetEntityIds.erase(timeoutData); ConstNetworkEntityHandle entityHandle = m_networkEntityManager.GetEntity(netEntityId); if (auto entity = entityHandle.GetEntity()) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h index edb1b26ca8..ae9aac04ea 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace Multiplayer { @@ -32,10 +33,9 @@ namespace Multiplayer private: NetworkEntityAuthorityTracker& operator= (const NetworkEntityAuthorityTracker&) = delete; - using TimeoutDataMap = AZStd::unordered_set; using EntityAuthorityMap = AZStd::unordered_map>; - TimeoutDataMap m_timeoutDataMap; + NetEntityIdSet m_timedOutNetEntityIds; EntityAuthorityMap m_entityAuthorityMap; INetworkEntityManager& m_networkEntityManager; From 97e10d82107684a329c1a07cf3033f5de4259dd6 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Thu, 4 Nov 2021 16:27:05 -0700 Subject: [PATCH 06/10] Fix mock and benchmark interfaces Signed-off-by: kberg-amzn --- Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h | 3 +++ Gems/Multiplayer/Code/Tests/MockInterfaces.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h index 3c3d77e011..9ffdddfd20 100644 --- a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h @@ -270,6 +270,9 @@ namespace Multiplayer [[maybe_unused]] EntityIsMigrating entityIsMigrating) override {} void HandleLocalRpcMessage( [[maybe_unused]] NetworkEntityRpcMessage& message) override {} + void HandleEntitiesExitDomain(const NetEntityIdSet&) override {} + void ForceAssumeAuthority(const ConstNetworkEntityHandle&) override {} + void SetMigrateTimeoutTimeMs(AZ::TimeMs) override {} mutable AZStd::map m_networkEntityMap; diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h index 8cebf280b9..f5207d94c8 100644 --- a/Gems/Multiplayer/Code/Tests/MockInterfaces.h +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -87,6 +87,9 @@ 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_METHOD1(HandleEntitiesExitDomain, void(const Multiplayer::NetEntityIdSet&)); + MOCK_METHOD1(ForceAssumeAuthority, void(const Multiplayer::ConstNetworkEntityHandle&)); + MOCK_METHOD1(SetMigrateTimeoutTimeMs, void(AZ::TimeMs)); MOCK_CONST_METHOD0(DebugDraw, void()); }; From dedb367e9affd4e7bd3665955b0e38595fa7b680 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Thu, 4 Nov 2021 16:29:41 -0700 Subject: [PATCH 07/10] Remove lots of mock interface code duplication Signed-off-by: kberg-amzn --- .../Code/Tests/CommonBenchmarkSetup.h | 66 +------------------ 1 file changed, 1 insertion(+), 65 deletions(-) diff --git a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h index 9ffdddfd20..3e4a33290a 100644 --- a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h @@ -212,7 +212,7 @@ namespace Multiplayer } }; - class BenchmarkNetworkEntityManager : public Multiplayer::INetworkEntityManager + class BenchmarkNetworkEntityManager : public MockNetworkEntityManager { public: BenchmarkNetworkEntityManager() : m_authorityTracker(*this) {} @@ -221,58 +221,6 @@ namespace Multiplayer NetworkEntityAuthorityTracker* GetNetworkEntityAuthorityTracker() override { return &m_authorityTracker; } MultiplayerComponentRegistry* GetMultiplayerComponentRegistry() override { return &m_multiplayerComponentRegistry; } const HostId& GetHostId() const override { return m_hostId; } - EntityList CreateEntitiesImmediate( - [[maybe_unused]] const PrefabEntityId& prefabEntryId, - [[maybe_unused]] NetEntityRole netEntityRole, - [[maybe_unused]] const AZ::Transform& transform, - [[maybe_unused]] AutoActivate autoActivate) override { - return {}; - } - EntityList CreateEntitiesImmediate( - [[maybe_unused]] const PrefabEntityId& prefabEntryId, - [[maybe_unused]] NetEntityId netEntityId, - [[maybe_unused]] NetEntityRole netEntityRole, - [[maybe_unused]] AutoActivate autoActivate, - [[maybe_unused]] const AZ::Transform& transform) override { - return {}; - } - void SetupNetEntity( - [[maybe_unused]] AZ::Entity* netEntity, - [[maybe_unused]] PrefabEntityId prefabEntityId, - [[maybe_unused]] NetEntityRole netEntityRole) override {} - uint32_t GetEntityCount() const override { return {}; } - void MarkForRemoval( - [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) override {} - bool IsMarkedForRemoval( - [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) const override { - return {}; - } - void ClearEntityFromRemovalList( - [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) override {} - void ClearAllEntities() override {} - void AddEntityMarkedDirtyHandler( - [[maybe_unused]] AZ::Event<>::Handler& entityMarkedDirtyHandle) override {} - void AddEntityNotifyChangesHandler( - [[maybe_unused]] AZ::Event<>::Handler& entityNotifyChangesHandle) override {} - void AddEntityExitDomainHandler( - [[maybe_unused]] EntityExitDomainEvent::Handler& entityExitDomainHandler) override {} - void AddControllersActivatedHandler( - [[maybe_unused]] ControllersActivatedEvent::Handler& controllersActivatedHandler) override {} - void AddControllersDeactivatedHandler( - [[maybe_unused]] ControllersDeactivatedEvent::Handler& controllersDeactivatedHandler) override {} - void NotifyEntitiesDirtied() override {} - void NotifyEntitiesChanged() override {} - void NotifyControllersActivated( - [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, - [[maybe_unused]] EntityIsMigrating entityIsMigrating) override {} - void NotifyControllersDeactivated( - [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, - [[maybe_unused]] EntityIsMigrating entityIsMigrating) override {} - void HandleLocalRpcMessage( - [[maybe_unused]] NetworkEntityRpcMessage& message) override {} - void HandleEntitiesExitDomain(const NetEntityIdSet&) override {} - void ForceAssumeAuthority(const ConstNetworkEntityHandle&) override {} - void SetMigrateTimeoutTimeMs(AZ::TimeMs) override {} mutable AZStd::map m_networkEntityMap; @@ -301,18 +249,6 @@ namespace Multiplayer return InvalidNetEntityId; } - [[nodiscard]] AZStd::unique_ptr RequestNetSpawnableInstantiation( - [[maybe_unused]] const AZ::Data::Asset& netSpawnable, - [[maybe_unused]] const AZ::Transform& transform) override - { - return {}; - } - - void Initialize([[maybe_unused]] const HostId& hostId, [[maybe_unused]] AZStd::unique_ptr entityDomain) override {} - bool IsInitialized() const override { return true; } - IEntityDomain* GetEntityDomain() const override { return nullptr; } - void DebugDraw() const override {} - NetworkEntityTracker m_tracker; NetworkEntityAuthorityTracker m_authorityTracker; MultiplayerComponentRegistry m_multiplayerComponentRegistry; From ed06ef7ed24dcffede00e3c2e1ccfcb49b5e989b Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Thu, 4 Nov 2021 16:48:39 -0700 Subject: [PATCH 08/10] Removing ITimeoutHandler to simplify timeout queue interface, removes some unneeded code Signed-off-by: kberg-amzn --- .../DataStructures/TimeoutQueue.cpp | 6 ---- .../DataStructures/TimeoutQueue.h | 20 ------------- .../UdpTransport/UdpFragmentQueue.cpp | 16 +++++----- .../UdpTransport/UdpFragmentQueue.h | 6 ---- .../EntityReplicationManager.h | 2 -- .../EntityReplicationManager.cpp | 30 +++++++++---------- 6 files changed, 21 insertions(+), 59 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp index b0f316cf50..eb07efe80f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp @@ -122,10 +122,4 @@ namespace AzNetworking m_timeoutItemMap.erase(itemTimeoutId); } } - - void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts) - { - TimeoutHandler handler([&timeoutHandler](TimeoutQueue::TimeoutItem& item) { return timeoutHandler.HandleTimeout(item); }); - UpdateTimeouts(handler, maxTimeouts); - } } diff --git a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h index 63417ea36f..097e45c960 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h @@ -23,8 +23,6 @@ namespace AzNetworking Delete }; - class ITimeoutHandler; - //! @class TimeoutQueue //! @brief class for managing timeout items. class TimeoutQueue @@ -70,11 +68,6 @@ namespace AzNetworking using TimeoutHandler = AZStd::function; void UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1); - //! Updates timeouts for all items, invokes timeout handlers if required. - //! @param timeoutHandler listener instance to call back on for timeouts - //! @param maxTimeouts the maximum number of timeouts to process before breaking iteration - void UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1); - private: struct TimeoutQueueItem @@ -94,19 +87,6 @@ namespace AzNetworking TimeoutItemMap m_timeoutItemMap; TimeoutItemQueue m_timeoutItemQueue; }; - - //! @class ITimeoutHandler - //! @brief interface class for managing timeout items. - class ITimeoutHandler - { - public: - virtual ~ITimeoutHandler() = default; - - //! Handler callback for timed out items. - //! @param item containing registered timeout details - //! @return ETimeoutResult for whether to re-register or discard the timeout params - virtual TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) = 0; - }; } #include diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.cpp index fa4ee78a92..0c710f5a14 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.cpp @@ -20,7 +20,13 @@ namespace AzNetworking void UdpFragmentQueue::Update() { - m_timeoutQueue.UpdateTimeouts(*this); + m_timeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) + { + const SequenceId fragmentSequence = static_cast(item.m_userData & 0xFF); + AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast(fragmentSequence)); + m_packetFragments.erase(fragmentSequence); + return TimeoutResult::Delete; + }); } void UdpFragmentQueue::Reset() @@ -163,12 +169,4 @@ namespace AzNetworking return handledPacket; } - - TimeoutResult UdpFragmentQueue::HandleTimeout(TimeoutQueue::TimeoutItem& item) - { - const SequenceId fragmentSequence = static_cast(item.m_userData & 0xFF); - AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast(fragmentSequence)); - m_packetFragments.erase(fragmentSequence); - return TimeoutResult::Delete; - } } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.h index 9c929d63e8..5efa767283 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.h @@ -26,7 +26,6 @@ namespace AzNetworking //! @class UdpFragmentQueue //! @brief Class for reconstructing packet chunks into the original unsegmented packet. class UdpFragmentQueue - : public ITimeoutHandler { public: @@ -51,11 +50,6 @@ namespace AzNetworking private: - //! Handler callback for timed out items. - //! @param item containing registered timeout details - //! @return ETimeoutResult for whether to re-register or discard the timeout params - TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override; - TimeoutQueue m_timeoutQueue; SequenceGenerator m_sequenceGenerator; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h index 10346ad777..74935d5746 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -150,7 +150,6 @@ namespace Multiplayer void ClearRemovedReplicators(); class OrphanedEntityRpcs - : public AzNetworking::ITimeoutHandler { public: OrphanedEntityRpcs(EntityReplicationManager& replicationManager); @@ -159,7 +158,6 @@ namespace Multiplayer 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; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index fa099842c5..583671d1f3 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -912,24 +912,22 @@ namespace Multiplayer ; } - AzNetworking::TimeoutResult EntityReplicationManager::OrphanedEntityRpcs::HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) - { - NetEntityId timedOutEntityId = aznumeric_cast(item.m_userData); - auto entityRpcsIter = m_entityRpcMap.find(timedOutEntityId); - if (entityRpcsIter != m_entityRpcMap.end()) - { - for (NetworkEntityRpcMessage& rpcMessage : entityRpcsIter->second.m_rpcMessages) - { - m_replicationManager.DispatchOrphanedRpc(rpcMessage, nullptr); - } - m_entityRpcMap.erase(entityRpcsIter); - } - return AzNetworking::TimeoutResult::Delete; - } - void EntityReplicationManager::OrphanedEntityRpcs::Update() { - m_timeoutQueue.UpdateTimeouts(*this); + m_timeoutQueue.UpdateTimeouts([this](AzNetworking::TimeoutQueue::TimeoutItem& item) + { + NetEntityId timedOutEntityId = aznumeric_cast(item.m_userData); + auto entityRpcsIter = m_entityRpcMap.find(timedOutEntityId); + if (entityRpcsIter != m_entityRpcMap.end()) + { + for (NetworkEntityRpcMessage& rpcMessage : entityRpcsIter->second.m_rpcMessages) + { + m_replicationManager.DispatchOrphanedRpc(rpcMessage, nullptr); + } + m_entityRpcMap.erase(entityRpcsIter); + } + return AzNetworking::TimeoutResult::Delete; + }); } bool EntityReplicationManager::OrphanedEntityRpcs::DispatchOrphanedRpcs(EntityReplicator& entityReplicator) From 988561920adeec83b4b3e6f597e8386807ec2ed8 Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Fri, 5 Nov 2021 12:26:10 -0700 Subject: [PATCH 09/10] Sets up the event scheduler system component for hierarchy tests Signed-off-by: kberg-amzn --- Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h index 249837b484..1b64efc5e2 100644 --- a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -119,6 +120,8 @@ namespace Multiplayer m_mockTime = AZStd::make_unique>(); AZ::Interface::Register(m_mockTime.get()); + m_eventScheduler = AZStd::make_unique(); + m_mockNetworkTime = AZStd::make_unique>(); AZ::Interface::Register(m_mockNetworkTime.get()); @@ -170,6 +173,7 @@ namespace Multiplayer AZ::Interface::Unregister(m_mockMultiplayer.get()); AZ::Interface::Unregister(m_mockComponentApplicationRequests.get()); + m_eventScheduler.reset(); m_mockTime.reset(); m_mockNetworkEntityManager.reset(); @@ -204,6 +208,7 @@ namespace Multiplayer AZStd::unique_ptr> m_mockMultiplayer; AZStd::unique_ptr m_mockNetworkEntityManager; AZStd::unique_ptr> m_mockTime; + AZStd::unique_ptr m_eventScheduler; AZStd::unique_ptr> m_mockNetworkTime; AZStd::unique_ptr> m_mockConnection; From 989952e106cc0326c1566832b36094f23e9214bd Mon Sep 17 00:00:00 2001 From: kberg-amzn Date: Fri, 5 Nov 2021 12:28:06 -0700 Subject: [PATCH 10/10] Fix comment Signed-off-by: kberg-amzn --- .../Code/Include/Multiplayer/EntityDomains/IEntityDomain.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h index 4b1bbbdba8..b650f9e1d9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h @@ -34,7 +34,7 @@ namespace Multiplayer //! This method will be invoked whenever we unexpectedly lose the authoritative entity replicator for an entity. //! This gives our entity domain a chance to determine whether or not it should assume authority in this instance. - //! @param entityHandle the network entity handle of the entity that has lost it's authoritative replicator + //! @param entityHandle the network entity handle of the entity that has lost its authoritative replicator virtual void HandleLossOfAuthoritativeReplicator(const ConstNetworkEntityHandle& entityHandle) = 0; //! Debug draw to visualize host entity domains.