Various local prediction and input processing related fixes
This commit is contained in:
@@ -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
|
||||
|
||||
+2
-1
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user