Merge branch 'upstream/development' into GitIssue3155_MultiplayerComponentsUsingNetInputRequirePlayerInputComponent
This commit is contained in:
@@ -5,12 +5,12 @@
|
||||
#}
|
||||
{%- macro ParseRpcParams(property, outNames, outTypes, outDefines, use_default_value=False) -%}
|
||||
{%- for Param in property.iter('Param') -%}
|
||||
{%- do outNames.append(Param.attrib['Name']) -%}
|
||||
{%- do outNames.append(LowerFirst(Param.attrib['Name'])) -%}
|
||||
{%- do outTypes.append(Param.attrib['Type']) -%}
|
||||
{%- if use_default_value and Param.attrib['DefaultValue'] -%}
|
||||
{%- do outDefines.append('const ' ~ Param.attrib['Type'] ~ '& ' + Param.attrib['Name'] + ' = ' + Param.attrib['DefaultValue']) -%}
|
||||
{%- do outDefines.append('const ' ~ Param.attrib['Type'] ~ '& ' + LowerFirst(Param.attrib['Name']) + ' = ' + Param.attrib['DefaultValue']) -%}
|
||||
{%- else -%}
|
||||
{%- do outDefines.append('const ' ~ Param.attrib['Type'] ~ '& ' ~ Param.attrib['Name']) -%}
|
||||
{%- do outDefines.append('const ' ~ Param.attrib['Type'] ~ '& ' ~ LowerFirst(Param.attrib['Name'])) -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endmacro -%}
|
||||
|
||||
@@ -130,9 +130,9 @@ void {{ PropertyName }}({{ ', '.join(paramDefines) }});
|
||||
{#
|
||||
|
||||
#}
|
||||
{% macro DeclareRpcInvocations(Component, Section, HandleOn, ProctectedSection) %}
|
||||
{% macro DeclareRpcInvocations(Component, Section, HandleOn, IsProtected) %}
|
||||
{% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, Section, HandleOn) %}
|
||||
{% if Property.attrib['IsPublic']|booleanTrue == ProctectedSection %}
|
||||
{% if Property.attrib['IsPublic']|booleanTrue != IsProtected %}
|
||||
{{ DeclareRpcInvocation(Property, HandleOn) -}}
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
@@ -386,8 +386,6 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
{{ DeclareNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', false)|indent(8) -}}
|
||||
{{ DeclareNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', true)|indent(8) -}}
|
||||
{{ DeclareArchetypePropertyGetters(Component)|indent(8) -}}
|
||||
{{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}}
|
||||
{{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}}
|
||||
{{ DeclareRpcInvocations(Component, 'Client', 'Authority', false)|indent(8) -}}
|
||||
{{ DeclareRpcInvocations(Component, 'Client', 'Authority', true)|indent(8) -}}
|
||||
{{ DeclareRpcInvocations(Component, 'Autonomous', 'Authority', false)|indent(8) -}}
|
||||
@@ -445,8 +443,8 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
|
||||
static AZStd::unique_ptr<Multiplayer::IMultiplayerComponentInput> AllocateComponentInput();
|
||||
|
||||
{{ ComponentBaseName }}() = default;
|
||||
~{{ ComponentBaseName }}() override = default;
|
||||
{{ ComponentBaseName }}();
|
||||
~{{ ComponentBaseName }}() override;
|
||||
|
||||
void Init() override;
|
||||
void Activate() override;
|
||||
|
||||
@@ -318,7 +318,11 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(par
|
||||
constexpr AzNetworking::ReliabilityType isReliable = Multiplayer::ReliabilityType::Unreliable;
|
||||
{% endif %}
|
||||
|
||||
{% if InvokeFrom == 'Server' or InvokeFrom =='Client' %}
|
||||
const Multiplayer::NetComponentId netComponentId = GetNetComponentId();
|
||||
{% else %}
|
||||
const Multiplayer::NetComponentId netComponentId = GetParent().GetNetComponentId();
|
||||
{% endif %}
|
||||
Multiplayer::NetworkEntityRpcMessage rpcMessage(Multiplayer::RpcDeliveryType::{{ InvokeFrom }}To{{ HandleOn }}, GetNetEntityId(), netComponentId, rpcId, isReliable);
|
||||
{% if paramNames|count > 0 %}
|
||||
{{ UpperFirst(Component.attrib['Name']) }}Internal::{{ UpperFirst(Property.attrib['Name']) }}RpcStruct rpcStruct({{ ', '.join(paramNames) }});
|
||||
@@ -345,9 +349,9 @@ void {{ ClassName }}::Signal{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.jo
|
||||
{#
|
||||
|
||||
#}
|
||||
{% macro DefineRpcInvocations(Component, ClassName, InvokeFrom, HandleOn, ProctectedSection) %}
|
||||
{% macro DefineRpcInvocations(Component, ClassName, InvokeFrom, HandleOn, IsProtected) %}
|
||||
{% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %}
|
||||
{% if Property.attrib['IsPublic']|booleanTrue == ProctectedSection %}
|
||||
{% if Property.attrib['IsPublic']|booleanTrue != IsProtected %}
|
||||
{{ DefineRpcInvocation(Component, ClassName, Property, InvokeFrom, HandleOn) -}}
|
||||
{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %}
|
||||
{{ DefineRpcSignal(Component, ClassName, Property, InvokeFrom) -}}
|
||||
@@ -632,7 +636,6 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
|
||||
{% if networkPropertyCount.value > 0 %}
|
||||
[[maybe_unused]] Multiplayer::MultiplayerStats& stats = Multiplayer::GetMultiplayer()->GetStats();
|
||||
// We modify the record if we are writing an update so that we don't notify for a change that really didn't change the value (just a duplicated send from the server)
|
||||
[[maybe_unused]] bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject;
|
||||
{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %}
|
||||
{% if Property.attrib['Container'] != 'None' and Property.attrib['Container'] != 'Object' %}
|
||||
{ // Serialization for Vector and Array Network Properties
|
||||
@@ -643,22 +646,28 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
|
||||
const uint32_t lastBit = static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'End') }});
|
||||
{% endif %}
|
||||
|
||||
{% if Property.attrib['IsRewindable']|booleanTrue %}
|
||||
AzNetworking::FixedSizeBitsetView deltaRecord(replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}, firstBit, lastBit - firstBit + 1);
|
||||
m_{{ LowerFirst(Property.attrib['Name']) }}.Serialize(serializer, deltaRecord);
|
||||
{% else %}
|
||||
if (deltaRecord.AnySet())
|
||||
{
|
||||
{% if Property.attrib['Container'] == 'Vector' %}
|
||||
serializer.Serialize<AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>>(m_{{ LowerFirst(Property.attrib['Name']) }}, "{{ LowerFirst(Property.attrib['Name']) }}");
|
||||
Multiplayer::SerializeNetworkPropertyHelperVector
|
||||
{% elif Property.attrib['Container'] == 'Array' %}
|
||||
serializer.Serialize<AZStd::array<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>>(m_{{ LowerFirst(Property.attrib['Name']) }}, "{{ LowerFirst(Property.attrib['Name']) }}");
|
||||
Multiplayer::SerializeNetworkPropertyHelperArray
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
(
|
||||
serializer,
|
||||
deltaRecord,
|
||||
m_{{ LowerFirst(Property.attrib['Name']) }},
|
||||
GetNetComponentId(),
|
||||
static_cast<Multiplayer::PropertyIndex>({{ UpperFirst(Component.attrib['Name']) }}Internal::NetworkProperties::{{ UpperFirst(Property.attrib['Name']) }}),
|
||||
stats
|
||||
);
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
Multiplayer::SerializeNetworkPropertyHelper
|
||||
(
|
||||
serializer,
|
||||
modifyRecord,
|
||||
replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }},
|
||||
static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }}),
|
||||
m_{{ LowerFirst(Property.attrib['Name']) }},
|
||||
@@ -1251,10 +1260,12 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
bool {{ RecordName }}::CanAttachRecord(Multiplayer::ReplicationRecord& replicationRecord)
|
||||
{
|
||||
bool canAttach{ true };
|
||||
AZ_PUSH_DISABLE_WARNING(4296, "-Wunknown-warning-option") // expression is always true
|
||||
canAttach &= replicationRecord.ContainsAuthorityToClientBits() ? (replicationRecord.GetRemainingAuthorityToClientBits() >= static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Client') }}::Count)) : true;
|
||||
canAttach &= replicationRecord.ContainsAuthorityToServerBits() ? (replicationRecord.GetRemainingAuthorityToServerBits() >= static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Server') }}::Count)) : true;
|
||||
canAttach &= replicationRecord.ContainsAuthorityToAutonomousBits() ? (replicationRecord.GetRemainingAuthorityToAutonomousBits() >= static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Autonomous') }}::Count)) : true;
|
||||
canAttach &= replicationRecord.ContainsAutonomousToAuthorityBits() ? (replicationRecord.GetRemainingAutonomousToAuthorityBits() >= static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Autonomous', 'Authority') }}::Count)) : true;
|
||||
AZ_POP_DISABLE_WARNING
|
||||
return canAttach;
|
||||
}
|
||||
|
||||
@@ -1496,6 +1507,10 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
{{ ComponentBaseName }}::{{ ComponentBaseName }}() = default;
|
||||
|
||||
{{ ComponentBaseName }}::~{{ ComponentBaseName }}() = default;
|
||||
|
||||
void {{ ComponentBaseName }}::Init()
|
||||
{
|
||||
if (m_netBindComponent == nullptr)
|
||||
@@ -1574,8 +1589,7 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
return s_netComponentId;
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4065) // switch statement contains 'default' but no 'case' labels
|
||||
AZ_PUSH_DISABLE_WARNING(4065, "-Wunknown-warning-option") // switch statement contains 'default' but no 'case' labels
|
||||
bool {{ ComponentBaseName }}::HandleRpcMessage
|
||||
(
|
||||
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
|
||||
@@ -1593,10 +1607,8 @@ namespace {{ Component.attrib['Namespace'] }}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
AZ_Assert(0, "Got unhandled RpcType %d in {{ ComponentBaseName }}", static_cast<int32_t>(rpcType));
|
||||
return false;
|
||||
}
|
||||
#pragma warning(pop)
|
||||
AZ_POP_DISABLE_WARNING
|
||||
|
||||
bool {{ ComponentBaseName }}::SerializeStateDeltaMessage(Multiplayer::ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer)
|
||||
{
|
||||
|
||||
-1
@@ -22,7 +22,6 @@
|
||||
<RemoteProcedure Name="SendClientInput" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="true" IsReliable="false" GenerateEventBindings="false" Description="Client to server move / input RPC">
|
||||
<Param Type="Multiplayer::NetworkInputArray" Name="inputArray" />
|
||||
<Param Type="AZ::HashValue32" Name="stateHash" />
|
||||
<Param Type="AzNetworking::PacketEncodingBuffer" Name="clientState" Description="This is for debugging desyncs only; release games should not populate this parameter" />
|
||||
</RemoteProcedure>
|
||||
|
||||
<RemoteProcedure Name="SendClientInputCorrection" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="true" IsReliable="false" GenerateEventBindings="false" Description="Autonomous proxy correction RPC">
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
<Include File="Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h" />
|
||||
<Include File="Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h" />
|
||||
|
||||
<Packet Name="Connect" Desc="Client connection packet, on success the server will reply with an Accept">
|
||||
<Packet Name="Connect" HandshakePacket="true" Desc="Client connection packet, on success the server will reply with an Accept">
|
||||
<Member Type="uint16_t" Name="networkProtocolVersion" Init="0" />
|
||||
<Member Type="Multiplayer::LongNetworkString" Name="ticket" />
|
||||
</Packet>
|
||||
|
||||
<Packet Name="Accept" Desc="Server accept packet">
|
||||
<Packet Name="Accept" HandshakePacket="true" Desc="Server accept packet">
|
||||
<Member Type="Multiplayer::HostId" Name="hostId" Init="Multiplayer::InvalidHostId" />
|
||||
<Member Type="Multiplayer::LongNetworkString" Name="map" />
|
||||
</Packet>
|
||||
|
||||
@@ -21,28 +21,53 @@ namespace Multiplayer
|
||||
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 AZ_RELEASE_BUILD
|
||||
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");
|
||||
AZ_CVAR(bool, cl_EnableDesyncDebugging, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, debug logs will contain verbose information on detected state desyncs");
|
||||
AZ_CVAR(bool, cl_EnableDesyncDebugging, true, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, debug logs will contain verbose information on detected state desyncs");
|
||||
AZ_CVAR(uint32_t, cl_PredictiveStateHistorySize, 120, nullptr, AZ::ConsoleFunctorFlags::Null, "Controls how many inputs of predictive state should be retained for debugging desyncs");
|
||||
#endif
|
||||
|
||||
AZ_CVAR(bool, sv_ForceCorrections, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, the server will force a correction for every input received for debugging");
|
||||
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)
|
||||
void PrintCorrectionDifferences(const AzNetworking::StringifySerializer& client, const AzNetworking::StringifySerializer& server)
|
||||
{
|
||||
AzNetworking::StringifySerializer serializer(',', false);
|
||||
input.Serialize(serializer);
|
||||
return serializer.GetString();
|
||||
const auto& clientMap = client.GetValueMap();
|
||||
const auto& serverMap = server.GetValueMap();
|
||||
|
||||
AzNetworking::StringifySerializer::ValueMap differences = clientMap;
|
||||
for (auto iter = server.GetValueMap().begin(); iter != server.GetValueMap().end(); ++iter)
|
||||
{
|
||||
if (iter->second == differences[iter->first])
|
||||
{
|
||||
differences.erase(iter->first);
|
||||
}
|
||||
}
|
||||
|
||||
if (differences.empty())
|
||||
{
|
||||
AZLOG_ERROR("The hash mismatched, but no differences were found.")
|
||||
}
|
||||
|
||||
for (auto iter = differences.begin(); iter != differences.end(); ++iter)
|
||||
{
|
||||
auto clientValueIter = clientMap.find(iter->first);
|
||||
auto serverValueIter = serverMap.find(iter->first);
|
||||
if (clientValueIter == clientMap.end() || serverValueIter == serverMap.end())
|
||||
{
|
||||
AZLOG_ERROR(" %s (Not found in server and/or client value map!)", iter->first.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
AZLOG_ERROR(" %s Server=%s Client=%s", iter->first.c_str(), serverValueIter->second.c_str(), clientValueIter->second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::string GetCorrectionDataString(NetBindComponent* netBindComponent)
|
||||
inline double ConvertTimeMsToSeconds(AZ::TimeMs value)
|
||||
{
|
||||
AzNetworking::StringifySerializer serializer(',', false);
|
||||
netBindComponent->SerializeEntityCorrection(serializer);
|
||||
return serializer.GetString();
|
||||
return static_cast<double>(static_cast<AZ::TimeMs>(value)) / 1000.0;
|
||||
}
|
||||
|
||||
void LocalPredictionPlayerInputComponent::LocalPredictionPlayerInputComponent::Reflect(AZ::ReflectContext* context)
|
||||
@@ -112,8 +137,7 @@ namespace Multiplayer
|
||||
(
|
||||
AzNetworking::IConnection* invokingConnection,
|
||||
const Multiplayer::NetworkInputArray& inputArray,
|
||||
const AZ::HashValue32& stateHash,
|
||||
[[maybe_unused]] const AzNetworking::PacketEncodingBuffer& clientState
|
||||
const AZ::HashValue32& stateHash
|
||||
)
|
||||
{
|
||||
if (invokingConnection == nullptr)
|
||||
@@ -137,7 +161,7 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
|
||||
const double clientInputRateSec = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
|
||||
const double clientInputRateSec = ConvertTimeMsToSeconds(cl_InputRateMs);
|
||||
m_lastInputReceivedTimeMs = currentTimeMs;
|
||||
|
||||
// Keep track of last inputs received, also allows us to update frame ids
|
||||
@@ -162,8 +186,8 @@ namespace Multiplayer
|
||||
if (m_clientBankedTime < sv_MaxBankTimeWindowSec)
|
||||
{
|
||||
// Client blends from previous frame to target so here we subtract blend factor to get to that state
|
||||
const float blendFactor = AZStd::min(AZStd::max(0.f, input.GetHostBlendFactor()), 1.f);
|
||||
const AZ::TimeMs blendMs = AZ::TimeMs(static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) * (1.f - blendFactor));
|
||||
const float blendFactor = AZStd::min(AZStd::max(0.f, input.GetHostBlendFactor()), 1.0f);
|
||||
const AZ::TimeMs blendMs = AZ::TimeMs(static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) * (1.0f - blendFactor));
|
||||
m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary
|
||||
{
|
||||
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs() - blendMs, input.GetHostBlendFactor(), invokingConnection->GetConnectionId());
|
||||
@@ -185,7 +209,7 @@ namespace Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
if (sv_EnableCorrections && (currentTimeMs - m_lastCorrectionSentTimeMs > sv_MinCorrectionTimeMs))
|
||||
if (sv_ForceCorrections || (sv_EnableCorrections && (currentTimeMs - m_lastCorrectionSentTimeMs > sv_MinCorrectionTimeMs)))
|
||||
{
|
||||
m_lastCorrectionSentTimeMs = currentTimeMs;
|
||||
|
||||
@@ -219,69 +243,6 @@ namespace Multiplayer
|
||||
|
||||
// Send correction
|
||||
SendClientInputCorrection(GetLastInputId(), correction);
|
||||
|
||||
#ifndef AZ_RELEASE_BUILD
|
||||
AZStd::string clientStateString;
|
||||
AZStd::string serverStateString;
|
||||
if (cl_EnableDesyncDebugging)
|
||||
{
|
||||
// In debug, show which states caused the correction
|
||||
// Write in client state
|
||||
AzNetworking::NetworkOutputSerializer clientStateSerializer(clientState.GetBuffer(), static_cast<uint32_t>(clientState.GetSize()));
|
||||
GetNetBindComponent()->SerializeEntityCorrection(clientStateSerializer);
|
||||
|
||||
// Read out state values
|
||||
AzNetworking::StringifySerializer clientValues;
|
||||
GetNetBindComponent()->SerializeEntityCorrection(clientValues);
|
||||
|
||||
// Restore server state
|
||||
AzNetworking::NetworkOutputSerializer serverStateSerializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetSize()));
|
||||
GetNetBindComponent()->SerializeEntityCorrection(serverStateSerializer);
|
||||
|
||||
// Read out state values
|
||||
AzNetworking::StringifySerializer serverValues;
|
||||
GetNetBindComponent()->SerializeEntityCorrection(serverValues);
|
||||
|
||||
AZStd::map<AZStd::string, AZStd::pair<AZStd::string, AZStd::string>> mapComparison;
|
||||
|
||||
// put the server value in the first part of the pair
|
||||
for (const auto& pair : serverValues.GetValueMap())
|
||||
{
|
||||
mapComparison[pair.first].first = pair.second;
|
||||
}
|
||||
|
||||
// put the client value in the second part of the pair
|
||||
for (const auto& pair : clientValues.GetValueMap())
|
||||
{
|
||||
mapComparison[pair.first].second = pair.second;
|
||||
}
|
||||
|
||||
bool firstIt = true;
|
||||
for (const auto& mapPair : mapComparison)
|
||||
{
|
||||
if (mapPair.second.first != mapPair.second.second)
|
||||
{
|
||||
if (!firstIt)
|
||||
{
|
||||
clientStateString += ",";
|
||||
serverStateString += ",";
|
||||
}
|
||||
firstIt = false;
|
||||
|
||||
AZStd::string clientValue = mapPair.second.second.empty() ? "<no value>" : mapPair.second.second;
|
||||
AZStd::string serverValue = mapPair.second.first.empty() ? "<no value>" : mapPair.second.first;
|
||||
clientStateString += mapPair.first + "=" + clientValue;
|
||||
serverStateString += mapPair.first + "=" + serverValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clientStateString = "available in debug only";
|
||||
serverStateString = "available in debug only";
|
||||
}
|
||||
AZLOG_ERROR("** Autonomous proxy desync detected! ** clientState=[%s], serverState=[%s]", clientStateString.c_str(), serverStateString.c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +269,7 @@ namespace Multiplayer
|
||||
return;
|
||||
}
|
||||
|
||||
const float clientInputRateSec = static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
|
||||
const double clientInputRateSec = ConvertTimeMsToSeconds(cl_InputRateMs);
|
||||
|
||||
// Copy array so we can modify input ids
|
||||
NetworkInputMigrationVector inputArrayCopy = inputArray;
|
||||
@@ -321,16 +282,9 @@ namespace Multiplayer
|
||||
input.SetClientInputId(GetLastInputId());
|
||||
|
||||
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), input.GetHostBlendFactor(), invokingConnection->GetConnectionId());
|
||||
GetNetBindComponent()->ProcessInput(input, clientInputRateSec);
|
||||
GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
|
||||
|
||||
AZLOG
|
||||
(
|
||||
NET_Prediction,
|
||||
"Migrated InputId=%d - i=[%s] o=[%s]",
|
||||
aznumeric_cast<int32_t>(input.GetClientInputId()),
|
||||
GetInputString(input).c_str(),
|
||||
GetCorrectionDataString(GetNetBindComponent()).c_str()
|
||||
);
|
||||
AZLOG(NET_Prediction, "Migrated InputId=%d", aznumeric_cast<int32_t>(input.GetClientInputId()));
|
||||
|
||||
// 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
|
||||
@@ -340,7 +294,7 @@ namespace Multiplayer
|
||||
|
||||
void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection
|
||||
(
|
||||
AzNetworking::IConnection* invokingConnection,
|
||||
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
|
||||
const Multiplayer::ClientInputId& inputId,
|
||||
const AzNetworking::PacketEncodingBuffer& correction
|
||||
)
|
||||
@@ -363,15 +317,26 @@ namespace Multiplayer
|
||||
// Apply the correction
|
||||
AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> serializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetSize()));
|
||||
GetNetBindComponent()->SerializeEntityCorrection(serializer);
|
||||
m_correctionEvent.Signal();
|
||||
GetNetBindComponent()->NotifyCorrection();
|
||||
|
||||
AZLOG
|
||||
(
|
||||
NET_Prediction,
|
||||
"Corrected InputId=%d - o=[%s]",
|
||||
aznumeric_cast<int32_t>(m_lastCorrectionInputId),
|
||||
GetCorrectionDataString(GetNetBindComponent()).c_str()
|
||||
);
|
||||
#ifndef AZ_RELEASE_BUILD
|
||||
if (cl_EnableDesyncDebugging)
|
||||
{
|
||||
AZLOG_INFO("** Autonomous Desync - Corrected clientInputId=%d ", aznumeric_cast<int32_t>(inputId));
|
||||
auto iter = m_predictiveStateHistory.find(inputId);
|
||||
if (iter != m_predictiveStateHistory.end())
|
||||
{
|
||||
// Read out state values
|
||||
AzNetworking::StringifySerializer serverValues;
|
||||
GetNetBindComponent()->SerializeEntityCorrection(serverValues);
|
||||
PrintCorrectionDifferences(*iter->second, serverValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZLOG_INFO("Received correction that is too old to diff, increase cl_PredictiveStateHistorySize");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint32_t inputHistorySize = static_cast<uint32_t>(m_inputHistory.Size());
|
||||
const uint32_t historicalDelta = aznumeric_cast<uint32_t>(m_clientInputId - inputId); // Do not replay the move we just corrected, that was already processed by the server
|
||||
@@ -379,46 +344,18 @@ namespace Multiplayer
|
||||
// If this correction is for a move outside our input history window, just start replaying from the oldest move we have available
|
||||
const uint32_t startReplayIndex = (inputHistorySize > historicalDelta) ? (inputHistorySize - historicalDelta) : 0;
|
||||
|
||||
// Flag that we are replaying inputs
|
||||
struct ScopedReplayingInput
|
||||
{
|
||||
ScopedReplayingInput(LocalPredictionPlayerInputComponentController* instance)
|
||||
: m_instance(instance)
|
||||
{
|
||||
m_instance->m_replayingInput = true;
|
||||
}
|
||||
~ScopedReplayingInput()
|
||||
{
|
||||
m_instance->m_replayingInput = false;
|
||||
}
|
||||
LocalPredictionPlayerInputComponentController* m_instance;
|
||||
};
|
||||
ScopedReplayingInput markReplayingInput(this);
|
||||
|
||||
const float clientInputRateSec = static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
|
||||
const double clientInputRateSec = ConvertTimeMsToSeconds(cl_InputRateMs);
|
||||
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(), input.GetHostBlendFactor(), invokingConnection->GetConnectionId());
|
||||
GetNetBindComponent()->ProcessInput(input, clientInputRateSec);
|
||||
GetNetBindComponent()->ReprocessInput(input, static_cast<float>(clientInputRateSec));
|
||||
|
||||
AZLOG
|
||||
(
|
||||
NET_Prediction,
|
||||
"Replayed InputId=%d - i=[%s] o=[%s]",
|
||||
aznumeric_cast<int32_t>(input.GetClientInputId()),
|
||||
GetInputString(input).c_str(),
|
||||
GetCorrectionDataString(GetNetBindComponent()).c_str()
|
||||
);
|
||||
AZLOG(NET_Prediction, "Replayed InputId=%d", aznumeric_cast<int32_t>(input.GetClientInputId()));
|
||||
}
|
||||
}
|
||||
|
||||
bool LocalPredictionPlayerInputComponentController::IsReplayingInput() const
|
||||
{
|
||||
return m_replayingInput;
|
||||
}
|
||||
|
||||
bool LocalPredictionPlayerInputComponentController::IsMigrating() const
|
||||
{
|
||||
return m_lastMigratedInputId != ClientInputId{ 0 };
|
||||
@@ -438,11 +375,6 @@ namespace Multiplayer
|
||||
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;
|
||||
@@ -483,9 +415,9 @@ namespace Multiplayer
|
||||
|
||||
void LocalPredictionPlayerInputComponentController::UpdateAutonomous(AZ::TimeMs deltaTimeMs)
|
||||
{
|
||||
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
|
||||
const double inputRate = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
|
||||
const double maxRewindHistory = static_cast<double>(static_cast<AZ::TimeMs>(cl_MaxRewindHistoryMs)) / 1000.0;
|
||||
const double deltaTime = ConvertTimeMsToSeconds(deltaTimeMs);
|
||||
const double clientInputRateSec = ConvertTimeMsToSeconds(cl_InputRateMs);
|
||||
const double maxRewindHistory = ConvertTimeMsToSeconds(cl_MaxRewindHistoryMs);
|
||||
|
||||
#ifndef AZ_RELEASE_BUILD
|
||||
m_moveAccumulator += deltaTime * cl_DebugHackTimeMultiplier;
|
||||
@@ -493,13 +425,13 @@ namespace Multiplayer
|
||||
m_moveAccumulator += deltaTime;
|
||||
#endif
|
||||
|
||||
const uint32_t maxClientInputs = inputRate > 0.0 ? static_cast<uint32_t>(maxRewindHistory / inputRate) : 0;
|
||||
const uint32_t maxClientInputs = clientInputRateSec > 0.0 ? static_cast<uint32_t>(maxRewindHistory / clientInputRateSec) : 0;
|
||||
|
||||
IMultiplayer* multiplayer = GetMultiplayer();
|
||||
INetworkTime* networkTime = GetNetworkTime();
|
||||
while (m_moveAccumulator >= inputRate)
|
||||
while (m_moveAccumulator >= clientInputRateSec)
|
||||
{
|
||||
m_moveAccumulator -= inputRate;
|
||||
m_moveAccumulator -= clientInputRateSec;
|
||||
++m_clientInputId;
|
||||
|
||||
NetworkInputArray inputArray(GetEntityHandle());
|
||||
@@ -511,35 +443,17 @@ namespace Multiplayer
|
||||
input.SetHostBlendFactor(multiplayer->GetCurrentBlendFactor());
|
||||
|
||||
// Allow components to form the input for this frame
|
||||
GetNetBindComponent()->CreateInput(input, inputRate);
|
||||
GetNetBindComponent()->CreateInput(input, static_cast<float>(clientInputRateSec));
|
||||
|
||||
// Process the input for this frame
|
||||
GetNetBindComponent()->ProcessInput(input, inputRate);
|
||||
GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
|
||||
|
||||
AZLOG
|
||||
(
|
||||
NET_Prediction,
|
||||
"Processed InutId=%d - i=[%s] o=[%s]",
|
||||
aznumeric_cast<int32_t>(m_clientInputId),
|
||||
GetInputString(input).c_str(),
|
||||
GetCorrectionDataString(GetNetBindComponent()).c_str()
|
||||
);
|
||||
AZLOG(NET_Prediction, "Processed InputId=%d", aznumeric_cast<int32_t>(m_clientInputId));
|
||||
|
||||
// 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;
|
||||
#ifndef AZ_RELEASE_BUILD
|
||||
if (cl_EnableDesyncDebugging)
|
||||
{
|
||||
AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), static_cast<uint32_t>(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)
|
||||
@@ -555,13 +469,26 @@ namespace Multiplayer
|
||||
{
|
||||
// Clamp to oldest element if history is too small
|
||||
const int64_t historyIndex = AZStd::max<int64_t>(inputHistorySize - 1 - i, 0);
|
||||
inputArray[i] = m_inputHistory[historyIndex];
|
||||
inputArray[static_cast<uint32_t>(i)] = m_inputHistory[historyIndex];
|
||||
}
|
||||
|
||||
#ifndef AZ_RELEASE_BUILD
|
||||
if (cl_EnableDesyncDebugging)
|
||||
{
|
||||
StateHistoryItem inputHistory = AZStd::make_unique<AzNetworking::StringifySerializer>();
|
||||
while (m_predictiveStateHistory.size() > cl_PredictiveStateHistorySize)
|
||||
{
|
||||
m_predictiveStateHistory.erase(m_predictiveStateHistory.begin());
|
||||
}
|
||||
GetNetBindComponent()->SerializeEntityCorrection(*inputHistory);
|
||||
m_predictiveStateHistory.emplace(m_clientInputId, AZStd::move(inputHistory));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Send the input to server (only when we are not migrating)
|
||||
if (!IsMigrating())
|
||||
{
|
||||
SendClientInput(inputArray, hashSerializer.GetHash(), processInputResult);
|
||||
SendClientInput(inputArray, hashSerializer.GetHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,8 +496,7 @@ namespace Multiplayer
|
||||
void LocalPredictionPlayerInputComponentController::UpdateBankedTime(AZ::TimeMs deltaTimeMs)
|
||||
{
|
||||
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
|
||||
const double inputRate = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
|
||||
const double maxRewindHistory = static_cast<double>(static_cast<AZ::TimeMs>(cl_MaxRewindHistoryMs)) / 1000.0;
|
||||
const double clientInputRateSec = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
|
||||
|
||||
// Update banked time accumulator
|
||||
m_clientBankedTime -= deltaTime;
|
||||
@@ -583,18 +509,11 @@ namespace Multiplayer
|
||||
|
||||
NetworkInput& input = m_lastInputReceived[0];
|
||||
{
|
||||
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), DefaultBlendFactor, AzNetworking::InvalidConnectionId);
|
||||
GetNetBindComponent()->ProcessInput(input, inputRate);
|
||||
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), DefaultBlendFactor, GetNetBindComponent()->GetOwningConnectionId());
|
||||
GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
|
||||
}
|
||||
|
||||
AZLOG
|
||||
(
|
||||
NET_Prediction,
|
||||
"Forced InputId=%d - i=[%s] o=[%s]",
|
||||
aznumeric_cast<int32_t>(input.GetClientInputId()),
|
||||
GetInputString(input).c_str(),
|
||||
GetCorrectionDataString(GetNetBindComponent()).c_str()
|
||||
);
|
||||
AZLOG(NET_Prediction, "Forced InputId=%d", aznumeric_cast<int32_t>(input.GetClientInputId()));
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -267,10 +267,15 @@ namespace Multiplayer
|
||||
return m_isProcessingInput;
|
||||
}
|
||||
|
||||
bool NetBindComponent::IsReprocessingInput() const
|
||||
{
|
||||
return m_isReprocessingInput;
|
||||
}
|
||||
|
||||
void NetBindComponent::CreateInput(NetworkInput& networkInput, float deltaTime)
|
||||
{
|
||||
// Only autonomous or authority runs this logic
|
||||
AZ_Assert(m_netEntityRole == NetEntityRole::Autonomous || m_netEntityRole == NetEntityRole::Authority, "Incorrect network role for input creation");
|
||||
// Only autonomous runs this logic
|
||||
AZ_Assert(IsNetEntityRoleAutonomous(), "Incorrect network role for input creation");
|
||||
for (MultiplayerComponent* multiplayerComponent : m_multiplayerInputComponentVector)
|
||||
{
|
||||
multiplayerComponent->GetController()->CreateInput(networkInput, deltaTime);
|
||||
@@ -279,12 +284,21 @@ namespace Multiplayer
|
||||
|
||||
void NetBindComponent::ProcessInput(NetworkInput& networkInput, float deltaTime)
|
||||
{
|
||||
m_isProcessingInput = true;
|
||||
// Only autonomous and authority runs this logic
|
||||
AZ_Assert((NetworkRoleHasController(m_netEntityRole)), "Incorrect network role for input processing");
|
||||
for (MultiplayerComponent* multiplayerComponent : m_multiplayerInputComponentVector)
|
||||
{
|
||||
multiplayerComponent->GetController()->ProcessInput(networkInput, deltaTime);
|
||||
}
|
||||
m_isProcessingInput = false;
|
||||
}
|
||||
|
||||
void NetBindComponent::ReprocessInput(NetworkInput& networkInput, float deltaTime)
|
||||
{
|
||||
m_isReprocessingInput = true;
|
||||
ProcessInput(networkInput, deltaTime);
|
||||
m_isReprocessingInput = false;
|
||||
}
|
||||
|
||||
bool NetBindComponent::HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole remoteRole, NetworkEntityRpcMessage& message)
|
||||
@@ -394,6 +408,11 @@ namespace Multiplayer
|
||||
m_entityPreRenderEvent.Signal(deltaTime, blendFactor);
|
||||
}
|
||||
|
||||
void NetBindComponent::NotifyCorrection()
|
||||
{
|
||||
m_entityCorrectionEvent.Signal();
|
||||
}
|
||||
|
||||
void NetBindComponent::AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler)
|
||||
{
|
||||
eventHandler.Connect(m_entityStopEvent);
|
||||
@@ -429,6 +448,11 @@ namespace Multiplayer
|
||||
eventHandler.Connect(m_entityPreRenderEvent);
|
||||
}
|
||||
|
||||
void NetBindComponent::AddEntityCorrectionEventHandler(EntityCorrectionEvent::Handler& eventHandler)
|
||||
{
|
||||
eventHandler.Connect(m_entityCorrectionEvent);
|
||||
}
|
||||
|
||||
bool NetBindComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer)
|
||||
{
|
||||
m_predictableRecord.ResetConsumedBits();
|
||||
@@ -447,12 +471,19 @@ namespace Multiplayer
|
||||
|
||||
bool NetBindComponent::SerializeStateDeltaMessage(ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer)
|
||||
{
|
||||
auto& stats = GetMultiplayer()->GetStats();
|
||||
stats.RecordEntitySerializeStart(serializer.GetSerializerMode(), GetEntityId(), GetEntity()->GetName().c_str());
|
||||
|
||||
bool success = true;
|
||||
for (auto iter = m_multiplayerSerializationComponentVector.begin(); iter != m_multiplayerSerializationComponentVector.end(); ++iter)
|
||||
{
|
||||
success &= (*iter)->SerializeStateDeltaMessage(replicationRecord, serializer);
|
||||
|
||||
stats.RecordComponentSerializeEnd(serializer.GetSerializerMode(), (*iter)->GetNetComponentId());
|
||||
}
|
||||
|
||||
stats.RecordEntitySerializeStop(serializer.GetSerializerMode(), GetEntityId(), GetEntity()->GetName().c_str());
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -649,6 +680,7 @@ namespace Multiplayer
|
||||
MultiplayerComponent* multiplayerComponent = azrtti_cast<MultiplayerComponent*>(component);
|
||||
if (multiplayerComponent != nullptr)
|
||||
{
|
||||
multiplayerComponent->SetOwningConnectionId(m_owningConnectionId);
|
||||
m_multiplayerInputComponentVector.push_back(multiplayerComponent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Multiplayer
|
||||
, m_scaleEventHandler([this](float scale) { OnScaleChangedEvent(scale); })
|
||||
, m_resetCountEventHandler([this](const uint8_t&) { OnResetCountChangedEvent(); })
|
||||
, m_entityPreRenderEventHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); })
|
||||
, m_entityCorrectionEventHandler([this]() { OnCorrection(); })
|
||||
{
|
||||
;
|
||||
}
|
||||
@@ -47,6 +48,7 @@ namespace Multiplayer
|
||||
ScaleAddEvent(m_scaleEventHandler);
|
||||
ResetCountAddEvent(m_resetCountEventHandler);
|
||||
GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler);
|
||||
GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler);
|
||||
|
||||
// When coming into relevance, reset all blending factors so we don't interpolate to our start position
|
||||
OnResetCountChangedEvent();
|
||||
@@ -119,6 +121,18 @@ namespace Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTransformComponent::OnCorrection()
|
||||
{
|
||||
// Snap to latest
|
||||
OnResetCountChangedEvent();
|
||||
|
||||
// Hard set the entities transform
|
||||
if (!GetTransformComponent()->GetWorldTM().IsClose(m_targetTransform))
|
||||
{
|
||||
GetTransformComponent()->SetWorldTM(m_targetTransform);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NetworkTransformComponentController::NetworkTransformComponentController(NetworkTransformComponent& parent)
|
||||
: NetworkTransformComponentControllerBase(parent)
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MultiplayerDebugByteReporter.h"
|
||||
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
|
||||
#include <iomanip> // for std::setfill
|
||||
#include <sstream>
|
||||
#include <AzCore/std/sort.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
MultiplayerDebugByteReporter::MultiplayerDebugByteReporter()
|
||||
{
|
||||
MultiplayerDebugByteReporter::Reset();
|
||||
}
|
||||
|
||||
void MultiplayerDebugByteReporter::ReportBytes(size_t byteSize)
|
||||
{
|
||||
m_count++;
|
||||
m_totalBytes += byteSize;
|
||||
m_totalBytesThisSecond += byteSize;
|
||||
m_minBytes = AZStd::min(m_minBytes, byteSize);
|
||||
m_maxBytes = AZStd::max(m_maxBytes, byteSize);
|
||||
}
|
||||
|
||||
void MultiplayerDebugByteReporter::AggregateBytes(size_t byteSize)
|
||||
{
|
||||
m_aggregateBytes += byteSize;
|
||||
}
|
||||
|
||||
void MultiplayerDebugByteReporter::ReportAggregateBytes()
|
||||
{
|
||||
ReportBytes(m_aggregateBytes);
|
||||
m_aggregateBytes = 0;
|
||||
}
|
||||
|
||||
float MultiplayerDebugByteReporter::GetAverageBytes() const
|
||||
{
|
||||
if (m_count == 0)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return aznumeric_cast<float>(m_totalBytes) / aznumeric_cast<float>(m_count);
|
||||
}
|
||||
|
||||
size_t MultiplayerDebugByteReporter::GetMaxBytes() const
|
||||
{
|
||||
return m_maxBytes;
|
||||
}
|
||||
|
||||
size_t MultiplayerDebugByteReporter::GetMinBytes() const
|
||||
{
|
||||
return m_minBytes;
|
||||
}
|
||||
|
||||
size_t MultiplayerDebugByteReporter::GetTotalBytes() const
|
||||
{
|
||||
return m_totalBytes;
|
||||
}
|
||||
|
||||
float MultiplayerDebugByteReporter::GetKbitsPerSecond()
|
||||
{
|
||||
const auto now = AZStd::chrono::monotonic_clock::now();
|
||||
|
||||
// Check the amount of time elapsed and update totals if necessary.
|
||||
// Time here is measured in whole seconds from the epoch, providing synchronization in
|
||||
// reporting intervals across all byte reporters.
|
||||
const AZStd::chrono::seconds nowSeconds = AZStd::chrono::duration_cast<AZStd::chrono::seconds>(now.time_since_epoch());
|
||||
const AZStd::chrono::seconds secondsSinceLastUpdate = nowSeconds -
|
||||
AZStd::chrono::duration_cast<AZStd::chrono::seconds>(m_lastUpdateTime.time_since_epoch());
|
||||
if (secondsSinceLastUpdate.count())
|
||||
{
|
||||
// normalize over elapsed milliseconds
|
||||
constexpr int k_millisecondsPerSecond = 1000;
|
||||
const auto msSinceLastUpdate = AZStd::chrono::duration_cast<AZStd::chrono::milliseconds>(now - m_lastUpdateTime);
|
||||
m_totalBytesLastSecond = k_millisecondsPerSecond * aznumeric_cast<float>(m_totalBytesThisSecond) / aznumeric_cast<float>(msSinceLastUpdate.count());
|
||||
m_totalBytesThisSecond = 0;
|
||||
m_lastUpdateTime = now;
|
||||
}
|
||||
|
||||
constexpr float bitsPerByte = 8.0f;
|
||||
constexpr int bitsPerKilobit = 1024;
|
||||
return bitsPerByte * m_totalBytesLastSecond / bitsPerKilobit;
|
||||
}
|
||||
|
||||
void MultiplayerDebugByteReporter::Combine(const MultiplayerDebugByteReporter& other)
|
||||
{
|
||||
m_count += other.m_count;
|
||||
m_totalBytes += other.m_totalBytes;
|
||||
m_totalBytesThisSecond += other.m_totalBytesThisSecond;
|
||||
m_minBytes = AZStd::GetMin(m_minBytes, other.m_minBytes);
|
||||
m_maxBytes = AZStd::GetMax(m_maxBytes, other.m_maxBytes);
|
||||
}
|
||||
|
||||
void MultiplayerDebugByteReporter::Reset()
|
||||
{
|
||||
m_count = 0;
|
||||
m_totalBytes = 0;
|
||||
m_totalBytesThisSecond = 0;
|
||||
m_totalBytesLastSecond = 0;
|
||||
m_minBytes = std::numeric_limits<decltype(m_minBytes)>::max();
|
||||
m_maxBytes = 0;
|
||||
m_aggregateBytes = 0;
|
||||
}
|
||||
|
||||
void MultiplayerDebugComponentReporter::ReportField(const char* fieldName, size_t byteSize)
|
||||
{
|
||||
MultiplayerDebugByteReporter::AggregateBytes(byteSize);
|
||||
m_fieldReports[fieldName].ReportBytes(byteSize);
|
||||
}
|
||||
|
||||
void MultiplayerDebugComponentReporter::ReportFragmentEnd()
|
||||
{
|
||||
MultiplayerDebugByteReporter::ReportAggregateBytes();
|
||||
m_componentDirtyBytes.ReportAggregateBytes();
|
||||
}
|
||||
|
||||
AZStd::vector<MultiplayerDebugComponentReporter::Report> MultiplayerDebugComponentReporter::GetFieldReports()
|
||||
{
|
||||
AZStd::vector<Report> copy;
|
||||
for (auto field = m_fieldReports.begin(); field != m_fieldReports.end(); ++field)
|
||||
{
|
||||
copy.emplace_back(field->first, &field->second);
|
||||
}
|
||||
|
||||
auto sortByFrequency = [](const Report& a, const Report& b)
|
||||
{
|
||||
return a.second->GetTotalCount() > b.second->GetTotalCount();
|
||||
};
|
||||
|
||||
AZStd::sort(copy.begin(), copy.end(), sortByFrequency);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
void MultiplayerDebugComponentReporter::Combine(const MultiplayerDebugComponentReporter& other)
|
||||
{
|
||||
MultiplayerDebugByteReporter::Combine(other);
|
||||
|
||||
for (const auto& fieldIterator : other.m_fieldReports)
|
||||
{
|
||||
m_fieldReports[fieldIterator.first].Combine(fieldIterator.second);
|
||||
}
|
||||
|
||||
m_componentDirtyBytes.Combine(other.m_componentDirtyBytes);
|
||||
}
|
||||
|
||||
void MultiplayerDebugEntityReporter::ReportField(AZ::u32 index, const char* componentName,
|
||||
const char* fieldName, size_t byteSize)
|
||||
{
|
||||
if (m_currentComponentReport == nullptr)
|
||||
{
|
||||
std::stringstream component;
|
||||
component << "[" << std::setw(2) << std::setfill('0') << aznumeric_cast<int>(index) << "]" << " " << componentName;
|
||||
m_currentComponentReport = &m_componentReports[component.str().c_str()];
|
||||
}
|
||||
|
||||
m_currentComponentReport->ReportField(fieldName, byteSize);
|
||||
MultiplayerDebugByteReporter::AggregateBytes(byteSize);
|
||||
}
|
||||
|
||||
void MultiplayerDebugEntityReporter::ReportFragmentEnd()
|
||||
{
|
||||
if (m_currentComponentReport)
|
||||
{
|
||||
m_currentComponentReport->ReportFragmentEnd();
|
||||
m_currentComponentReport = nullptr;
|
||||
}
|
||||
|
||||
MultiplayerDebugByteReporter::ReportAggregateBytes();
|
||||
}
|
||||
|
||||
void MultiplayerDebugEntityReporter::Combine(const MultiplayerDebugEntityReporter& other)
|
||||
{
|
||||
MultiplayerDebugByteReporter::Combine(other);
|
||||
|
||||
for (const auto& componentIterator : other.m_componentReports)
|
||||
{
|
||||
m_componentReports[componentIterator.first].Combine(componentIterator.second);
|
||||
}
|
||||
|
||||
SetEntityName(other.GetEntityName());
|
||||
}
|
||||
|
||||
void MultiplayerDebugEntityReporter::Reset()
|
||||
{
|
||||
MultiplayerDebugByteReporter::Reset();
|
||||
|
||||
m_componentReports.clear();
|
||||
}
|
||||
|
||||
AZStd::map<AZStd::string, MultiplayerDebugComponentReporter>& MultiplayerDebugEntityReporter::GetComponentReports()
|
||||
{
|
||||
return m_componentReports;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/chrono/chrono.h>
|
||||
#include <AzCore/std/containers/map.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
class MultiplayerDebugByteReporter
|
||||
{
|
||||
public:
|
||||
MultiplayerDebugByteReporter();
|
||||
virtual ~MultiplayerDebugByteReporter() = default;
|
||||
|
||||
void ReportBytes(size_t byteSize);
|
||||
void AggregateBytes(size_t byteSize);
|
||||
void ReportAggregateBytes();
|
||||
|
||||
float GetAverageBytes() const;
|
||||
size_t GetMaxBytes() const;
|
||||
size_t GetMinBytes() const;
|
||||
size_t GetTotalBytes() const;
|
||||
float GetKbitsPerSecond();
|
||||
|
||||
void Combine(const MultiplayerDebugByteReporter& other);
|
||||
virtual void Reset();
|
||||
|
||||
size_t GetTotalCount() const { return m_count; }
|
||||
|
||||
private:
|
||||
size_t m_count;
|
||||
size_t m_totalBytes;
|
||||
size_t m_totalBytesThisSecond;
|
||||
float m_totalBytesLastSecond;
|
||||
size_t m_minBytes;
|
||||
size_t m_maxBytes;
|
||||
size_t m_aggregateBytes;
|
||||
|
||||
AZStd::chrono::monotonic_clock::time_point m_lastUpdateTime;
|
||||
};
|
||||
|
||||
class MultiplayerDebugComponentReporter final
|
||||
: public MultiplayerDebugByteReporter
|
||||
{
|
||||
public:
|
||||
MultiplayerDebugComponentReporter() = default;
|
||||
|
||||
void ReportField(const char* fieldName, size_t byteSize);
|
||||
void ReportFragmentEnd();
|
||||
|
||||
using Report = AZStd::pair<AZStd::string, MultiplayerDebugByteReporter*>;
|
||||
AZStd::vector<Report> GetFieldReports();
|
||||
|
||||
void Combine(const MultiplayerDebugComponentReporter& other);
|
||||
|
||||
private:
|
||||
AZStd::map<AZStd::string, MultiplayerDebugByteReporter> m_fieldReports;
|
||||
MultiplayerDebugByteReporter m_componentDirtyBytes;
|
||||
};
|
||||
|
||||
class MultiplayerDebugEntityReporter final
|
||||
: public MultiplayerDebugByteReporter
|
||||
{
|
||||
public:
|
||||
MultiplayerDebugEntityReporter() = default;
|
||||
|
||||
void ReportField(AZ::u32 index, const char* componentName, const char* fieldName, size_t byteSize);
|
||||
void ReportFragmentEnd();
|
||||
|
||||
void Combine(const MultiplayerDebugEntityReporter& other);
|
||||
void Reset() override;
|
||||
|
||||
const char* GetEntityName() const { return m_entityName.c_str(); }
|
||||
void SetEntityName(const char* entityName)
|
||||
{
|
||||
// copying because the entity might go away
|
||||
m_entityName = entityName;
|
||||
}
|
||||
|
||||
AZStd::map<AZStd::string, MultiplayerDebugComponentReporter>& GetComponentReports();
|
||||
|
||||
private:
|
||||
MultiplayerDebugComponentReporter* m_currentComponentReport = nullptr;
|
||||
AZStd::map<AZStd::string, MultiplayerDebugComponentReporter> m_componentReports;
|
||||
AZStd::string m_entityName;
|
||||
};
|
||||
}
|
||||
@@ -28,4 +28,4 @@ namespace Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
AZ_DECLARE_MODULE_CLASS(Gem_Multiplayer_Imgui, Multiplayer::MultiplayerDebugModule);
|
||||
AZ_DECLARE_MODULE_CLASS(Gem_Multiplayer_Debug, Multiplayer::MultiplayerDebugModule);
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MultiplayerDebugPerEntityReporter.h"
|
||||
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/Math/ToString.h>
|
||||
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
|
||||
#if defined(IMGUI_ENABLED)
|
||||
#include <imgui/imgui.h>
|
||||
#endif
|
||||
|
||||
AZ_CVAR(float, net_DebugEntities_ShowAboveKbps, 1.f, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"Prints bandwidth on network entities with higher kpbs than this value");
|
||||
|
||||
AZ_CVAR(float, net_DebugEntities_WarnAboveKbps, 10.f, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"Prints bandwidth on network entities with higher kpbs than this value");
|
||||
|
||||
AZ_CVAR(AZ::Color, net_DebugEntities_WarningColor, AZ::Colors::Red, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"If true, prints debug text over entities that use a considerable amount of network traffic");
|
||||
|
||||
AZ_CVAR(AZ::Color, net_DebugEntities_BelowWarningColor, AZ::Colors::Grey, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"If true, prints debug text over entities that use a considerable amount of network traffic");
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
#if defined(IMGUI_ENABLED)
|
||||
static const ImVec4 k_ImGuiTomato = ImVec4(1.0f, 0.4f, 0.3f, 1.0f);
|
||||
static const ImVec4 k_ImGuiKhaki = ImVec4(0.9f, 0.8f, 0.5f, 1.0f);
|
||||
static const ImVec4 k_ImGuiCyan = ImVec4(0.5f, 1.0f, 1.0f, 1.0f);
|
||||
static const ImVec4 k_ImGuiDusk = ImVec4(0.7f, 0.7f, 1.0f, 1.0f);
|
||||
static const ImVec4 k_ImGuiWhite = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
template <typename Reporter>
|
||||
bool ReplicatedStateTreeNode(const AZStd::string& name, Reporter& report, const ImVec4& color, int depth = 0)
|
||||
{
|
||||
const int defaultPadAmount = 55;
|
||||
const int depthReduction = 3;
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
|
||||
const bool expanded = ImGui::TreeNode(name.c_str(),
|
||||
"%-*s %7.2f kbps %7.2f B Avg. %4zu B Max %10zu B Payload",
|
||||
defaultPadAmount - depthReduction * depth,
|
||||
name.c_str(),
|
||||
report.GetKbitsPerSecond(),
|
||||
report.GetAverageBytes(),
|
||||
report.GetMaxBytes(),
|
||||
report.GetTotalBytes());
|
||||
ImGui::PopStyleColor();
|
||||
return expanded;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
void DisplayReplicatedStateReport(AZStd::map<AZStd::string, MultiplayerDebugComponentReporter>& componentReports, float kbpsWarn, float maxWarn)
|
||||
{
|
||||
for (auto& componentPair : componentReports)
|
||||
{
|
||||
ImGui::Separator();
|
||||
MultiplayerDebugComponentReporter& componentReport = componentPair.second;
|
||||
|
||||
if (ReplicatedStateTreeNode(componentPair.first, componentReport, k_ImGuiCyan, 1))
|
||||
{
|
||||
ImGui::Separator();
|
||||
ImGui::Columns(6, "replicated_field_columns");
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("kbps");
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Avg. Bytes");
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Min Bytes");
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Max Bytes");
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Total Bytes");
|
||||
ImGui::NextColumn();
|
||||
|
||||
auto fieldReports = componentReport.GetFieldReports();
|
||||
for (auto& fieldPair : fieldReports)
|
||||
{
|
||||
MultiplayerDebugByteReporter& fieldReport = *fieldPair.second;
|
||||
const float kbitsLastSecond = fieldReport.GetKbitsPerSecond();
|
||||
|
||||
const ImVec4* textColor = &k_ImGuiWhite;
|
||||
if (aznumeric_cast<float>(fieldReport.GetMaxBytes()) > maxWarn)
|
||||
{
|
||||
textColor = &k_ImGuiKhaki;
|
||||
}
|
||||
|
||||
if (kbitsLastSecond > kbpsWarn)
|
||||
{
|
||||
textColor = &k_ImGuiTomato;
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, *textColor);
|
||||
|
||||
ImGui::Text("%s", fieldPair.first.c_str());
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%.2f", kbitsLastSecond);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%.2f", fieldReport.GetAverageBytes());
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%zu", fieldReport.GetMinBytes());
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%zu", fieldReport.GetMaxBytes());
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%zu", fieldReport.GetTotalBytes());
|
||||
ImGui::NextColumn();
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::Columns(1);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
MultiplayerDebugPerEntityReporter::MultiplayerDebugPerEntityReporter()
|
||||
: m_updateDebugOverlay([this]() { UpdateDebugOverlay(); }, AZ::Name("UpdateDebugPerEntityOverlay"))
|
||||
{
|
||||
m_updateDebugOverlay.Enqueue(AZ::TimeMs{ 0 }, true);
|
||||
|
||||
m_eventHandlers.m_entitySerializeStart = decltype(m_eventHandlers.m_entitySerializeStart)([this](AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName)
|
||||
{
|
||||
RecordEntitySerializeStart(mode, entityId, entityName);
|
||||
});
|
||||
m_eventHandlers.m_componentSerializeEnd = decltype(m_eventHandlers.m_componentSerializeEnd)([this](AzNetworking::SerializerMode mode,
|
||||
NetComponentId netComponentId)
|
||||
{
|
||||
RecordComponentSerializeEnd(mode, netComponentId);
|
||||
});
|
||||
m_eventHandlers.m_entitySerializeStop = decltype(m_eventHandlers.m_entitySerializeStop)([this](AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName)
|
||||
{
|
||||
RecordEntitySerializeStop(mode, entityId, entityName);
|
||||
});
|
||||
m_eventHandlers.m_propertySent = decltype(m_eventHandlers.m_propertySent)([this](NetComponentId netComponentId,
|
||||
PropertyIndex propertyId, uint32_t totalBytes)
|
||||
{
|
||||
RecordPropertySent(netComponentId, propertyId, totalBytes);
|
||||
});
|
||||
m_eventHandlers.m_propertyReceived = decltype(m_eventHandlers.m_propertyReceived)([this](NetComponentId netComponentId,
|
||||
PropertyIndex propertyId, uint32_t totalBytes)
|
||||
{
|
||||
RecordPropertyReceived(netComponentId, propertyId, totalBytes);
|
||||
});
|
||||
m_eventHandlers.m_rpcSent = decltype(m_eventHandlers.m_rpcSent)([this](AZ::EntityId entityId, const char* entityName,
|
||||
NetComponentId netComponentId,
|
||||
RpcIndex rpcId, uint32_t totalBytes)
|
||||
{
|
||||
RecordRpcSent(entityId, entityName, netComponentId, rpcId, totalBytes);
|
||||
});
|
||||
m_eventHandlers.m_rpcReceived = decltype(m_eventHandlers.m_rpcReceived)([this](AZ::EntityId entityId, const char* entityName,
|
||||
NetComponentId netComponentId,
|
||||
RpcIndex rpcId, uint32_t totalBytes)
|
||||
{
|
||||
RecordRpcSent(entityId, entityName, netComponentId, rpcId, totalBytes);
|
||||
});
|
||||
|
||||
GetMultiplayer()->GetStats().ConnectHandlers(m_eventHandlers);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
void MultiplayerDebugPerEntityReporter::OnImGuiUpdate()
|
||||
{
|
||||
#if defined(IMGUI_ENABLED)
|
||||
static ImGuiTextFilter filter;
|
||||
filter.Draw();
|
||||
|
||||
if (ImGui::CollapsingHeader("Receiving Entities"))
|
||||
{
|
||||
for (AZStd::pair<AZ::EntityId, MultiplayerDebugEntityReporter>& entityPair : m_receivingEntityReports)
|
||||
{
|
||||
if (!filter.PassFilter(entityPair.second.GetEntityName()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (ReplicatedStateTreeNode(entityPair.second.GetEntityName(), entityPair.second, k_ImGuiDusk))
|
||||
{
|
||||
DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Sending Entities"))
|
||||
{
|
||||
for (AZStd::pair<AZ::EntityId, MultiplayerDebugEntityReporter>& entityPair : m_sendingEntityReports)
|
||||
{
|
||||
const char* name = entityPair.second.GetEntityName();
|
||||
if (!filter.PassFilter(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (ReplicatedStateTreeNode(name, entityPair.second, k_ImGuiDusk))
|
||||
{
|
||||
DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStart(AzNetworking::SerializerMode mode,
|
||||
[[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const char* entityName)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AzNetworking::SerializerMode::ReadFromObject:
|
||||
m_currentSendingEntityReport.Reset();
|
||||
m_currentSendingEntityReport.SetEntityName(entityName);
|
||||
break;
|
||||
case AzNetworking::SerializerMode::WriteToObject:
|
||||
m_currentReceivingEntityReport.Reset();
|
||||
m_currentReceivingEntityReport.SetEntityName(entityName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerDebugPerEntityReporter::RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, [[maybe_unused]] NetComponentId
|
||||
netComponentId)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AzNetworking::SerializerMode::ReadFromObject:
|
||||
m_currentSendingEntityReport.ReportFragmentEnd();
|
||||
break;
|
||||
case AzNetworking::SerializerMode::WriteToObject:
|
||||
m_currentReceivingEntityReport.ReportFragmentEnd();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStop(AzNetworking::SerializerMode mode,
|
||||
[[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const char* entityName)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AzNetworking::SerializerMode::ReadFromObject:
|
||||
m_sendingEntityReports[entityId].Combine(m_currentSendingEntityReport);
|
||||
break;
|
||||
case AzNetworking::SerializerMode::WriteToObject:
|
||||
m_receivingEntityReports[entityId].Combine(m_currentReceivingEntityReport);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerDebugPerEntityReporter::RecordPropertySent(
|
||||
NetComponentId netComponentId,
|
||||
PropertyIndex propertyId,
|
||||
uint32_t totalBytes)
|
||||
{
|
||||
if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry())
|
||||
{
|
||||
m_currentSendingEntityReport.ReportField(static_cast<AZ::u32>(netComponentId),
|
||||
componentRegistry->GetComponentName(netComponentId),
|
||||
componentRegistry->GetComponentPropertyName(netComponentId, propertyId), totalBytes);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerDebugPerEntityReporter::RecordPropertyReceived(
|
||||
NetComponentId netComponentId,
|
||||
PropertyIndex propertyId,
|
||||
uint32_t totalBytes)
|
||||
{
|
||||
if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry())
|
||||
{
|
||||
m_currentReceivingEntityReport.ReportField(static_cast<AZ::u32>(netComponentId),
|
||||
componentRegistry->GetComponentName(netComponentId),
|
||||
componentRegistry->GetComponentPropertyName(netComponentId, propertyId), totalBytes);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerDebugPerEntityReporter::RecordRpcSent(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId,
|
||||
RpcIndex rpcId, uint32_t totalBytes)
|
||||
{
|
||||
if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry())
|
||||
{
|
||||
// MultiplayerDebugByteReporter requires a
|
||||
RecordEntitySerializeStart(AzNetworking::SerializerMode::ReadFromObject, entityId, entityName);
|
||||
|
||||
m_currentSendingEntityReport.ReportField(static_cast<AZ::u32>(netComponentId),
|
||||
componentRegistry->GetComponentName(netComponentId),
|
||||
componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes);
|
||||
|
||||
RecordComponentSerializeEnd(AzNetworking::SerializerMode::ReadFromObject, netComponentId);
|
||||
RecordEntitySerializeStop(AzNetworking::SerializerMode::ReadFromObject, entityId, entityName);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerDebugPerEntityReporter::RecordRpcReceived(
|
||||
AZ::EntityId entityId, const char* entityName,
|
||||
NetComponentId netComponentId,
|
||||
RpcIndex rpcId,
|
||||
uint32_t totalBytes)
|
||||
{
|
||||
if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry())
|
||||
{
|
||||
RecordEntitySerializeStart(AzNetworking::SerializerMode::WriteToObject, entityId, entityName);
|
||||
|
||||
m_currentReceivingEntityReport.ReportField(static_cast<AZ::u32>(netComponentId),
|
||||
componentRegistry->GetComponentName(netComponentId),
|
||||
componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes);
|
||||
|
||||
RecordComponentSerializeEnd(AzNetworking::SerializerMode::WriteToObject, netComponentId);
|
||||
RecordEntitySerializeStop(AzNetworking::SerializerMode::WriteToObject, entityId, entityName);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerDebugPerEntityReporter::UpdateDebugOverlay()
|
||||
{
|
||||
m_networkEntitiesTraffic.clear();
|
||||
|
||||
// Merging up and down traffic to provide a unified debug text per entity
|
||||
for (AZStd::pair<AZ::EntityId, MultiplayerDebugEntityReporter>& entityPair : m_receivingEntityReports)
|
||||
{
|
||||
m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName();
|
||||
m_networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond();
|
||||
}
|
||||
for (AZStd::pair<AZ::EntityId, MultiplayerDebugEntityReporter>& entityPair : m_sendingEntityReports)
|
||||
{
|
||||
m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName();
|
||||
m_networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond();
|
||||
}
|
||||
|
||||
if (m_debugDisplay == nullptr)
|
||||
{
|
||||
AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
|
||||
AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId);
|
||||
m_debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
|
||||
}
|
||||
|
||||
const AZ::u32 stateBefore = m_debugDisplay->GetState();
|
||||
|
||||
for (const AZStd::pair<AZ::EntityId, NetworkEntityTraffic>& networkEntity : m_networkEntitiesTraffic)
|
||||
{
|
||||
if (networkEntity.second.m_down < net_DebugEntities_ShowAboveKbps && networkEntity.second.m_up < net_DebugEntities_ShowAboveKbps)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (networkEntity.second.m_down > net_DebugEntities_WarnAboveKbps || networkEntity.second.m_up > net_DebugEntities_WarnAboveKbps)
|
||||
{
|
||||
m_debugDisplay->SetColor(net_DebugEntities_WarningColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_debugDisplay->SetColor(net_DebugEntities_BelowWarningColor);
|
||||
}
|
||||
|
||||
if (networkEntity.second.m_down > net_DebugEntities_ShowAboveKbps && networkEntity.second.m_up > net_DebugEntities_ShowAboveKbps)
|
||||
{
|
||||
azsprintf(m_statusBuffer, "[%s] %.0f down / %0.f up (kbps)", networkEntity.second.m_name,
|
||||
networkEntity.second.m_down, networkEntity.second.m_up);
|
||||
}
|
||||
else if (networkEntity.second.m_down > net_DebugEntities_ShowAboveKbps)
|
||||
{
|
||||
azsprintf(m_statusBuffer, "[%s] %.0f down (kbps)", networkEntity.second.m_name, networkEntity.second.m_down);
|
||||
}
|
||||
else
|
||||
{
|
||||
azsprintf(m_statusBuffer, "[%s] %.0f up (kbps)", networkEntity.second.m_name, networkEntity.second.m_up);
|
||||
}
|
||||
|
||||
AZ::Vector3 entityPosition = AZ::Vector3::CreateZero();
|
||||
AZ::TransformBus::EventResult(entityPosition, networkEntity.first, &AZ::TransformBus::Events::GetWorldTranslation);
|
||||
if (entityPosition.IsZero() == false)
|
||||
{
|
||||
constexpr bool centerText = true;
|
||||
m_debugDisplay->DrawTextLabel(entityPosition, 1.0f, m_statusBuffer, centerText, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
m_debugDisplay->SetState(stateBefore);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "MultiplayerDebugByteReporter.h"
|
||||
|
||||
#include <AzCore/Component/EntityId.h>
|
||||
#include <AzCore/EBus/ScheduledEvent.h>
|
||||
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
|
||||
#include <Multiplayer/MultiplayerStats.h>
|
||||
#include <Multiplayer/MultiplayerTypes.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
/**
|
||||
* \brief Multiplayer traffic live analysis tool via ImGui.
|
||||
*/
|
||||
class MultiplayerDebugPerEntityReporter
|
||||
{
|
||||
public:
|
||||
MultiplayerDebugPerEntityReporter();
|
||||
|
||||
//! main update loop
|
||||
void OnImGuiUpdate();
|
||||
|
||||
//! Event handlers
|
||||
// @{
|
||||
void RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName);
|
||||
void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, NetComponentId netComponentId);
|
||||
void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName);
|
||||
void RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes);
|
||||
void RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes);
|
||||
void RecordRpcSent(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes);
|
||||
void RecordRpcReceived(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes);
|
||||
// }@
|
||||
|
||||
//! Draws bandwidth text over entities
|
||||
void UpdateDebugOverlay();
|
||||
|
||||
private:
|
||||
|
||||
AZ::ScheduledEvent m_updateDebugOverlay;
|
||||
MultiplayerStats::EventHandlers m_eventHandlers;
|
||||
|
||||
AZStd::map<AZ::EntityId, MultiplayerDebugEntityReporter> m_sendingEntityReports{};
|
||||
MultiplayerDebugEntityReporter m_currentSendingEntityReport;
|
||||
|
||||
AZStd::map<AZ::EntityId, MultiplayerDebugEntityReporter> m_receivingEntityReports{};
|
||||
MultiplayerDebugEntityReporter m_currentReceivingEntityReport;
|
||||
|
||||
[[maybe_unused]] float m_replicatedStateKbpsWarn = 10.f;
|
||||
[[maybe_unused]] float m_replicatedStateMaxSizeWarn = 30.f;
|
||||
|
||||
char m_statusBuffer[100] = {};
|
||||
|
||||
struct NetworkEntityTraffic
|
||||
{
|
||||
const char* m_name = nullptr;
|
||||
float m_up = 0.f;
|
||||
float m_down = 0.f;
|
||||
};
|
||||
|
||||
AZStd::unordered_map<AZ::EntityId, NetworkEntityTraffic> m_networkEntitiesTraffic;
|
||||
|
||||
AzFramework::DebugDisplayRequests* m_debugDisplay = nullptr;
|
||||
};
|
||||
}
|
||||
@@ -13,6 +13,11 @@
|
||||
#include <AzNetworking/Framework/INetworkInterface.h>
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
|
||||
void OnDebugEntities_ShowBandwidth_Changed(const bool& showBandwidth);
|
||||
|
||||
AZ_CVAR(bool, net_DebugEntities_ShowBandwidth, false, &OnDebugEntities_ShowBandwidth_Changed, AZ::ConsoleFunctorFlags::Null,
|
||||
"If true, prints bandwidth values over entities that use a considerable amount of network traffic");
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
void MultiplayerDebugSystemComponent::Reflect(AZ::ReflectContext* context)
|
||||
@@ -47,6 +52,17 @@ namespace Multiplayer
|
||||
ImGui::ImGuiUpdateListenerBus::Handler::BusDisconnect();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MultiplayerDebugSystemComponent::ShowEntityBandwidthDebugOverlay()
|
||||
{
|
||||
m_reporter = AZStd::make_unique<MultiplayerDebugPerEntityReporter>();
|
||||
}
|
||||
|
||||
void MultiplayerDebugSystemComponent::HideEntityBandwidthDebugOverlay()
|
||||
{
|
||||
m_reporter.reset();
|
||||
}
|
||||
|
||||
#ifdef IMGUI_ENABLED
|
||||
void MultiplayerDebugSystemComponent::OnImGuiMainMenuUpdate()
|
||||
{
|
||||
@@ -54,6 +70,7 @@ namespace Multiplayer
|
||||
{
|
||||
ImGui::Checkbox("Networking Stats", &m_displayNetworkingStats);
|
||||
ImGui::Checkbox("Multiplayer Stats", &m_displayMultiplayerStats);
|
||||
ImGui::Checkbox("Multiplayer Entity Stats", &m_displayPerEntityStats);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
@@ -213,7 +230,6 @@ namespace Multiplayer
|
||||
void DrawNetworkingStats()
|
||||
{
|
||||
const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
|
||||
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
|
||||
|
||||
const ImGuiTableFlags flags = ImGuiTableFlags_BordersV
|
||||
| ImGuiTableFlags_BordersOuterH
|
||||
@@ -366,7 +382,6 @@ namespace Multiplayer
|
||||
void DrawMultiplayerStats()
|
||||
{
|
||||
const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
|
||||
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
|
||||
|
||||
IMultiplayer* multiplayer = AZ::Interface<IMultiplayer>::Get();
|
||||
MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry();
|
||||
@@ -432,6 +447,36 @@ namespace Multiplayer
|
||||
DrawMultiplayerStats();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_displayPerEntityStats)
|
||||
{
|
||||
if (ImGui::Begin("Multiplayer Per Entity Stats", &m_displayPerEntityStats, ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
// This overrides @net_DebugNetworkEntity_ShowBandwidth value
|
||||
if (m_reporter == nullptr)
|
||||
{
|
||||
ShowEntityBandwidthDebugOverlay();
|
||||
}
|
||||
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->OnImGuiUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnDebugEntities_ShowBandwidth_Changed(const bool& showBandwidth)
|
||||
{
|
||||
if (showBandwidth)
|
||||
{
|
||||
AZ::Interface<Multiplayer::IMultiplayerDebug>::Get()->ShowEntityBandwidthDebugOverlay();
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ::Interface<Multiplayer::IMultiplayerDebug>::Get()->HideEntityBandwidthDebugOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <Debug/MultiplayerDebugPerEntityReporter.h>
|
||||
#include <Multiplayer/IMultiplayerDebug.h>
|
||||
|
||||
#ifdef IMGUI_ENABLED
|
||||
# include <imgui/imgui.h>
|
||||
@@ -19,6 +22,7 @@ namespace Multiplayer
|
||||
{
|
||||
class MultiplayerDebugSystemComponent final
|
||||
: public AZ::Component
|
||||
, public AZ::Interface<IMultiplayerDebug>::Registrar
|
||||
#ifdef IMGUI_ENABLED
|
||||
, public ImGui::ImGuiUpdateListenerBus::Handler
|
||||
#endif
|
||||
@@ -29,7 +33,7 @@ namespace Multiplayer
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
|
||||
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
|
||||
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatbile);
|
||||
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
|
||||
|
||||
~MultiplayerDebugSystemComponent() override = default;
|
||||
|
||||
@@ -39,6 +43,12 @@ namespace Multiplayer
|
||||
void Deactivate() override;
|
||||
//! @}
|
||||
|
||||
//! IMultiplayerDebug overrides
|
||||
//! @{
|
||||
void ShowEntityBandwidthDebugOverlay() override;
|
||||
void HideEntityBandwidthDebugOverlay() override;
|
||||
//! @}
|
||||
|
||||
#ifdef IMGUI_ENABLED
|
||||
//! ImGui::ImGuiUpdateListenerBus overrides
|
||||
//! @{
|
||||
@@ -49,5 +59,8 @@ namespace Multiplayer
|
||||
private:
|
||||
bool m_displayNetworkingStats = false;
|
||||
bool m_displayMultiplayerStats = false;
|
||||
|
||||
bool m_displayPerEntityStats = false;
|
||||
AZStd::unique_ptr<MultiplayerDebugPerEntityReporter> m_reporter;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Multiplayer
|
||||
: m_byteStream(&m_buffer)
|
||||
{
|
||||
m_networkEditorInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
|
||||
AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
|
||||
AZ::Name(MpEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
|
||||
m_networkEditorInterface->SetTimeoutEnabled(false);
|
||||
if (editorsv_isDedicated)
|
||||
{
|
||||
@@ -109,7 +109,7 @@ namespace Multiplayer
|
||||
|
||||
// Setup the normal multiplayer connection
|
||||
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer);
|
||||
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName));
|
||||
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpNetworkInterfaceName));
|
||||
|
||||
uint16_t serverPort = DefaultServerPort;
|
||||
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console)
|
||||
@@ -168,7 +168,7 @@ namespace Multiplayer
|
||||
;
|
||||
}
|
||||
|
||||
bool MultiplayerEditorConnection::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer)
|
||||
AzNetworking::PacketDispatchResult MultiplayerEditorConnection::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer)
|
||||
{
|
||||
return MultiplayerEditorPackets::DispatchPacket(connection, packetHeader, serializer, *this);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Multiplayer
|
||||
MultiplayerEditorConnection();
|
||||
~MultiplayerEditorConnection() = default;
|
||||
|
||||
bool IsHandshakeComplete() const { return true; };
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet);
|
||||
|
||||
@@ -40,7 +41,7 @@ namespace Multiplayer
|
||||
//! @{
|
||||
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
|
||||
void OnConnect(AzNetworking::IConnection* connection) override;
|
||||
bool OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
|
||||
AzNetworking::PacketDispatchResult OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
|
||||
void OnPacketLost(AzNetworking::IConnection* connection, AzNetworking::PacketId packetId) override;
|
||||
void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override;
|
||||
//! @}
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Multiplayer
|
||||
m_serverProcess->TerminateProcess(0);
|
||||
m_serverProcess = nullptr;
|
||||
}
|
||||
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName));
|
||||
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
|
||||
if (editorNetworkInterface)
|
||||
{
|
||||
editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient);
|
||||
@@ -194,7 +194,7 @@ namespace Multiplayer
|
||||
AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
|
||||
|
||||
// Now that the server has launched, attempt to connect the NetworkInterface
|
||||
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName));
|
||||
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
|
||||
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.");
|
||||
m_editorConnId = editorNetworkInterface->Connect(
|
||||
AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp));
|
||||
|
||||
@@ -29,6 +29,21 @@ namespace Multiplayer
|
||||
m_componentStats[netComponentIndex].m_rpcsRecv.resize(rpcCount);
|
||||
}
|
||||
|
||||
void MultiplayerStats::RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName)
|
||||
{
|
||||
m_events.m_entitySerializeStart.Signal(mode, entityId, entityName);
|
||||
}
|
||||
|
||||
void MultiplayerStats::RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, NetComponentId netComponentId)
|
||||
{
|
||||
m_events.m_componentSerializeEnd.Signal(mode, netComponentId);
|
||||
}
|
||||
|
||||
void MultiplayerStats::RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName)
|
||||
{
|
||||
m_events.m_entitySerializeStop.Signal(mode, entityId, entityName);
|
||||
}
|
||||
|
||||
void MultiplayerStats::RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes)
|
||||
{
|
||||
const uint16_t netComponentIndex = aznumeric_cast<uint16_t>(netComponentId);
|
||||
@@ -37,6 +52,8 @@ namespace Multiplayer
|
||||
m_componentStats[netComponentIndex].m_propertyUpdatesSent[propertyIndex].m_totalBytes += totalBytes;
|
||||
m_componentStats[netComponentIndex].m_propertyUpdatesSent[propertyIndex].m_callHistory[m_recordMetricIndex]++;
|
||||
m_componentStats[netComponentIndex].m_propertyUpdatesSent[propertyIndex].m_byteHistory[m_recordMetricIndex] += totalBytes;
|
||||
|
||||
m_events.m_propertySent.Signal(netComponentId, propertyId, totalBytes);
|
||||
}
|
||||
|
||||
void MultiplayerStats::RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes)
|
||||
@@ -47,9 +64,11 @@ namespace Multiplayer
|
||||
m_componentStats[netComponentIndex].m_propertyUpdatesRecv[propertyIndex].m_totalBytes += totalBytes;
|
||||
m_componentStats[netComponentIndex].m_propertyUpdatesRecv[propertyIndex].m_callHistory[m_recordMetricIndex]++;
|
||||
m_componentStats[netComponentIndex].m_propertyUpdatesRecv[propertyIndex].m_byteHistory[m_recordMetricIndex] += totalBytes;
|
||||
|
||||
m_events.m_propertyReceived.Signal(netComponentId, propertyId, totalBytes);
|
||||
}
|
||||
|
||||
void MultiplayerStats::RecordRpcSent(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes)
|
||||
void MultiplayerStats::RecordRpcSent(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes)
|
||||
{
|
||||
const uint16_t netComponentIndex = aznumeric_cast<uint16_t>(netComponentId);
|
||||
const uint16_t rpcIndex = aznumeric_cast<uint16_t>(rpcId);
|
||||
@@ -57,9 +76,11 @@ namespace Multiplayer
|
||||
m_componentStats[netComponentIndex].m_rpcsSent[rpcIndex].m_totalBytes += totalBytes;
|
||||
m_componentStats[netComponentIndex].m_rpcsSent[rpcIndex].m_callHistory[m_recordMetricIndex]++;
|
||||
m_componentStats[netComponentIndex].m_rpcsSent[rpcIndex].m_byteHistory[m_recordMetricIndex] += totalBytes;
|
||||
|
||||
m_events.m_rpcSent.Signal(entityId, entityName, netComponentId, rpcId, totalBytes);
|
||||
}
|
||||
|
||||
void MultiplayerStats::RecordRpcReceived(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes)
|
||||
void MultiplayerStats::RecordRpcReceived(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes)
|
||||
{
|
||||
const uint16_t netComponentIndex = aznumeric_cast<uint16_t>(netComponentId);
|
||||
const uint16_t rpcIndex = aznumeric_cast<uint16_t>(rpcId);
|
||||
@@ -67,6 +88,8 @@ namespace Multiplayer
|
||||
m_componentStats[netComponentIndex].m_rpcsRecv[rpcIndex].m_totalBytes += totalBytes;
|
||||
m_componentStats[netComponentIndex].m_rpcsRecv[rpcIndex].m_callHistory[m_recordMetricIndex]++;
|
||||
m_componentStats[netComponentIndex].m_rpcsRecv[rpcIndex].m_byteHistory[m_recordMetricIndex] += totalBytes;
|
||||
|
||||
m_events.m_rpcReceived.Signal(entityId, entityName, netComponentId, rpcId, totalBytes);
|
||||
}
|
||||
|
||||
void MultiplayerStats::TickStats(AZ::TimeMs metricFrameTimeMs)
|
||||
@@ -186,4 +209,15 @@ namespace Multiplayer
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void MultiplayerStats::ConnectHandlers(EventHandlers& handlers)
|
||||
{
|
||||
handlers.m_entitySerializeStart.Connect(m_events.m_entitySerializeStart);
|
||||
handlers.m_componentSerializeEnd.Connect(m_events.m_componentSerializeEnd);
|
||||
handlers.m_entitySerializeStop.Connect(m_events.m_entitySerializeStop);
|
||||
handlers.m_propertySent.Connect(m_events.m_propertySent);
|
||||
handlers.m_propertyReceived.Connect(m_events.m_propertyReceived);
|
||||
handlers.m_rpcSent.Connect(m_events.m_rpcSent);
|
||||
handlers.m_rpcReceived.Connect(m_events.m_rpcReceived);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace Multiplayer
|
||||
{
|
||||
AZ::TickBus::Handler::BusConnect();
|
||||
AzFramework::SessionNotificationBus::Handler::BusConnect();
|
||||
m_networkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(AZ::Name(MPNetworkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this);
|
||||
m_networkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(AZ::Name(MpNetworkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this);
|
||||
if (AZ::Interface<AZ::IConsole>::Get())
|
||||
{
|
||||
m_consoleCommandHandler.Connect(AZ::Interface<AZ::IConsole>::Get()->GetConsoleCommandInvokedEvent());
|
||||
@@ -197,7 +197,7 @@ namespace Multiplayer
|
||||
AZ::Interface<AzFramework::ISessionHandlingClientRequests>::Unregister(this);
|
||||
AZ::Interface<IMultiplayer>::Unregister(this);
|
||||
m_consoleCommandHandler.Disconnect();
|
||||
AZ::Interface<INetworking>::Get()->DestroyNetworkInterface(AZ::Name(MPNetworkInterfaceName));
|
||||
AZ::Interface<INetworking>::Get()->DestroyNetworkInterface(AZ::Name(MpNetworkInterfaceName));
|
||||
AzFramework::SessionNotificationBus::Handler::BusDisconnect();
|
||||
AZ::TickBus::Handler::BusDisconnect();
|
||||
}
|
||||
@@ -441,6 +441,11 @@ namespace Multiplayer
|
||||
MultiplayerPackets::SyncConsole m_syncPacket;
|
||||
};
|
||||
|
||||
bool MultiplayerSystemComponent::IsHandshakeComplete() const
|
||||
{
|
||||
return m_didHandshake;
|
||||
}
|
||||
|
||||
bool MultiplayerSystemComponent::HandleRequest
|
||||
(
|
||||
[[maybe_unused]] AzNetworking::IConnection* connection,
|
||||
@@ -465,6 +470,8 @@ namespace Multiplayer
|
||||
|
||||
if (connection->SendReliablePacket(MultiplayerPackets::Accept(InvalidHostId, sv_map)))
|
||||
{
|
||||
m_didHandshake = true;
|
||||
|
||||
// Sync our console
|
||||
ConsoleReplicator consoleReplicator(connection);
|
||||
AZ::Interface<AZ::IConsole>::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); });
|
||||
@@ -480,6 +487,8 @@ namespace Multiplayer
|
||||
[[maybe_unused]] MultiplayerPackets::Accept& packet
|
||||
)
|
||||
{
|
||||
m_didHandshake = true;
|
||||
|
||||
AZ::CVarFixedString commandString = "sv_map " + packet.GetMap();
|
||||
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(commandString.c_str());
|
||||
|
||||
@@ -554,7 +563,7 @@ namespace Multiplayer
|
||||
m_tickFactor = 0.0f;
|
||||
m_lastReplicatedHostTimeMs = packet.GetHostTimeMs();
|
||||
m_lastReplicatedHostFrameId = packet.GetHostFrameId();
|
||||
m_networkTime.AlterTime(m_lastReplicatedHostFrameId, m_lastReplicatedHostTimeMs, AzNetworking::InvalidConnectionId);
|
||||
m_networkTime.ForceSetTime(m_lastReplicatedHostFrameId, m_lastReplicatedHostTimeMs);
|
||||
}
|
||||
|
||||
for (AZStd::size_t i = 0; i < packet.GetEntityMessages().size(); ++i)
|
||||
@@ -643,6 +652,7 @@ namespace Multiplayer
|
||||
{
|
||||
controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId());
|
||||
}
|
||||
controlledEntity.Activate();
|
||||
|
||||
if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so
|
||||
{
|
||||
@@ -669,7 +679,7 @@ namespace Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiplayerSystemComponent::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer)
|
||||
AzNetworking::PacketDispatchResult MultiplayerSystemComponent::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer)
|
||||
{
|
||||
return MultiplayerPackets::DispatchPacket(connection, packetHeader, serializer, *this);
|
||||
}
|
||||
@@ -747,7 +757,6 @@ namespace Multiplayer
|
||||
{
|
||||
m_initEvent.Signal(m_networkInterface);
|
||||
|
||||
const AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-16384.0f), AZ::Vector3(16384.0f));
|
||||
//const AZ::Aabb worldBounds = AZ::Interface<IPhysics>.Get()->GetWorldBounds();
|
||||
AZStd::unique_ptr<IEntityDomain> newDomain = AZStd::make_unique<FullOwnershipEntityDomain>();
|
||||
m_networkEntityManager.Initialize(InvalidHostId, AZStd::move(newDomain));
|
||||
@@ -763,6 +772,7 @@ namespace Multiplayer
|
||||
{
|
||||
controlledEntityNetBindComponent->SetAllowAutonomy(true);
|
||||
}
|
||||
controlledEntity.Activate();
|
||||
}
|
||||
|
||||
AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType));
|
||||
@@ -861,7 +871,7 @@ namespace Multiplayer
|
||||
{
|
||||
m_tickFactor += deltaTime / serverRateSeconds;
|
||||
// Linear close to the origin, but asymptote at y = 1
|
||||
m_renderBlendFactor = AZStd::clamp(1.0f - (std::pow(cl_renderTickBlendBase, m_tickFactor)), 0.0f, 1.0f);
|
||||
m_renderBlendFactor = AZStd::clamp(1.0f - (std::pow(cl_renderTickBlendBase, m_tickFactor)), 0.0f, m_tickFactor);
|
||||
AZLOG
|
||||
(
|
||||
NET_Blending,
|
||||
@@ -892,9 +902,8 @@ namespace Multiplayer
|
||||
|
||||
// Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system
|
||||
AZStd::vector<NetBindComponent*> gatheredEntities;
|
||||
AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get();
|
||||
AZ::Interface<AzFramework::IVisibilitySystem>::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum,
|
||||
[&gatheredEntities, entityBoundsUnion](const AzFramework::IVisibilityScene::NodeData& nodeData)
|
||||
[&gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData)
|
||||
{
|
||||
gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size());
|
||||
for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries)
|
||||
@@ -969,7 +978,7 @@ namespace Multiplayer
|
||||
NetworkEntityHandle MultiplayerSystemComponent::SpawnDefaultPlayerPrefab()
|
||||
{
|
||||
PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset).c_str()));
|
||||
INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity());
|
||||
INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate);
|
||||
|
||||
NetworkEntityHandle controlledEntity;
|
||||
if (entityList.size() > 0)
|
||||
@@ -1006,7 +1015,7 @@ namespace Multiplayer
|
||||
const char* addressStr = mutableAddress;
|
||||
const char* portStr = &(mutableAddress[portSeparator + 1]);
|
||||
int32_t portNumber = atol(portStr);
|
||||
AZ::Interface<IMultiplayer>::Get()->Connect(addressStr, portNumber);
|
||||
AZ::Interface<IMultiplayer>::Get()->Connect(addressStr, static_cast<uint16_t>(portNumber));
|
||||
}
|
||||
}
|
||||
AZ_CONSOLEFREEFUNC(connect, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection to a remote host");
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Multiplayer
|
||||
int GetTickOrder() override;
|
||||
//! @}
|
||||
|
||||
bool IsHandshakeComplete() const;
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::Connect& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::Accept& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet);
|
||||
@@ -89,7 +90,7 @@ namespace Multiplayer
|
||||
//! @{
|
||||
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
|
||||
void OnConnect(AzNetworking::IConnection* connection) override;
|
||||
bool OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
|
||||
AzNetworking::PacketDispatchResult OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
|
||||
void OnPacketLost(AzNetworking::IConnection* connection, AzNetworking::PacketId packetId) override;
|
||||
void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override;
|
||||
//! @}
|
||||
@@ -158,6 +159,7 @@ namespace Multiplayer
|
||||
double m_serverSendAccumulator = 0.0;
|
||||
float m_renderBlendFactor = 0.0f;
|
||||
float m_tickFactor = 0.0f;
|
||||
bool m_didHandshake = false;
|
||||
|
||||
#if !defined(AZ_RELEASE_BUILD)
|
||||
MultiplayerEditorConnection m_editorConnectionListener;
|
||||
|
||||
+11
-3
@@ -524,6 +524,7 @@ namespace Multiplayer
|
||||
|
||||
bool EntityReplicationManager::HandlePropertyChangeMessage
|
||||
(
|
||||
AzNetworking::IConnection* invokingConnection,
|
||||
EntityReplicator* entityReplicator,
|
||||
AzNetworking::PacketId packetId,
|
||||
NetEntityId netEntityId,
|
||||
@@ -558,6 +559,12 @@ namespace Multiplayer
|
||||
NetBindComponent* netBindComponent = replicatorEntity.GetNetBindComponent();
|
||||
AZ_Assert(netBindComponent != nullptr, "No NetBindComponent");
|
||||
|
||||
if (createEntity)
|
||||
{
|
||||
// Always set our invoking connectionId for any newly created entities, since this connection now 'owns' them from a rewind perspective
|
||||
netBindComponent->SetOwningConnectionId(invokingConnection->GetConnectionId());
|
||||
}
|
||||
|
||||
const bool changeNetworkRole = (netBindComponent->GetNetEntityRole() != localNetworkRole);
|
||||
if (changeNetworkRole)
|
||||
{
|
||||
@@ -744,7 +751,7 @@ namespace Multiplayer
|
||||
|
||||
bool EntityReplicationManager::HandleEntityUpdateMessage
|
||||
(
|
||||
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
|
||||
AzNetworking::IConnection* invokingConnection,
|
||||
const AzNetworking::IPacketHeader& packetHeader,
|
||||
const NetworkEntityUpdateMessage& updateMessage
|
||||
)
|
||||
@@ -794,7 +801,7 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
// This may implicitly create a replicator for us
|
||||
bool handled = HandlePropertyChangeMessage(entityReplicator, packetHeader.GetPacketId(), updateMessage.GetEntityId(), updateMessage.GetNetworkRole(), outputSerializer, prefabEntityId);
|
||||
bool handled = HandlePropertyChangeMessage(invokingConnection, entityReplicator, packetHeader.GetPacketId(), updateMessage.GetEntityId(), updateMessage.GetNetworkRole(), outputSerializer, prefabEntityId);
|
||||
AZ_Assert(handled, "Failed to handle NetworkEntityUpdateMessage message");
|
||||
|
||||
return handled;
|
||||
@@ -1121,7 +1128,7 @@ namespace Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
bool EntityReplicationManager::HandleEntityMigration([[maybe_unused]] AzNetworking::IConnection* invokingConnection, EntityMigrationMessage& message)
|
||||
bool EntityReplicationManager::HandleEntityMigration(AzNetworking::IConnection* invokingConnection, EntityMigrationMessage& message)
|
||||
{
|
||||
EntityReplicator* replicator = GetEntityReplicator(message.m_entityId);
|
||||
{
|
||||
@@ -1130,6 +1137,7 @@ namespace Multiplayer
|
||||
AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> outputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast<uint32_t>(message.m_propertyUpdateData.GetSize()));
|
||||
if (!HandlePropertyChangeMessage
|
||||
(
|
||||
invokingConnection,
|
||||
replicator,
|
||||
AzNetworking::InvalidPacketId,
|
||||
message.m_entityId,
|
||||
|
||||
+1
@@ -136,6 +136,7 @@ namespace Multiplayer
|
||||
|
||||
bool HandlePropertyChangeMessage
|
||||
(
|
||||
AzNetworking::IConnection* invokingConnection,
|
||||
EntityReplicator* entityReplicator,
|
||||
AzNetworking::PacketId packetId,
|
||||
NetEntityId netEntityId,
|
||||
|
||||
@@ -441,7 +441,8 @@ namespace Multiplayer
|
||||
{
|
||||
// Received rpc metrics, log rpc sent, number of bytes, and the componentId/rpcId for bandwidth metrics
|
||||
MultiplayerStats& stats = GetMultiplayer()->GetStats();
|
||||
stats.RecordRpcSent(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize());
|
||||
stats.RecordRpcSent(GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str(),
|
||||
entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize());
|
||||
|
||||
m_replicationManager.AddDeferredRpcMessage(entityRpcMessage);
|
||||
}
|
||||
@@ -515,7 +516,7 @@ namespace Multiplayer
|
||||
&& (GetRemoteNetworkRole() == NetEntityRole::Server))
|
||||
{
|
||||
// We are on a server, and we received this message from another server, therefore we should forward this to our autonomous player
|
||||
// This can occur if we've recently migrated
|
||||
// This can occur if we've recently migrated
|
||||
result = RpcValidationResult::ForwardToAutonomous;
|
||||
}
|
||||
}
|
||||
@@ -624,7 +625,8 @@ namespace Multiplayer
|
||||
{
|
||||
// Received rpc metrics, log rpc received, time spent, number of bytes, and the componentId/rpcId for bandwidth metrics
|
||||
MultiplayerStats& stats = GetMultiplayer()->GetStats();
|
||||
stats.RecordRpcReceived(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize());
|
||||
stats.RecordRpcReceived(GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str(),
|
||||
entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize());
|
||||
|
||||
if (!m_netBindComponent)
|
||||
{
|
||||
|
||||
@@ -40,7 +40,6 @@ namespace Multiplayer
|
||||
|
||||
// The last packet to have been received about this entity
|
||||
AzNetworking::PacketId m_lastReceivedPacketId = AzNetworking::InvalidPacketId;
|
||||
AZ::TimeMs m_lastRecievedTimeMs = AZ::TimeMs{ 0 };
|
||||
AZ::TimeMs m_markForRemovalTimeMs = AZ::TimeMs{ 0 };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,6 +73,11 @@ namespace Multiplayer
|
||||
return m_networkEntityTracker.Get(netEntityId);
|
||||
}
|
||||
|
||||
NetEntityId NetworkEntityManager::GetNetEntityIdById(const AZ::EntityId& entityId) const
|
||||
{
|
||||
return m_networkEntityTracker.Get(entityId);
|
||||
}
|
||||
|
||||
uint32_t NetworkEntityManager::GetEntityCount() const
|
||||
{
|
||||
return static_cast<uint32_t>(m_networkEntityTracker.size());
|
||||
@@ -304,7 +309,7 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
INetworkEntityManager::EntityList NetworkEntityManager::CreateEntitiesImmediate(
|
||||
const AzFramework::Spawnable& spawnable, NetEntityRole netEntityRole)
|
||||
const AzFramework::Spawnable& spawnable, NetEntityRole netEntityRole, AutoActivate autoActivate)
|
||||
{
|
||||
INetworkEntityManager::EntityList returnList;
|
||||
|
||||
@@ -354,6 +359,11 @@ namespace Multiplayer
|
||||
const NetEntityId netEntityId = NextId();
|
||||
netBindComponent->PreInit(clone, prefabEntityId, netEntityId, netEntityRole);
|
||||
|
||||
if (autoActivate == AutoActivate::DoNotActivate)
|
||||
{
|
||||
clone->SetRuntimeActiveByDefault(false);
|
||||
}
|
||||
|
||||
AzFramework::GameEntityContextRequestBus::Broadcast(
|
||||
&AzFramework::GameEntityContextRequestBus::Events::AddGameEntity, clone);
|
||||
|
||||
@@ -373,10 +383,11 @@ namespace Multiplayer
|
||||
(
|
||||
const PrefabEntityId& prefabEntryId,
|
||||
NetEntityRole netEntityRole,
|
||||
const AZ::Transform& transform
|
||||
const AZ::Transform& transform,
|
||||
AutoActivate autoActivate
|
||||
)
|
||||
{
|
||||
return CreateEntitiesImmediate(prefabEntryId, NextId(), netEntityRole, AutoActivate::Activate, transform);
|
||||
return CreateEntitiesImmediate(prefabEntryId, NextId(), netEntityRole, autoActivate, transform);
|
||||
}
|
||||
|
||||
INetworkEntityManager::EntityList NetworkEntityManager::CreateEntitiesImmediate
|
||||
@@ -409,7 +420,7 @@ namespace Multiplayer
|
||||
|
||||
if (entityIndex == PrefabEntityId::AllIndices)
|
||||
{
|
||||
return CreateEntitiesImmediate(*netSpawnable, netEntityRole);
|
||||
return CreateEntitiesImmediate(*netSpawnable, netEntityRole, autoActivate);
|
||||
}
|
||||
|
||||
const AzFramework::Spawnable::EntityList& entities = netSpawnable->GetEntities();
|
||||
|
||||
@@ -41,13 +41,15 @@ namespace Multiplayer
|
||||
MultiplayerComponentRegistry* GetMultiplayerComponentRegistry() override;
|
||||
HostId GetHostId() const override;
|
||||
ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const override;
|
||||
NetEntityId GetNetEntityIdById(const AZ::EntityId& entityId) const override;
|
||||
|
||||
EntityList CreateEntitiesImmediate(const AzFramework::Spawnable& spawnable, NetEntityRole netEntityRole);
|
||||
EntityList CreateEntitiesImmediate(const AzFramework::Spawnable& spawnable, NetEntityRole netEntityRole, AutoActivate autoActivate);
|
||||
EntityList CreateEntitiesImmediate
|
||||
(
|
||||
const PrefabEntityId& prefabEntryId,
|
||||
NetEntityRole netEntityRole,
|
||||
const AZ::Transform& transform
|
||||
const AZ::Transform& transform,
|
||||
AutoActivate autoActivate = AutoActivate::Activate
|
||||
) override;
|
||||
EntityList CreateEntitiesImmediate
|
||||
(
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Multiplayer
|
||||
++m_addChangeDirty;
|
||||
AZ_Assert(m_entityMap.end() == m_entityMap.find(netEntityId), "Attempting to add the same entity to the entity map multiple times");
|
||||
m_entityMap[netEntityId] = entity;
|
||||
m_netEntityIdMap[entity->GetId()] = netEntityId;
|
||||
}
|
||||
|
||||
NetworkEntityHandle NetworkEntityTracker::Get(NetEntityId netEntityId)
|
||||
@@ -32,6 +33,16 @@ namespace Multiplayer
|
||||
return ConstNetworkEntityHandle(entity, netEntityId, this);
|
||||
}
|
||||
|
||||
NetEntityId NetworkEntityTracker::Get(const AZ::EntityId& entityId) const
|
||||
{
|
||||
auto found = m_netEntityIdMap.find(entityId);
|
||||
if (found != m_netEntityIdMap.end())
|
||||
{
|
||||
return found->second;
|
||||
}
|
||||
return Multiplayer::InvalidNetEntityId;
|
||||
}
|
||||
|
||||
bool NetworkEntityTracker::Exists(NetEntityId netEntityId) const
|
||||
{
|
||||
return (m_entityMap.find(netEntityId) != m_entityMap.end());
|
||||
@@ -50,12 +61,22 @@ namespace Multiplayer
|
||||
void NetworkEntityTracker::erase(NetEntityId netEntityId)
|
||||
{
|
||||
++m_deleteChangeDirty;
|
||||
m_entityMap.erase(netEntityId);
|
||||
|
||||
auto found = m_entityMap.find(netEntityId);
|
||||
if (found != m_entityMap.end())
|
||||
{
|
||||
m_netEntityIdMap.erase(found->second->GetId());
|
||||
m_entityMap.erase(found);
|
||||
}
|
||||
}
|
||||
|
||||
NetworkEntityTracker::EntityMap::iterator NetworkEntityTracker::erase(EntityMap::iterator iter)
|
||||
{
|
||||
++m_deleteChangeDirty;
|
||||
if (iter != m_entityMap.end())
|
||||
{
|
||||
m_netEntityIdMap.erase(iter->second->GetId());
|
||||
}
|
||||
return m_entityMap.erase(iter);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Multiplayer
|
||||
public:
|
||||
|
||||
using EntityMap = AZStd::unordered_map<NetEntityId, AZ::Entity*>;
|
||||
using NetEntityIdMap = AZStd::unordered_map<AZ::EntityId, NetEntityId>;
|
||||
using iterator = EntityMap::iterator;
|
||||
using const_iterator = EntityMap::const_iterator;
|
||||
|
||||
@@ -36,6 +37,8 @@ namespace Multiplayer
|
||||
NetworkEntityHandle Get(NetEntityId netEntityId);
|
||||
ConstNetworkEntityHandle Get(NetEntityId netEntityId) const;
|
||||
|
||||
NetEntityId Get(const AZ::EntityId& entityId) const;
|
||||
|
||||
//! Returns true if the netEntityId exists.
|
||||
bool Exists(NetEntityId netEntityId) const;
|
||||
|
||||
@@ -74,6 +77,7 @@ namespace Multiplayer
|
||||
private:
|
||||
|
||||
EntityMap m_entityMap;
|
||||
NetEntityIdMap m_netEntityIdMap;
|
||||
uint32_t m_deleteChangeDirty = 0;
|
||||
uint32_t m_addChangeDirty = 0;
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace Multiplayer
|
||||
inline void NetworkEntityTracker::clear()
|
||||
{
|
||||
m_entityMap.clear();
|
||||
m_netEntityIdMap.clear();
|
||||
}
|
||||
|
||||
inline uint32_t NetworkEntityTracker::GetChangeDirty(const AZ::Entity* entity) const
|
||||
|
||||
@@ -70,6 +70,15 @@ namespace Multiplayer
|
||||
return (IsTimeRewound() && (rewindConnectionId == m_rewindingConnectionId)) ? m_unalteredFrameId : m_hostFrameId;
|
||||
}
|
||||
|
||||
void NetworkTime::ForceSetTime(HostFrameId frameId, AZ::TimeMs timeMs)
|
||||
{
|
||||
AZ_Assert(!IsTimeRewound(), "Forcibly setting network time is unsupported under a rewound time scope");
|
||||
m_unalteredFrameId = frameId;
|
||||
m_hostFrameId = frameId;
|
||||
m_hostTimeMs = timeMs;
|
||||
m_rewindingConnectionId = AzNetworking::InvalidConnectionId;
|
||||
}
|
||||
|
||||
void NetworkTime::AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId)
|
||||
{
|
||||
m_hostFrameId = frameId;
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Multiplayer
|
||||
float GetHostBlendFactor() const override;
|
||||
AzNetworking::ConnectionId GetRewindingConnectionId() const override;
|
||||
HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override;
|
||||
void ForceSetTime(HostFrameId frameId, AZ::TimeMs timeMs) override;
|
||||
void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) override;
|
||||
void AlterBlendFactor(float blendFactor) override;
|
||||
void SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) override;
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Multiplayer
|
||||
|
||||
// convert Prefab DOM into Prefab Instance.
|
||||
AZStd::unique_ptr<Instance> sourceInstance(aznew Instance());
|
||||
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*sourceInstance, prefab, PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId))
|
||||
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*sourceInstance, prefab, PrefabDomUtils::LoadFlags::AssignRandomEntityId))
|
||||
{
|
||||
PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
|
||||
|
||||
|
||||
@@ -181,9 +181,9 @@ namespace Multiplayer
|
||||
|
||||
void ServerToClientReplicationWindow::DebugDraw() const
|
||||
{
|
||||
static const float BoundaryStripeHeight = 1.0f;
|
||||
static const float BoundaryStripeSpacing = 0.5f;
|
||||
static const int32_t BoundaryStripeCount = 10;
|
||||
//static const float BoundaryStripeHeight = 1.0f;
|
||||
//static const float BoundaryStripeSpacing = 0.5f;
|
||||
//static const int32_t BoundaryStripeCount = 10;
|
||||
|
||||
//if (auto localEnt = m_ControlledEntity.lock())
|
||||
//{
|
||||
@@ -289,7 +289,6 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
const bool isQueueFull = (m_candidateQueue.size() >= sv_MaxEntitiesToTrackReplication); // See if have the maximum number of entities in our set
|
||||
const bool isBetterChoice = !m_candidateQueue.empty() && (priority > m_candidateQueue.top().m_priority); // Check if the new thing we are adding is better than the worst item in our set
|
||||
const bool isInReplicationSet = m_replicationSet.find(entityHandle) != m_replicationSet.end();
|
||||
if (!isInReplicationSet)
|
||||
{
|
||||
|
||||
@@ -76,7 +76,6 @@ namespace Multiplayer
|
||||
//NetBindComponent* m_controlledNetBindComponent = nullptr;
|
||||
|
||||
const AzNetworking::IConnection* m_connection = nullptr;
|
||||
float m_minPriorityReplicated = 0.0f; ///< Lowest replicated entity priority in last update
|
||||
|
||||
// Cached values to detect a poor network connection
|
||||
uint32_t m_lastCheckedSentPackets = 0;
|
||||
|
||||
Reference in New Issue
Block a user