Ported the local prediction player controller component

This commit is contained in:
karlberg
2021-05-05 20:07:16 -07:00
parent bbe3fcfdd9
commit a1fe8fe419
55 changed files with 1164 additions and 275 deletions
@@ -0,0 +1,162 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzNetworking/Serialization/StringifySerializer.h>
namespace AzNetworking
{
StringifySerializer::StringifySerializer(char delimeter, bool outputFieldNames, const AZStd::string& seperator)
: m_delimeter(delimeter)
, m_outputFieldNames(outputFieldNames)
, m_separator(seperator)
{
;
}
const AZStd::string& StringifySerializer::GetString() const
{
return m_string;
}
const StringifySerializer::StringMap& StringifySerializer::GetValueMap() const
{
return m_map;
}
SerializerMode StringifySerializer::GetSerializerMode() const
{
return SerializerMode::ReadFromObject;
}
bool StringifySerializer::Serialize(bool& value, const char* name)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(char& value, const char* name, char, char)
{
const int val = value; // Print chars as integers
return ProcessData(name, val);
}
bool StringifySerializer::Serialize(int8_t& value, const char* name, int8_t, int8_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(int16_t& value, const char* name, int16_t, int16_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(int32_t& value, const char* name, int32_t, int32_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(int64_t& value, const char* name, int64_t, int64_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(uint8_t& value, const char* name, uint8_t, uint8_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(uint16_t& value, const char* name, uint16_t, uint16_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(uint32_t& value, const char* name, uint32_t, uint32_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(uint64_t& value, const char* name, uint64_t, uint64_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(float& value, const char* name, float, float)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(double& value, const char* name, double, double)
{
return ProcessData(name, value);
}
bool StringifySerializer::SerializeBytes(uint8_t* buffer, uint32_t, bool isString, uint32_t&, const char* name)
{
if (isString)
{
AZ::CVarFixedString value = reinterpret_cast<char*>(buffer);
return ProcessData(name, value);
}
return false;
}
bool StringifySerializer::BeginObject(const char* name, const char*)
{
m_prefixSizeStack.push_back(m_prefix.size());
m_prefix += name;
m_prefix += ".";
return true;
}
bool StringifySerializer::EndObject(const char*, const char*)
{
m_prefix.resize(m_prefixSizeStack.back());
m_prefixSizeStack.pop_back();
return true;
}
const uint8_t* StringifySerializer::GetBuffer() const
{
return nullptr;
}
uint32_t StringifySerializer::GetCapacity() const
{
return 0;
}
uint32_t StringifySerializer::GetSize() const
{
return 0;
}
template <typename T>
bool StringifySerializer::ProcessData(const char* name, const T& value)
{
// Only add delimeters after we have processed at least one element
if (!m_string.empty())
{
m_string += m_delimeter;
}
if (m_outputFieldNames)
{
m_string += m_prefix;
m_string += name;
m_string += m_separator;
}
AZ::CVarFixedString string = AZ::ConsoleTypeHelpers::ValueToString(value);
m_string += string.c_str();
m_map[m_prefix + name] = string.c_str();
return true;
}
}
@@ -0,0 +1,80 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzCore/std/containers/map.h>
namespace AzNetworking
{
// StringifySerializer
// Generate a debug string of a serializable object
class StringifySerializer
: public ISerializer
{
public:
using StringMap = AZStd::map<AZStd::string, AZStd::string>;
StringifySerializer(char delimeter = ' ', bool outputFieldNames = true, const AZStd::string& seperator = "=");
// GetString
// After serializing objects, get the serialized values as a single string
const AZStd::string& GetString() const;
// GetValueMap
// After serializing objects, get the serialized values as key value pairs
const StringMap& GetValueMap() const;
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
bool Serialize(bool& value, const char* name) override;
bool Serialize(char& value, const char* name, char minValue, char maxValue) override;
bool Serialize(int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
bool Serialize(int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
bool Serialize(int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
bool Serialize(int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
bool Serialize(uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
bool Serialize(float& value, const char* name, float minValue, float maxValue) override;
bool Serialize(double& value, const char* name, double minValue, double maxValue) override;
bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
bool BeginObject(const char* name, const char* typeName) override;
bool EndObject(const char* name, const char* typeName) override;
const uint8_t* GetBuffer() const override;
uint32_t GetCapacity() const override;
uint32_t GetSize() const override;
void ClearTrackedChangesFlag() override {}
bool GetTrackedChangesFlag() const override { return false; }
// ISerializer interfaces
private:
template <typename T>
bool ProcessData(const char* name, const T& value);
private:
char m_delimeter;
bool m_outputFieldNames = true;
StringMap m_map;
AZStd::string m_string;
AZStd::string m_prefix;
AZStd::string m_separator;
AZStd::deque<AZStd::size_t> m_prefixSizeStack;
};
}
@@ -65,6 +65,8 @@ set(FILES
Serialization/NetworkOutputSerializer.cpp
Serialization/NetworkOutputSerializer.h
Serialization/NetworkOutputSerializer.inl
Serialization/StringifySerializer.cpp
Serialization/StringifySerializer.h
Serialization/TrackChangedSerializer.h
Serialization/TrackChangedSerializer.inl
TcpTransport/TcpConnection.cpp
@@ -12,11 +12,13 @@
#pragma once
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
namespace Multiplayer
{
class EntityReplicationManager;
enum class ConnectionDataType
{
ClientToServer,
@@ -42,8 +44,8 @@ namespace Multiplayer
virtual EntityReplicationManager& GetReplicationManager() = 0;
//! Creates and manages sending updates to the remote endpoint.
//! @param serverGameTimeMs current server game time in milliseconds
virtual void Update(AZ::TimeMs serverGameTimeMs) = 0;
//! @param hostTimeMs current server game time in milliseconds
virtual void Update(AZ::TimeMs hostTimeMs) = 0;
//! Returns whether update messages can be sent to the connection.
//! @return true if update messages can be sent
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
namespace Multiplayer
{
+10 -1
View File
@@ -15,6 +15,7 @@
#include <AzCore/RTTI/RTTI.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/DataStructures/ByteBuffer.h>
#include <Include/INetworkTime.h>
#include <Include/MultiplayerStats.h>
namespace AzNetworking
@@ -56,7 +57,7 @@ namespace Multiplayer
//! Gets the type of Agent this IMultiplayer impl represents
//! @return The type of agents represented
virtual MultiplayerAgentType GetAgentType() = 0;
virtual MultiplayerAgentType GetAgentType() const = 0;
//! Sets the type of this Multiplayer connection and calls any related callback
//! @param state The state of this connection
@@ -78,6 +79,14 @@ namespace Multiplayer
//! @param readyForEntityUpdates Ready for entity updates or not
virtual void SendReadyForEntityUpdates(bool readyForEntityUpdates) = 0;
//! Returns the current server time in milliseconds.
//! This can be one of three possible values:
//! 1. On the host outside of rewind scope, this will return the latest application elapsed time in ms.
//! 2. On the host within rewind scope, this will return the rewound time in ms.
//! 3. On the client, this will return the most recently replicated server time in ms.
//! @return the current server time in milliseconds
virtual AZ::TimeMs GetCurrentHostTimeMs() const = 0;
//! Returns the gem name associated with the provided component index.
//! @param netComponentId the componentId to return the gem name of
//! @return the name of the gem that contains the requested component
@@ -13,7 +13,7 @@
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/Asset/AssetCommon.h>
@@ -14,13 +14,10 @@
#include <AzCore/Time/ITime.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <Include/MultiplayerTypes.h>
namespace Multiplayer
{
//! This is a strong typedef for representing the number of application frames since application start.
AZ_TYPE_SAFE_INTEGRAL(ApplicationFrameId, uint32_t);
static constexpr ApplicationFrameId InvalidApplicationFrameId = ApplicationFrameId{0xFFFFFFFF};
//! @class INetworkTime
//! @brief This is an AZ::Interface<> for managing multiplayer specific time related operations.
class INetworkTime
@@ -31,30 +28,24 @@ namespace Multiplayer
INetworkTime() = default;
virtual ~INetworkTime() = default;
//! Converts from an ApplicationFrameId to a corresponding TimeMs.
//! @param frameId the ApplicationFrameId to convert to a TimeMs
//! @return the TimeMs that corresponds to the provided ApplicationFrameId
virtual AZ::TimeMs ConvertFrameIdToTimeMs(ApplicationFrameId frameId) const = 0;
//! Returns true if the host timeMs and frameId has been temporarily altered.
//! @return true if the host timeMs and frameId has been altered, false otherwise
virtual bool IsTimeRewound() const = 0;
//! Converts from a TimeMs to an ApplicationFrameId.
//! @param timeMs the TimeMs to convert to an ApplicationFrameId
//! @return the ApplicationFrameId that corresponds to the provided TimeMs
virtual ApplicationFrameId ConvertTimeMsToFrameId(AZ::TimeMs timeMs) const = 0;
//! Retrieves the hosts current frameId (may be rewound on the server during backward reconciliation).
//! @return the hosts current frameId
virtual HostFrameId GetHostFrameId() const = 0;
//! Returns true if the application frameId has been temporarily altered.
//! @return true if the application frameId has been altered, false otherwise
virtual bool IsApplicationFrameIdRewound() const = 0;
//! Retrieves the unaltered hosts current frameId.
//! @return the hosts current frameId, unaltered by any scoped time instance
virtual HostFrameId GetUnalteredHostFrameId() const = 0;
//! Retrieves the applications current frameId (may be rewound on the server during backward reconciliation).
//! @return the applications current frameId
virtual ApplicationFrameId GetApplicationFrameId() const = 0;
//! Increments the hosts current frameId.
virtual void IncrementHostFrameId() = 0;
//! Retrieves the unaltered applications current frameId.
//! @return the applications current frameId, unaltered by any scoped time instance
virtual ApplicationFrameId GetUnalteredApplicationFrameId() const = 0;
//! Increments the applications current frameId.
virtual void IncrementApplicationFrameId() = 0;
//! Retrieves the hosts current timeMs (may be rewound on the server during backward reconciliation).
//! @return the hosts current timeMs
virtual AZ::TimeMs GetHostTimeMs() const = 0;
//! Synchronizes rewindable entity state for the current application time.
virtual void SyncRewindableEntityState() = 0;
@@ -66,14 +57,15 @@ namespace Multiplayer
//! 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
//! @param rewindConnectionId if this parameter matches the current rewindConnectionId, it will return the unaltered applicationFrameId
//! @return the ApplicationFrameId taking into account the provided rewinding connectionId
virtual ApplicationFrameId GetApplicationFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const = 0;
//! @param rewindConnectionId if this parameter matches the current rewindConnectionId, it will return the unaltered hostFrameId
//! @return the HostFrameId taking into account the provided rewinding connectionId
virtual HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const = 0;
//! Alters the current ApplicationFrameId and binds that alteration to the provided ConnectionId.
//! @param frameId the new ApplicationFrameId to use
//! Alters the current HostFrameId and binds that alteration to the provided ConnectionId.
//! @param frameId the new HostFrameId to use
//! @param timeMs the new HostTimeMs to use
//! @param rewindConnectionId the rewinding ConnectionId
virtual void AlterApplicationFrameId(ApplicationFrameId frameId, AzNetworking::ConnectionId rewindConnectionId) = 0;
virtual void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) = 0;
AZ_DISABLE_COPY_MOVE(INetworkTime);
};
@@ -93,22 +85,22 @@ namespace Multiplayer
class ScopedAlterTime final
{
public:
inline ScopedAlterTime(ApplicationFrameId frameId, AzNetworking::ConnectionId connectionId)
inline ScopedAlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId connectionId)
{
INetworkTime* time = AZ::Interface<INetworkTime>::Get();
m_previousApplicationFrameId = time->GetApplicationFrameId();
m_previousHostFrameId = time->GetHostFrameId();
m_previousHostTimeMs = time->GetHostTimeMs();
m_previousRewindConnectionId = time->GetRewindingConnectionId();
time->AlterApplicationFrameId(frameId, connectionId);
time->AlterTime(frameId, timeMs, connectionId);
}
inline ~ScopedAlterTime()
{
INetworkTime* time = AZ::Interface<INetworkTime>::Get();
time->AlterApplicationFrameId(m_previousApplicationFrameId, m_previousRewindConnectionId);
time->AlterTime(m_previousHostFrameId, m_previousHostTimeMs, m_previousRewindConnectionId);
}
private:
ApplicationFrameId m_previousApplicationFrameId = InvalidApplicationFrameId;
HostFrameId m_previousHostFrameId = InvalidHostFrameId;
AZ::TimeMs m_previousHostTimeMs = AZ::TimeMs{ 0 };
AzNetworking::ConnectionId m_previousRewindConnectionId = AzNetworking::InvalidConnectionId;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ApplicationFrameId);
@@ -13,7 +13,7 @@
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/unordered_map.h>
namespace Multiplayer
@@ -36,6 +36,12 @@ namespace Multiplayer
AZ_TYPE_SAFE_INTEGRAL(PropertyIndex, uint16_t);
AZ_TYPE_SAFE_INTEGRAL(RpcIndex, uint16_t);
AZ_TYPE_SAFE_INTEGRAL(ClientInputId, uint16_t);
//! This is a strong typedef for representing the number of application frames since application start.
AZ_TYPE_SAFE_INTEGRAL(HostFrameId, uint32_t);
static constexpr HostFrameId InvalidHostFrameId = HostFrameId{ 0xFFFFFFFF };
using LongNetworkString = AZ::CVarFixedString;
using ReliabilityType = AzNetworking::ReliabilityType;
@@ -122,3 +128,5 @@ AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetEntityId);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetComponentId);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::PropertyIndex);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::RpcIndex);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ClientInputId);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::HostFrameId);
@@ -138,4 +138,4 @@ namespace Multiplayer
};
}
#include "Source/NetworkEntity/NetworkEntityHandle.inl"
#include <Include/NetworkEntityHandle.inl>
@@ -1,6 +1,6 @@
#include <AzCore/Component/Component.h>
#include <Source/Components/MultiplayerComponentRegistry.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
{% for Component in dataFiles %}
{% set ComponentDerived = Component.attrib['OverrideComponent']|booleanTrue %}
{% set ControllerDerived = Component.attrib['OverrideController']|booleanTrue %}
@@ -170,12 +170,12 @@ AZ::Event<{{ Property.attrib['Type'] }}>
{% set PropertyName = UpperFirst(Property.attrib['Name']) %}
{{ ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }}
{% if IsOverride %}
void Handle{{ PropertyName }}({{ ', '.join(paramDefines) }}) override {}
void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ ', '.join(paramDefines) }}) override {}
{% else %}
//! {{ PropertyName }} Handler
//! {{ Property.attrib['Description'] }}
//! HandleOn {{ HandleOn }}
virtual void Handle{{ PropertyName }}({{ ', '.join(paramDefines) }}) = 0;
virtual void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ ', '.join(paramDefines) }}) = 0;
{% endif %}
{% endmacro %}
{#
@@ -221,11 +221,11 @@ AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }
#include <AzCore/EBus/Event.h>
#include <AzCore/EBus/ScheduledEvent.h>
#include <AzNetworking/DataStructures/FixedSizeBitsetView.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/NetworkEntityHandle.h>
#include <Source/NetworkEntity/EntityReplication/ReplicationRecord.h>
#include <Source/Components/MultiplayerComponent.h>
#include <Source/Components/MultiplayerController.h>
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Source/NetworkTime/RewindableObject.h>
#include <Include/MultiplayerTypes.h>
{% call(Include) AutoComponentMacros.ParseIncludes(Component) %}
@@ -435,7 +435,7 @@ namespace {{ Component.attrib['Namespace'] }}
//! MultiplayerComponent interface
//! @{
NetComponentId GetNetComponentId() const override;
bool HandleRpcMessage(Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& rpcMessage) override;
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& rpcMessage) override;
bool SerializeStateDeltaMessage(Multiplayer::ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) override;
void NotifyStateDeltaChanges(Multiplayer::ReplicationRecord& replicationRecord) override;
bool HasController() const override;
@@ -336,7 +336,7 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp
if (m_controller)
{
AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Authority, "Entity proxy does not have authority");
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }});
}
{% if Property.attrib['IsReliable']|booleanTrue %}
{# if the rpc is not reliable we can simply drop it, also note message reliability type is default reliable in EntityRpcMessage #}
@@ -350,7 +350,7 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp
if (m_controller)
{
AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Autonomous, "Entity proxy does not have autonomy");
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }});
}
{% else %}
Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
@@ -1251,7 +1251,12 @@ namespace {{ Component.attrib['Namespace'] }}
#pragma warning(push)
#pragma warning(disable: 4065) // switch statement contains 'default' but no 'case' labels
bool {{ ComponentBaseName }}::HandleRpcMessage([[maybe_unused]] Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& message)
bool {{ ComponentBaseName }}::HandleRpcMessage
(
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
[[maybe_unused]] Multiplayer::NetEntityRole remoteRole,
Multiplayer::NetworkEntityRpcMessage& message
)
{
const {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure rpcType = static_cast<{{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure>(message.GetRpcIndex());
switch (rpcType)
@@ -16,11 +16,11 @@
<Include File="Source/NetworkInput/NetworkInputVector.h"/>
<Include File="AzNetworking/DataStructures/ByteBuffer.h"/>
<NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{0}" ReplicateFrom="Authority" ReplicateTo="Authority" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" GenerateEventBindings="false" />
<NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{ 0 }" ReplicateFrom="Authority" ReplicateTo="Authority" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" GenerateEventBindings="false" />
<RemoteProcedure Name="SendClientInput" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="true" IsReliable="false" Description="Client to server move / input RPC">
<Param Type="Multiplayer::NetworkInputVector" Name="inputArray" />
<Param Type="uint32_t" Name="stateHash" />
<Param Type="AZ::HashValue64" Name="stateHash" />
<Param Type="AzNetworking::PacketEncodingBuffer" Name="clientState" Description="This is for debugging desyncs only; release games should not populate this parameter" />
</RemoteProcedure>
@@ -3,6 +3,7 @@
<PacketGroup Name="MultiplayerPackets" PacketStart="CorePackets::PacketType::MAX">
<Include File="AzNetworking/AutoGen/CorePackets.AutoPackets.h" />
<Include File="Include/MultiplayerTypes.h" />
<Include File="Include/INetworkTime.h" />
<Include File="Source/NetworkEntity/NetworkEntityRpcMessage.h" />
<Include File="Source/NetworkEntity/NetworkEntityUpdateMessage.h" />
@@ -35,6 +36,7 @@
<Packet Name="EntityUpdates" Desc="A packet that contains multiple entity updates">
<Member Type="AZ::TimeMs" Name="hostTimeMs" Init="AZ::TimeMs{ 0 }" />
<Member Type="Multiplayer::HostFrameId" Name="hostFrameId" Init="Multiplayer::InvalidHostFrameId" />
<Member Type="Multiplayer::NetworkEntityUpdateMessage" Name="entityMessages" Container="Vector" Count="Multiplayer::MaxAggregateEntityMessages" SuppressFromInitializerList="true" />
</Packet>
@@ -13,9 +13,41 @@
#include <Source/Components/LocalPredictionPlayerInputComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzNetworking/Serialization/HashSerializer.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/Serialization/StringifySerializer.h>
#include <AzNetworking/Serialization/TrackChangedSerializer.h>
namespace Multiplayer
{
AZ_CVAR(AZ::TimeMs, cl_InputRateMs, AZ::TimeMs{ 33 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Rate at which to sample and process client inputs");
AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay");
#ifndef _RELEASE
AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat");
#endif
AZ_CVAR(bool, sv_EnableCorrections, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables server corrections on autonomous proxy desyncs");
AZ_CVAR(double, sv_MaxBankTimeWindowSec, 0.2, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum bank time we allow before we start rejecting autonomous proxy move inputs due to anticheat kicking in");
AZ_CVAR(double, sv_BankTimeDecay, 0.025, nullptr, AZ::ConsoleFunctorFlags::Null, "Amount to decay bank time by, in case of more permanent shifts in client latency");
AZ_CVAR(AZ::TimeMs, sv_MinCorrectionTimeMs, AZ::TimeMs{ 100 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum time to wait between sending out corrections in order to avoid flooding corrections on high-latency connections");
AZ_CVAR(AZ::TimeMs, sv_InputUpdateTimeMs, AZ::TimeMs{ 5 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum time between component updates");
// Debug helper functions
AZStd::string GetInputString(NetworkInput& input)
{
AzNetworking::StringifySerializer serializer(',', false);
input.Serialize(serializer);
return serializer.GetString();
}
AZStd::string GetCorrectionDataString(NetBindComponent* netBindComponent)
{
AzNetworking::StringifySerializer serializer(',', false);
netBindComponent->SerializeEntityCorrection(serializer);
return serializer.GetString();
}
void LocalPredictionPlayerInputComponent::LocalPredictionPlayerInputComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
@@ -27,30 +59,544 @@ namespace Multiplayer
LocalPredictionPlayerInputComponentBase::Reflect(context);
}
void LocalPredictionPlayerInputComponent::OnInit()
{
;
}
void LocalPredictionPlayerInputComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void LocalPredictionPlayerInputComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
LocalPredictionPlayerInputComponentController::LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent)
: LocalPredictionPlayerInputComponentControllerBase(parent)
, m_autonomousUpdateEvent([this]() { UpdateAutonomous(m_autonomousUpdateEvent.TimeInQueueMs()); }, AZ::Name("AutonomousUpdate Event"))
, m_updateBankedTimeEvent([this]() { UpdateBankedTime(m_updateBankedTimeEvent.TimeInQueueMs()); }, AZ::Name("BankTimeUpdate Event"))
, m_migrateStartHandler([this](ClientInputId migratedInputId) { OnMigrateStart(migratedInputId); })
, m_migrateEndHandler([this]() { OnMigrateEnd(); })
{
if (GetNetEntityRole() == NetEntityRole::Autonomous)
{
m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true);
parent.GetNetBindComponent()->AddEntityMigrationStartEventHandler(m_migrateStartHandler);
parent.GetNetBindComponent()->AddEntityMigrationEndEventHandler(m_migrateEndHandler);
}
}
void LocalPredictionPlayerInputComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
if (entityIsMigrating == EntityIsMigrating::True)
{
m_allowMigrateClientInput = true;
m_serverMigrateFrameId = AZ::Interface<INetworkTime>::Get()->GetHostFrameId();
}
}
void LocalPredictionPlayerInputComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void LocalPredictionPlayerInputComponentController::HandleSendClientInput
(
[[maybe_unused]] const Multiplayer::NetworkInputVector& inputArray,
[[maybe_unused]] const uint32_t& stateHash,
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputVector& inputArray,
const AZ::HashValue64& stateHash,
[[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 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 = NetworkInputVector::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)
{
NetworkInput& input = m_lastInputReceived[inputArrayIndex];
// 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", input.GetClientInputId());
}
else
{
AZLOG(NET_Prediction, "Processed InputId=%u", input.GetClientInputId());
}
}
else
{
AZLOG(NET_Prediction, "Dropped InputId=%u", input.GetClientInputId());
}
--inputArrayIndex;
}
if (sv_EnableCorrections && (currentTimeMs - m_lastCorrectionSentTimeMs > sv_MinCorrectionTimeMs))
{
m_lastCorrectionSentTimeMs = currentTimeMs;
AzNetworking::HashSerializer hashSerializer;
GetNetBindComponent()->SerializeEntityCorrection(hashSerializer);
const AZ::HashValue64 localAuthorityHash = hashSerializer.GetHash();
if (stateHash != localAuthorityHash)
{
// Produce correction for client
AzNetworking::PacketEncodingBuffer correction;
correction.Resize(correction.GetCapacity());
AzNetworking::NetworkInputSerializer serializer(correction.GetBuffer(), correction.GetCapacity());
// only deserialize if we have data (for client/server profile/debug mismatches)
if (correction.GetSize() > 0)
{
GetNetBindComponent()->SerializeEntityCorrection(serializer);
}
correction.Resize(serializer.GetSize());
// Send correction
SendClientInputCorrection(GetLastInputId(), correction);
#ifdef _DEBUG
// In debug, show which states caused the correction
AZStd::string clientStateString;
AZStd::string serverStateString;
{
// Write in client state
AzNetworking::NetworkOutputSerializer clientStateSerializer(clientState.GetBuffer(), clientState.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(clientStateSerializer);
// Read out state values
AzNetworking::StringifySerializer clientValues;
GetNetBindComponent()->SerializeEntityCorrection(clientValues);
// Restore server state
AzNetworking::NetworkOutputSerializer serverStateSerializer(correction.GetBuffer(), correction.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(serverStateSerializer);
// Read out state values
AzNetworking::StringifySerializer serverValues;
GetNetBindComponent()->SerializeEntityCorrection(serverValues);
AZStd::map<AZStd::string, AZStd::pair<AZStd::string, AZStd::string>> mapComparison;
// put the server value in the first part of the pair
for (const auto& pair : serverValues.GetValueMap())
{
mapComparison[pair.first].first = pair.second;
}
// put the client value in the second part of the pair
for (const auto& pair : clientValues.GetValueMap())
{
mapComparison[pair.first].second = pair.second;
}
bool firstIt = true;
for (const auto& mapPair : mapComparison)
{
if (mapPair.second.first != mapPair.second.second)
{
if (!firstIt)
{
clientStateString += ",";
serverStateString += ",";
}
firstIt = false;
AZStd::string clientValue = mapPair.second.second.empty() ? "<no value>" : mapPair.second.second;
AZStd::string serverValue = mapPair.second.first.empty() ? "<no value>" : mapPair.second.first;
clientStateString += mapPair.first + "=" + clientValue;
serverStateString += mapPair.first + "=" + serverValue;
}
}
}
#else
const AZStd::string clientStateString = "available in debug only";
const AZStd::string serverStateString = "available in debug only";
#endif
AZLOG_ERROR("** Autonomous proxy desync detected! ** clientState=[%s], serverState=[%s]", clientStateString.c_str(), serverStateString.c_str());
}
}
}
void LocalPredictionPlayerInputComponentController::HandleSendMigrateClientInput
(
[[maybe_unused]] const Multiplayer::MigrateNetworkInputVector& inputArray
AzNetworking::IConnection* invokingConnection,
const Multiplayer::MigrateNetworkInputVector& inputArray
)
{
;
if (!m_allowMigrateClientInput)
{
AZLOG_ERROR("Client attempting to SendMigrateClientInput message when server was not expecting it. This may be an attempt to cheat");
return;
}
// We only allow the client to send this message exactly once, when the component has been migrated
// Any further processing of these messages from the client would be exploitable
m_allowMigrateClientInput = false;
if (invokingConnection == nullptr)
{
// Discard any input migration messages that were locally dispatched or sent by disconnected clients
return;
}
const float clientInputRateSec = static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
// Copy array so we can modify input ids
MigrateNetworkInputVector inputArrayCopy = inputArray;
for (uint32_t i = 0; i < inputArrayCopy.GetSize(); ++i)
{
NetworkInput& input = inputArrayCopy[i];
++ModifyLastInputId();
input.SetClientInputId(GetLastInputId());
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, clientInputRateSec);
AZLOG
(
NET_Prediction,
"Migrated InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
// Don't bother checking for corrections here, the next regular input will trigger any corrections if necessary
// Also don't bother with any cheat detection here, because the input array is limited in size and at most and can only be sent once
// So this highly constrains anything a malicious client can do
}
}
void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection
(
[[maybe_unused]] const Multiplayer::ClientInputId& inputId,
[[maybe_unused]] const AzNetworking::PacketEncodingBuffer& correction
AzNetworking::IConnection* invokingConnection,
const Multiplayer::ClientInputId& inputId,
const AzNetworking::PacketEncodingBuffer& correction
)
{
;
AZ_Assert(inputId <= m_clientInputId, "Invalid correction frame id, correction is for a move the client has not yet submitted to the server");
if (inputId > m_clientInputId)
{
AZLOG_ERROR("Discarding correction for non-existent move, correction represents a move we haven't sent to the server yet");
return;
}
if (inputId <= m_lastCorrectionInputId)
{
AZLOG(NET_Prediction, "Discarding old correction for client frame %u", aznumeric_cast<uint32_t>(inputId));
return;
}
m_lastCorrectionInputId = inputId;
// Apply the correction
AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> serializer(correction.GetBuffer(), correction.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(serializer);
m_correctionEvent.Signal();
AZLOG
(
NET_Prediction,
"Corrected InputId=%d - o=[%s]",
aznumeric_cast<int32_t>(m_lastCorrectionInputId),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
const uint32_t inputHistorySize = m_inputHistory.Size();
const uint32_t historicalDelta = aznumeric_cast<uint32_t>(m_clientInputId - inputId); // Do not replay the move we just corrected, that was already processed by the server
// If this correction is for a move outside our input history window, just start replaying from the oldest move we have available
const uint32_t startReplayIndex = (inputHistorySize > historicalDelta) ? (inputHistorySize - historicalDelta) : 0;
// Flag that we are replaying inputs
struct ScopedReplayingInput
{
ScopedReplayingInput(LocalPredictionPlayerInputComponentController* instance)
: m_instance(instance)
{
m_instance->m_replayingInput = true;
}
~ScopedReplayingInput()
{
m_instance->m_replayingInput = false;
}
LocalPredictionPlayerInputComponentController* m_instance;
};
ScopedReplayingInput markReplayingInput(this);
const float clientInputRateSec = static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
for (uint32_t replayIndex = startReplayIndex; replayIndex < inputHistorySize; ++replayIndex)
{
// Reprocess the input for this frame
NetworkInput& input = m_inputHistory[replayIndex];
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, clientInputRateSec);
AZLOG
(
NET_Prediction,
"Replayed InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
}
}
bool LocalPredictionPlayerInputComponentController::IsReplayingInput() const
{
return m_replayingInput;
}
bool LocalPredictionPlayerInputComponentController::IsMigrating() const
{
return m_lastMigratedInputId != ClientInputId{ 0 };
}
ClientInputId LocalPredictionPlayerInputComponentController::GetLastInputId() const
{
return m_clientInputId;
}
HostFrameId LocalPredictionPlayerInputComponentController::GetInputFrameId(const NetworkInput& input) const
{
// If the client has sent us an invalid server frame id
// this is because they are in the process of migrating from one server to another
// In this situation, use whatever the server frame id was when this component was migrated
// This will match the closest state to what the client sees
return (input.GetHostFrameId() == InvalidHostFrameId) ? m_serverMigrateFrameId : input.GetHostFrameId();
}
void LocalPredictionPlayerInputComponentController::CorrectionEventAddHandle(CorrectionEvent::Handler& handler)
{
handler.Connect(m_correctionEvent);
}
void LocalPredictionPlayerInputComponentController::OnMigrateStart(ClientInputId migratedInputId)
{
m_lastMigratedInputId = migratedInputId;
}
void LocalPredictionPlayerInputComponentController::OnMigrateEnd()
{
MigrateNetworkInputVector inputArray;
// Roll up all inputs that the new server doesn't have and send them now
for (AZStd::size_t i = 0; i < m_inputHistory.Size(); ++i)
{
NetworkInput& input = m_inputHistory[i];
// New server already has these inputs
if (input.GetClientInputId() <= m_lastMigratedInputId)
{
continue;
}
// Clear out the old server frame id
// We don't know what server frame ids to use for the new server yet, but the new server will figure out how to deal with this
input.SetHostFrameId(InvalidHostFrameId);
// New server doesn't have these inputs
if (!inputArray.PushBack(input))
{
break; // Reached capacity
}
}
// Send these inputs to the server
SendMigrateClientInput(inputArray);
// Done migrating
m_lastMigratedInputId = ClientInputId{ 0 };
}
void LocalPredictionPlayerInputComponentController::UpdateAutonomous(AZ::TimeMs deltaTimeMs)
{
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
const double inputRate = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
const double maxRewindHistory = static_cast<double>(static_cast<AZ::TimeMs>(cl_MaxRewindHistoryMs)) / 1000.0;
#ifndef _RELEASE
m_moveAccumulator += deltaTime * cl_DebugHackTimeMultiplier;
#else
m_moveAccumulator += deltaTime;
#endif
const uint32_t maxClientInputs = inputRate > 0.0 ? static_cast<uint32_t>(maxRewindHistory / inputRate) : 0;
INetworkTime* networkTime = AZ::Interface<INetworkTime>::Get();
IMultiplayer* multiplayer = AZ::Interface<IMultiplayer>::Get();
while (m_moveAccumulator >= inputRate)
{
m_moveAccumulator -= inputRate;
++m_clientInputId;
NetworkInputVector inputArray(GetEntityHandle());
NetworkInput& input = inputArray[0];
input.SetClientInputId(m_clientInputId);
input.SetHostFrameId(networkTime->GetHostFrameId());
input.SetHostTimeMs(multiplayer->GetCurrentHostTimeMs());
// Allow components to form the input for this frame
GetNetBindComponent()->CreateInput(input, inputRate);
// Process the input for this frame
GetNetBindComponent()->ProcessInput(input, inputRate);
AZLOG
(
NET_Prediction,
"Processed InutId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(m_clientInputId),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
// Generate a hash based on the current client predicted states
AzNetworking::HashSerializer hashSerializer;
GetNetBindComponent()->SerializeEntityCorrection(hashSerializer);
// In debug, send the entire client output state to the server to make it easier to debug desync issues
AzNetworking::PacketEncodingBuffer processInputResult;
#ifdef _DEBUG
AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity());
GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer);
processInputResult.Resize(processInputResultSerializer.GetSize());
#endif
// Save this input and discard move history outside our client rewind window
m_inputHistory.PushBack(input);
while (m_inputHistory.Size() > maxClientInputs)
{
m_inputHistory.PopFront();
}
const size_t inputHistorySize = 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 < NetworkInputVector::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;
}
}
// Send the input to server (only when we are not migrating)
if (!IsMigrating())
{
SendClientInput(inputArray, hashSerializer.GetHash(), processInputResult);
}
}
}
void LocalPredictionPlayerInputComponentController::UpdateBankedTime(AZ::TimeMs deltaTimeMs)
{
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
const double inputRate = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
const double maxRewindHistory = static_cast<double>(static_cast<AZ::TimeMs>(cl_MaxRewindHistoryMs)) / 1000.0;
// Update banked time accumulator
m_clientBankedTime -= deltaTime;
// Forcibly tick any clients who are too far behind our variable latency window
// Client may be slow hacking
if (m_clientBankedTime < -sv_MaxBankTimeWindowSec)
{
m_clientBankedTime = -sv_MaxBankTimeWindowSec; // clamp to boundary
NetworkInput& input = m_lastInputReceived[0];
{
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), AzNetworking::InvalidConnectionId);
GetNetBindComponent()->ProcessInput(input, inputRate);
}
AZLOG
(
NET_Prediction,
"Forced InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
}
// Decay our bank time window, in case the remote endpoint has suffered a more persistent shift in latency, this should cause the client to eventually recover
m_clientBankedTime = m_clientBankedTime * (1.0 - sv_BankTimeDecay);
}
}
@@ -13,9 +13,12 @@
#pragma once
#include <Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.h>
#include <Source/Components/NetBindComponent.h>
namespace Multiplayer
{
using CorrectionEvent = AZ::Event<>;
class LocalPredictionPlayerInputComponent
: public LocalPredictionPlayerInputComponentBase
{
@@ -24,24 +27,87 @@ namespace Multiplayer
static void Reflect([[maybe_unused]] AZ::ReflectContext* context);
void OnInit() override {}
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnInit() override;
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
};
class LocalPredictionPlayerInputComponentController
: public LocalPredictionPlayerInputComponentControllerBase
{
public:
LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent) : LocalPredictionPlayerInputComponentControllerBase(parent) {}
LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent);
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void HandleSendClientInput(const Multiplayer::NetworkInputVector& inputArray, const uint32_t& stateHash, const AzNetworking::PacketEncodingBuffer& clientState) override;
void HandleSendMigrateClientInput(const Multiplayer::MigrateNetworkInputVector& inputArray) override;
void HandleSendClientInputCorrection(const Multiplayer::ClientInputId& inputId, const AzNetworking::PacketEncodingBuffer& correction) override;
void HandleSendClientInput
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputVector& inputArray,
const AZ::HashValue64& stateHash,
const AzNetworking::PacketEncodingBuffer& clientState
) override;
void HandleSendMigrateClientInput
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::MigrateNetworkInputVector& inputArray
) override;
void HandleSendClientInputCorrection
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::ClientInputId& inputId,
const AzNetworking::PacketEncodingBuffer& correction
) override;
//! Return true if we're currently replaying inputs after a correction.
//! If this value returns true, effects, audio, and other cosmetic triggers should be suppressed
//! @return true if we're within correction scope and replaying inputs
bool IsReplayingInput() const;
//! Return true if we're currently migrating from one host to another.
//! @return boolean true if we're currently migrating from one host to another
bool IsMigrating() const;
ClientInputId GetLastInputId() const;
HostFrameId GetInputFrameId(const NetworkInput& input) const;
void CorrectionEventAddHandle(CorrectionEvent::Handler& handler);
private:
void OnMigrateStart(ClientInputId migratedInputId);
void OnMigrateEnd();
void UpdateAutonomous(AZ::TimeMs deltaTimeMs);
void UpdateBankedTime(AZ::TimeMs deltaTimeMs);
// Implicitly sorted player input history, back() is the input that corresponds to the latest client input Id
NetworkInputHistory m_inputHistory;
// Anti-cheat accumulator for clients who purposely mess with their clock rate
NetworkInputVector m_lastInputReceived;
AZ::ScheduledEvent m_autonomousUpdateEvent; // Drives autonomous input collection
AZ::ScheduledEvent m_updateBankedTimeEvent; // Drives authority bank time updates
CorrectionEvent m_correctionEvent;
EntityMigrationStartEvent::Handler m_migrateStartHandler;
EntityMigrationEndEvent::Handler m_migrateEndHandler;
double m_moveAccumulator = 0.0;
double m_clientBankedTime = 0.0;
AZ::TimeMs m_lastInputReceivedTimeMs = AZ::TimeMs{ 0 };
AZ::TimeMs m_lastCorrectionSentTimeMs = AZ::TimeMs{ 0 };
ClientInputId m_clientInputId = ClientInputId{ 0 };
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;
bool m_replayingInput = false; // True if we're replaying inputs under a correction event (use this to suppress effects or audio)
bool m_allowMigrateClientInput = false; // True if this component was migrated, we will allow the client to send us migrated inputs (one time only)
};
}
@@ -15,7 +15,7 @@
#include <AzCore/Component/Component.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/DataStructures/FixedSizeBitsetView.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <Include/MultiplayerTypes.h>
#include <Include/IMultiplayer.h>
@@ -69,7 +69,7 @@ namespace Multiplayer
virtual NetComponentId GetNetComponentId() const = 0;
virtual bool HandleRpcMessage(NetEntityRole netEntityRole, NetworkEntityRpcMessage& rpcMessage) = 0;
virtual bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole netEntityRole, NetworkEntityRpcMessage& rpcMessage) = 0;
virtual bool SerializeStateDeltaMessage(ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) = 0;
virtual void NotifyStateDeltaChanges(ReplicationRecord& replicationRecord) = 0;
virtual bool HasController() const = 0;
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/Math/Aabb.h>
namespace Multiplayer
@@ -13,10 +13,10 @@
#include <Source/Components/NetBindComponent.h>
#include <Source/Components/MultiplayerComponent.h>
#include <Source/Components/MultiplayerController.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/NetworkEntity/NetworkEntityUpdateMessage.h>
#include <Source/NetworkInput/NetworkInput.h>
#include <Include/INetworkEntityManager.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
@@ -192,12 +192,12 @@ namespace Multiplayer
return bounds;
}
bool NetBindComponent::HandleRpcMessage(NetEntityRole remoteRole, NetworkEntityRpcMessage& message)
bool NetBindComponent::HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole remoteRole, NetworkEntityRpcMessage& message)
{
auto findIt = m_multiplayerComponentMap.find(message.GetComponentId());
if (findIt != m_multiplayerComponentMap.end())
{
return findIt->second->HandleRpcMessage(remoteRole, message);
return findIt->second->HandleRpcMessage(invokingConnection, remoteRole, message);
}
return false;
}
@@ -274,9 +274,19 @@ namespace Multiplayer
m_localNotificationRecord.Clear();
}
void NetBindComponent::NotifyMigration(HostId remoteHostId, AzNetworking::ConnectionId connectionId)
void NetBindComponent::NotifyMigrationStart(ClientInputId migratedInputId)
{
m_entityMigrationEvent.Signal(m_netEntityHandle, remoteHostId, connectionId);
m_entityMigrationStartEvent.Signal(migratedInputId);
}
void NetBindComponent::NotifyMigrationEnd()
{
m_entityMigrationEndEvent.Signal();
}
void NetBindComponent::NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId)
{
m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId);
}
void NetBindComponent::AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler)
@@ -289,9 +299,19 @@ namespace Multiplayer
eventHandler.Connect(m_dirtiedEvent);
}
void NetBindComponent::AddEntityMigrationEventHandler(EntityMigrationEvent::Handler& eventHandler)
void NetBindComponent::AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityMigrationEvent);
eventHandler.Connect(m_entityMigrationStartEvent);
}
void NetBindComponent::AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityMigrationEndEvent);
}
void NetBindComponent::AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityServerMigrationEvent);
}
bool NetBindComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer)
@@ -21,8 +21,9 @@
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <Source/NetworkEntity/EntityReplication/ReplicationRecord.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Include/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/INetworkTime.h>
#include <Include/MultiplayerTypes.h>
#include <AzCore/EBus/Event.h>
@@ -34,7 +35,9 @@ namespace Multiplayer
using EntityStopEvent = AZ::Event<const ConstNetworkEntityHandle&>;
using EntityDirtiedEvent = AZ::Event<>;
using EntityMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, HostId, AzNetworking::ConnectionId>;
using EntityMigrationStartEvent = AZ::Event<ClientInputId>;
using EntityMigrationEndEvent = AZ::Event<>;
using EntityServerMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, HostId, AzNetworking::ConnectionId>;
//! @class NetBindComponent
//! @brief Component that provides net-binding to a networked entity.
@@ -72,7 +75,7 @@ namespace Multiplayer
void ProcessInput(NetworkInput& networkInput, float deltaTime);
AZ::Aabb GetRewindBoundsForInput(const NetworkInput& networkInput, float deltaTime) const;
bool HandleRpcMessage(NetEntityRole remoteRole, NetworkEntityRpcMessage& message);
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole remoteRole, NetworkEntityRpcMessage& message);
bool HandlePropertyChangeMessage(AzNetworking::ISerializer& serializer, bool notifyChanges = true);
RpcSendEvent& GetSendAuthorityToClientRpcEvent();
@@ -84,11 +87,15 @@ namespace Multiplayer
void MarkDirty();
void NotifyLocalChanges();
void NotifyMigration(HostId remoteHostId, AzNetworking::ConnectionId connectionId);
void NotifyMigrationStart(ClientInputId migratedInputId);
void NotifyMigrationEnd();
void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId);
void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler);
void AddEntityDirtiedEventHandler(EntityDirtiedEvent::Handler& eventHandler);
void AddEntityMigrationEventHandler(EntityMigrationEvent::Handler& eventHandler);
void AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler);
void AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler);
void AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler);
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
@@ -133,7 +140,9 @@ namespace Multiplayer
EntityStopEvent m_entityStopEvent;
EntityDirtiedEvent m_dirtiedEvent;
EntityMigrationEvent m_entityMigrationEvent;
EntityMigrationStartEvent m_entityMigrationStartEvent;
EntityMigrationEndEvent m_entityMigrationEndEvent;
EntityServerMigrationEvent m_entityServerMigrationEvent;
AZ::Event<> m_onRemove;
RpcSendEvent::Handler m_handleLocalServerRpcMessageEventHandle;
AZ::Event<>::Handler m_handleMarkedDirty;
@@ -50,7 +50,7 @@ namespace Multiplayer
return m_entityReplicationManager;
}
void ClientToServerConnectionData::Update([[maybe_unused]] AZ::TimeMs serverGameTimeMs)
void ClientToServerConnectionData::Update([[maybe_unused]] AZ::TimeMs hostTimeMs)
{
m_entityReplicationManager.ActivatePendingEntities();
}
@@ -12,7 +12,8 @@
#pragma once
#include <Source/ConnectionData/IConnectionData.h>
#include <Include/IConnectionData.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
namespace Multiplayer
{
@@ -32,7 +33,7 @@ namespace Multiplayer
ConnectionDataType GetConnectionDataType() const override;
AzNetworking::IConnection* GetConnection() const override;
EntityReplicationManager& GetReplicationManager() override;
void Update(AZ::TimeMs serverGameTimeMs) override;
void Update(AZ::TimeMs hostTimeMs) override;
bool CanSendUpdates() const override;
void SetCanSendUpdates(bool canSendUpdates) override;
//! @}
@@ -35,7 +35,7 @@ namespace Multiplayer
if (netBindComponent != nullptr)
{
netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler);
netBindComponent->AddEntityMigrationEventHandler(m_controlledEntityMigrationHandler);
netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler);
}
m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(sv_ClientMaxRemoteEntitiesPendingCreationCount);
@@ -63,7 +63,7 @@ namespace Multiplayer
return m_entityReplicationManager;
}
void ServerToClientConnectionData::Update(AZ::TimeMs serverGameTimeMs)
void ServerToClientConnectionData::Update(AZ::TimeMs hostTimeMs)
{
m_entityReplicationManager.ActivatePendingEntities();
@@ -73,7 +73,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(serverGameTimeMs);
m_entityReplicationManager.SendUpdates(hostTimeMs);
}
}
}
@@ -12,7 +12,8 @@
#pragma once
#include <Source/ConnectionData/IConnectionData.h>
#include <Include/IConnectionData.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
namespace Multiplayer
{
@@ -33,7 +34,7 @@ namespace Multiplayer
ConnectionDataType GetConnectionDataType() const override;
AzNetworking::IConnection* GetConnection() const override;
EntityReplicationManager& GetReplicationManager() override;
void Update(AZ::TimeMs serverGameTimeMs) override;
void Update(AZ::TimeMs hostTimeMs) override;
bool CanSendUpdates() const override;
void SetCanSendUpdates(bool canSendUpdates) override;
//! @}
@@ -49,7 +50,7 @@ namespace Multiplayer
EntityReplicationManager m_entityReplicationManager;
NetworkEntityHandle m_controlledEntity;
EntityStopEvent::Handler m_controlledEntityRemovedHandler;
EntityMigrationEvent::Handler m_controlledEntityMigrationHandler;
EntityServerMigrationEvent::Handler m_controlledEntityMigrationHandler;
AzNetworking::IConnection* m_connection = nullptr;
bool m_canSendUpdates = false;
};
@@ -12,7 +12,7 @@
#pragma once
#include <Source/EntityDomains/IEntityDomain.h>
#include <Include/IEntityDomain.h>
namespace Multiplayer
{
@@ -127,7 +127,7 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
AZ::TimeMs serverGameTimeMs = AZ::GetElapsedTimeMs();
AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs();
// Handle deferred local rpc messages that were generated during the updates
m_networkEntityManager.DispatchLocalDeferredRpcMessages();
@@ -143,12 +143,12 @@ namespace Multiplayer
// Send out the game state update to all connections
{
auto sendNetworkUpdates = [serverGameTimeMs, &stats](IConnection& connection)
auto sendNetworkUpdates = [hostTimeMs, &stats](IConnection& connection)
{
if (connection.GetUserData() != nullptr)
{
IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection.GetUserData());
connectionData->Update(serverGameTimeMs);
connectionData->Update(hostTimeMs);
if (connectionData->GetConnectionDataType() == ConnectionDataType::ServerToClient)
{
stats.m_clientConnectionCount++;
@@ -481,7 +481,7 @@ namespace Multiplayer
}
}
MultiplayerAgentType MultiplayerSystemComponent::GetAgentType()
MultiplayerAgentType MultiplayerSystemComponent::GetAgentType() const
{
return m_agentType;
}
@@ -528,6 +528,18 @@ namespace Multiplayer
});
}
AZ::TimeMs MultiplayerSystemComponent::GetCurrentHostTimeMs() const
{
if (GetAgentType() == MultiplayerAgentType::Client)
{
return m_lastReplicatedHostTimeMs;
}
else // ClientServer or DedicatedServer
{
return m_networkTime.GetHostTimeMs();
}
}
const char* MultiplayerSystemComponent::GetComponentGemName(NetComponentId netComponentId) const
{
return GetMultiplayerComponentRegistry()->GetComponentGemName(netComponentId);
@@ -84,12 +84,13 @@ namespace Multiplayer
//! IMultiplayer interface
//! @{
MultiplayerAgentType GetAgentType() override;
MultiplayerAgentType GetAgentType() const override;
void InitializeMultiplayer(MultiplayerAgentType state) override;
void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override;
void AddSessionInitHandler(SessionInitEvent::Handler& handler) override;
void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override;
void SendReadyForEntityUpdates(bool readyForEntityUpdates) override;
AZ::TimeMs GetCurrentHostTimeMs() const override;
const char* GetComponentGemName(NetComponentId netComponentId) const override;
const char* GetComponentName(NetComponentId netComponentId) const override;
const char* GetComponentPropertyName(NetComponentId netComponentId, PropertyIndex propertyIndex) const override;
@@ -119,5 +120,8 @@ namespace Multiplayer
SessionInitEvent m_initEvent;
SessionShutdownEvent m_shutdownEvent;
ConnectionAcquiredEvent m_connAcquiredEvent;
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId;
};
}
@@ -14,14 +14,14 @@
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/EntityReplication/PropertyPublisher.h>
#include <Source/NetworkEntity/EntityReplication/PropertySubscriber.h>
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Source/NetworkEntity/NetworkEntityUpdateMessage.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/Components/NetBindComponent.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Include/IEntityDomain.h>
#include <Include/IMultiplayer.h>
#include <Include/INetworkEntityManager.h>
#include <Include/IReplicationWindow.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
@@ -93,10 +93,10 @@ namespace Multiplayer
}
}
void EntityReplicationManager::SendUpdates(AZ::TimeMs serverGameTimeMs)
void EntityReplicationManager::SendUpdates(AZ::TimeMs hostTimeMs)
{
m_frameTimeMs = AZ::GetElapsedTimeMs();
SendEntityUpdates(serverGameTimeMs);
SendEntityUpdates(hostTimeMs);
SendEntityRpcs(m_deferredRpcMessagesReliable, true);
SendEntityRpcs(m_deferredRpcMessagesUnreliable, false);
@@ -118,7 +118,7 @@ namespace Multiplayer
void EntityReplicationManager::SendEntityUpdatesPacketHelper
(
AZ::TimeMs serverGameTimeMs,
AZ::TimeMs hostTimeMs,
EntityReplicatorList& toSendList,
uint32_t maxPayloadSize,
AzNetworking::IConnection& connection
@@ -127,7 +127,8 @@ namespace Multiplayer
uint32_t pendingPacketSize = 0;
EntityReplicatorList replicatorUpdatedList;
MultiplayerPackets::EntityUpdates entityUpdatePacket;
entityUpdatePacket.SetHostTimeMs(serverGameTimeMs);
entityUpdatePacket.SetHostTimeMs(hostTimeMs);
entityUpdatePacket.SetHostFrameId(InvalidHostFrameId);
// Serialize everything
while (!toSendList.empty())
{
@@ -249,7 +250,7 @@ namespace Multiplayer
return toSendList;
}
void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs serverGameTimeMs)
void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs hostTimeMs)
{
EntityReplicatorList toSendList = GenerateEntityUpdateList();
@@ -264,7 +265,7 @@ namespace Multiplayer
// While our to send list is not empty, build up another packet to send
do
{
SendEntityUpdatesPacketHelper(serverGameTimeMs, toSendList, m_maxPayloadSize, m_connection);
SendEntityUpdatesPacketHelper(hostTimeMs, toSendList, m_maxPayloadSize, m_connection);
} while (!toSendList.empty());
}
@@ -747,7 +748,7 @@ namespace Multiplayer
bool EntityReplicationManager::HandleEntityUpdateMessage
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
const AzNetworking::IPacketHeader& packetHeader,
const NetworkEntityUpdateMessage& updateMessage
)
@@ -803,7 +804,7 @@ namespace Multiplayer
return handled;
}
bool EntityReplicationManager::HandleEntityRpcMessage([[maybe_unused]] AzNetworking::IConnection* connection, NetworkEntityRpcMessage& message)
bool EntityReplicationManager::HandleEntityRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& message)
{
EntityReplicator* entityReplicator = GetEntityReplicator(message.GetEntityId());
const bool isReplicatorValid = (entityReplicator != nullptr) && !entityReplicator->IsMarkedForRemoval();
@@ -815,7 +816,7 @@ namespace Multiplayer
}
else
{
return entityReplicator->HandleRpcMessage(message);
return entityReplicator->HandleRpcMessage(invokingConnection, message);
}
}
@@ -833,8 +834,7 @@ namespace Multiplayer
);
return false;
}
return entityReplicator->HandleRpcMessage(message);
return entityReplicator->HandleRpcMessage(nullptr, message);
}
AZ::TimeMs EntityReplicationManager::GetResendTimeoutTimeMs() const
@@ -877,7 +877,6 @@ namespace Multiplayer
AzNetworking::TimeoutResult EntityReplicationManager::OrphanedEntityRpcs::HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item)
{
NetEntityId timedOutEntityId = aznumeric_cast<NetEntityId>(item.m_userData);
auto entityRpcsIter = m_entityRpcMap.find(timedOutEntityId);
if (entityRpcsIter != m_entityRpcMap.end())
{
@@ -887,7 +886,6 @@ namespace Multiplayer
}
m_entityRpcMap.erase(entityRpcsIter);
}
return AzNetworking::TimeoutResult::Delete;
}
@@ -1089,7 +1087,7 @@ namespace Multiplayer
if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer)
{
netBindComponent->NotifyMigration(GetRemoteHostId(), GetConnection().GetConnectionId());
netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetConnection().GetConnectionId());
}
bool didSucceed = true;
@@ -1127,7 +1125,7 @@ namespace Multiplayer
}
}
bool EntityReplicationManager::HandleMessage([[maybe_unused]] AzNetworking::IConnection* connection, MultiplayerPackets::EntityMigration& message)
bool EntityReplicationManager::HandleMessage([[maybe_unused]] AzNetworking::IConnection* invokingConnection, MultiplayerPackets::EntityMigration& message)
{
EntityReplicator* replicator = GetEntityReplicator(message.GetEntityId());
{
@@ -13,11 +13,11 @@
#pragma once
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Source/Components/NetBindComponent.h>
#include <Include/INetworkEntityManager.h>
#include <Include/IReplicationWindow.h>
#include <Include/IEntityDomain.h>
#include <Include/NetworkEntityHandle.h>
#include <AzNetworking/DataStructures/TimeoutQueue.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
#include <AzCore/std/containers/map.h>
@@ -59,7 +59,7 @@ namespace Multiplayer
HostId GetRemoteHostId() const;
void ActivatePendingEntities();
void SendUpdates(AZ::TimeMs serverGameTimeMs);
void SendUpdates(AZ::TimeMs hostTimeMs);
void Clear(bool forMigration);
bool SetEntityRebasing(NetworkEntityHandle& entityHandle);
@@ -82,10 +82,10 @@ namespace Multiplayer
void AddAutonomousEntityReplicatorCreatedHandle(AZ::Event<NetEntityId>::Handler& handler);
bool HandleMessage(AzNetworking::IConnection* connection, MultiplayerPackets::EntityMigration& message);
bool HandleMessage(AzNetworking::IConnection* invokingConnection, MultiplayerPackets::EntityMigration& message);
bool HandleEntityDeleteMessage(EntityReplicator* entityReplicator, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityUpdateMessage(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityRpcMessage(AzNetworking::IConnection* connection, NetworkEntityRpcMessage& message);
bool HandleEntityUpdateMessage(AzNetworking::IConnection* invokingConnection, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& message);
AZ::TimeMs GetResendTimeoutTimeMs() const;
@@ -118,9 +118,9 @@ namespace Multiplayer
using EntityReplicatorList = AZStd::deque<EntityReplicator*>;
EntityReplicatorList GenerateEntityUpdateList();
void SendEntityUpdatesPacketHelper(AZ::TimeMs serverGameTimeMs, EntityReplicatorList& toSendList, uint32_t maxPayloadSize, AzNetworking::IConnection& connection);
void SendEntityUpdatesPacketHelper(AZ::TimeMs hostTimeMs, EntityReplicatorList& toSendList, uint32_t maxPayloadSize, AzNetworking::IConnection& connection);
void SendEntityUpdates(AZ::TimeMs serverGameTimeMs);
void SendEntityUpdates(AZ::TimeMs hostTimeMs);
void SendEntityRpcs(RpcMessages& deferredRpcs, bool reliable);
void MigrateEntityInternal(NetEntityId entityId);
@@ -155,31 +155,27 @@ namespace Multiplayer
OrphanedEntityRpcs(EntityReplicationManager& replicationManager);
void Update();
bool DispatchOrphanedRpcs(EntityReplicator& entityReplicator);
void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityPrcMessage);
void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityRpcMessage);
AZStd::size_t Size() const { return m_entityRpcMap.size(); }
private:
AzNetworking::TimeoutResult HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) override;
struct OrphanedRpcs
{
OrphanedRpcs() = default;
OrphanedRpcs(OrphanedRpcs&& rhs)
{
m_rpcMessages.swap(rhs.m_rpcMessages);
m_timeoutId = rhs.m_timeoutId;
rhs.m_timeoutId = AzNetworking::TimeoutId{ 0 };
m_rpcMessages.swap(rhs.m_rpcMessages);
}
AzNetworking::TimeoutId m_timeoutId = AzNetworking::TimeoutId{ 0 };
RpcMessages m_rpcMessages;
AzNetworking::TimeoutId m_timeoutId = AzNetworking::TimeoutId{ 0 };
};
typedef AZStd::unordered_map<NetEntityId, OrphanedRpcs> EntityRpcMap;
EntityRpcMap m_entityRpcMap;
AzNetworking::TimeoutQueue m_timeoutQueue;
EntityReplicationManager& m_replicationManager;
};
OrphanedEntityRpcs m_orphanedEntityRpcs;
EntityReplicatorMap m_entityReplicatorMap;
@@ -628,7 +628,7 @@ namespace Multiplayer
return result;
}
bool EntityReplicator::HandleRpcMessage(NetworkEntityRpcMessage& entityRpcMessage)
bool EntityReplicator::HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& entityRpcMessage)
{
// Received rpc metrics, log rpc received, time spent, number of bytes, and the componentId/rpcId for bandwidth metrics
MultiplayerStats& stats = AZ::Interface<IMultiplayer>::Get()->GetStats();
@@ -676,7 +676,7 @@ namespace Multiplayer
switch (result)
{
case RpcValidationResult::HandleRpc:
return m_netBindComponent->HandleRpcMessage(GetRemoteNetworkRole(), entityRpcMessage);
return m_netBindComponent->HandleRpcMessage(invokingConnection, GetRemoteNetworkRole(), entityRpcMessage);
case RpcValidationResult::DropRpc:
return true;
case RpcValidationResult::DropRpcAndDisconnect:
@@ -703,7 +703,7 @@ namespace Multiplayer
break;
}
AZ_Assert(false, "Unhandled ERpcValidationResult %d", result);
AZ_Assert(false, "Unhandled RpcValidationResult %d", result);
return false;
}
}
@@ -75,8 +75,8 @@ namespace Multiplayer
const PropertyPublisher* GetPropertyPublisher() const;
PropertySubscriber* GetPropertySubscriber();
// Handlers for messages
bool HandleRpcMessage(NetworkEntityRpcMessage& entityRpcMessage);
// Handlers for Rpc messages
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& entityRpcMessage);
//! AZ::EntityBus overrides
//! @{
@@ -1,41 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Source/NetworkEntity/INetworkEntityManager.h>
namespace Multiplayer
{
//! @class INetworkEntityDomain
//! @brief A class that determines if an entity should belong to a particular EntityManager.
class INetworkEntityDomain
{
public:
using EntitiesNotInDomain = AZStd::unordered_set<NetEntityId>;
virtual ~INetworkEntityDomain() = default;
//! Enable Entity Domain Exit Tracking for entities on the server.
//! @param ownedEntitySet
virtual void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) = 0;
//! Return the set of entities not in this domain.
//! @param outEntitiesNotInDomain
virtual void RetrieveEntitiesNotInDomain(EntitiesNotInDomain& outEntitiesNotInDomain) const = 0;
//! Returns whether or not an entity should be owned by an entity manager.
//! @param entityHandle the handle of the entity to check for inclusion in the domain
//! @return false if this entity should not belong to the entity manger, true if it could be owned by the entity manager
virtual bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const = 0;
};
}
@@ -11,8 +11,8 @@
*/
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/Components/NetBindComponent.h>
#include <Include/INetworkEntityManager.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
@@ -10,7 +10,7 @@
*
*/
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/Components/NetBindComponent.h>
#include <Source/Components/MultiplayerController.h>
@@ -212,7 +212,7 @@ namespace Multiplayer
{
NetBindComponent* netBindComponent = entity->FindComponent<NetBindComponent>();
AZ_Assert(netBindComponent != nullptr, "Attempting to send an RPC to an entity with no NetBindComponent");
netBindComponent->HandleRpcMessage(NetEntityRole::Server, rpcMessage);
netBindComponent->HandleRpcMessage(nullptr, NetEntityRole::Server, rpcMessage);
}
}
m_localDeferredRpcMessages.clear();
@@ -15,11 +15,11 @@
#include <AzCore/EBus/ScheduledEvent.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Include/IEntityDomain.h>
#include <Include/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkSpawnableLibrary.h>
#include <Source/Components/MultiplayerComponentRegistry.h>
@@ -11,7 +11,7 @@
*/
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
@@ -13,7 +13,7 @@
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/Component/Entity.h>
@@ -48,19 +48,34 @@ namespace Multiplayer
return m_inputId;
}
void NetworkInput::SetServerTimeMs(AZ::TimeMs serverTimeMs)
void NetworkInput::SetHostFrameId(HostFrameId hostFrameId)
{
m_serverTimeMs = serverTimeMs;
m_hostFrameId = hostFrameId;
}
AZ::TimeMs NetworkInput::GetServerTimeMs() const
HostFrameId NetworkInput::GetHostFrameId() const
{
return m_serverTimeMs;
return m_hostFrameId;
}
AZ::TimeMs& NetworkInput::ModifyServerTimeMs()
HostFrameId& NetworkInput::ModifyHostFrameId()
{
return m_serverTimeMs;
return m_hostFrameId;
}
void NetworkInput::SetHostTimeMs(AZ::TimeMs hostTimeMs)
{
m_hostTimeMs = hostTimeMs;
}
AZ::TimeMs NetworkInput::GetHostTimeMs() const
{
return m_hostTimeMs;
}
AZ::TimeMs& NetworkInput::ModifyHostTimeMs()
{
return m_hostTimeMs;
}
void NetworkInput::AttachNetBindComponent(NetBindComponent* netBindComponent)
@@ -76,17 +91,19 @@ namespace Multiplayer
bool NetworkInput::Serialize(AzNetworking::ISerializer& serializer)
{
if (!serializer.Serialize(m_inputId, "InputId"))
if (!serializer.Serialize(m_inputId, "InputId")
|| !serializer.Serialize(m_hostTimeMs, "HostTimeMs")
|| !serializer.Serialize(m_hostFrameId, "HostFrameId"))
{
return false;
}
uint8_t componentInputCount = static_cast<uint8_t>(m_componentInputs.size());
uint16_t componentInputCount = static_cast<uint16_t>(m_componentInputs.size());
serializer.Serialize(componentInputCount, "ComponentInputCount");
m_componentInputs.resize(componentInputCount);
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{
for (uint8_t i = 0; i < componentInputCount; ++i)
for (uint16_t i = 0; i < componentInputCount; ++i)
{
// We need to do a little extra work here, the delta serializer won't actually write out values if they were the same as the parent.
// We need to make sure we don't lose state that is intrinsic to the underlying type
@@ -148,7 +165,7 @@ namespace Multiplayer
void NetworkInput::CopyInternal(const NetworkInput& rhs)
{
m_inputId = rhs.m_inputId;
m_serverTimeMs = rhs.m_serverTimeMs;
m_hostTimeMs = rhs.m_hostTimeMs;
m_componentInputs.resize(rhs.m_componentInputs.size());
for (int32_t i = 0; i < rhs.m_componentInputs.size(); ++i)
{
@@ -12,8 +12,9 @@
#pragma once
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/INetworkTime.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
namespace Multiplayer
@@ -21,8 +22,6 @@ namespace Multiplayer
// Forwards
class NetBindComponent;
AZ_TYPE_SAFE_INTEGRAL(ClientInputId, uint16_t);
//! @class NetworkInput
//! @brief A single networked client input command.
class NetworkInput final
@@ -42,9 +41,13 @@ namespace Multiplayer
ClientInputId GetClientInputId() const;
ClientInputId& ModifyClientInputId();
void SetServerTimeMs(AZ::TimeMs serverTimeMs);
AZ::TimeMs GetServerTimeMs() const;
AZ::TimeMs& ModifyServerTimeMs();
void SetHostFrameId(HostFrameId hostFrameId);
HostFrameId GetHostFrameId() const;
HostFrameId& ModifyHostFrameId();
void SetHostTimeMs(AZ::TimeMs hostTimeMs);
AZ::TimeMs GetHostTimeMs() const;
AZ::TimeMs& ModifyHostTimeMs();
void AttachNetBindComponent(NetBindComponent* netBindComponent);
@@ -72,10 +75,9 @@ namespace Multiplayer
MultiplayerComponentInputVector m_componentInputs;
ClientInputId m_inputId = ClientInputId{ 0 };
AZ::TimeMs m_serverTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_hostFrameId = InvalidHostFrameId;
AZ::TimeMs m_hostTimeMs = AZ::TimeMs{ 0 };
ConstNetworkEntityHandle m_owner;
bool m_wasAttached = false;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ClientInputId);
@@ -11,7 +11,7 @@
*/
#include <Source/NetworkInput/NetworkInputChild.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h>
namespace Multiplayer
@@ -11,7 +11,7 @@
*/
#include <Source/NetworkInput/NetworkInputVector.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Serialization/DeltaSerializer.h>
@@ -13,7 +13,7 @@
#pragma once
#include <Source/NetworkInput/NetworkInput.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace Multiplayer
@@ -24,36 +24,31 @@ namespace Multiplayer
AZ::Interface<INetworkTime>::Unregister(this);
}
AZ::TimeMs NetworkTime::ConvertFrameIdToTimeMs([[maybe_unused]] ApplicationFrameId frameId) const
{
return AZ::TimeMs{0};
}
ApplicationFrameId NetworkTime::ConvertTimeMsToFrameId([[maybe_unused]] AZ::TimeMs timeMs) const
{
return ApplicationFrameId{0};
}
bool NetworkTime::IsApplicationFrameIdRewound() const
bool NetworkTime::IsTimeRewound() const
{
return m_rewindingConnectionId != AzNetworking::InvalidConnectionId;
}
ApplicationFrameId NetworkTime::GetApplicationFrameId() const
HostFrameId NetworkTime::GetHostFrameId() const
{
return m_applicationFrameId;
return m_hostFrameId;
}
ApplicationFrameId NetworkTime::GetUnalteredApplicationFrameId() const
HostFrameId NetworkTime::GetUnalteredHostFrameId() const
{
return m_unalteredFrameId;
}
void NetworkTime::IncrementApplicationFrameId()
void NetworkTime::IncrementHostFrameId()
{
AZ_Assert(!IsApplicationFrameIdRewound(), "Incrementing the global application frameId is unsupported under a rewound time scope");
AZ_Assert(!IsTimeRewound(), "Incrementing the global application frameId is unsupported under a rewound time scope");
++m_unalteredFrameId;
m_applicationFrameId = m_unalteredFrameId;
m_hostFrameId = m_unalteredFrameId;
}
AZ::TimeMs NetworkTime::GetHostTimeMs() const
{
return m_hostTimeMs;
}
void NetworkTime::SyncRewindableEntityState()
@@ -66,14 +61,15 @@ namespace Multiplayer
return m_rewindingConnectionId;
}
ApplicationFrameId NetworkTime::GetApplicationFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const
HostFrameId NetworkTime::GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const
{
return (IsApplicationFrameIdRewound() && (rewindConnectionId == m_rewindingConnectionId)) ? m_unalteredFrameId : m_applicationFrameId;
return (IsTimeRewound() && (rewindConnectionId == m_rewindingConnectionId)) ? m_unalteredFrameId : m_hostFrameId;
}
void NetworkTime::AlterApplicationFrameId(ApplicationFrameId frameId, AzNetworking::ConnectionId rewindConnectionId)
void NetworkTime::AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId)
{
m_applicationFrameId = frameId;
m_hostFrameId = frameId;
m_hostTimeMs = timeMs;
m_rewindingConnectionId = rewindConnectionId;
}
}
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkTime/INetworkTime.h>
#include <Include/INetworkTime.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Console/IConsole.h>
@@ -28,22 +28,22 @@ namespace Multiplayer
//! INetworkTime overrides.
//! @{
AZ::TimeMs ConvertFrameIdToTimeMs(ApplicationFrameId frameId) const override;
ApplicationFrameId ConvertTimeMsToFrameId(AZ::TimeMs timeMs) const override;
bool IsApplicationFrameIdRewound() const override;
ApplicationFrameId GetApplicationFrameId() const override;
ApplicationFrameId GetUnalteredApplicationFrameId() const override;
void IncrementApplicationFrameId() override;
bool IsTimeRewound() const override;
HostFrameId GetHostFrameId() const override;
HostFrameId GetUnalteredHostFrameId() const override;
void IncrementHostFrameId() override;
AZ::TimeMs GetHostTimeMs() const override;
void SyncRewindableEntityState() override;
AzNetworking::ConnectionId GetRewindingConnectionId() const override;
ApplicationFrameId GetApplicationFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override;
void AlterApplicationFrameId(ApplicationFrameId frameId, AzNetworking::ConnectionId rewindConnectionId) override;
HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override;
void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) override;
//! @}
private:
ApplicationFrameId m_applicationFrameId = ApplicationFrameId{0};
ApplicationFrameId m_unalteredFrameId = ApplicationFrameId{0};
HostFrameId m_hostFrameId = HostFrameId{ 0 };
HostFrameId m_unalteredFrameId = HostFrameId{ 0 };
AZ::TimeMs m_hostTimeMs = AZ::TimeMs{ 0 };
AzNetworking::ConnectionId m_rewindingConnectionId = AzNetworking::InvalidConnectionId;
};
}
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkTime/INetworkTime.h>
#include <Include/INetworkTime.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
@@ -87,25 +87,25 @@ namespace Multiplayer
//! Returns what the appropriate current time is for this rewindable property.
//! @return the appropriate current time is for this rewindable property
ApplicationFrameId GetCurrentTimeForProperty() const;
HostFrameId GetCurrentTimeForProperty() const;
//! Updates the latest value for this object instance, if frameTime represents a current or future time.
//! Any attempts to set old values on the object will fail
//! @param value the new value to set in the object history
//! @param frameTime the time to set the value for
void SetValueForTime(const BASE_TYPE& value, ApplicationFrameId frameTime);
void SetValueForTime(const BASE_TYPE& value, HostFrameId frameTime);
//! Const value accessor, returns the correct value for the provided input time.
//! @param frameTime the frame time to return the associated value for
//! @return value given the current input time
const BASE_TYPE& GetValueForTime(ApplicationFrameId frameTime) const;
const BASE_TYPE& GetValueForTime(HostFrameId frameTime) const;
//! Helper method to compute clamped array index values accounting for the offset head index.
AZStd::size_t GetOffsetIndex(AZStd::size_t absoluteIndex) const;
AZStd::array<BASE_TYPE, REWIND_SIZE> m_history;
AzNetworking::ConnectionId m_owningConnectionId = AzNetworking::InvalidConnectionId;
ApplicationFrameId m_headTime = ApplicationFrameId{0};
HostFrameId m_headTime = HostFrameId{0};
uint32_t m_headIndex = 0;
};
}
@@ -48,7 +48,7 @@ namespace Multiplayer
inline RewindableObject<BASE_TYPE, REWIND_SIZE> &RewindableObject<BASE_TYPE, REWIND_SIZE>::operator =(const RewindableObject<BASE_TYPE, REWIND_SIZE>& rhs)
{
INetworkTime* networkTime = AZ::Interface<INetworkTime>::Get();
SetValueForTime(rhs.GetValueForTime(networkTime->GetApplicationFrameId()), GetCurrentTimeForProperty());
SetValueForTime(rhs.GetValueForTime(networkTime->GetHostFrameId()), GetCurrentTimeForProperty());
return *this;
}
@@ -73,7 +73,7 @@ namespace Multiplayer
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline BASE_TYPE& RewindableObject<BASE_TYPE, REWIND_SIZE>::Modify()
{
const ApplicationFrameId frameTime = GetCurrentTimeForProperty();
const HostFrameId frameTime = GetCurrentTimeForProperty();
if (frameTime < m_headTime)
{
AZ_Assert(false, "Trying to mutate a rewindable in the past");
@@ -103,7 +103,7 @@ namespace Multiplayer
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline bool RewindableObject<BASE_TYPE, REWIND_SIZE>::Serialize(AzNetworking::ISerializer& serializer)
{
const ApplicationFrameId frameTime = GetCurrentTimeForProperty();
const HostFrameId frameTime = GetCurrentTimeForProperty();
BASE_TYPE value = GetValueForTime(frameTime);
if (serializer.Serialize(value, "Element") && (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject))
{
@@ -113,14 +113,14 @@ namespace Multiplayer
}
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline ApplicationFrameId RewindableObject<BASE_TYPE, REWIND_SIZE>::GetCurrentTimeForProperty() const
inline HostFrameId RewindableObject<BASE_TYPE, REWIND_SIZE>::GetCurrentTimeForProperty() const
{
INetworkTime* networkTime = AZ::Interface<INetworkTime>::Get();
return networkTime->GetApplicationFrameIdForRewindingConnection(m_owningConnectionId);
return networkTime->GetHostFrameIdForRewindingConnection(m_owningConnectionId);
}
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline void RewindableObject<BASE_TYPE, REWIND_SIZE>::SetValueForTime(const BASE_TYPE& value, ApplicationFrameId frameTime)
inline void RewindableObject<BASE_TYPE, REWIND_SIZE>::SetValueForTime(const BASE_TYPE& value, HostFrameId frameTime)
{
if (frameTime < m_headTime)
{
@@ -155,7 +155,7 @@ namespace Multiplayer
}
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline const BASE_TYPE &RewindableObject<BASE_TYPE, REWIND_SIZE>::GetValueForTime(ApplicationFrameId frameTime) const
inline const BASE_TYPE &RewindableObject<BASE_TYPE, REWIND_SIZE>::GetValueForTime(HostFrameId frameTime) const
{
if (frameTime > m_headTime)
{
@@ -12,7 +12,7 @@
#pragma once
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Include/IReplicationWindow.h>
namespace Multiplayer
{
@@ -12,9 +12,9 @@
#pragma once
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
#include <Include/IReplicationWindow.h>
#include <Include/NetworkEntityHandle.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzCore/Component/EntityBus.h>
#include <AzCore/EBus/ScheduledEvent.h>
@@ -10,10 +10,18 @@
#
set(FILES
Include/IConnectionData.h
Include/IEntityDomain.h
Include/IMultiplayer.h
Include/IMultiplayerComponentInput.h
Include/INetworkEntityManager.h
Include/INetworkTime.h
Include/IReplicationWindow.h
Include/MultiplayerStats.cpp
Include/MultiplayerStats.h
Include/MultiplayerTypes.h
Include/NetworkEntityHandle.h
Include/NetworkEntityHandle.inl
Source/Multiplayer_precompiled.cpp
Source/Multiplayer_precompiled.h
Source/MultiplayerSystemComponent.cpp
@@ -41,13 +49,11 @@ set(FILES
Source/ConnectionData/ClientToServerConnectionData.cpp
Source/ConnectionData/ClientToServerConnectionData.h
Source/ConnectionData/ClientToServerConnectionData.inl
Source/ConnectionData/IConnectionData.h
Source/ConnectionData/ServerToClientConnectionData.cpp
Source/ConnectionData/ServerToClientConnectionData.h
Source/ConnectionData/ServerToClientConnectionData.inl
Source/EntityDomains/FullOwnershipEntityDomain.cpp
Source/EntityDomains/FullOwnershipEntityDomain.h
Source/EntityDomains/IEntityDomain.h
Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp
Source/NetworkEntity/EntityReplication/EntityReplicationManager.h
Source/NetworkEntity/EntityReplication/EntityReplicator.cpp
@@ -59,12 +65,9 @@ set(FILES
Source/NetworkEntity/EntityReplication/PropertySubscriber.h
Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp
Source/NetworkEntity/EntityReplication/ReplicationRecord.h
Source/NetworkEntity/INetworkEntityManager.h
Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp
Source/NetworkEntity/NetworkEntityAuthorityTracker.h
Source/NetworkEntity/NetworkEntityHandle.cpp
Source/NetworkEntity/NetworkEntityHandle.h
Source/NetworkEntity/NetworkEntityHandle.inl
Source/NetworkEntity/NetworkEntityManager.cpp
Source/NetworkEntity/NetworkEntityManager.h
Source/NetworkEntity/NetworkSpawnableLibrary.cpp
@@ -76,7 +79,6 @@ set(FILES
Source/NetworkEntity/NetworkEntityTracker.inl
Source/NetworkEntity/NetworkEntityUpdateMessage.cpp
Source/NetworkEntity/NetworkEntityUpdateMessage.h
Source/NetworkInput/IMultiplayerComponentInput.h
Source/NetworkInput/NetworkInput.cpp
Source/NetworkInput/NetworkInput.h
Source/NetworkInput/NetworkInputChild.cpp
@@ -85,7 +87,6 @@ set(FILES
Source/NetworkInput/NetworkInputHistory.h
Source/NetworkInput/NetworkInputVector.cpp
Source/NetworkInput/NetworkInputVector.h
Source/NetworkTime/INetworkTime.h
Source/NetworkTime/NetworkTime.cpp
Source/NetworkTime/NetworkTime.h
Source/NetworkTime/RewindableObject.h
@@ -96,7 +97,6 @@ set(FILES
Source/Pipeline/NetworkSpawnableHolderComponent.h
Source/ReplicationWindows/NullReplicationWindow.cpp
Source/ReplicationWindows/NullReplicationWindow.h
Source/ReplicationWindows/IReplicationWindow.h
Source/ReplicationWindows/ServerToClientReplicationWindow.cpp
Source/ReplicationWindows/ServerToClientReplicationWindow.h
)