Various local prediction and input processing related fixes

This commit is contained in:
karlberg
2021-05-15 15:10:17 -07:00
parent 61c24ee265
commit c6ea0c0a46
13 changed files with 78 additions and 74 deletions
@@ -22,7 +22,9 @@ namespace AzNetworking
AZ::HashValue32 HashSerializer::GetHash() const
{
// Just truncate the upper bits
return static_cast<AZ::HashValue32>(m_hash);
const AZ::HashValue32 lower = static_cast<AZ::HashValue32>(m_hash);
const AZ::HashValue32 upper = static_cast<AZ::HashValue32>(m_hash >> 32);
return lower ^ upper;
}
SerializerMode HashSerializer::GetSerializerMode() const
@@ -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;
@@ -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;
@@ -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;
@@ -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<AZStd::unique_ptr<IMultiplayerComponentInput>>;
@@ -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;
@@ -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<const {{ ComponentName }}NetworkInput*>(&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;
@@ -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<uint32_t>(m_lastClientInputId), aznumeric_cast<uint32_t>(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<double>(static_cast<AZ::TimeMs>(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<uint32_t>(GetLastInputId()),
aznumeric_cast<uint32_t>(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<uint32_t>(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<float>(clientInputRateSec));
}
if (lostInput)
{
AZLOG(NET_Prediction, "InputLost InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
@@ -193,7 +177,6 @@ namespace Multiplayer
{
AZLOG(NET_Prediction, "Dropped InputId=%u", aznumeric_cast<uint32_t>(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<uint32_t>(stateHash),
aznumeric_cast<uint32_t>(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<int64_t>(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<int64_t>(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<int64_t>(inputHistorySize - 1 - i, 0);
inputArray[i] = m_inputHistory[historyIndex];
}
// Send the input to server (only when we are not migrating)
@@ -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
@@ -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
@@ -9,7 +9,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma optimize ("", off)
#include <Multiplayer/NetworkInput/NetworkInput.h>
#include <Multiplayer/Components/MultiplayerComponentRegistry.h>
#include <Multiplayer/Components/NetBindComponent.h>
@@ -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;
}
@@ -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;
}
}
@@ -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<Wrapper, MaxElements> m_inputs;
ClientInputId m_previousInputId;
};
}