diff --git a/Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.cpp b/Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.cpp new file mode 100644 index 0000000000..bcd09f274c --- /dev/null +++ b/Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.cpp @@ -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 + +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(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 + 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; + } +} diff --git a/Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.h b/Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.h new file mode 100644 index 0000000000..aa8c58ae60 --- /dev/null +++ b/Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.h @@ -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 +#include + +namespace AzNetworking +{ + // StringifySerializer + // Generate a debug string of a serializable object + class StringifySerializer + : public ISerializer + { + public: + + using StringMap = AZStd::map; + + 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 + 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 m_prefixSizeStack; + }; +} diff --git a/Code/Framework/AzNetworking/AzNetworking/aznetworking_files.cmake b/Code/Framework/AzNetworking/AzNetworking/aznetworking_files.cmake index ea84f3fdc9..b8d488a1e4 100644 --- a/Code/Framework/AzNetworking/AzNetworking/aznetworking_files.cmake +++ b/Code/Framework/AzNetworking/AzNetworking/aznetworking_files.cmake @@ -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 diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/IConnectionData.h b/Gems/Multiplayer/Code/Include/IConnectionData.h similarity index 87% rename from Gems/Multiplayer/Code/Source/ConnectionData/IConnectionData.h rename to Gems/Multiplayer/Code/Include/IConnectionData.h index a7ceffd289..dcc2c940ef 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/IConnectionData.h +++ b/Gems/Multiplayer/Code/Include/IConnectionData.h @@ -12,11 +12,13 @@ #pragma once -#include -#include +#include +#include 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 diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/IEntityDomain.h b/Gems/Multiplayer/Code/Include/IEntityDomain.h similarity index 97% rename from Gems/Multiplayer/Code/Source/EntityDomains/IEntityDomain.h rename to Gems/Multiplayer/Code/Include/IEntityDomain.h index 56ec24618d..6571797d05 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/IEntityDomain.h +++ b/Gems/Multiplayer/Code/Include/IEntityDomain.h @@ -12,7 +12,7 @@ #pragma once -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Include/IMultiplayer.h b/Gems/Multiplayer/Code/Include/IMultiplayer.h index 47d0d5a05e..039b86b2a6 100644 --- a/Gems/Multiplayer/Code/Include/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/IMultiplayer.h @@ -15,6 +15,7 @@ #include #include #include +#include #include 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 diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/IMultiplayerComponentInput.h b/Gems/Multiplayer/Code/Include/IMultiplayerComponentInput.h similarity index 100% rename from Gems/Multiplayer/Code/Source/NetworkInput/IMultiplayerComponentInput.h rename to Gems/Multiplayer/Code/Include/IMultiplayerComponentInput.h diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/INetworkEntityManager.h similarity index 99% rename from Gems/Multiplayer/Code/Source/NetworkEntity/INetworkEntityManager.h rename to Gems/Multiplayer/Code/Include/INetworkEntityManager.h index 891a4606ea..d9b611ece0 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/INetworkEntityManager.h @@ -13,7 +13,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/INetworkTime.h b/Gems/Multiplayer/Code/Include/INetworkTime.h similarity index 50% rename from Gems/Multiplayer/Code/Source/NetworkTime/INetworkTime.h rename to Gems/Multiplayer/Code/Include/INetworkTime.h index 703fba5fb5..5346a0e0d0 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/INetworkTime.h +++ b/Gems/Multiplayer/Code/Include/INetworkTime.h @@ -14,13 +14,10 @@ #include #include +#include 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::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::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); diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/IReplicationWindow.h b/Gems/Multiplayer/Code/Include/IReplicationWindow.h similarity index 96% rename from Gems/Multiplayer/Code/Source/ReplicationWindows/IReplicationWindow.h rename to Gems/Multiplayer/Code/Include/IReplicationWindow.h index 27c14f8049..eb34a2f87d 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/IReplicationWindow.h +++ b/Gems/Multiplayer/Code/Include/IReplicationWindow.h @@ -13,7 +13,7 @@ #pragma once #include -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Include/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/MultiplayerTypes.h index 5c690958b5..1bee9867e3 100644 --- a/Gems/Multiplayer/Code/Include/MultiplayerTypes.h +++ b/Gems/Multiplayer/Code/Include/MultiplayerTypes.h @@ -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); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.h b/Gems/Multiplayer/Code/Include/NetworkEntityHandle.h similarity index 99% rename from Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.h rename to Gems/Multiplayer/Code/Include/NetworkEntityHandle.h index 0f84ed4477..9b8546ef2c 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.h +++ b/Gems/Multiplayer/Code/Include/NetworkEntityHandle.h @@ -138,4 +138,4 @@ namespace Multiplayer }; } -#include "Source/NetworkEntity/NetworkEntityHandle.inl" +#include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.inl b/Gems/Multiplayer/Code/Include/NetworkEntityHandle.inl similarity index 100% rename from Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.inl rename to Gems/Multiplayer/Code/Include/NetworkEntityHandle.inl diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja index 57375b39f2..2bae618d94 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja @@ -1,6 +1,6 @@ #include #include -#include +#include {% for Component in dataFiles %} {% set ComponentDerived = Component.attrib['OverrideComponent']|booleanTrue %} {% set ControllerDerived = Component.attrib['OverrideController']|booleanTrue %} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja index fb7de56bb1..d211b933c5 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja @@ -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 %} {# diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 14a68cddf1..faaa009e34 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -221,11 +221,11 @@ AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] } #include #include #include -#include +#include +#include #include #include #include -#include #include #include {% 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; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index fb802541ce..6e54ec3d58 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -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) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml index 4eaf7ff0fb..45dfc43e49 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml @@ -16,11 +16,11 @@ - + - + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 5de466899c..daf55c3d92 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -3,6 +3,7 @@ + @@ -35,6 +36,7 @@ + diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 24986148c4..56192c96d7 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -13,9 +13,41 @@ #include #include #include +#include +#include +#include +#include +#include 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(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::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(static_cast(cl_InputRateMs)) / 1000.0; + m_lastInputReceivedTimeMs = currentTimeMs; + + // Keep track of last inputs received, also allows us to update frame ids + m_lastInputReceived = inputArray; + + // Figure out which index from the input array we want + // we start at the oldest input that has not been processed + int32_t inputArrayIndex = -1; + for (int32_t i = 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(GetLastInputId()), + aznumeric_cast(m_lastInputReceived[0].GetClientInputId()) + ); + return; + } + + bool lostInput = false; + if (GetLastInputId() < inputArray.GetPreviousInputId()) + { + // last move id processed is older than the previous input id, we missed some input packets + lostInput = true; + } + + SetLastInputId(m_lastInputReceived[0].GetClientInputId()); // Set this variable in case of migration + + while (inputArrayIndex >= 0) + { + 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(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> 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() ? "" : mapPair.second.second; + AZStd::string serverValue = mapPair.second.first.empty() ? "" : 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(static_cast(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(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(inputId)); + return; + } + + m_lastCorrectionInputId = inputId; + + // Apply the correction + AzNetworking::TrackChangedSerializer serializer(correction.GetBuffer(), correction.GetSize()); + GetNetBindComponent()->SerializeEntityCorrection(serializer); + m_correctionEvent.Signal(); + + AZLOG + ( + NET_Prediction, + "Corrected InputId=%d - o=[%s]", + aznumeric_cast(m_lastCorrectionInputId), + GetCorrectionDataString(GetNetBindComponent()).c_str() + ); + + const uint32_t inputHistorySize = m_inputHistory.Size(); + const uint32_t historicalDelta = aznumeric_cast(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(static_cast(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(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(deltaTimeMs) / 1000.0; + const double inputRate = static_cast(static_cast(cl_InputRateMs)) / 1000.0; + const double maxRewindHistory = static_cast(static_cast(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(maxRewindHistory / inputRate) : 0; + + INetworkTime* networkTime = AZ::Interface::Get(); + IMultiplayer* multiplayer = AZ::Interface::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(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(deltaTimeMs) / 1000.0; + const double inputRate = static_cast(static_cast(cl_InputRateMs)) / 1000.0; + const double maxRewindHistory = static_cast(static_cast(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(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); } } diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h index b332315799..0a4e574d65 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h @@ -13,9 +13,12 @@ #pragma once #include +#include 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) }; } diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.h b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.h index 3642a55476..0f64221dde 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.h +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -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; diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerController.h b/Gems/Multiplayer/Code/Source/Components/MultiplayerController.h index 9e3c7d68ab..de07e39e66 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerController.h +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerController.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index bb1dac5a4e..eba09734a9 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -13,10 +13,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -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) diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.h index e992dbfd72..4a4c4d0549 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.h @@ -21,8 +21,9 @@ #include #include #include -#include -#include +#include +#include +#include #include #include @@ -34,7 +35,9 @@ namespace Multiplayer using EntityStopEvent = AZ::Event; using EntityDirtiedEvent = AZ::Event<>; - using EntityMigrationEvent = AZ::Event; + using EntityMigrationStartEvent = AZ::Event; + using EntityMigrationEndEvent = AZ::Event<>; + using EntityServerMigrationEvent = AZ::Event; //! @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; diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp index 4420e0689c..3aeaac5103 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp @@ -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(); } diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h index 76a809b351..b72a6aad2b 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h @@ -12,7 +12,8 @@ #pragma once -#include +#include +#include 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; //! @} diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp index 7a7e266a38..d2440d28f4 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp @@ -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); } } } diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h index 7ea62b15fd..b02e6de9aa 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h @@ -12,7 +12,8 @@ #pragma once -#include +#include +#include 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; }; diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h index 95bfc211cf..c1abbe74cd 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h @@ -12,7 +12,7 @@ #pragma once -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 03c661ab03..90d55d8b26 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -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(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); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index f25e530b61..477745e6b5 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -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; }; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 74bdcd5cf0..1e7649f561 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -14,14 +14,14 @@ #include #include #include -#include -#include #include #include -#include #include #include +#include #include +#include +#include #include #include #include @@ -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(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()); { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h index a6470e6c34..4fc14e210f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -13,11 +13,11 @@ #pragma once #include -#include -#include -#include -#include #include +#include +#include +#include +#include #include #include #include @@ -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::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; 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 EntityRpcMap; EntityRpcMap m_entityRpcMap; AzNetworking::TimeoutQueue m_timeoutQueue; EntityReplicationManager& m_replicationManager; }; - OrphanedEntityRpcs m_orphanedEntityRpcs; EntityReplicatorMap m_entityReplicatorMap; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index b645101677..7431b95a22 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -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::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; } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h index 66274f638e..93494ab07c 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h @@ -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 //! @{ diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/INetworkEntityDomain.h b/Gems/Multiplayer/Code/Source/NetworkEntity/INetworkEntityDomain.h deleted file mode 100644 index 24e43de66a..0000000000 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/INetworkEntityDomain.h +++ /dev/null @@ -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 - -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; - - 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; - }; -} diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp index a37d444046..797d67e3ef 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp @@ -11,8 +11,8 @@ */ #include -#include #include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp index 650b113186..ca0275d20b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp @@ -10,7 +10,7 @@ * */ -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index d1701bbb89..dfe427bd88 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -212,7 +212,7 @@ namespace Multiplayer { NetBindComponent* netBindComponent = entity->FindComponent(); 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(); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 142730188b..436ae01f5a 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -15,11 +15,11 @@ #include #include #include -#include #include #include #include -#include +#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp index 4ced19f8d9..69f715317a 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h index ffc150e5cc..4cfb242154 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h @@ -13,7 +13,7 @@ #pragma once #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp index a02bc4209d..0ab1d5ffcc 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp @@ -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(m_componentInputs.size()); + uint16_t componentInputCount = static_cast(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) { diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.h index 43768c4196..2c33d1ccf0 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.h @@ -12,8 +12,9 @@ #pragma once -#include -#include +#include +#include +#include #include 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); diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.cpp index bb5f9a488d..8f70f7e1fa 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.cpp index 466a112e12..6d284a6835 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.h index be41495577..91970040c5 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.h @@ -13,7 +13,7 @@ #pragma once #include -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index b062569dce..9a0e784d36 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -24,36 +24,31 @@ namespace Multiplayer AZ::Interface::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; } } diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h index 55ccb45e69..06e758b349 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include #include @@ -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; }; } diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.h b/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.h index cdea98dc07..4e830d4480 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.h +++ b/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include #include #include @@ -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 m_history; AzNetworking::ConnectionId m_owningConnectionId = AzNetworking::InvalidConnectionId; - ApplicationFrameId m_headTime = ApplicationFrameId{0}; + HostFrameId m_headTime = HostFrameId{0}; uint32_t m_headIndex = 0; }; } diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.inl b/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.inl index 69752210bb..0835421ebd 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.inl +++ b/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.inl @@ -48,7 +48,7 @@ namespace Multiplayer inline RewindableObject &RewindableObject::operator =(const RewindableObject& rhs) { INetworkTime* networkTime = AZ::Interface::Get(); - SetValueForTime(rhs.GetValueForTime(networkTime->GetApplicationFrameId()), GetCurrentTimeForProperty()); + SetValueForTime(rhs.GetValueForTime(networkTime->GetHostFrameId()), GetCurrentTimeForProperty()); return *this; } @@ -73,7 +73,7 @@ namespace Multiplayer template inline BASE_TYPE& RewindableObject::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 inline bool RewindableObject::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 - inline ApplicationFrameId RewindableObject::GetCurrentTimeForProperty() const + inline HostFrameId RewindableObject::GetCurrentTimeForProperty() const { INetworkTime* networkTime = AZ::Interface::Get(); - return networkTime->GetApplicationFrameIdForRewindingConnection(m_owningConnectionId); + return networkTime->GetHostFrameIdForRewindingConnection(m_owningConnectionId); } template - inline void RewindableObject::SetValueForTime(const BASE_TYPE& value, ApplicationFrameId frameTime) + inline void RewindableObject::SetValueForTime(const BASE_TYPE& value, HostFrameId frameTime) { if (frameTime < m_headTime) { @@ -155,7 +155,7 @@ namespace Multiplayer } template - inline const BASE_TYPE &RewindableObject::GetValueForTime(ApplicationFrameId frameTime) const + inline const BASE_TYPE &RewindableObject::GetValueForTime(HostFrameId frameTime) const { if (frameTime > m_headTime) { diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h index 76562a34e2..1922e65941 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h @@ -12,7 +12,7 @@ #pragma once -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h index 4dad4bd01f..423334476e 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h @@ -12,9 +12,9 @@ #pragma once -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index f26e40355e..b340913e47 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -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 )