diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 23885e6c64..17d2cffafd 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -186,18 +186,20 @@ namespace Multiplayer class ScopedAlterTime final { public: - inline ScopedAlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId connectionId) + inline ScopedAlterTime(HostFrameId frameId, AZ::TimeMs timeMs, float blendFactor, AzNetworking::ConnectionId connectionId) { INetworkTime* time = GetNetworkTime(); m_previousHostFrameId = time->GetHostFrameId(); m_previousHostTimeMs = time->GetHostTimeMs(); m_previousRewindConnectionId = time->GetRewindingConnectionId(); time->AlterTime(frameId, timeMs, connectionId); + time->AlterBlendFactor(blendFactor); } inline ~ScopedAlterTime() { INetworkTime* time = GetNetworkTime(); time->AlterTime(m_previousHostFrameId, m_previousHostTimeMs, m_previousRewindConnectionId); + time->AlterBlendFactor(DefaultBlendFactor); } private: HostFrameId m_previousHostFrameId = InvalidHostFrameId; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h index 9f81ca97a9..4fdf428e37 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h @@ -21,6 +21,9 @@ namespace Multiplayer //! The default number of rewindable samples for us to store. static constexpr uint32_t RewindHistorySize = 128; + //! 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); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h index c88c971636..5e5e7aab25 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h @@ -42,6 +42,10 @@ namespace Multiplayer //! @return the hosts current timeMs virtual AZ::TimeMs GetHostTimeMs() const = 0; + //! Retrieves the hosts current blend factor (may be rewound on the server during backward reconciliation). + //! @return the hosts current blend factor + virtual float GetHostBlendFactor() const = 0; + //! Get the controlling connection that may be currently altering global game time. //! Note this abstraction is required at a relatively high level to allow for 'don't rewind the shooter' semantics //! @return the ConnectionId of the connection requesting the rewind operation @@ -59,6 +63,10 @@ namespace Multiplayer //! @param rewindConnectionId the rewinding ConnectionId virtual void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) = 0; + //! Alters the current Host blend factor. Used to drive interpolation in rewound states. + //! @param blendFactor the blend factor to use + virtual void AlterBlendFactor(float blendFactor) = 0; + //! Syncs all entities contained within a volume to the current rewind state. //! @param rewindVolume the volume to rewind entities within (needed for physics entities) virtual void SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h index 796eec5412..2eff6d8bb0 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h @@ -59,6 +59,10 @@ namespace Multiplayer //! @return value in const base type form const BASE_TYPE& Get() const; + //! Const base type retriever for one host frame behind Get(). + //! @return value in const base type form + const BASE_TYPE& GetPrevious() const; + //! Base type retriever. //! @return value in base type form BASE_TYPE& Modify(); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl index dc8d98fb45..7027069b27 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl @@ -65,6 +65,12 @@ namespace Multiplayer return GetValueForTime(GetCurrentTimeForProperty()); } + template + inline const BASE_TYPE& RewindableObject::GetPrevious() const + { + return GetValueForTime(GetCurrentTimeForProperty() - HostFrameId(1)); + } + template inline BASE_TYPE& RewindableObject::Modify() { diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index da299401d8..15945b12ff 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -155,11 +155,12 @@ namespace Multiplayer if (m_clientBankedTime < sv_MaxBankTimeWindowSec) { // Client blends from previous frame to target so here we subtract blend factor to get to that state - const float adjustedBlendFactor = std::pow(0.2f, input.GetHostBlendFactor()); + const float blendFactor = AZStd::max(0.f, input.GetHostBlendFactor()); + const float adjustedBlendFactor = std::pow(0.2f, blendFactor); const AZ::TimeMs blendMs = AZ::TimeMs(static_cast(static_cast(cl_InputRateMs)) * adjustedBlendFactor); m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary { - ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs() - blendMs, invokingConnection->GetConnectionId()); + ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs() - blendMs, input.GetHostBlendFactor(), invokingConnection->GetConnectionId()); GetNetBindComponent()->ProcessInput(input, static_cast(clientInputRateSec)); } @@ -313,7 +314,7 @@ namespace Multiplayer ++ModifyLastInputId(); input.SetClientInputId(GetLastInputId()); - ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId()); + ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), DefaultBlendFactor, invokingConnection->GetConnectionId()); GetNetBindComponent()->ProcessInput(input, clientInputRateSec); AZLOG @@ -393,7 +394,7 @@ namespace Multiplayer { // Reprocess the input for this frame NetworkInput& input = m_inputHistory[replayIndex]; - ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId()); + ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), DefaultBlendFactor, invokingConnection->GetConnectionId()); GetNetBindComponent()->ProcessInput(input, clientInputRateSec); AZLOG @@ -576,7 +577,7 @@ namespace Multiplayer NetworkInput& input = m_lastInputReceived[0]; { - ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), AzNetworking::InvalidConnectionId); + ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), DefaultBlendFactor, AzNetworking::InvalidConnectionId); GetNetBindComponent()->ProcessInput(input, inputRate); } diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index 88ffa0b5d7..b15125b5d2 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -54,6 +54,11 @@ namespace Multiplayer return m_hostTimeMs; } + float NetworkTime::GetHostBlendFactor() const + { + return m_hostBlendFactor; + } + AzNetworking::ConnectionId NetworkTime::GetRewindingConnectionId() const { return m_rewindingConnectionId; @@ -71,6 +76,11 @@ namespace Multiplayer m_rewindingConnectionId = rewindConnectionId; } + void NetworkTime::AlterBlendFactor(float blendFactor) + { + m_hostBlendFactor = blendFactor; + } + void NetworkTime::SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) { // Since the vis system doesn't support rewound queries, first query with an expanded volume to catch any fast moving entities @@ -94,6 +104,7 @@ namespace Multiplayer if (networkTransform != nullptr) { + // We're not presently factoring in interpolated position here const AZ::Vector3 rewindCenter = networkTransform->GetTranslation(); // Get the rewound position 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 diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h index 53c9540843..f6b2907c92 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h @@ -29,9 +29,11 @@ namespace Multiplayer HostFrameId GetUnalteredHostFrameId() const override; void IncrementHostFrameId() override; AZ::TimeMs GetHostTimeMs() const override; + float GetHostBlendFactor() const override; AzNetworking::ConnectionId GetRewindingConnectionId() const override; HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override; void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) override; + void AlterBlendFactor(float blendFactor) override; void SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) override; void ClearRewoundEntities() override; //! @} @@ -43,6 +45,7 @@ namespace Multiplayer HostFrameId m_hostFrameId = HostFrameId{ 0 }; HostFrameId m_unalteredFrameId = HostFrameId{ 0 }; AZ::TimeMs m_hostTimeMs = AZ::TimeMs{ 0 }; + float m_hostBlendFactor = DefaultBlendFactor; AzNetworking::ConnectionId m_rewindingConnectionId = AzNetworking::InvalidConnectionId; }; } diff --git a/Gems/Multiplayer/Code/Tests/RewindableContainerTests.cpp b/Gems/Multiplayer/Code/Tests/RewindableContainerTests.cpp index 2e3a65a5e5..ae56fdcf39 100644 --- a/Gems/Multiplayer/Code/Tests/RewindableContainerTests.cpp +++ b/Gems/Multiplayer/Code/Tests/RewindableContainerTests.cpp @@ -42,7 +42,7 @@ namespace UnitTest // Test rewind for all pushed values and overall size for (uint32_t idx = 0; idx < RewindableContainerSize; ++idx) { - Multiplayer::ScopedAlterTime time(static_cast(idx), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time(static_cast(idx), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(idx + 1, test.size()); EXPECT_EQ(idx, test.back()); } @@ -69,9 +69,9 @@ namespace UnitTest EXPECT_TRUE(test.empty()); // Test rewind for pop_back and clear - Multiplayer::ScopedAlterTime pop_time(static_cast(RewindableContainerSize), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime pop_time(static_cast(RewindableContainerSize), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(RewindableContainerSize - 1, test.size()); - Multiplayer::ScopedAlterTime clear_time(static_cast(RewindableContainerSize + 1), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime clear_time(static_cast(RewindableContainerSize + 1), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(0, test.size()); // Test copy_values and resize_no_construct @@ -99,7 +99,7 @@ namespace UnitTest // Test rewind for all values and overall size for (uint32_t idx = 1; idx <= RewindableContainerSize; ++idx) { - Multiplayer::ScopedAlterTime time(static_cast(idx), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time(static_cast(idx), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); for (uint32_t testIdx = 0; testIdx < RewindableContainerSize; ++testIdx) { if (testIdx < idx) diff --git a/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp b/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp index 472a1ce148..e992eb6848 100644 --- a/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp +++ b/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp @@ -38,7 +38,7 @@ namespace UnitTest for (uint32_t i = 0; i < 16; ++i) { - Multiplayer::ScopedAlterTime time(static_cast(i), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time(static_cast(i), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(i, test); } @@ -51,7 +51,7 @@ namespace UnitTest for (uint32_t i = 16; i < 48; ++i) { - Multiplayer::ScopedAlterTime time(static_cast(i), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time(static_cast(i), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(i, test); } } @@ -69,7 +69,7 @@ namespace UnitTest { // Note that we didn't actually set any value for time rewindableBufferFrames, so we're testing fetching a value past the last time set - Multiplayer::ScopedAlterTime time(static_cast(RewindableBufferFrames), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time(static_cast(RewindableBufferFrames), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(RewindableBufferFrames - 1, test); } } @@ -92,7 +92,7 @@ namespace UnitTest for (uint32_t i = 0; i < RewindableBufferFrames; ++i) { - Multiplayer::ScopedAlterTime time(static_cast(i), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time(static_cast(i), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); const Object& value = test; EXPECT_EQ(value.value, i); } @@ -101,19 +101,19 @@ namespace UnitTest TEST_F(RewindableObjectTests, TestBackfillOnLargeTimestep) { Multiplayer::RewindableObject test(0); - Multiplayer::ScopedAlterTime time1(static_cast(0), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time1(static_cast(0), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); test = 1; - Multiplayer::ScopedAlterTime time2(static_cast(31), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time2(static_cast(31), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); test = 2; for (uint32_t i = 0; i < 31; ++i) { - Multiplayer::ScopedAlterTime time(static_cast(i), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time(static_cast(i), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(1, test); } - Multiplayer::ScopedAlterTime time3(static_cast(31), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time3(static_cast(31), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(2, test); } @@ -129,7 +129,7 @@ namespace UnitTest for (uint32_t i = 0; i < 1000; ++i) { - Multiplayer::ScopedAlterTime time(static_cast(1000 - i), AZ::TimeMs{ 0 }, AzNetworking::InvalidConnectionId); + Multiplayer::ScopedAlterTime time(static_cast(1000 - i), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); EXPECT_EQ(1000, test); } }