From c6ea0c0a46c2d6df0bc20734440735349af073e3 Mon Sep 17 00:00:00 2001 From: karlberg Date: Sat, 15 May 2021 15:10:17 -0700 Subject: [PATCH] Various local prediction and input processing related fixes --- .../Serialization/HashSerializer.cpp | 4 +- .../LocalPredictionPlayerInputComponent.h | 3 +- .../Components/MultiplayerComponent.h | 1 + .../Multiplayer/Components/NetBindComponent.h | 1 + .../NetworkInput/IMultiplayerComponentInput.h | 1 + .../Source/AutoGen/AutoComponent_Header.jinja | 4 +- .../Source/AutoGen/AutoComponent_Source.jinja | 16 ++++ .../LocalPredictionPlayerInputComponent.cpp | 93 ++++++++----------- .../Source/Components/NetBindComponent.cpp | 8 ++ .../Source/MultiplayerSystemComponent.cpp | 1 + .../Code/Source/NetworkInput/NetworkInput.cpp | 5 +- .../Source/NetworkInput/NetworkInputArray.cpp | 11 --- .../Source/NetworkInput/NetworkInputArray.h | 4 - 13 files changed, 78 insertions(+), 74 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp b/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp index c7f47cbc04..479299a87b 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp @@ -22,7 +22,9 @@ namespace AzNetworking AZ::HashValue32 HashSerializer::GetHash() const { // Just truncate the upper bits - return static_cast(m_hash); + const AZ::HashValue32 lower = static_cast(m_hash); + const AZ::HashValue32 upper = static_cast(m_hash >> 32); + return lower ^ upper; } SerializerMode HashSerializer::GetSerializerMode() const diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h index 7a6105a7e3..c69623a9e9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h @@ -102,7 +102,8 @@ namespace Multiplayer AZ::TimeMs m_lastInputReceivedTimeMs = AZ::TimeMs{ 0 }; AZ::TimeMs m_lastCorrectionSentTimeMs = AZ::TimeMs{ 0 }; - ClientInputId m_clientInputId = ClientInputId{ 0 }; + ClientInputId m_clientInputId = ClientInputId{ 0 }; // Clients incrementing inputId + ClientInputId m_lastClientInputId = ClientInputId{ 0 }; // Last inputId processed by the server ClientInputId m_lastCorrectionInputId = ClientInputId{ 0 }; ClientInputId m_lastMigratedInputId = ClientInputId{ 0 }; // Used to resend inputs that were queued during a migration event HostFrameId m_serverMigrateFrameId = InvalidHostFrameId; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h index 6f410dd97c..19689171c5 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h @@ -71,6 +71,7 @@ namespace Multiplayer NetworkEntityHandle GetEntityHandle(); void MarkDirty(); + virtual void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) = 0; virtual NetComponentId GetNetComponentId() const = 0; virtual bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole netEntityRole, NetworkEntityRpcMessage& rpcMessage) = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 257e7cab4b..41503396fb 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -72,6 +72,7 @@ namespace Multiplayer ConstNetworkEntityHandle GetEntityHandle() const; NetworkEntityHandle GetEntityHandle(); + void SetOwningConnectionId(AzNetworking::ConnectionId connectionId); void SetAllowAutonomy(bool value); MultiplayerComponentInputVector AllocateComponentInputs(); bool IsProcessingInput() const; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h index 13faff3a7e..7af1bf3f60 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h @@ -30,6 +30,7 @@ namespace Multiplayer virtual ~IMultiplayerComponentInput() = default; virtual NetComponentId GetNetComponentId() const = 0; virtual bool Serialize(AzNetworking::ISerializer& serializer) = 0; + virtual IMultiplayerComponentInput& operator= (const IMultiplayerComponentInput&) { return *this; } }; using MultiplayerComponentInputVector = AZStd::vector>; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 0d4e7146a0..8eede21452 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -329,6 +329,7 @@ namespace {{ Component.attrib['Namespace'] }} public: Multiplayer::NetComponentId GetNetComponentId() const override; bool Serialize(AzNetworking::ISerializer& serializer) override; + Multiplayer::IMultiplayerComponentInput& operator =(const Multiplayer::IMultiplayerComponentInput& rhs) override; {% call(Input) AutoComponentMacros.ParseNetworkInputs(Component) %} {{ Input.attrib['Type'] }} m_{{ LowerFirst(Input.attrib['Name']) }} = {{ Input.attrib['Type'] }}({{ Input.attrib['Init'] }}); @@ -439,6 +440,7 @@ namespace {{ Component.attrib['Namespace'] }} //! MultiplayerComponent interface //! @{ + void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) override; Multiplayer::NetComponentId GetNetComponentId() const override; bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& rpcMessage) override; bool SerializeStateDeltaMessage(Multiplayer::ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) override; @@ -519,7 +521,7 @@ namespace {{ Component.attrib['Namespace'] }} //! Archetype Properties {{ DeclareArchetypePropertyVars(Component)|indent(8) }} {% call(Type, Name) AutoComponentMacros.ParseComponentServiceTypeAndName(Component) %} - {{ Type }}* {{ Name }} = nullptr; + {{ Type }}* {{ Name }} = nullptr; {% endcall %} static Multiplayer::NetComponentId s_netComponentId; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 516e6a05e8..3b3fa817cd 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -1068,6 +1068,13 @@ namespace {{ Component.attrib['Namespace'] }} return serializer.IsValid(); } + Multiplayer::IMultiplayerComponentInput& {{ ComponentName }}NetworkInput::operator =([[maybe_unused]] const Multiplayer::IMultiplayerComponentInput& rhs) + { + AZ_Assert(s_netComponentId == rhs.GetNetComponentId(), "AttachNetSystemComponent was not called on the owning NetworkInput"); + *this = *static_cast(&rhs); + return *this; + } + {% endif %} {{ ControllerBaseName }}::{{ ControllerBaseName }}({{ ComponentName }}& parent) : MultiplayerController(parent) @@ -1278,6 +1285,15 @@ namespace {{ Component.attrib['Namespace'] }} {{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', false)|indent(4) -}} {{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', true)|indent(4) }} + void {{ ComponentBaseName }}::SetOwningConnectionId([[maybe_unused]] AzNetworking::ConnectionId connectionId) + { +{% for Property in Component.iter('NetworkProperty') %} +{% if Property.attrib['IsRewindable']|booleanTrue %} + m_{{ LowerFirst(Property.attrib['Name']) }}.SetOwningConnectionId(connectionId); +{% endif %} +{% endfor %} + } + Multiplayer::NetComponentId {{ ComponentBaseName }}::GetNetComponentId() const { return s_netComponentId; diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 46136fcde9..10a0d17e73 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -113,73 +113,57 @@ namespace Multiplayer [[maybe_unused]] const AzNetworking::PacketEncodingBuffer& clientState ) { - // After receiving the first input from the client, start the update event to check for slow hacking - if (!m_updateBankedTimeEvent.IsScheduled()) - { - m_updateBankedTimeEvent.Enqueue(sv_InputUpdateTimeMs, true); - } - if (invokingConnection == nullptr) { // Discard any input messages that were locally dispatched or sent by disconnected clients return; } + const ClientInputId clientInputId = inputArray[0].GetClientInputId(); + if (clientInputId <= m_lastClientInputId) + { + AZLOG(NET_Prediction, "Discarding old or out of order move input (current: %u, received %u)", + aznumeric_cast(m_lastClientInputId), aznumeric_cast(clientInputId)); + return; + } + + // After receiving the first input from the client, start the update event to check for slow hacking + if (!m_updateBankedTimeEvent.IsScheduled()) + { + m_updateBankedTimeEvent.Enqueue(sv_InputUpdateTimeMs, true); + } + const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs(); const double clientInputRateSec = static_cast(static_cast(cl_InputRateMs)) / 1000.0; m_lastInputReceivedTimeMs = currentTimeMs; // Keep track of last inputs received, also allows us to update frame ids m_lastInputReceived = inputArray; - - // Figure out which index from the input array we want - // we start at the oldest input that has not been processed - int32_t inputArrayIndex = -1; - for (int32_t i = NetworkInputArray::MaxElements - 1; i >= 0; --i) - { - // Find an input that is newer than the last one we processed - if (m_lastInputReceived[i].GetClientInputId() > GetLastInputId()) - { - inputArrayIndex = i; - break; - } - } - - if (inputArrayIndex < 0) - { - AZLOG - ( - NET_Prediction, - "Discarding old or out of order move input (current: %u, received %u)", - aznumeric_cast(GetLastInputId()), - aznumeric_cast(m_lastInputReceived[0].GetClientInputId()) - ); - return; - } - - bool lostInput = false; - if (GetLastInputId() < inputArray.GetPreviousInputId()) - { - // last move id processed is older than the previous input id, we missed some input packets - lostInput = true; - } - SetLastInputId(m_lastInputReceived[0].GetClientInputId()); // Set this variable in case of migration - while (inputArrayIndex >= 0) + while (m_lastClientInputId < clientInputId) { - NetworkInput& input = m_lastInputReceived[inputArrayIndex]; + ++m_lastClientInputId; + + // Figure out which index from the input array we want + // If we have skipped an id, check if it was sent to us in the array. If we have lost too many, just use the oldest one in the array + const uint32_t deltaFrameId = aznumeric_cast(clientInputId - m_lastClientInputId); // always >= 0 because of while loop check + const uint32_t inputArrayIdx = AZStd::min(deltaFrameId, NetworkInputArray::MaxElements - 1); + const bool lostInput = deltaFrameId >= NetworkInputArray::MaxElements; // For logging only + + NetworkInput &input = m_lastInputReceived[inputArrayIdx]; + input.SetClientInputId(m_lastClientInputId); // Anticheat, if we're receiving too many inputs, and fall outside our variable latency input window // Discard move input events, client may be speed hacking if (m_clientBankedTime < sv_MaxBankTimeWindowSec) { m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary - { ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId()); GetNetBindComponent()->ProcessInput(input, static_cast(clientInputRateSec)); } + if (lostInput) { AZLOG(NET_Prediction, "InputLost InputId=%u", aznumeric_cast(input.GetClientInputId())); @@ -193,7 +177,6 @@ namespace Multiplayer { AZLOG(NET_Prediction, "Dropped InputId=%u", aznumeric_cast(input.GetClientInputId())); } - --inputArrayIndex; } if (sv_EnableCorrections && (currentTimeMs - m_lastCorrectionSentTimeMs > sv_MinCorrectionTimeMs)) @@ -205,6 +188,14 @@ namespace Multiplayer const AZ::HashValue32 localAuthorityHash = hashSerializer.GetHash(); + AZLOG + ( + NET_Prediction, + "Hash values for ProcessInput: client=%u, server=%u", + aznumeric_cast(stateHash), + aznumeric_cast(localAuthorityHash) + ); + if (stateHash != localAuthorityHash) { // Produce correction for client @@ -542,21 +533,15 @@ namespace Multiplayer m_inputHistory.PopFront(); } - const size_t inputHistorySize = m_inputHistory.Size(); + const int64_t inputHistorySize = aznumeric_cast(m_inputHistory.Size()); // Form the rest of the input array using the n most recent elements in the history buffer // NOTE: inputArray[0] has already been initialized hence start at i = 1 - for (uint32_t i = 1; i < NetworkInputArray::MaxElements; ++i) + for (int64_t i = 1; i < aznumeric_cast(NetworkInputArray::MaxElements); ++i) { - if (i < inputHistorySize) - { - inputArray[i] = m_inputHistory[inputHistorySize - 1 - i]; - } - else // History is too small? - { - // Plug in the most recent input - inputArray[i] = input; - } + // Clamp to oldest element if history is too small + const int64_t historyIndex = AZStd::max(inputHistorySize - 1 - i, 0); + inputArray[i] = m_inputHistory[historyIndex]; } // Send the input to server (only when we are not migrating) diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index 6449f57e8e..e48d0b0d09 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -152,6 +152,14 @@ namespace Multiplayer return m_netEntityHandle; } + void NetBindComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId) + { + for (MultiplayerComponent* multiplayerComponent : m_multiplayerInputComponentVector) + { + multiplayerComponent->SetOwningConnectionId(connectionId); + } + } + void NetBindComponent::SetAllowAutonomy(bool value) { // This flag allows a player host to autonomously control their player entity, even though the entity is in an authority role diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index a10bbf8a1a..723fada6d1 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -448,6 +448,7 @@ namespace Multiplayer if (entityList.size() > 0) { controlledEntity = entityList[0]; + controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId()); } if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp index 7b9cba5a93..ba87849047 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp @@ -9,7 +9,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ - +#pragma optimize ("", off) #include #include #include @@ -166,6 +166,7 @@ namespace Multiplayer void NetworkInput::CopyInternal(const NetworkInput& rhs) { m_inputId = rhs.m_inputId; + m_hostFrameId = rhs.m_hostFrameId; m_hostTimeMs = rhs.m_hostTimeMs; m_componentInputs.resize(rhs.m_componentInputs.size()); for (int32_t i = 0; i < rhs.m_componentInputs.size(); ++i) @@ -175,7 +176,7 @@ namespace Multiplayer { m_componentInputs[i] = AZStd::move(GetMultiplayerComponentRegistry()->AllocateComponentInput(rhsComponentId)); } - *m_componentInputs[i] = *rhs.m_componentInputs[i]; + *(m_componentInputs[i]) = *(rhs.m_componentInputs[i]); } m_wasAttached = rhs.m_wasAttached; } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp index d95310e4b5..e736cd4da2 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp @@ -48,16 +48,6 @@ namespace Multiplayer return m_inputs[index].m_networkInput; } - void NetworkInputArray::SetPreviousInputId(ClientInputId previousInputId) - { - m_previousInputId = previousInputId; - } - - ClientInputId NetworkInputArray::GetPreviousInputId() const - { - return m_previousInputId; - } - bool NetworkInputArray::Serialize(AzNetworking::ISerializer& serializer) { // Always serialize the full first element @@ -102,7 +92,6 @@ namespace Multiplayer } } } - serializer.Serialize(m_previousInputId, "PreviousInputId"); return true; } } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h index b67ce79112..293fb18928 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h @@ -33,9 +33,6 @@ namespace Multiplayer NetworkInput& operator[](uint32_t index); const NetworkInput& operator[](uint32_t index) const; - void SetPreviousInputId(ClientInputId previousInputId); - ClientInputId GetPreviousInputId() const; - bool Serialize(AzNetworking::ISerializer& serializer); private: @@ -49,6 +46,5 @@ namespace Multiplayer ConstNetworkEntityHandle m_owner; AZStd::array m_inputs; - ClientInputId m_previousInputId; }; }