diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index 168807cd97..7e9accffd7 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -1367,9 +1367,6 @@ namespace AZ #endif } - //========================================================================= - // Tick - //========================================================================= void ComponentApplication::Tick(float deltaOverride /*= -1.f*/) { { @@ -1397,9 +1394,6 @@ namespace AZ } } - //========================================================================= - // Tick - //========================================================================= void ComponentApplication::TickSystem() { AZ_PROFILE_SCOPE(System, "Component application tick"); @@ -1547,5 +1541,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 bfc541ca09..8e95ff5fa3 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -5,6 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ + #pragma once #include diff --git a/Code/Framework/AzCore/AzCore/Math/Aabb.h b/Code/Framework/AzCore/AzCore/Math/Aabb.h index 488808bc4c..438d95622e 100644 --- a/Code/Framework/AzCore/AzCore/Math/Aabb.h +++ b/Code/Framework/AzCore/AzCore/Math/Aabb.h @@ -5,7 +5,6 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - #pragma once #include diff --git a/Code/Framework/AzCore/AzCore/Time/ITime.h b/Code/Framework/AzCore/AzCore/Time/ITime.h index 845017bd14..a97ba2319a 100644 --- a/Code/Framework/AzCore/AzCore/Time/ITime.h +++ b/Code/Framework/AzCore/AzCore/Time/ITime.h @@ -13,14 +13,26 @@ #include #include #include +#include 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. + //! AZ::ITime and associated types may not operate in realtime. These abstractions are to allow our application + //! simulation to operate both slower and faster than realtime in a well defined and user controllable manner + //! The rate at which time passes for AZ::ITime is controlled by the cvar t_scale + //! t_scale == 0 means simulation time should halt + //! 0 < t_scale < 1 will cause time to pass slower than realtime, with t_scale 0.1 being roughly 1/10th realtime + //! t_scale == 1 will cause time to pass at roughly realtime + //! t_scale > 1 will cause time to pass faster than normal, with t_scale 10 being roughly 10x realtime class ITime { public: @@ -33,6 +45,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 +67,53 @@ 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; + } + + //! Converts from milliseconds to AZStd::chrono::time_point + inline auto TimeMsToChrono(TimeMs value) + { + auto epoch = AZStd::chrono::time_point(); + auto chronoValue = AZStd::chrono::milliseconds(aznumeric_cast(value)); + return epoch + chronoValue; + } + + //! Converts from microseconds to AZStd::chrono::time_point + inline auto TimeUsToChrono(TimeUs value) + { + auto epoch = AZStd::chrono::time_point(); + auto chronoValue = AZStd::chrono::microseconds(aznumeric_cast(value)); + return epoch + chronoValue; + } +} // 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}; }; } 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/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/DataStructures/TimeoutQueue.h b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h index 76defd0b32..239af5be0e 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h @@ -94,6 +94,7 @@ namespace AzNetworking class ITimeoutHandler { public: + virtual ~ITimeoutHandler() = default; //! Handler callback for timed out items. //! @param item containing registered timeout details 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..1ccff7be50 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_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) { ; } @@ -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/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp index 272683febc..e469b70640 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp @@ -53,18 +53,11 @@ namespace AzNetworking { Close(); - if (!SocketCreateInternal()) - { - return false; - } - - if (!BindSocketForListenInternal(port)) - { - return false; - } - - if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd))) + if (!SocketCreateInternal() + || !BindSocketForListenInternal(port) + || !(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd))) { + Close(); return false; } @@ -75,18 +68,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 bf01ece458..b0e64f93e3 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_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,6 +61,7 @@ namespace AzNetworking , m_connectionListener(connectionListener) , m_socket(net_UdpUseEncryption ? new DtlsSocket() : new UdpSocket()) , m_readerThread(readerThread) + , m_timeoutMs(net_UdpDefaultTimeoutMs) { 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/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/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()); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Header.jinja index 41f0fa1b02..5cfeb250fa 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Include/Multiplayer/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/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/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/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 743315e8bc..f604102e37 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -32,9 +32,7 @@ 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 EntityServerMigrationEvent = AZ::Event; using EntityPreRenderEvent = AZ::Event; using EntityCorrectionEvent = AZ::Event<>; @@ -115,17 +113,13 @@ namespace Multiplayer void MarkDirty(); void NotifyLocalChanges(); void NotifySyncRewindState(); - void NotifyMigrationStart(ClientInputId migratedInputId); - void NotifyMigrationEnd(); - void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId); + void NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId); void NotifyPreRender(float deltaTime); void NotifyCorrection(); 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/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h index 9af22b1fdf..f0768f33f1 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -33,7 +33,6 @@ namespace Multiplayer }; typedef AZ::EBus NetworkCharacterRequestBus; - //! NetworkCharacterComponent //! Provides multiplayer support for game-play player characters. @@ -47,20 +46,11 @@ namespace Multiplayer AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkCharacterComponent, s_networkCharacterComponentConcreteUuid, Multiplayer::NetworkCharacterComponentBase) static void Reflect(AZ::ReflectContext* context); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); NetworkCharacterComponent(); - static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) - { - incompatible.push_back(AZ_CRC_CE("NetworkRigidBodyService")); - } - - static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) - { - NetworkCharacterComponentBase::GetRequiredServices(required); - required.push_back(AZ_CRC_CE("PhysXCharacterControllerService")); - } - // 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 cec73b1b71..72fd00cad9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h @@ -5,6 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ + #pragma once #include @@ -60,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/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/EntityDomains/IEntityDomain.h b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h index 66e39419e7..fc41a9b4d0 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 @@ -31,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 e32b6188eb..32af39a43b 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -42,7 +42,11 @@ namespace Multiplayer AzNetworking::ByteBuffer<2048> m_userData; }; + using ClientMigrationStartEvent = AZ::Event; + 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; @@ -78,26 +82,42 @@ 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; + 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 + // 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 ClientDisconnectedEvent Handler which is invoked on the client when a disconnection occurs + //! 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; + //! 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 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; @@ -110,7 +130,18 @@ 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(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; @@ -122,7 +153,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; @@ -141,9 +172,21 @@ namespace Multiplayer //! @param entityFilter non-owning pointer, the caller is responsible for memory management. virtual void SetFilterEntityManager(IFilterEntityManager* entityFilter) = 0; - //! @return pointer to the user-defined filtering manager of entities. By default, this isn't set and returns nullptr. + //! Returns a pointer to the user-defined filtering manager of entities. + //! @return pointer to the filtered entity manager, or nullptr if not set virtual IFilterEntityManager* GetFilterEntityManager() = 0; + //! Enables or disables automatic instantiation of netbound entities. + //! This setting is controlled by the networking layer and should not be touched + //! If enabled, netbound entities will instantiate as spawnables are loaded into the game world, generally true for the server + //! If disabled, netbound entities will only stream from a host, always true for a client + //! @param value boolean value controlling netbound entity instantiation behaviour + virtual void SetShouldSpawnNetworkEntities(bool value) = 0; + + //! Retrieves the current network entity instantiation behaviour. + //! @return boolean true if netbound entities should be auto instantiated, false if not + virtual bool GetShouldSpawnNetworkEntities() const = 0; + //! Retrieve the stats object bound to this multiplayer instance. //! @return the stats object bound to this multiplayer instance MultiplayerStats& GetStats() { return m_stats; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h index 4ac20abc83..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); @@ -105,9 +106,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,9 +136,23 @@ 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); AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetEntityId); AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetComponentId); AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::PropertyIndex); @@ -145,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/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h similarity index 89% rename from Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h index ef05e22e3e..2e1f83ae38 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -8,7 +8,8 @@ #pragma once -#include +#include +#include #include #include #include @@ -35,7 +36,9 @@ namespace Multiplayer { class IEntityDomain; class EntityReplicator; - + + using SendMigrateEntityEvent = AZ::Event; + //! @class EntityReplicationManager //! @brief Handles replication of relevant entities for one connection. class EntityReplicationManager final @@ -54,11 +57,11 @@ 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); + void SendUpdates(); void Clear(bool forMigration); bool SetEntityRebasing(NetworkEntityHandle& entityHandle); @@ -69,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(); @@ -79,7 +82,8 @@ 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); bool HandleEntityDeleteMessage(EntityReplicator* entityReplicator, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage); @@ -117,14 +121,12 @@ 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); - void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, HostId remoteHostId, AzNetworking::ConnectionId connectionId); + void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId); EntityReplicator* AddEntityReplicator(const ConstNetworkEntityHandle& entityHandle, NetEntityRole netEntityRole); @@ -153,7 +155,6 @@ namespace Multiplayer { public: OrphanedEntityRpcs(EntityReplicationManager& replicationManager); - virtual ~OrphanedEntityRpcs() = default; void Update(); bool DispatchOrphanedRpcs(EntityReplicator& entityReplicator); void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityRpcMessage); @@ -192,6 +193,8 @@ 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/Source/NetworkEntity/EntityReplication/EntityReplicator.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h similarity index 97% rename from Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h index e4dc62bc26..d239682c93 100644 --- a/Gems/Multiplayer/Code/Source/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; @@ -141,4 +142,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 5bcf038eff..43915127ed 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -21,6 +21,7 @@ namespace Multiplayer class NetworkEntityAuthorityTracker; class NetworkEntityRpcMessage; class MultiplayerComponentRegistry; + class IEntityDomain; using EntityExitDomainEvent = AZ::Event; using ControllersActivatedEvent = AZ::Event; @@ -38,6 +39,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(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 + 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; @@ -52,7 +66,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 @@ -166,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/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 c553bc5351..091043f034 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -13,10 +13,9 @@ - - + @@ -32,16 +31,16 @@ - + - + - - + + 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/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/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index c5f9b265f9..747f53d37b 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/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/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index 7b7cb52fe1..fbf83b6e6c 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 @@ -59,7 +60,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()) @@ -76,7 +77,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()) @@ -93,7 +94,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()) @@ -110,7 +111,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()) @@ -172,6 +173,8 @@ namespace Multiplayer { GetNetworkEntityManager()->NotifyControllersDeactivated(m_netEntityHandle, EntityIsMigrating::False); } + + GetNetworkEntityTracker()->UnregisterNetBindComponent(this); } NetEntityRole NetBindComponent::GetNetEntityRole() const @@ -391,17 +394,7 @@ 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) + void NetBindComponent::NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId) { m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); } @@ -431,16 +424,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); @@ -523,6 +506,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/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp index 9e15ae6ce0..7041f3ae9e 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -96,6 +96,17 @@ namespace Multiplayer NetworkCharacterComponentController::Reflect(context); } + void NetworkCharacterComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + NetworkCharacterComponentBase::GetRequiredServices(required); + required.push_back(AZ_CRC_CE("PhysXCharacterControllerService")); + } + + 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); }) { @@ -196,7 +207,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/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/ConnectionData/ClientToServerConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp index 7ac28fc078..392b020748 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() @@ -48,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 e5f1d0edfc..77df604b49 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 { @@ -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 5a9e8b4d4b..a9b8e03126 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 { @@ -25,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) { @@ -66,7 +69,7 @@ namespace Multiplayer return m_entityReplicationManager; } - void ServerToClientConnectionData::Update(AZ::TimeMs hostTimeMs) + void ServerToClientConnectionData::Update() { m_entityReplicationManager.ActivatePendingEntities(); @@ -76,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(); } } } @@ -91,40 +94,32 @@ namespace Multiplayer void ServerToClientConnectionData::OnControlledEntityMigration ( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, - [[maybe_unused]] HostId remoteHostId, + [[maybe_unused]] const HostId& remoteHostId, [[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; + 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(remoteHostId, randomUserIdentifier, migratedClientInputId); + GetConnection()->SendReliablePacket(clientMigration); + + m_controlledEntity = NetworkEntityHandle(); + m_canSendUpdates = false; } void ServerToClientConnectionData::OnGameplayStarted() diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h index 9e9afc4413..8dcf08c480 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 { @@ -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; //! @} @@ -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/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..9d53990fb4 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; @@ -20,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 3fe164a31f..ae80c16ab8 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h @@ -21,10 +21,15 @@ 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; + 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 023c5ea3af..b7dde3b9e7 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -72,7 +72,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"); @@ -80,14 +80,13 @@ 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"); 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) { @@ -95,9 +94,6 @@ namespace Multiplayer { serializeContext->Class() ->Version(1); - - serializeContext->Class() - ->Version(1); serializeContext->Class() ->Version(1); serializeContext->Class() @@ -113,7 +109,6 @@ namespace Multiplayer } else if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { - behaviorContext->Class(); behaviorContext->Class(); behaviorContext->Class(); behaviorContext->Class(); @@ -174,7 +169,12 @@ namespace Multiplayer AZ::ConsoleInvokedFrom invokedFrom ) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); }) { - ; + AZ::Interface::Register(this); + } + + MultiplayerSystemComponent::~MultiplayerSystemComponent() + { + AZ::Interface::Unregister(this); } void MultiplayerSystemComponent::Activate() @@ -186,7 +186,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 @@ -196,11 +195,12 @@ 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(); AZ::TickBus::Handler::BusDisconnect(); + + m_networkEntityManager.Reset(); } bool MultiplayerSystemComponent::StartHosting(uint16_t port, bool isDedicated) @@ -209,7 +209,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()); @@ -311,7 +311,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; @@ -348,12 +347,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++; @@ -395,6 +394,11 @@ namespace Multiplayer { m_networkInterface->GetConnectionSet().VisitConnections(visitor); } + + if (bg_multiplayerDebugDraw) + { + m_networkEntityManager.DebugDraw(); + } } int MultiplayerSystemComponent::GetTickOrder() @@ -460,7 +464,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); @@ -469,7 +473,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; @@ -489,10 +493,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; @@ -607,7 +609,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 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(packet.GetLastClientInputId()); + m_networkInterface->Connect(packet.GetRemoteServerAddress()); + return true; } ConnectResult MultiplayerSystemComponent::ValidateConnect @@ -641,7 +658,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 @@ -655,27 +672,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); - } - - AZStd::unique_ptr window = AZStd::make_unique(); - reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetEntityActivationTimeSliceMs(cl_defaultNetworkEntityActivationTimeSliceMs); + connection->SetUserData(new ClientToServerConnectionData(connection, *this, providerTicket)); + AZStd::unique_ptr window = AZStd::make_unique(connection); reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); } } @@ -754,13 +758,21 @@ namespace Multiplayer if (m_agentType == MultiplayerAgentType::Uninitialized) { + m_spawnNetboundEntities = false; if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer) { + m_spawnNetboundEntities = true; 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 + 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()); + } } } m_agentType = multiplayerType; @@ -779,14 +791,34 @@ 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); } + void MultiplayerSystemComponent::AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler) + { + handler.Connect(m_notifyClientMigrationEvent); + } + + void MultiplayerSystemComponent::AddNotifyEntityMigrationEventHandler(NotifyEntityMigrationEvent::Handler& handler) + { + handler.Connect(m_notifyEntityMigrationEvent); + } + void MultiplayerSystemComponent::AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) { - handler.Connect(m_connAcquiredEvent); + handler.Connect(m_connectionAcquiredEvent); } void MultiplayerSystemComponent::AddSessionInitHandler(SessionInitEvent::Handler& handler) @@ -799,6 +831,16 @@ namespace Multiplayer handler.Connect(m_shutdownEvent); } + void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) + { + 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(); @@ -845,6 +887,16 @@ namespace Multiplayer return m_filterEntityManager; } + void MultiplayerSystemComponent::SetShouldSpawnNetworkEntities(bool value) + { + m_spawnNetboundEntities = value; + } + + bool MultiplayerSystemComponent::GetShouldSpawnNetworkEntities() const + { + return m_spawnNetboundEntities; + } + void MultiplayerSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { const MultiplayerStats& stats = GetStats(); @@ -904,7 +956,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) @@ -912,7 +964,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); @@ -932,7 +984,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); @@ -991,7 +1043,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 c467ed9ad9..262168b536 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,19 @@ namespace Multiplayer //! @{ MultiplayerAgentType GetAgentType() const override; void InitializeMultiplayer(MultiplayerAgentType state) override; + bool StartHosting(uint16_t port, bool isDedicated = true) 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; 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; - 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 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; @@ -119,6 +125,8 @@ namespace Multiplayer INetworkEntityManager* GetNetworkEntityManager() override; void SetFilterEntityManager(IFilterEntityManager* entityFilter) override; IFilterEntityManager* GetFilterEntityManager() override; + void SetShouldSpawnNetworkEntities(bool value) override; + bool GetShouldSpawnNetworkEntities() const override; //! @} //! Console commands. @@ -148,8 +156,12 @@ namespace Multiplayer SessionInitEvent m_initEvent; SessionShutdownEvent m_shutdownEvent; - ConnectionAcquiredEvent m_connAcquiredEvent; + ConnectionAcquiredEvent m_connectionAcquiredEvent; ClientDisconnectedEvent m_clientDisconnectedEvent; + ClientMigrationStartEvent m_clientMigrationStartEvent; + ClientMigrationEndEvent m_clientMigrationEndEvent; + NotifyClientMigrationEvent m_notifyClientMigrationEvent; + NotifyEntityMigrationEvent m_notifyEntityMigrationEvent; AZStd::queue m_pendingConnectionTickets; @@ -160,6 +172,7 @@ namespace Multiplayer float m_renderBlendFactor = 0.0f; float m_tickFactor = 0.0f; bool m_didHandshake = false; + bool m_spawnNetboundEntities = true; #if !defined(AZ_RELEASE_BUILD) MultiplayerEditorConnection m_editorConnectionListener; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index b45c05cda7..6f65d01e50 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -6,11 +6,10 @@ * */ -#include -#include +#include +#include #include #include -#include #include #include #include @@ -46,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; @@ -61,14 +61,16 @@ namespace Multiplayer { networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler); } + + GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler); } - 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; } @@ -107,10 +109,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); @@ -120,9 +146,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()), @@ -130,65 +156,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,69 +227,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 %d to %d", toSendList.size(), (uint8_t)GetNetworkEntityManager()->GetHostId(), (uint8_t)GetRemoteHostId()); - - // 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); } } @@ -368,10 +358,11 @@ 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 + // 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()) { @@ -391,19 +382,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 @@ -453,11 +458,16 @@ 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); } + void EntityReplicationManager::AddSendMigrateEntityEventHandler(SendMigrateEntityEvent::Handler& handler) + { + handler.Connect(m_sendMigrateEntityEvent); + } + const EntityReplicator* EntityReplicationManager::GetEntityReplicator(NetEntityId netEntityId) const { auto it = m_entityReplicatorMap.find(netEntityId); @@ -492,18 +502,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 @@ -519,17 +529,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()); } } @@ -698,8 +708,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()), @@ -750,13 +760,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()); } } } @@ -1062,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(); } @@ -1108,7 +1118,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) @@ -1133,9 +1143,12 @@ 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); - AZLOG(NET_RepDeletes, "Migration packet sent %u to remote manager id %d", netEntityId, aznumeric_cast(GetRemoteHostId())); + + 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); @@ -1144,7 +1157,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) { @@ -1154,7 +1167,7 @@ namespace Multiplayer invokingConnection, replicator, AzNetworking::InvalidPacketId, - message.m_entityId, + message.m_netEntityId, NetEntityRole::Server, outputSerializer, message.m_prefabEntityId @@ -1168,7 +1181,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"); @@ -1188,7 +1201,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; } @@ -1200,11 +1213,11 @@ namespace Multiplayer } } - void EntityReplicationManager::OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, 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 b684473ea3..05001b5960 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -12,11 +12,10 @@ #include #include #include -#include +#include +#include #include #include -#include -#include #include #include @@ -55,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(); } } @@ -95,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()); @@ -126,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 @@ -147,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(); } @@ -169,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()) @@ -471,19 +472,19 @@ 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()); } 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()); } @@ -495,6 +496,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/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/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..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,37 +33,18 @@ namespace Multiplayer if (entity) { 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(); + m_netBindComponent = networkEntityTracker->GetNetBindComponent(entity); + if (m_netBindComponent != nullptr) + { + m_netEntityId = m_netBindComponent->GetNetEntityId(); + } + else + { + *this = ConstNetworkEntityHandle(); + } } } - 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) @@ -144,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 bcd4c9aad8..db7f7243cc 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 @@ -41,11 +43,22 @@ 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); m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true); + m_entityDomain->ActivateTracking(m_ownedEntities); + } + + bool NetworkEntityManager::IsInitialized() const + { + return m_entityDomain != nullptr; + } + + IEntityDomain* NetworkEntityManager::GetEntityDomain() const + { + return m_entityDomain.get(); } NetworkEntityTracker* NetworkEntityManager::GetNetworkEntityTracker() @@ -63,7 +76,7 @@ namespace Multiplayer return &m_multiplayerComponentRegistry; } - HostId NetworkEntityManager::GetHostId() const + const HostId& NetworkEntityManager::GetHostId() const { return m_hostId; } @@ -86,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) @@ -96,8 +109,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 }); @@ -204,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) @@ -211,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); } @@ -226,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); } @@ -239,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) @@ -279,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; @@ -330,6 +366,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) { @@ -366,7 +403,6 @@ namespace Multiplayer &AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone); returnList.push_back(netBindComponent->GetEntityHandle()); - } else { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 50e6beedad..133c35dce0 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -31,15 +31,15 @@ namespace Multiplayer NetworkEntityManager(); ~NetworkEntityManager(); - //! Only invoked for authoritative hosts - void Initialize(HostId hostId, AZStd::unique_ptr entityDomain); - //! INetworkEntityManager overrides. //! @{ + 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; @@ -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,17 +79,22 @@ 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; void OnRootSpawnableReleased(uint32_t generation) override; //! @} + //! Used to release all memory prior to shutdown. + void Reset(); + private: void RemoveEntities(); NetEntityId NextId(); @@ -105,7 +108,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 b37f485ff4..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); - const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityLocalBoundsUnion(entity->GetId()); - const AZ::Vector3 currentCenter = currentBounds.GetCenter(); + 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/Pipeline/NetworkSpawnableHolderComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp index 198deebbe3..af99d832e5 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp @@ -38,20 +38,19 @@ namespace Multiplayer void NetworkSpawnableHolderComponent::Activate() { - const auto agentType = GetMultiplayer()->GetAgentType(); - const bool shouldSpawnNetEntities = - (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); + IMultiplayer* multiplayer = GetMultiplayer(); + const bool shouldSpawnNetEntities = multiplayer->GetShouldSpawnNetworkEntities(); - if(shouldSpawnNetEntities) + if (shouldSpawnNetEntities) { AZ::Transform rootEntityTransform = AZ::Transform::CreateIdentity(); - if(auto* transformInterface = GetEntity()->GetTransform()) + if (auto* transformInterface = GetEntity()->GetTransform()) { rootEntityTransform = transformInterface->GetWorldTM(); } - INetworkEntityManager* networkEntityManager = GetNetworkEntityManager(); + INetworkEntityManager* networkEntityManager = multiplayer->GetNetworkEntityManager(); AZ_Assert(networkEntityManager != nullptr, "Network Entity Manager must be initialized before NetworkSpawnableHolderComponent is activated"); 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..9d9cc74d2b 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); }) @@ -106,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 @@ -117,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; } @@ -148,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 @@ -179,6 +181,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; @@ -204,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()) @@ -237,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/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/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index 316f85c214..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 { diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h index 3a5409c7ed..d6f65918c0 100644 --- a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -26,10 +26,10 @@ #include #include #include +#include +#include #include #include -#include -#include namespace Multiplayer { @@ -206,7 +206,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 { diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 7782444a6e..f16483e663 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 @@ -60,6 +63,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 @@ -90,10 +94,7 @@ set(FILES Source/MultiplayerSystemComponent.cpp Source/MultiplayerSystemComponent.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