Merge branch 'main' into SPEC6476_ReflectNetworkProperties

This commit is contained in:
Gene Walters
2021-05-11 10:46:54 -07:00
1107 changed files with 14949 additions and 131023 deletions
@@ -1,6 +1,6 @@
#include <AzCore/Component/Component.h>
#include <Source/Components/MultiplayerComponentRegistry.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
{% for Component in dataFiles %}
{% set ComponentDerived = Component.attrib['OverrideComponent']|booleanTrue %}
{% set ControllerDerived = Component.attrib['OverrideController']|booleanTrue %}
@@ -170,12 +170,12 @@ AZ::Event<{{ Property.attrib['Type'] }}>
{% set PropertyName = UpperFirst(Property.attrib['Name']) %}
{{ ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }}
{% if IsOverride %}
void Handle{{ PropertyName }}({{ ', '.join(paramDefines) }}) override {}
void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ ', '.join(paramDefines) }}) override {}
{% else %}
//! {{ PropertyName }} Handler
//! {{ Property.attrib['Description'] }}
//! HandleOn {{ HandleOn }}
virtual void Handle{{ PropertyName }}({{ ', '.join(paramDefines) }}) = 0;
virtual void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ ', '.join(paramDefines) }}) = 0;
{% endif %}
{% endmacro %}
{#
@@ -221,11 +221,11 @@ AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }
#include <AzCore/EBus/Event.h>
#include <AzCore/EBus/ScheduledEvent.h>
#include <AzNetworking/DataStructures/FixedSizeBitsetView.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/NetworkEntityHandle.h>
#include <Source/NetworkEntity/EntityReplication/ReplicationRecord.h>
#include <Source/Components/MultiplayerComponent.h>
#include <Source/Components/MultiplayerController.h>
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Source/NetworkTime/RewindableObject.h>
#include <Include/MultiplayerTypes.h>
{% call(Include) AutoComponentMacros.ParseIncludes(Component) %}
@@ -436,7 +436,7 @@ namespace {{ Component.attrib['Namespace'] }}
//! MultiplayerComponent interface
//! @{
NetComponentId GetNetComponentId() const override;
bool HandleRpcMessage(Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& rpcMessage) override;
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& rpcMessage) override;
bool SerializeStateDeltaMessage(Multiplayer::ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) override;
void NotifyStateDeltaChanges(Multiplayer::ReplicationRecord& replicationRecord) override;
bool HasController() const override;
@@ -336,7 +336,7 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp
if (m_controller)
{
AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Authority, "Entity proxy does not have authority");
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }});
}
{% if Property.attrib['IsReliable']|booleanTrue %}
{# if the rpc is not reliable we can simply drop it, also note message reliability type is default reliable in EntityRpcMessage #}
@@ -350,7 +350,7 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp
if (m_controller)
{
AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Autonomous, "Entity proxy does not have autonomy");
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }});
}
{% else %}
Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
@@ -1291,7 +1291,12 @@ namespace {{ Component.attrib['Namespace'] }}
#pragma warning(push)
#pragma warning(disable: 4065) // switch statement contains 'default' but no 'case' labels
bool {{ ComponentBaseName }}::HandleRpcMessage([[maybe_unused]] Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& message)
bool {{ ComponentBaseName }}::HandleRpcMessage
(
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
[[maybe_unused]] Multiplayer::NetEntityRole remoteRole,
Multiplayer::NetworkEntityRpcMessage& message
)
{
const {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure rpcType = static_cast<{{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure>(message.GetRpcIndex());
switch (rpcType)
@@ -12,15 +12,16 @@
<Include File="Include/MultiplayerTypes.h"/>
<Include File="Source/NetworkInput/NetworkInput.h"/>
<Include File="Source/NetworkInput/NetworkInputArray.h"/>
<Include File="Source/NetworkInput/NetworkInputHistory.h"/>
<Include File="Source/NetworkInput/NetworkInputVector.h"/>
<Include File="Source/NetworkInput/NetworkInputMigrationVector.h"/>
<Include File="AzNetworking/DataStructures/ByteBuffer.h"/>
<NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{0}" ReplicateFrom="Authority" ReplicateTo="Authority" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" GenerateEventBindings="false" />
<NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{ 0 }" ReplicateFrom="Authority" ReplicateTo="Authority" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" GenerateEventBindings="false" />
<RemoteProcedure Name="SendClientInput" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="true" IsReliable="false" Description="Client to server move / input RPC">
<Param Type="Multiplayer::NetworkInputVector" Name="inputArray" />
<Param Type="uint32_t" Name="stateHash" />
<Param Type="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>
@@ -30,6 +31,6 @@
</RemoteProcedure>
<RemoteProcedure Name="SendMigrateClientInput" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="true" IsReliable="false" Description="Client to server migrate move / input RPC">
<Param Type="Multiplayer::MigrateNetworkInputVector" Name="inputArray" />
<Param Type="Multiplayer::NetworkInputMigrationVector" Name="inputArray" />
</RemoteProcedure>
</Component>
@@ -3,6 +3,7 @@
<PacketGroup Name="MultiplayerPackets" PacketStart="CorePackets::PacketType::MAX">
<Include File="AzNetworking/AutoGen/CorePackets.AutoPackets.h" />
<Include File="Include/MultiplayerTypes.h" />
<Include File="Include/INetworkTime.h" />
<Include File="Source/NetworkEntity/NetworkEntityRpcMessage.h" />
<Include File="Source/NetworkEntity/NetworkEntityUpdateMessage.h" />
@@ -35,6 +36,7 @@
<Packet Name="EntityUpdates" Desc="A packet that contains multiple entity updates">
<Member Type="AZ::TimeMs" Name="hostTimeMs" Init="AZ::TimeMs{ 0 }" />
<Member Type="Multiplayer::HostFrameId" Name="hostFrameId" Init="Multiplayer::InvalidHostFrameId" />
<Member Type="Multiplayer::NetworkEntityUpdateMessage" Name="entityMessages" Container="Vector" Count="Multiplayer::MaxAggregateEntityMessages" SuppressFromInitializerList="true" />
</Packet>
@@ -13,9 +13,41 @@
#include <Source/Components/LocalPredictionPlayerInputComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzNetworking/Serialization/HashSerializer.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/Serialization/StringifySerializer.h>
#include <AzNetworking/Serialization/TrackChangedSerializer.h>
namespace Multiplayer
{
AZ_CVAR(AZ::TimeMs, cl_InputRateMs, AZ::TimeMs{ 33 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Rate at which to sample and process client inputs");
AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay");
#ifndef _RELEASE
AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat");
#endif
AZ_CVAR(bool, sv_EnableCorrections, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables server corrections on autonomous proxy desyncs");
AZ_CVAR(double, sv_MaxBankTimeWindowSec, 0.2, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum bank time we allow before we start rejecting autonomous proxy move inputs due to anticheat kicking in");
AZ_CVAR(double, sv_BankTimeDecay, 0.025, nullptr, AZ::ConsoleFunctorFlags::Null, "Amount to decay bank time by, in case of more permanent shifts in client latency");
AZ_CVAR(AZ::TimeMs, sv_MinCorrectionTimeMs, AZ::TimeMs{ 100 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum time to wait between sending out corrections in order to avoid flooding corrections on high-latency connections");
AZ_CVAR(AZ::TimeMs, sv_InputUpdateTimeMs, AZ::TimeMs{ 5 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum time between component updates");
// Debug helper functions
AZStd::string GetInputString(NetworkInput& input)
{
AzNetworking::StringifySerializer serializer(',', false);
input.Serialize(serializer);
return serializer.GetString();
}
AZStd::string GetCorrectionDataString(NetBindComponent* netBindComponent)
{
AzNetworking::StringifySerializer serializer(',', false);
netBindComponent->SerializeEntityCorrection(serializer);
return serializer.GetString();
}
void LocalPredictionPlayerInputComponent::LocalPredictionPlayerInputComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
@@ -27,30 +59,544 @@ namespace Multiplayer
LocalPredictionPlayerInputComponentBase::Reflect(context);
}
void LocalPredictionPlayerInputComponent::OnInit()
{
;
}
void LocalPredictionPlayerInputComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void LocalPredictionPlayerInputComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
LocalPredictionPlayerInputComponentController::LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent)
: LocalPredictionPlayerInputComponentControllerBase(parent)
, m_autonomousUpdateEvent([this]() { UpdateAutonomous(m_autonomousUpdateEvent.TimeInQueueMs()); }, AZ::Name("AutonomousUpdate Event"))
, m_updateBankedTimeEvent([this]() { UpdateBankedTime(m_updateBankedTimeEvent.TimeInQueueMs()); }, AZ::Name("BankTimeUpdate Event"))
, m_migrateStartHandler([this](ClientInputId migratedInputId) { OnMigrateStart(migratedInputId); })
, m_migrateEndHandler([this]() { OnMigrateEnd(); })
{
if (GetNetEntityRole() == NetEntityRole::Autonomous)
{
m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true);
parent.GetNetBindComponent()->AddEntityMigrationStartEventHandler(m_migrateStartHandler);
parent.GetNetBindComponent()->AddEntityMigrationEndEventHandler(m_migrateEndHandler);
}
}
void LocalPredictionPlayerInputComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
if (entityIsMigrating == EntityIsMigrating::True)
{
m_allowMigrateClientInput = true;
m_serverMigrateFrameId = AZ::Interface<INetworkTime>::Get()->GetHostFrameId();
}
}
void LocalPredictionPlayerInputComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void LocalPredictionPlayerInputComponentController::HandleSendClientInput
(
[[maybe_unused]] const Multiplayer::NetworkInputVector& inputArray,
[[maybe_unused]] const uint32_t& stateHash,
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputArray& inputArray,
const AZ::HashValue32& stateHash,
[[maybe_unused]] const AzNetworking::PacketEncodingBuffer& clientState
)
{
;
// After receiving the first input from the client, start the update event to check for slow hacking
if (!m_updateBankedTimeEvent.IsScheduled())
{
m_updateBankedTimeEvent.Enqueue(sv_InputUpdateTimeMs, true);
}
if (invokingConnection == nullptr)
{
// Discard any input messages that were locally dispatched or sent by disconnected clients
return;
}
const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
const double clientInputRateSec = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
m_lastInputReceivedTimeMs = currentTimeMs;
// Keep track of last inputs received, also allows us to update frame ids
m_lastInputReceived = inputArray;
// Figure out which index from the input array we want
// we start at the oldest input that has not been processed
int32_t inputArrayIndex = -1;
for (int32_t i = NetworkInputArray::MaxElements - 1; i >= 0; --i)
{
// Find an input that is newer than the last one we processed
if (m_lastInputReceived[i].GetClientInputId() > GetLastInputId())
{
inputArrayIndex = i;
break;
}
}
if (inputArrayIndex < 0)
{
AZLOG
(
NET_Prediction,
"Discarding old or out of order move input (current: %u, received %u)",
aznumeric_cast<uint32_t>(GetLastInputId()),
aznumeric_cast<uint32_t>(m_lastInputReceived[0].GetClientInputId())
);
return;
}
bool lostInput = false;
if (GetLastInputId() < inputArray.GetPreviousInputId())
{
// last move id processed is older than the previous input id, we missed some input packets
lostInput = true;
}
SetLastInputId(m_lastInputReceived[0].GetClientInputId()); // Set this variable in case of migration
while (inputArrayIndex >= 0)
{
NetworkInput& input = m_lastInputReceived[inputArrayIndex];
// Anticheat, if we're receiving too many inputs, and fall outside our variable latency input window
// Discard move input events, client may be speed hacking
if (m_clientBankedTime < sv_MaxBankTimeWindowSec)
{
m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary
{
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
}
if (lostInput)
{
AZLOG(NET_Prediction, "InputLost InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
}
else
{
AZLOG(NET_Prediction, "Processed InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
}
}
else
{
AZLOG(NET_Prediction, "Dropped InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
}
--inputArrayIndex;
}
if (sv_EnableCorrections && (currentTimeMs - m_lastCorrectionSentTimeMs > sv_MinCorrectionTimeMs))
{
m_lastCorrectionSentTimeMs = currentTimeMs;
AzNetworking::HashSerializer hashSerializer;
GetNetBindComponent()->SerializeEntityCorrection(hashSerializer);
const AZ::HashValue32 localAuthorityHash = hashSerializer.GetHash();
if (stateHash != localAuthorityHash)
{
// Produce correction for client
AzNetworking::PacketEncodingBuffer correction;
correction.Resize(correction.GetCapacity());
AzNetworking::NetworkInputSerializer serializer(correction.GetBuffer(), correction.GetCapacity());
// only deserialize if we have data (for client/server profile/debug mismatches)
if (correction.GetSize() > 0)
{
GetNetBindComponent()->SerializeEntityCorrection(serializer);
}
correction.Resize(serializer.GetSize());
// Send correction
SendClientInputCorrection(GetLastInputId(), correction);
#ifdef _DEBUG
// In debug, show which states caused the correction
AZStd::string clientStateString;
AZStd::string serverStateString;
{
// Write in client state
AzNetworking::NetworkOutputSerializer clientStateSerializer(clientState.GetBuffer(), clientState.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(clientStateSerializer);
// Read out state values
AzNetworking::StringifySerializer clientValues;
GetNetBindComponent()->SerializeEntityCorrection(clientValues);
// Restore server state
AzNetworking::NetworkOutputSerializer serverStateSerializer(correction.GetBuffer(), correction.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(serverStateSerializer);
// Read out state values
AzNetworking::StringifySerializer serverValues;
GetNetBindComponent()->SerializeEntityCorrection(serverValues);
AZStd::map<AZStd::string, AZStd::pair<AZStd::string, AZStd::string>> mapComparison;
// put the server value in the first part of the pair
for (const auto& pair : serverValues.GetValueMap())
{
mapComparison[pair.first].first = pair.second;
}
// put the client value in the second part of the pair
for (const auto& pair : clientValues.GetValueMap())
{
mapComparison[pair.first].second = pair.second;
}
bool firstIt = true;
for (const auto& mapPair : mapComparison)
{
if (mapPair.second.first != mapPair.second.second)
{
if (!firstIt)
{
clientStateString += ",";
serverStateString += ",";
}
firstIt = false;
AZStd::string clientValue = mapPair.second.second.empty() ? "<no value>" : mapPair.second.second;
AZStd::string serverValue = mapPair.second.first.empty() ? "<no value>" : mapPair.second.first;
clientStateString += mapPair.first + "=" + clientValue;
serverStateString += mapPair.first + "=" + serverValue;
}
}
}
#else
const AZStd::string clientStateString = "available in debug only";
const AZStd::string serverStateString = "available in debug only";
#endif
AZLOG_ERROR("** Autonomous proxy desync detected! ** clientState=[%s], serverState=[%s]", clientStateString.c_str(), serverStateString.c_str());
}
}
}
void LocalPredictionPlayerInputComponentController::HandleSendMigrateClientInput
(
[[maybe_unused]] const Multiplayer::MigrateNetworkInputVector& inputArray
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputMigrationVector& inputArray
)
{
;
if (!m_allowMigrateClientInput)
{
AZLOG_ERROR("Client attempting to SendMigrateClientInput message when server was not expecting it. This may be an attempt to cheat");
return;
}
// We only allow the client to send this message exactly once, when the component has been migrated
// Any further processing of these messages from the client would be exploitable
m_allowMigrateClientInput = false;
if (invokingConnection == nullptr)
{
// Discard any input migration messages that were locally dispatched or sent by disconnected clients
return;
}
const float clientInputRateSec = static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
// Copy array so we can modify input ids
NetworkInputMigrationVector inputArrayCopy = inputArray;
for (uint32_t i = 0; i < inputArrayCopy.GetSize(); ++i)
{
NetworkInput& input = inputArrayCopy[i];
++ModifyLastInputId();
input.SetClientInputId(GetLastInputId());
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, clientInputRateSec);
AZLOG
(
NET_Prediction,
"Migrated InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
// Don't bother checking for corrections here, the next regular input will trigger any corrections if necessary
// Also don't bother with any cheat detection here, because the input array is limited in size and at most and can only be sent once
// So this highly constrains anything a malicious client can do
}
}
void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection
(
[[maybe_unused]] const Multiplayer::ClientInputId& inputId,
[[maybe_unused]] const AzNetworking::PacketEncodingBuffer& correction
AzNetworking::IConnection* invokingConnection,
const Multiplayer::ClientInputId& inputId,
const AzNetworking::PacketEncodingBuffer& correction
)
{
;
AZ_Assert(inputId <= m_clientInputId, "Invalid correction frame id, correction is for a move the client has not yet submitted to the server");
if (inputId > m_clientInputId)
{
AZLOG_ERROR("Discarding correction for non-existent move, correction represents a move we haven't sent to the server yet");
return;
}
if (inputId <= m_lastCorrectionInputId)
{
AZLOG(NET_Prediction, "Discarding old correction for client frame %u", aznumeric_cast<uint32_t>(inputId));
return;
}
m_lastCorrectionInputId = inputId;
// Apply the correction
AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> serializer(correction.GetBuffer(), correction.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(serializer);
m_correctionEvent.Signal();
AZLOG
(
NET_Prediction,
"Corrected InputId=%d - o=[%s]",
aznumeric_cast<int32_t>(m_lastCorrectionInputId),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
const uint32_t inputHistorySize = m_inputHistory.Size();
const uint32_t historicalDelta = aznumeric_cast<uint32_t>(m_clientInputId - inputId); // Do not replay the move we just corrected, that was already processed by the server
// If this correction is for a move outside our input history window, just start replaying from the oldest move we have available
const uint32_t startReplayIndex = (inputHistorySize > historicalDelta) ? (inputHistorySize - historicalDelta) : 0;
// Flag that we are replaying inputs
struct ScopedReplayingInput
{
ScopedReplayingInput(LocalPredictionPlayerInputComponentController* instance)
: m_instance(instance)
{
m_instance->m_replayingInput = true;
}
~ScopedReplayingInput()
{
m_instance->m_replayingInput = false;
}
LocalPredictionPlayerInputComponentController* m_instance;
};
ScopedReplayingInput markReplayingInput(this);
const float clientInputRateSec = static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
for (uint32_t replayIndex = startReplayIndex; replayIndex < inputHistorySize; ++replayIndex)
{
// Reprocess the input for this frame
NetworkInput& input = m_inputHistory[replayIndex];
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, clientInputRateSec);
AZLOG
(
NET_Prediction,
"Replayed InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
}
}
bool LocalPredictionPlayerInputComponentController::IsReplayingInput() const
{
return m_replayingInput;
}
bool LocalPredictionPlayerInputComponentController::IsMigrating() const
{
return m_lastMigratedInputId != ClientInputId{ 0 };
}
ClientInputId LocalPredictionPlayerInputComponentController::GetLastInputId() const
{
return m_clientInputId;
}
HostFrameId LocalPredictionPlayerInputComponentController::GetInputFrameId(const NetworkInput& input) const
{
// If the client has sent us an invalid server frame id
// this is because they are in the process of migrating from one server to another
// In this situation, use whatever the server frame id was when this component was migrated
// This will match the closest state to what the client sees
return (input.GetHostFrameId() == InvalidHostFrameId) ? m_serverMigrateFrameId : input.GetHostFrameId();
}
void LocalPredictionPlayerInputComponentController::CorrectionEventAddHandle(CorrectionEvent::Handler& handler)
{
handler.Connect(m_correctionEvent);
}
void LocalPredictionPlayerInputComponentController::OnMigrateStart(ClientInputId migratedInputId)
{
m_lastMigratedInputId = migratedInputId;
}
void LocalPredictionPlayerInputComponentController::OnMigrateEnd()
{
NetworkInputMigrationVector inputArray;
// Roll up all inputs that the new server doesn't have and send them now
for (AZStd::size_t i = 0; i < m_inputHistory.Size(); ++i)
{
NetworkInput& input = m_inputHistory[i];
// New server already has these inputs
if (input.GetClientInputId() <= m_lastMigratedInputId)
{
continue;
}
// Clear out the old server frame id
// We don't know what server frame ids to use for the new server yet, but the new server will figure out how to deal with this
input.SetHostFrameId(InvalidHostFrameId);
// New server doesn't have these inputs
if (!inputArray.PushBack(input))
{
break; // Reached capacity
}
}
// Send these inputs to the server
SendMigrateClientInput(inputArray);
// Done migrating
m_lastMigratedInputId = ClientInputId{ 0 };
}
void LocalPredictionPlayerInputComponentController::UpdateAutonomous(AZ::TimeMs deltaTimeMs)
{
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
const double inputRate = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
const double maxRewindHistory = static_cast<double>(static_cast<AZ::TimeMs>(cl_MaxRewindHistoryMs)) / 1000.0;
#ifndef _RELEASE
m_moveAccumulator += deltaTime * cl_DebugHackTimeMultiplier;
#else
m_moveAccumulator += deltaTime;
#endif
const uint32_t maxClientInputs = inputRate > 0.0 ? static_cast<uint32_t>(maxRewindHistory / inputRate) : 0;
INetworkTime* networkTime = AZ::Interface<INetworkTime>::Get();
IMultiplayer* multiplayer = AZ::Interface<IMultiplayer>::Get();
while (m_moveAccumulator >= inputRate)
{
m_moveAccumulator -= inputRate;
++m_clientInputId;
NetworkInputArray inputArray(GetEntityHandle());
NetworkInput& input = inputArray[0];
input.SetClientInputId(m_clientInputId);
input.SetHostFrameId(networkTime->GetHostFrameId());
input.SetHostTimeMs(multiplayer->GetCurrentHostTimeMs());
// Allow components to form the input for this frame
GetNetBindComponent()->CreateInput(input, inputRate);
// Process the input for this frame
GetNetBindComponent()->ProcessInput(input, inputRate);
AZLOG
(
NET_Prediction,
"Processed InutId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(m_clientInputId),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
// Generate a hash based on the current client predicted states
AzNetworking::HashSerializer hashSerializer;
GetNetBindComponent()->SerializeEntityCorrection(hashSerializer);
// In debug, send the entire client output state to the server to make it easier to debug desync issues
AzNetworking::PacketEncodingBuffer processInputResult;
#ifdef _DEBUG
AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity());
GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer);
processInputResult.Resize(processInputResultSerializer.GetSize());
#endif
// Save this input and discard move history outside our client rewind window
m_inputHistory.PushBack(input);
while (m_inputHistory.Size() > maxClientInputs)
{
m_inputHistory.PopFront();
}
const size_t inputHistorySize = m_inputHistory.Size();
// Form the rest of the input array using the n most recent elements in the history buffer
// NOTE: inputArray[0] has already been initialized hence start at i = 1
for (uint32_t i = 1; i < NetworkInputArray::MaxElements; ++i)
{
if (i < inputHistorySize)
{
inputArray[i] = m_inputHistory[inputHistorySize - 1 - i];
}
else // History is too small?
{
// Plug in the most recent input
inputArray[i] = input;
}
}
// Send the input to server (only when we are not migrating)
if (!IsMigrating())
{
SendClientInput(inputArray, hashSerializer.GetHash(), processInputResult);
}
}
}
void LocalPredictionPlayerInputComponentController::UpdateBankedTime(AZ::TimeMs deltaTimeMs)
{
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
const double inputRate = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
const double maxRewindHistory = static_cast<double>(static_cast<AZ::TimeMs>(cl_MaxRewindHistoryMs)) / 1000.0;
// Update banked time accumulator
m_clientBankedTime -= deltaTime;
// Forcibly tick any clients who are too far behind our variable latency window
// Client may be slow hacking
if (m_clientBankedTime < -sv_MaxBankTimeWindowSec)
{
m_clientBankedTime = -sv_MaxBankTimeWindowSec; // clamp to boundary
NetworkInput& input = m_lastInputReceived[0];
{
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), AzNetworking::InvalidConnectionId);
GetNetBindComponent()->ProcessInput(input, inputRate);
}
AZLOG
(
NET_Prediction,
"Forced InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
}
// Decay our bank time window, in case the remote endpoint has suffered a more persistent shift in latency, this should cause the client to eventually recover
m_clientBankedTime = m_clientBankedTime * (1.0 - sv_BankTimeDecay);
}
}
@@ -13,9 +13,12 @@
#pragma once
#include <Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.h>
#include <Source/Components/NetBindComponent.h>
namespace Multiplayer
{
using CorrectionEvent = AZ::Event<>;
class LocalPredictionPlayerInputComponent
: public LocalPredictionPlayerInputComponentBase
{
@@ -24,24 +27,87 @@ namespace Multiplayer
static void Reflect([[maybe_unused]] AZ::ReflectContext* context);
void OnInit() override {}
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnInit() override;
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
};
class LocalPredictionPlayerInputComponentController
: public LocalPredictionPlayerInputComponentControllerBase
{
public:
LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent) : LocalPredictionPlayerInputComponentControllerBase(parent) {}
LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent);
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void HandleSendClientInput(const Multiplayer::NetworkInputVector& inputArray, const uint32_t& stateHash, const AzNetworking::PacketEncodingBuffer& clientState) override;
void HandleSendMigrateClientInput(const Multiplayer::MigrateNetworkInputVector& inputArray) override;
void HandleSendClientInputCorrection(const Multiplayer::ClientInputId& inputId, const AzNetworking::PacketEncodingBuffer& correction) override;
void HandleSendClientInput
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputArray& inputArray,
const AZ::HashValue32& stateHash,
const AzNetworking::PacketEncodingBuffer& clientState
) override;
void HandleSendMigrateClientInput
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputMigrationVector& inputArray
) override;
void HandleSendClientInputCorrection
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::ClientInputId& inputId,
const AzNetworking::PacketEncodingBuffer& correction
) override;
//! Return true if we're currently replaying inputs after a correction.
//! If this value returns true, effects, audio, and other cosmetic triggers should be suppressed
//! @return true if we're within correction scope and replaying inputs
bool IsReplayingInput() const;
//! Return true if we're currently migrating from one host to another.
//! @return boolean true if we're currently migrating from one host to another
bool IsMigrating() const;
ClientInputId GetLastInputId() const;
HostFrameId GetInputFrameId(const NetworkInput& input) const;
void CorrectionEventAddHandle(CorrectionEvent::Handler& handler);
private:
void OnMigrateStart(ClientInputId migratedInputId);
void OnMigrateEnd();
void UpdateAutonomous(AZ::TimeMs deltaTimeMs);
void UpdateBankedTime(AZ::TimeMs deltaTimeMs);
// Implicitly sorted player input history, back() is the input that corresponds to the latest client input Id
NetworkInputHistory m_inputHistory;
// Anti-cheat accumulator for clients who purposely mess with their clock rate
NetworkInputArray m_lastInputReceived;
AZ::ScheduledEvent m_autonomousUpdateEvent; // Drives autonomous input collection
AZ::ScheduledEvent m_updateBankedTimeEvent; // Drives authority bank time updates
CorrectionEvent m_correctionEvent;
EntityMigrationStartEvent::Handler m_migrateStartHandler;
EntityMigrationEndEvent::Handler m_migrateEndHandler;
double m_moveAccumulator = 0.0;
double m_clientBankedTime = 0.0;
AZ::TimeMs m_lastInputReceivedTimeMs = AZ::TimeMs{ 0 };
AZ::TimeMs m_lastCorrectionSentTimeMs = AZ::TimeMs{ 0 };
ClientInputId m_clientInputId = ClientInputId{ 0 };
ClientInputId m_lastCorrectionInputId = ClientInputId{ 0 };
ClientInputId m_lastMigratedInputId = ClientInputId{ 0 }; // Used to resend inputs that were queued during a migration event
HostFrameId m_serverMigrateFrameId = InvalidHostFrameId;
bool m_replayingInput = false; // True if we're replaying inputs under a correction event (use this to suppress effects or audio)
bool m_allowMigrateClientInput = false; // True if this component was migrated, we will allow the client to send us migrated inputs (one time only)
};
}
@@ -15,7 +15,7 @@
#include <AzCore/Component/Component.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/DataStructures/FixedSizeBitsetView.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <Include/MultiplayerTypes.h>
#include <Include/IMultiplayer.h>
@@ -69,7 +69,7 @@ namespace Multiplayer
virtual NetComponentId GetNetComponentId() const = 0;
virtual bool HandleRpcMessage(NetEntityRole netEntityRole, NetworkEntityRpcMessage& rpcMessage) = 0;
virtual bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole netEntityRole, NetworkEntityRpcMessage& rpcMessage) = 0;
virtual bool SerializeStateDeltaMessage(ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) = 0;
virtual void NotifyStateDeltaChanges(ReplicationRecord& replicationRecord) = 0;
virtual bool HasController() const = 0;
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/Math/Aabb.h>
namespace Multiplayer
@@ -13,10 +13,10 @@
#include <Source/Components/NetBindComponent.h>
#include <Source/Components/MultiplayerComponent.h>
#include <Source/Components/MultiplayerController.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/NetworkEntity/NetworkEntityUpdateMessage.h>
#include <Source/NetworkInput/NetworkInput.h>
#include <Include/INetworkEntityManager.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
@@ -192,12 +192,12 @@ namespace Multiplayer
return bounds;
}
bool NetBindComponent::HandleRpcMessage(NetEntityRole remoteRole, NetworkEntityRpcMessage& message)
bool NetBindComponent::HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole remoteRole, NetworkEntityRpcMessage& message)
{
auto findIt = m_multiplayerComponentMap.find(message.GetComponentId());
if (findIt != m_multiplayerComponentMap.end())
{
return findIt->second->HandleRpcMessage(remoteRole, message);
return findIt->second->HandleRpcMessage(invokingConnection, remoteRole, message);
}
return false;
}
@@ -274,9 +274,19 @@ namespace Multiplayer
m_localNotificationRecord.Clear();
}
void NetBindComponent::NotifyMigration(HostId remoteHostId, AzNetworking::ConnectionId connectionId)
void NetBindComponent::NotifyMigrationStart(ClientInputId migratedInputId)
{
m_entityMigrationEvent.Signal(m_netEntityHandle, remoteHostId, connectionId);
m_entityMigrationStartEvent.Signal(migratedInputId);
}
void NetBindComponent::NotifyMigrationEnd()
{
m_entityMigrationEndEvent.Signal();
}
void NetBindComponent::NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId)
{
m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId);
}
void NetBindComponent::AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler)
@@ -289,9 +299,19 @@ namespace Multiplayer
eventHandler.Connect(m_dirtiedEvent);
}
void NetBindComponent::AddEntityMigrationEventHandler(EntityMigrationEvent::Handler& eventHandler)
void NetBindComponent::AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityMigrationEvent);
eventHandler.Connect(m_entityMigrationStartEvent);
}
void NetBindComponent::AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityMigrationEndEvent);
}
void NetBindComponent::AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityServerMigrationEvent);
}
bool NetBindComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer)
@@ -21,8 +21,9 @@
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <Source/NetworkEntity/EntityReplication/ReplicationRecord.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Include/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/INetworkTime.h>
#include <Include/MultiplayerTypes.h>
#include <AzCore/EBus/Event.h>
@@ -34,7 +35,9 @@ namespace Multiplayer
using EntityStopEvent = AZ::Event<const ConstNetworkEntityHandle&>;
using EntityDirtiedEvent = AZ::Event<>;
using EntityMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, HostId, AzNetworking::ConnectionId>;
using EntityMigrationStartEvent = AZ::Event<ClientInputId>;
using EntityMigrationEndEvent = AZ::Event<>;
using EntityServerMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, HostId, AzNetworking::ConnectionId>;
//! @class NetBindComponent
//! @brief Component that provides net-binding to a networked entity.
@@ -72,7 +75,7 @@ namespace Multiplayer
void ProcessInput(NetworkInput& networkInput, float deltaTime);
AZ::Aabb GetRewindBoundsForInput(const NetworkInput& networkInput, float deltaTime) const;
bool HandleRpcMessage(NetEntityRole remoteRole, NetworkEntityRpcMessage& message);
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole remoteRole, NetworkEntityRpcMessage& message);
bool HandlePropertyChangeMessage(AzNetworking::ISerializer& serializer, bool notifyChanges = true);
RpcSendEvent& GetSendAuthorityToClientRpcEvent();
@@ -84,11 +87,15 @@ namespace Multiplayer
void MarkDirty();
void NotifyLocalChanges();
void NotifyMigration(HostId remoteHostId, AzNetworking::ConnectionId connectionId);
void NotifyMigrationStart(ClientInputId migratedInputId);
void NotifyMigrationEnd();
void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId);
void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler);
void AddEntityDirtiedEventHandler(EntityDirtiedEvent::Handler& eventHandler);
void AddEntityMigrationEventHandler(EntityMigrationEvent::Handler& eventHandler);
void AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler);
void AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler);
void AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler);
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
@@ -119,7 +126,7 @@ namespace Multiplayer
ReplicationRecord m_currentRecord = NetEntityRole::InvalidRole;
ReplicationRecord m_totalRecord = NetEntityRole::InvalidRole;
ReplicationRecord m_predictableRecord = NetEntityRole::InvalidRole;
ReplicationRecord m_predictableRecord = NetEntityRole::Autonomous;
ReplicationRecord m_localNotificationRecord = NetEntityRole::InvalidRole;
PrefabEntityId m_prefabEntityId;
AZStd::unordered_map<NetComponentId, MultiplayerComponent*> m_multiplayerComponentMap;
@@ -133,7 +140,9 @@ namespace Multiplayer
EntityStopEvent m_entityStopEvent;
EntityDirtiedEvent m_dirtiedEvent;
EntityMigrationEvent m_entityMigrationEvent;
EntityMigrationStartEvent m_entityMigrationStartEvent;
EntityMigrationEndEvent m_entityMigrationEndEvent;
EntityServerMigrationEvent m_entityServerMigrationEvent;
AZ::Event<> m_onRemove;
RpcSendEvent::Handler m_handleLocalServerRpcMessageEventHandle;
AZ::Event<>::Handler m_handleMarkedDirty;
@@ -50,8 +50,9 @@ namespace Multiplayer
return m_entityReplicationManager;
}
void ClientToServerConnectionData::Update([[maybe_unused]] AZ::TimeMs serverGameTimeMs)
void ClientToServerConnectionData::Update(AZ::TimeMs hostTimeMs)
{
m_entityReplicationManager.ActivatePendingEntities();
m_entityReplicationManager.SendUpdates(hostTimeMs);
}
}
@@ -12,7 +12,8 @@
#pragma once
#include <Source/ConnectionData/IConnectionData.h>
#include <Include/IConnectionData.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
namespace Multiplayer
{
@@ -32,7 +33,7 @@ namespace Multiplayer
ConnectionDataType GetConnectionDataType() const override;
AzNetworking::IConnection* GetConnection() const override;
EntityReplicationManager& GetReplicationManager() override;
void Update(AZ::TimeMs serverGameTimeMs) override;
void Update(AZ::TimeMs hostTimeMs) override;
bool CanSendUpdates() const override;
void SetCanSendUpdates(bool canSendUpdates) override;
//! @}
@@ -1,56 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
namespace Multiplayer
{
enum class ConnectionDataType
{
ClientToServer,
ServerToClient,
ServerToServer
};
class IConnectionData
{
public:
virtual ~IConnectionData() = default;
//! Returns whether or not this is a ServerToClient or ServerToServer connection data instance.
//! @return ConnectionDataType::ServerToClient or ConnectionDataType::ServerToServer
virtual ConnectionDataType GetConnectionDataType() const = 0;
//! Returns the connection bound to this connection data instance.
//! @return pointer to the connection bound to this connection data instance
virtual AzNetworking::IConnection* GetConnection() const = 0;
//! Returns the EntityReplicationManager for this connection data instance.
//! @return reference to the EntityReplicationManager for this connection data instance
virtual EntityReplicationManager& GetReplicationManager() = 0;
//! Creates and manages sending updates to the remote endpoint.
//! @param serverGameTimeMs current server game time in milliseconds
virtual void Update(AZ::TimeMs serverGameTimeMs) = 0;
//! Returns whether update messages can be sent to the connection.
//! @return true if update messages can be sent
virtual bool CanSendUpdates() const = 0;
//! Sets the state of connection whether update messages can be sent or not.
//! @param canSendUpdates the state value
virtual void SetCanSendUpdates(bool canSendUpdates) = 0;
};
}
@@ -35,7 +35,7 @@ namespace Multiplayer
if (netBindComponent != nullptr)
{
netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler);
netBindComponent->AddEntityMigrationEventHandler(m_controlledEntityMigrationHandler);
netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler);
}
m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(sv_ClientMaxRemoteEntitiesPendingCreationCount);
@@ -63,7 +63,7 @@ namespace Multiplayer
return m_entityReplicationManager;
}
void ServerToClientConnectionData::Update(AZ::TimeMs serverGameTimeMs)
void ServerToClientConnectionData::Update(AZ::TimeMs hostTimeMs)
{
m_entityReplicationManager.ActivatePendingEntities();
@@ -73,7 +73,7 @@ namespace Multiplayer
// potentially false if we just migrated the player, if that is the case, don't send any more updates
if (netBindComponent != nullptr && (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority))
{
m_entityReplicationManager.SendUpdates(serverGameTimeMs);
m_entityReplicationManager.SendUpdates(hostTimeMs);
}
}
}
@@ -12,7 +12,8 @@
#pragma once
#include <Source/ConnectionData/IConnectionData.h>
#include <Include/IConnectionData.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
namespace Multiplayer
{
@@ -33,7 +34,7 @@ namespace Multiplayer
ConnectionDataType GetConnectionDataType() const override;
AzNetworking::IConnection* GetConnection() const override;
EntityReplicationManager& GetReplicationManager() override;
void Update(AZ::TimeMs serverGameTimeMs) override;
void Update(AZ::TimeMs hostTimeMs) override;
bool CanSendUpdates() const override;
void SetCanSendUpdates(bool canSendUpdates) override;
//! @}
@@ -49,7 +50,7 @@ namespace Multiplayer
EntityReplicationManager m_entityReplicationManager;
NetworkEntityHandle m_controlledEntity;
EntityStopEvent::Handler m_controlledEntityRemovedHandler;
EntityMigrationEvent::Handler m_controlledEntityMigrationHandler;
EntityServerMigrationEvent::Handler m_controlledEntityMigrationHandler;
AzNetworking::IConnection* m_connection = nullptr;
bool m_canSendUpdates = false;
};
@@ -235,7 +235,7 @@ namespace Multiplayer
if (m_displayMultiplayerStats)
{
if (ImGui::Begin("Multiplayer Stats", &m_displayMultiplayerStats, ImGuiWindowFlags_HorizontalScrollbar))
if (ImGui::Begin("Multiplayer Stats", &m_displayMultiplayerStats, ImGuiWindowFlags_None))
{
IMultiplayer* multiplayer = AZ::Interface<IMultiplayer>::Get();
const Multiplayer::MultiplayerStats& stats = multiplayer->GetStats();
@@ -254,7 +254,7 @@ namespace Multiplayer
if (ImGui::BeginTable("", 5, flags))
{
// The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide, TEXT_BASE_WIDTH * 36.0f);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Total Calls", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
ImGui::TableSetupColumn("Total Bytes", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
ImGui::TableSetupColumn("Calls/Sec", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
@@ -12,7 +12,7 @@
#pragma once
#include <Source/EntityDomains/IEntityDomain.h>
#include <Include/IEntityDomain.h>
namespace Multiplayer
{
@@ -1,44 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Source/NetworkEntity/INetworkEntityManager.h>
namespace Multiplayer
{
//! @class IEntityDomain
//! @brief A class that determines if an entity should belong to a particular INetworkEntityManager.
class IEntityDomain
{
public:
using EntitiesNotInDomain = AZStd::unordered_set<NetEntityId>;
virtual ~IEntityDomain() = default;
//! Returns whether or not an entity should be owned by an entity manager.
//! @param entityHandle the handle of the netbound entity to check
//! @return false if this entity should not belong to the entity manger, true if it could be owned by the entity manager
virtual bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const = 0;
//! Enable Entity Domain Exit Tracking for entities on the host.
//! @param ownedEntitySet the set of entities to activate tracking for
virtual void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) = 0;
//! Return the set of netbound entities not included in this domain.
//! @param outEntitiesNotInDomain the set of known networked entities not included in this domain
virtual void RetrieveEntitiesNotInDomain(EntitiesNotInDomain& outEntitiesNotInDomain) const = 0;
//! Debug draw to visualize host entity domains.
virtual void DebugDraw() const = 0;
};
}
@@ -127,7 +127,8 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
AZ::TimeMs serverGameTimeMs = AZ::GetElapsedTimeMs();
AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs();
// Handle deferred local rpc messages that were generated during the updates
m_networkEntityManager.DispatchLocalDeferredRpcMessages();
@@ -137,18 +138,19 @@ namespace Multiplayer
m_networkEntityManager.NotifyEntitiesDirtied();
MultiplayerStats& stats = GetStats();
stats.TickStats(deltaTimeMs);
stats.m_entityCount = GetNetworkEntityManager()->GetEntityCount();
stats.m_serverConnectionCount = 0;
stats.m_clientConnectionCount = 0;
// Send out the game state update to all connections
{
auto sendNetworkUpdates = [serverGameTimeMs, &stats](IConnection& connection)
auto sendNetworkUpdates = [hostTimeMs, &stats](IConnection& connection)
{
if (connection.GetUserData() != nullptr)
{
IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection.GetUserData());
connectionData->Update(serverGameTimeMs);
connectionData->Update(hostTimeMs);
if (connectionData->GetConnectionDataType() == ConnectionDataType::ServerToClient)
{
stats.m_clientConnectionCount++;
@@ -481,7 +483,7 @@ namespace Multiplayer
}
}
MultiplayerAgentType MultiplayerSystemComponent::GetAgentType()
MultiplayerAgentType MultiplayerSystemComponent::GetAgentType() const
{
return m_agentType;
}
@@ -528,6 +530,18 @@ namespace Multiplayer
});
}
AZ::TimeMs MultiplayerSystemComponent::GetCurrentHostTimeMs() const
{
if (GetAgentType() == MultiplayerAgentType::Client)
{
return m_lastReplicatedHostTimeMs;
}
else // ClientServer or DedicatedServer
{
return m_networkTime.GetHostTimeMs();
}
}
const char* MultiplayerSystemComponent::GetComponentGemName(NetComponentId netComponentId) const
{
return GetMultiplayerComponentRegistry()->GetComponentGemName(netComponentId);
@@ -84,12 +84,13 @@ namespace Multiplayer
//! IMultiplayer interface
//! @{
MultiplayerAgentType GetAgentType() override;
MultiplayerAgentType GetAgentType() const override;
void InitializeMultiplayer(MultiplayerAgentType state) override;
void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override;
void AddSessionInitHandler(SessionInitEvent::Handler& handler) override;
void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override;
void SendReadyForEntityUpdates(bool readyForEntityUpdates) override;
AZ::TimeMs GetCurrentHostTimeMs() const override;
const char* GetComponentGemName(NetComponentId netComponentId) const override;
const char* GetComponentName(NetComponentId netComponentId) const override;
const char* GetComponentPropertyName(NetComponentId netComponentId, PropertyIndex propertyIndex) const override;
@@ -119,5 +120,8 @@ namespace Multiplayer
SessionInitEvent m_initEvent;
SessionShutdownEvent m_shutdownEvent;
ConnectionAcquiredEvent m_connAcquiredEvent;
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId;
};
}
@@ -14,14 +14,14 @@
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/EntityReplication/PropertyPublisher.h>
#include <Source/NetworkEntity/EntityReplication/PropertySubscriber.h>
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Source/NetworkEntity/NetworkEntityUpdateMessage.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/Components/NetBindComponent.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Include/IEntityDomain.h>
#include <Include/IMultiplayer.h>
#include <Include/INetworkEntityManager.h>
#include <Include/IReplicationWindow.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
@@ -93,10 +93,10 @@ namespace Multiplayer
}
}
void EntityReplicationManager::SendUpdates(AZ::TimeMs serverGameTimeMs)
void EntityReplicationManager::SendUpdates(AZ::TimeMs hostTimeMs)
{
m_frameTimeMs = AZ::GetElapsedTimeMs();
SendEntityUpdates(serverGameTimeMs);
SendEntityUpdates(hostTimeMs);
SendEntityRpcs(m_deferredRpcMessagesReliable, true);
SendEntityRpcs(m_deferredRpcMessagesUnreliable, false);
@@ -118,7 +118,7 @@ namespace Multiplayer
void EntityReplicationManager::SendEntityUpdatesPacketHelper
(
AZ::TimeMs serverGameTimeMs,
AZ::TimeMs hostTimeMs,
EntityReplicatorList& toSendList,
uint32_t maxPayloadSize,
AzNetworking::IConnection& connection
@@ -127,7 +127,8 @@ namespace Multiplayer
uint32_t pendingPacketSize = 0;
EntityReplicatorList replicatorUpdatedList;
MultiplayerPackets::EntityUpdates entityUpdatePacket;
entityUpdatePacket.SetHostTimeMs(serverGameTimeMs);
entityUpdatePacket.SetHostTimeMs(hostTimeMs);
entityUpdatePacket.SetHostFrameId(InvalidHostFrameId);
// Serialize everything
while (!toSendList.empty())
{
@@ -249,7 +250,7 @@ namespace Multiplayer
return toSendList;
}
void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs serverGameTimeMs)
void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs hostTimeMs)
{
EntityReplicatorList toSendList = GenerateEntityUpdateList();
@@ -264,7 +265,7 @@ namespace Multiplayer
// While our to send list is not empty, build up another packet to send
do
{
SendEntityUpdatesPacketHelper(serverGameTimeMs, toSendList, m_maxPayloadSize, m_connection);
SendEntityUpdatesPacketHelper(hostTimeMs, toSendList, m_maxPayloadSize, m_connection);
} while (!toSendList.empty());
}
@@ -747,7 +748,7 @@ namespace Multiplayer
bool EntityReplicationManager::HandleEntityUpdateMessage
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
const AzNetworking::IPacketHeader& packetHeader,
const NetworkEntityUpdateMessage& updateMessage
)
@@ -803,7 +804,7 @@ namespace Multiplayer
return handled;
}
bool EntityReplicationManager::HandleEntityRpcMessage([[maybe_unused]] AzNetworking::IConnection* connection, NetworkEntityRpcMessage& message)
bool EntityReplicationManager::HandleEntityRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& message)
{
EntityReplicator* entityReplicator = GetEntityReplicator(message.GetEntityId());
const bool isReplicatorValid = (entityReplicator != nullptr) && !entityReplicator->IsMarkedForRemoval();
@@ -815,7 +816,7 @@ namespace Multiplayer
}
else
{
return entityReplicator->HandleRpcMessage(message);
return entityReplicator->HandleRpcMessage(invokingConnection, message);
}
}
@@ -833,8 +834,7 @@ namespace Multiplayer
);
return false;
}
return entityReplicator->HandleRpcMessage(message);
return entityReplicator->HandleRpcMessage(nullptr, message);
}
AZ::TimeMs EntityReplicationManager::GetResendTimeoutTimeMs() const
@@ -877,7 +877,6 @@ namespace Multiplayer
AzNetworking::TimeoutResult EntityReplicationManager::OrphanedEntityRpcs::HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item)
{
NetEntityId timedOutEntityId = aznumeric_cast<NetEntityId>(item.m_userData);
auto entityRpcsIter = m_entityRpcMap.find(timedOutEntityId);
if (entityRpcsIter != m_entityRpcMap.end())
{
@@ -887,7 +886,6 @@ namespace Multiplayer
}
m_entityRpcMap.erase(entityRpcsIter);
}
return AzNetworking::TimeoutResult::Delete;
}
@@ -1089,7 +1087,7 @@ namespace Multiplayer
if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer)
{
netBindComponent->NotifyMigration(GetRemoteHostId(), GetConnection().GetConnectionId());
netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetConnection().GetConnectionId());
}
bool didSucceed = true;
@@ -1127,7 +1125,7 @@ namespace Multiplayer
}
}
bool EntityReplicationManager::HandleMessage([[maybe_unused]] AzNetworking::IConnection* connection, MultiplayerPackets::EntityMigration& message)
bool EntityReplicationManager::HandleMessage([[maybe_unused]] AzNetworking::IConnection* invokingConnection, MultiplayerPackets::EntityMigration& message)
{
EntityReplicator* replicator = GetEntityReplicator(message.GetEntityId());
{
@@ -13,11 +13,11 @@
#pragma once
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Source/Components/NetBindComponent.h>
#include <Include/INetworkEntityManager.h>
#include <Include/IReplicationWindow.h>
#include <Include/IEntityDomain.h>
#include <Include/NetworkEntityHandle.h>
#include <AzNetworking/DataStructures/TimeoutQueue.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
#include <AzCore/std/containers/map.h>
@@ -59,7 +59,7 @@ namespace Multiplayer
HostId GetRemoteHostId() const;
void ActivatePendingEntities();
void SendUpdates(AZ::TimeMs serverGameTimeMs);
void SendUpdates(AZ::TimeMs hostTimeMs);
void Clear(bool forMigration);
bool SetEntityRebasing(NetworkEntityHandle& entityHandle);
@@ -82,10 +82,10 @@ namespace Multiplayer
void AddAutonomousEntityReplicatorCreatedHandle(AZ::Event<NetEntityId>::Handler& handler);
bool HandleMessage(AzNetworking::IConnection* connection, MultiplayerPackets::EntityMigration& message);
bool HandleMessage(AzNetworking::IConnection* invokingConnection, MultiplayerPackets::EntityMigration& message);
bool HandleEntityDeleteMessage(EntityReplicator* entityReplicator, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityUpdateMessage(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityRpcMessage(AzNetworking::IConnection* connection, NetworkEntityRpcMessage& message);
bool HandleEntityUpdateMessage(AzNetworking::IConnection* invokingConnection, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& message);
AZ::TimeMs GetResendTimeoutTimeMs() const;
@@ -118,9 +118,9 @@ namespace Multiplayer
using EntityReplicatorList = AZStd::deque<EntityReplicator*>;
EntityReplicatorList GenerateEntityUpdateList();
void SendEntityUpdatesPacketHelper(AZ::TimeMs serverGameTimeMs, EntityReplicatorList& toSendList, uint32_t maxPayloadSize, AzNetworking::IConnection& connection);
void SendEntityUpdatesPacketHelper(AZ::TimeMs hostTimeMs, EntityReplicatorList& toSendList, uint32_t maxPayloadSize, AzNetworking::IConnection& connection);
void SendEntityUpdates(AZ::TimeMs serverGameTimeMs);
void SendEntityUpdates(AZ::TimeMs hostTimeMs);
void SendEntityRpcs(RpcMessages& deferredRpcs, bool reliable);
void MigrateEntityInternal(NetEntityId entityId);
@@ -155,31 +155,27 @@ namespace Multiplayer
OrphanedEntityRpcs(EntityReplicationManager& replicationManager);
void Update();
bool DispatchOrphanedRpcs(EntityReplicator& entityReplicator);
void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityPrcMessage);
void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityRpcMessage);
AZStd::size_t Size() const { return m_entityRpcMap.size(); }
private:
AzNetworking::TimeoutResult HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) override;
struct OrphanedRpcs
{
OrphanedRpcs() = default;
OrphanedRpcs(OrphanedRpcs&& rhs)
{
m_rpcMessages.swap(rhs.m_rpcMessages);
m_timeoutId = rhs.m_timeoutId;
rhs.m_timeoutId = AzNetworking::TimeoutId{ 0 };
m_rpcMessages.swap(rhs.m_rpcMessages);
}
AzNetworking::TimeoutId m_timeoutId = AzNetworking::TimeoutId{ 0 };
RpcMessages m_rpcMessages;
AzNetworking::TimeoutId m_timeoutId = AzNetworking::TimeoutId{ 0 };
};
typedef AZStd::unordered_map<NetEntityId, OrphanedRpcs> EntityRpcMap;
EntityRpcMap m_entityRpcMap;
AzNetworking::TimeoutQueue m_timeoutQueue;
EntityReplicationManager& m_replicationManager;
};
OrphanedEntityRpcs m_orphanedEntityRpcs;
EntityReplicatorMap m_entityReplicatorMap;
@@ -628,7 +628,7 @@ namespace Multiplayer
return result;
}
bool EntityReplicator::HandleRpcMessage(NetworkEntityRpcMessage& entityRpcMessage)
bool EntityReplicator::HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& entityRpcMessage)
{
// Received rpc metrics, log rpc received, time spent, number of bytes, and the componentId/rpcId for bandwidth metrics
MultiplayerStats& stats = AZ::Interface<IMultiplayer>::Get()->GetStats();
@@ -676,7 +676,7 @@ namespace Multiplayer
switch (result)
{
case RpcValidationResult::HandleRpc:
return m_netBindComponent->HandleRpcMessage(GetRemoteNetworkRole(), entityRpcMessage);
return m_netBindComponent->HandleRpcMessage(invokingConnection, GetRemoteNetworkRole(), entityRpcMessage);
case RpcValidationResult::DropRpc:
return true;
case RpcValidationResult::DropRpcAndDisconnect:
@@ -703,7 +703,7 @@ namespace Multiplayer
break;
}
AZ_Assert(false, "Unhandled ERpcValidationResult %d", result);
AZ_Assert(false, "Unhandled RpcValidationResult %d", result);
return false;
}
}
@@ -75,8 +75,8 @@ namespace Multiplayer
const PropertyPublisher* GetPropertyPublisher() const;
PropertySubscriber* GetPropertySubscriber();
// Handlers for messages
bool HandleRpcMessage(NetworkEntityRpcMessage& entityRpcMessage);
// Handlers for Rpc messages
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& entityRpcMessage);
//! AZ::EntityBus overrides
//! @{
@@ -1,41 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Source/NetworkEntity/INetworkEntityManager.h>
namespace Multiplayer
{
//! @class INetworkEntityDomain
//! @brief A class that determines if an entity should belong to a particular EntityManager.
class INetworkEntityDomain
{
public:
using EntitiesNotInDomain = AZStd::unordered_set<NetEntityId>;
virtual ~INetworkEntityDomain() = default;
//! Enable Entity Domain Exit Tracking for entities on the server.
//! @param ownedEntitySet
virtual void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) = 0;
//! Return the set of entities not in this domain.
//! @param outEntitiesNotInDomain
virtual void RetrieveEntitiesNotInDomain(EntitiesNotInDomain& outEntitiesNotInDomain) const = 0;
//! Returns whether or not an entity should be owned by an entity manager.
//! @param entityHandle the handle of the entity to check for inclusion in the domain
//! @return false if this entity should not belong to the entity manger, true if it could be owned by the entity manager
virtual bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const = 0;
};
}
@@ -1,158 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/Asset/AssetCommon.h>
namespace Multiplayer
{
class NetworkEntityTracker;
class NetworkEntityAuthorityTracker;
class NetworkEntityRpcMessage;
class MultiplayerComponentRegistry;
using EntityExitDomainEvent = AZ::Event<const ConstNetworkEntityHandle&>;
using ControllersActivatedEvent = AZ::Event<const ConstNetworkEntityHandle&, EntityIsMigrating>;
using ControllersDeactivatedEvent = AZ::Event<const ConstNetworkEntityHandle&, EntityIsMigrating>;
//! @class INetworkEntityManager
//! @brief The interface for managing all networked entities.
class INetworkEntityManager
{
public:
AZ_RTTI(INetworkEntityManager, "{109759DE-9492-439C-A0B1-AE46E6FD029C}");
using OwnedEntitySet = AZStd::unordered_set<ConstNetworkEntityHandle>;
using EntityList = AZStd::vector<NetworkEntityHandle>;
virtual ~INetworkEntityManager() = default;
//! Returns the NetworkEntityTracker for this INetworkEntityManager instance.
//! @return the NetworkEntityTracker for this INetworkEntityManager instance
virtual NetworkEntityTracker* GetNetworkEntityTracker() = 0;
//! Returns the NetworkEntityAuthorityTracker for this INetworkEntityManager instance.
//! @return the NetworkEntityAuthorityTracker for this INetworkEntityManager instance
virtual NetworkEntityAuthorityTracker* GetNetworkEntityAuthorityTracker() = 0;
//! Returns the MultiplayerComponentRegistry for this INetworkEntityManager instance.
//! @return the MultiplayerComponentRegistry for this INetworkEntityManager instance
virtual MultiplayerComponentRegistry* GetMultiplayerComponentRegistry() = 0;
//! Returns the HostId for this INetworkEntityManager instance.
//! @return the HostId for this INetworkEntityManager instance
virtual HostId GetHostId() const = 0;
//! Creates new entities of the given archetype
//! @param prefabEntryId the name of the spawnable to spawn
virtual EntityList CreateEntitiesImmediate(
const PrefabEntityId& prefabEntryId, NetEntityId netEntityId, NetEntityRole netEntityRole, AutoActivate autoActivate,
const AZ::Transform& transform) = 0;
//! Returns an ConstEntityPtr for the provided entityId.
//! @param netEntityId the netEntityId to get an ConstEntityPtr for
//! @return the requested ConstEntityPtr
virtual ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const = 0;
//! Returns the total number of entities tracked by this INetworkEntityManager instance.
//! @return the total number of entities tracked by this INetworkEntityManager instance
virtual uint32_t GetEntityCount() const = 0;
//! Adds the provided entity to the internal entity map identified by the provided netEntityId.
//! @param netEntityId the identifier to use for the added entity
//! @param entity the entity to add to the internal entity map
//! @return a NetworkEntityHandle for the newly added entity
virtual NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) = 0;
//! Marks the specified entity for removal and deletion.
//! @param entityHandle the entity to remove and delete
virtual void MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) = 0;
//! Returns true if the indicated entity is marked for removal.
//! @param entityHandle the entity to test if marked for removal
//! @return boolean true if the specified entity is marked for removal, false otherwise
virtual bool IsMarkedForRemoval(const ConstNetworkEntityHandle& entityHandle) const = 0;
//! Unmarks the specified entity so it will no longer be removed and deleted.
//! @param entityHandle the entity to unmark for removal and deletion
virtual void ClearEntityFromRemovalList(const ConstNetworkEntityHandle& entityHandle) = 0;
//! Clears out and deletes all entities registered with the entity manager.
virtual void ClearAllEntities() = 0;
//! Adds an event handler to be invoked when we notify which entities have been marked dirty.
//! @param entityMarkedDirtyHandle event handler for the dirtied entity
virtual void AddEntityMarkedDirtyHandler(AZ::Event<>::Handler& entityMarkedDirtyHandle) = 0;
//! Adds an event handler to be invoked when we notify entities to send their change notifications.
//! @param entityNotifyChangesHandle event handler for the dirtied entity
virtual void AddEntityNotifyChangesHandler(AZ::Event<>::Handler& entityNotifyChangesHandle) = 0;
//! Adds an event handler to be invoked when we notify entities to send their change notifications.
//! @param entityNotifyChangesHandle event handler for the dirtied entity
virtual void AddEntityExitDomainHandler(EntityExitDomainEvent::Handler& entityExitDomainHandler) = 0;
//! Adds an event handler to be invoked when an entities controllers have activated
//! @param controllersActivatedHandler event handler for the entity
virtual void AddControllersActivatedHandler(ControllersActivatedEvent::Handler& controllersActivatedHandler) = 0;
//! Adds an event handler to be invoked when an entities controllers have been deactivated
//! @param controllersDeactivatedHandler event handler for the entity
virtual void AddControllersDeactivatedHandler(ControllersDeactivatedEvent::Handler& controllersDeactivatedHandler) = 0;
//! Notifies entities that they should process their dirty state.
virtual void NotifyEntitiesDirtied() = 0;
//! Notifies entities that they should process change notifications.
virtual void NotifyEntitiesChanged() = 0;
//! Notifies that an entities controllers have activated.
//! @param entityHandle handle to the entity whose controllers have activated
//! @param entityIsMigrating true if the entity is activating after a migration
virtual void NotifyControllersActivated(const ConstNetworkEntityHandle& entityHandle, EntityIsMigrating entityIsMigrating) = 0;
//! Notifies that an entities controllers have been deactivated.
//! @param entityHandle handle to the entity whose controllers have been deactivated
//! @param entityIsMigrating true if the entity is deactivating due to a migration
virtual void NotifyControllersDeactivated(const ConstNetworkEntityHandle& entityHandle, EntityIsMigrating entityIsMigrating) = 0;
//! Handle a local rpc message.
//! @param entityRpcMessage the local rpc message to handle
virtual void HandleLocalRpcMessage(NetworkEntityRpcMessage& message) = 0;
};
// Convenience helpers
inline INetworkEntityManager* GetNetworkEntityManager()
{
return AZ::Interface<INetworkEntityManager>::Get();
}
inline NetworkEntityTracker* GetNetworkEntityTracker()
{
return GetNetworkEntityManager()->GetNetworkEntityTracker();
}
inline NetworkEntityAuthorityTracker* GetNetworkEntityAuthorityTracker()
{
return GetNetworkEntityManager()->GetNetworkEntityAuthorityTracker();
}
inline MultiplayerComponentRegistry* GetMultiplayerComponentRegistry()
{
return GetNetworkEntityManager()->GetMultiplayerComponentRegistry();
}
}
@@ -11,8 +11,8 @@
*/
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/Components/NetBindComponent.h>
#include <Include/INetworkEntityManager.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
@@ -10,7 +10,7 @@
*
*/
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/Components/NetBindComponent.h>
#include <Source/Components/MultiplayerController.h>
@@ -1,141 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Entity.h>
#include <Include/MultiplayerTypes.h>
namespace Multiplayer
{
class MultiplayerController;
class NetworkEntityTracker;
class NetBindComponent;
//! @class ConstNetworkEntityHandle
//! @brief This class provides a wrapping around handle ids.
//! It is optimized to avoid using the hashmap lookup unless the hashmap has had an item removed.
class ConstNetworkEntityHandle
{
public:
//! Constructs a nullptr handle.
ConstNetworkEntityHandle() = default;
//! Constructs a ConstNetworkEntityHandle given an entity, an entity tracker
//! @param entity pointer to the entity to construct a ConstNetworkEntityHandle for
//! @param entityTracker pointer to the entity tracker that tracks the entity
ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* entityTracker);
//! Constructs a ConstNetworkEntityHandle given an entity, a networkEntityId, and an entity tracker
//! @param entity pointer to the entity to construct a ConstNetworkEntityHandle for
//! @param netEntityId the networkEntityId of the entity
//! @param entityTracker pointer to the entity tracker that tracks the entity
ConstNetworkEntityHandle(AZ::Entity* entity, NetEntityId netEntityId, const NetworkEntityTracker* entityTracker);
//! Constructs a ConstNetworkEntityHandle given an entity, a networked entityId, an entity tracker, and a dirty version
//! @param netBindComponent pointer to the entities NetBindComponent
//! @param entityTracker pointer to the entity tracker that tracks the entity
ConstNetworkEntityHandle(NetBindComponent* netBindComponent, const NetworkEntityTracker* entityTracker);
ConstNetworkEntityHandle(const ConstNetworkEntityHandle&) = default;
//! Access the AZ::Entity if it safely exists, nullptr or false is returned if the entity does not exist.
//! @{
bool Exists() const;
AZ::Entity* GetEntity();
const AZ::Entity* GetEntity() const;
//! @}
//! Operators providing pointer semantics.
//! @{
bool operator ==(const ConstNetworkEntityHandle& rhs) const;
bool operator !=(const ConstNetworkEntityHandle& rhs) const;
friend bool operator ==(const ConstNetworkEntityHandle& lhs, AZStd::nullptr_t);
friend bool operator ==(AZStd::nullptr_t, const ConstNetworkEntityHandle& rhs);
friend bool operator !=(const ConstNetworkEntityHandle& lhs, AZStd::nullptr_t);
friend bool operator !=(AZStd::nullptr_t, const ConstNetworkEntityHandle& rhs);
friend bool operator ==(const ConstNetworkEntityHandle& lhs, const AZ::Entity* rhs);
friend bool operator ==(const AZ::Entity* lhs, const ConstNetworkEntityHandle& rhs);
friend bool operator !=(const ConstNetworkEntityHandle& lhs, const AZ::Entity* rhs);
friend bool operator !=(const AZ::Entity* lhs, const ConstNetworkEntityHandle& rhs);
//! @}
bool operator <(const ConstNetworkEntityHandle& rhs) const;
explicit operator bool() const;
//! Resets the handle to a nullptr state.
void Reset();
void Reset(const ConstNetworkEntityHandle& handle);
//! Returns the networkEntityId of the entity this handle points to.
//! @return the networkEntityId of the entity this handle points to
NetEntityId GetNetEntityId() const;
//! Returns the cached netBindComponent for this entity, or nullptr if it doesn't exist.
//! @return the cached netBindComponent for this entity, or nullptr if it doesn't exist
NetBindComponent* GetNetBindComponent() const;
//! Returns a specific component on of entity given a typeId.
//! @param typeId the typeId of the component to find and return
//! @return pointer to the requested component, or nullptr if it doesn't exist on the entity
const AZ::Component* FindComponent(const AZ::TypeId& typeId) const;
//! Returns a specific component on of entity by class type.
//! @return pointer to the requested component, or nullptr if it doesn't exist on the entity
template <typename Component>
const Component* FindComponent() const;
//! Helper function for sorting EntityHandles by netEntityId.
static bool Compare(const ConstNetworkEntityHandle& lhs, const ConstNetworkEntityHandle& rhs);
protected:
mutable uint32_t m_changeDirty = 0; // Optimization so we don't need to recheck the hashmap
mutable AZ::Entity* m_entity = nullptr;
mutable NetBindComponent* m_netBindComponent = nullptr;
const NetworkEntityTracker* m_networkEntityTracker = nullptr;
NetEntityId m_netEntityId = InvalidNetEntityId;
};
class NetworkEntityHandle
: public ConstNetworkEntityHandle
{
public:
using ConstNetworkEntityHandle::ConstNetworkEntityHandle;
//! Initializes the underlying entity if possible.
void Init();
//! Activates the underlying entity if possible.
void Activate();
//! Deactivates the underlying entity if possible.
void Deactivate();
//! Gets the BaseController from the first component on an Entity with the supplied typeId AND which inherits from Multiplayer::BaseComponent
MultiplayerController* FindController(const AZ::TypeId& typeId);
template <typename Controller>
Controller* FindController();
using ConstNetworkEntityHandle::FindComponent;
AZ::Component* FindComponent(const AZ::TypeId& typeId);
template <typename ComponentType>
ComponentType* FindComponent();
};
}
#include "Source/NetworkEntity/NetworkEntityHandle.inl"
@@ -1,138 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
namespace Multiplayer
{
inline bool operator ==(const ConstNetworkEntityHandle& lhs, AZStd::nullptr_t)
{
return !lhs.Exists();
}
inline bool operator ==(AZStd::nullptr_t, const ConstNetworkEntityHandle& rhs)
{
return !rhs.Exists();
}
inline bool operator !=(const ConstNetworkEntityHandle& lhs, AZStd::nullptr_t)
{
return lhs.Exists();
}
inline bool operator !=(AZStd::nullptr_t, const ConstNetworkEntityHandle& rhs)
{
return rhs.Exists();
}
inline bool operator==(const ConstNetworkEntityHandle& lhs, const AZ::Entity* rhs)
{
return lhs.m_entity == rhs;
}
inline bool operator==(const AZ::Entity* lhs, const ConstNetworkEntityHandle& rhs)
{
return operator==(rhs, lhs);
}
inline bool operator!=(const ConstNetworkEntityHandle& lhs, const AZ::Entity* rhs)
{
return lhs.m_entity != rhs;
}
inline bool operator!=(const AZ::Entity* lhs, const ConstNetworkEntityHandle& rhs)
{
return operator!=(rhs, lhs);
}
inline NetEntityId ConstNetworkEntityHandle::GetNetEntityId() const
{
return m_netEntityId;
}
template <class ComponentType>
inline const ComponentType* ConstNetworkEntityHandle::FindComponent() const
{
if (const AZ::Entity* entity{ GetEntity() })
{
return entity->template FindComponent<ComponentType>();
}
return nullptr;
}
inline bool ConstNetworkEntityHandle::Compare(const ConstNetworkEntityHandle& lhs, const ConstNetworkEntityHandle& rhs)
{
return lhs.m_netEntityId < rhs.m_netEntityId;
}
inline void NetworkEntityHandle::Init()
{
if (AZ::Entity* entity{ GetEntity() })
{
entity->Init();
}
}
inline void NetworkEntityHandle::Activate()
{
if (AZ::Entity* entity{ GetEntity() })
{
entity->Activate();
}
}
inline void NetworkEntityHandle::Deactivate()
{
if (AZ::Entity* entity{ GetEntity() })
{
entity->Deactivate();
}
}
template <typename ControllerType>
inline ControllerType* NetworkEntityHandle::FindController()
{
return static_cast<ControllerType*>(FindController(ControllerType::ComponentType::RTTI_Type()));
}
template <typename ComponentType>
inline ComponentType* NetworkEntityHandle::FindComponent()
{
if (AZ::Entity* entity{ GetEntity() })
{
return entity->template FindComponent<ComponentType>();
}
return nullptr;
}
}
//! AZStd::hash support.
namespace AZStd
{
template <>
class hash<Multiplayer::NetworkEntityHandle>
{
public:
size_t operator()(const Multiplayer::NetworkEntityHandle &rhs) const
{
return hash<Multiplayer::NetEntityId>()(rhs.GetNetEntityId());
}
};
template <>
class hash<Multiplayer::ConstNetworkEntityHandle>
{
public:
size_t operator()(const Multiplayer::ConstNetworkEntityHandle &rhs) const
{
return hash<Multiplayer::NetEntityId>()(rhs.GetNetEntityId());
}
};
}
@@ -218,7 +218,7 @@ namespace Multiplayer
{
NetBindComponent* netBindComponent = entity->FindComponent<NetBindComponent>();
AZ_Assert(netBindComponent != nullptr, "Attempting to send an RPC to an entity with no NetBindComponent");
netBindComponent->HandleRpcMessage(NetEntityRole::Server, rpcMessage);
netBindComponent->HandleRpcMessage(nullptr, NetEntityRole::Server, rpcMessage);
}
}
m_localDeferredRpcMessages.clear();
@@ -15,11 +15,11 @@
#include <AzCore/EBus/ScheduledEvent.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Include/IEntityDomain.h>
#include <Include/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkSpawnableLibrary.h>
#include <Source/Components/MultiplayerComponentRegistry.h>
@@ -11,7 +11,7 @@
*/
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
@@ -13,7 +13,7 @@
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/Component/Entity.h>
@@ -1,36 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Include/MultiplayerTypes.h>
#include <AzNetworking/DataStructures/FixedSizeBitset.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AzNetworking
{
class ISerializer;
}
namespace Multiplayer
{
class IMultiplayerComponentInput
{
public:
virtual ~IMultiplayerComponentInput() = default;
virtual NetComponentId GetComponentId() const = 0;
virtual bool Serialize(AzNetworking::ISerializer& serializer) = 0;
};
using MultiplayerComponentInputVector = AZStd::vector<AZStd::unique_ptr<IMultiplayerComponentInput>>;
}
@@ -48,19 +48,34 @@ namespace Multiplayer
return m_inputId;
}
void NetworkInput::SetServerTimeMs(AZ::TimeMs serverTimeMs)
void NetworkInput::SetHostFrameId(HostFrameId hostFrameId)
{
m_serverTimeMs = serverTimeMs;
m_hostFrameId = hostFrameId;
}
AZ::TimeMs NetworkInput::GetServerTimeMs() const
HostFrameId NetworkInput::GetHostFrameId() const
{
return m_serverTimeMs;
return m_hostFrameId;
}
AZ::TimeMs& NetworkInput::ModifyServerTimeMs()
HostFrameId& NetworkInput::ModifyHostFrameId()
{
return m_serverTimeMs;
return m_hostFrameId;
}
void NetworkInput::SetHostTimeMs(AZ::TimeMs hostTimeMs)
{
m_hostTimeMs = hostTimeMs;
}
AZ::TimeMs NetworkInput::GetHostTimeMs() const
{
return m_hostTimeMs;
}
AZ::TimeMs& NetworkInput::ModifyHostTimeMs()
{
return m_hostTimeMs;
}
void NetworkInput::AttachNetBindComponent(NetBindComponent* netBindComponent)
@@ -76,17 +91,19 @@ namespace Multiplayer
bool NetworkInput::Serialize(AzNetworking::ISerializer& serializer)
{
if (!serializer.Serialize(m_inputId, "InputId"))
if (!serializer.Serialize(m_inputId, "InputId")
|| !serializer.Serialize(m_hostTimeMs, "HostTimeMs")
|| !serializer.Serialize(m_hostFrameId, "HostFrameId"))
{
return false;
}
uint8_t componentInputCount = static_cast<uint8_t>(m_componentInputs.size());
uint16_t componentInputCount = static_cast<uint16_t>(m_componentInputs.size());
serializer.Serialize(componentInputCount, "ComponentInputCount");
m_componentInputs.resize(componentInputCount);
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{
for (uint8_t i = 0; i < componentInputCount; ++i)
for (uint16_t i = 0; i < componentInputCount; ++i)
{
// We need to do a little extra work here, the delta serializer won't actually write out values if they were the same as the parent.
// We need to make sure we don't lose state that is intrinsic to the underlying type
@@ -148,7 +165,7 @@ namespace Multiplayer
void NetworkInput::CopyInternal(const NetworkInput& rhs)
{
m_inputId = rhs.m_inputId;
m_serverTimeMs = rhs.m_serverTimeMs;
m_hostTimeMs = rhs.m_hostTimeMs;
m_componentInputs.resize(rhs.m_componentInputs.size());
for (int32_t i = 0; i < rhs.m_componentInputs.size(); ++i)
{
@@ -12,8 +12,9 @@
#pragma once
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/INetworkTime.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
namespace Multiplayer
@@ -21,8 +22,6 @@ namespace Multiplayer
// Forwards
class NetBindComponent;
AZ_TYPE_SAFE_INTEGRAL(ClientInputId, uint16_t);
//! @class NetworkInput
//! @brief A single networked client input command.
class NetworkInput final
@@ -30,8 +29,8 @@ namespace Multiplayer
public:
//! Intentionally restrict instancing of these objects to associated containers classes only
//! This is a mechanism used to restrict calling autonomous client predicted setter functions to the ProcessInput call chain only
friend class NetworkInputVector;
friend class MigrateNetworkInputVector;
friend class NetworkInputArray;
friend class NetworkInputMigrationVector;
friend class NetworkInputHistory;
friend class NetworkInputChild;
@@ -42,9 +41,13 @@ namespace Multiplayer
ClientInputId GetClientInputId() const;
ClientInputId& ModifyClientInputId();
void SetServerTimeMs(AZ::TimeMs serverTimeMs);
AZ::TimeMs GetServerTimeMs() const;
AZ::TimeMs& ModifyServerTimeMs();
void SetHostFrameId(HostFrameId hostFrameId);
HostFrameId GetHostFrameId() const;
HostFrameId& ModifyHostFrameId();
void SetHostTimeMs(AZ::TimeMs hostTimeMs);
AZ::TimeMs GetHostTimeMs() const;
AZ::TimeMs& ModifyHostTimeMs();
void AttachNetBindComponent(NetBindComponent* netBindComponent);
@@ -72,10 +75,9 @@ namespace Multiplayer
MultiplayerComponentInputVector m_componentInputs;
ClientInputId m_inputId = ClientInputId{ 0 };
AZ::TimeMs m_serverTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_hostFrameId = InvalidHostFrameId;
AZ::TimeMs m_hostTimeMs = AZ::TimeMs{ 0 };
ConstNetworkEntityHandle m_owner;
bool m_wasAttached = false;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ClientInputId);
@@ -10,21 +10,21 @@
*
*/
#include <Source/NetworkInput/NetworkInputVector.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkInput/NetworkInputArray.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Serialization/DeltaSerializer.h>
namespace Multiplayer
{
NetworkInputVector::NetworkInputVector()
NetworkInputArray::NetworkInputArray()
: m_owner()
, m_inputs()
{
;
}
NetworkInputVector::NetworkInputVector(const ConstNetworkEntityHandle& entityHandle)
NetworkInputArray::NetworkInputArray(const ConstNetworkEntityHandle& entityHandle)
: m_owner(entityHandle)
, m_inputs()
{
@@ -38,27 +38,27 @@ namespace Multiplayer
}
}
NetworkInput& NetworkInputVector::operator[](uint32_t index)
NetworkInput& NetworkInputArray::operator[](uint32_t index)
{
return m_inputs[index].m_networkInput;
}
const NetworkInput& NetworkInputVector::operator[](uint32_t index) const
const NetworkInput& NetworkInputArray::operator[](uint32_t index) const
{
return m_inputs[index].m_networkInput;
}
void NetworkInputVector::SetPreviousInputId(ClientInputId previousInputId)
void NetworkInputArray::SetPreviousInputId(ClientInputId previousInputId)
{
m_previousInputId = previousInputId;
}
ClientInputId NetworkInputVector::GetPreviousInputId() const
ClientInputId NetworkInputArray::GetPreviousInputId() const
{
return m_previousInputId;
}
bool NetworkInputVector::Serialize(AzNetworking::ISerializer& serializer)
bool NetworkInputArray::Serialize(AzNetworking::ISerializer& serializer)
{
// Always serialize the full first element
if (!m_inputs[0].m_networkInput.Serialize(serializer))
@@ -105,67 +105,4 @@ namespace Multiplayer
serializer.Serialize(m_previousInputId, "PreviousInputId");
return true;
}
MigrateNetworkInputVector::MigrateNetworkInputVector()
: m_owner()
{
;
}
MigrateNetworkInputVector::MigrateNetworkInputVector(const ConstNetworkEntityHandle& entityHandle)
: m_owner(entityHandle)
{
;
}
uint32_t MigrateNetworkInputVector::GetSize() const
{
return aznumeric_cast<uint32_t>(m_inputs.size());
}
NetworkInput& MigrateNetworkInputVector::operator[](uint32_t index)
{
return m_inputs[index].m_networkInput;
}
const NetworkInput& MigrateNetworkInputVector::operator[](uint32_t index) const
{
return m_inputs[index].m_networkInput;
}
bool MigrateNetworkInputVector::PushBack(const NetworkInput& networkInput)
{
if (m_inputs.size() < m_inputs.capacity())
{
m_inputs.push_back(networkInput);
return true;
}
return false;
}
bool MigrateNetworkInputVector::Serialize(AzNetworking::ISerializer& serializer)
{
NetEntityId ownerId = m_owner.GetNetEntityId();
serializer.Serialize(ownerId, "OwnerId");
uint32_t inputCount = aznumeric_cast<uint32_t>(m_inputs.size());
serializer.Serialize(inputCount, "InputCount");
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{
// make sure all the possible NetworkInputs get attached prior to serialization, this double sends the size, but this message is only sent on server migration
m_inputs.resize(inputCount);
m_owner = GetNetworkEntityManager()->GetEntity(ownerId);
NetBindComponent* netBindComponent = m_owner.GetNetBindComponent();
if (netBindComponent)
{
for (uint32_t i = 0; i < m_inputs.size(); ++i)
{
m_inputs[i].m_networkInput.AttachNetBindComponent(netBindComponent);
}
}
}
return serializer.Serialize(m_inputs, "Inputs");
}
}
@@ -0,0 +1,54 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Source/NetworkInput/NetworkInput.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace Multiplayer
{
//! @class NetworkInputArray
//! @brief An array of network inputs. Used to mitigate loss of input packets on the server. Compresses subsequent elements.
class NetworkInputArray final
{
public:
static constexpr uint32_t MaxElements = 8; // Never try to replicate a list larger than this amount
NetworkInputArray();
NetworkInputArray(const ConstNetworkEntityHandle& entityHandle);
~NetworkInputArray() = default;
NetworkInput& operator[](uint32_t index);
const NetworkInput& operator[](uint32_t index) const;
void SetPreviousInputId(ClientInputId previousInputId);
ClientInputId GetPreviousInputId() const;
bool Serialize(AzNetworking::ISerializer& serializer);
private:
struct Wrapper // Strictly a workaround to deal with the private constructor of NetworkInput
{
Wrapper() : m_networkInput() {}
Wrapper(const NetworkInput& networkInput) : m_networkInput(networkInput) {}
NetworkInput m_networkInput;
};
ConstNetworkEntityHandle m_owner;
AZStd::array<Wrapper, MaxElements> m_inputs;
ClientInputId m_previousInputId;
};
}
@@ -11,7 +11,7 @@
*/
#include <Source/NetworkInput/NetworkInputChild.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h>
namespace Multiplayer
@@ -0,0 +1,80 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Source/NetworkInput/NetworkInputMigrationVector.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h>
namespace Multiplayer
{
NetworkInputMigrationVector::NetworkInputMigrationVector()
: m_owner()
{
;
}
NetworkInputMigrationVector::NetworkInputMigrationVector(const ConstNetworkEntityHandle& entityHandle)
: m_owner(entityHandle)
{
;
}
uint32_t NetworkInputMigrationVector::GetSize() const
{
return aznumeric_cast<uint32_t>(m_inputs.size());
}
NetworkInput& NetworkInputMigrationVector::operator[](uint32_t index)
{
return m_inputs[index].m_networkInput;
}
const NetworkInput& NetworkInputMigrationVector::operator[](uint32_t index) const
{
return m_inputs[index].m_networkInput;
}
bool NetworkInputMigrationVector::PushBack(const NetworkInput& networkInput)
{
if (m_inputs.size() < m_inputs.capacity())
{
m_inputs.push_back(networkInput);
return true;
}
return false;
}
bool NetworkInputMigrationVector::Serialize(AzNetworking::ISerializer& serializer)
{
NetEntityId ownerId = m_owner.GetNetEntityId();
serializer.Serialize(ownerId, "OwnerId");
uint32_t inputCount = aznumeric_cast<uint32_t>(m_inputs.size());
serializer.Serialize(inputCount, "InputCount");
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{
// make sure all the possible NetworkInputs get attached prior to serialization, this double sends the size, but this message is only sent on server migration
m_inputs.resize(inputCount);
m_owner = GetNetworkEntityManager()->GetEntity(ownerId);
NetBindComponent* netBindComponent = m_owner.GetNetBindComponent();
if (netBindComponent)
{
for (uint32_t i = 0; i < m_inputs.size(); ++i)
{
m_inputs[i].m_networkInput.AttachNetBindComponent(netBindComponent);
}
}
}
return serializer.Serialize(m_inputs, "Inputs");
}
}
@@ -13,54 +13,22 @@
#pragma once
#include <Source/NetworkInput/NetworkInput.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace Multiplayer
{
//! @class NetworkInputVector
//! @brief An array of network inputs. Used to mitigate loss of input packets on the server. Compresses subsequent elements.
class NetworkInputVector final
{
public:
static constexpr uint32_t MaxElements = 8; // Never try to replicate a list larger than this amount
NetworkInputVector();
NetworkInputVector(const ConstNetworkEntityHandle& entityHandle);
~NetworkInputVector() = default;
NetworkInput& operator[](uint32_t index);
const NetworkInput& operator[](uint32_t index) const;
void SetPreviousInputId(ClientInputId previousInputId);
ClientInputId GetPreviousInputId() const;
bool Serialize(AzNetworking::ISerializer& serializer);
private:
struct Wrapper // Strictly a workaround to deal with the private constructor of NetworkInput
{
Wrapper() : m_networkInput() {}
Wrapper(const NetworkInput& networkInput) : m_networkInput(networkInput) {}
NetworkInput m_networkInput;
};
ConstNetworkEntityHandle m_owner;
AZStd::fixed_vector<Wrapper, MaxElements> m_inputs;
ClientInputId m_previousInputId;
};
//! @class MigrateNetworkInputVector
//! @class NetworkInputMigrationVector
//! @brief A variable sized array of input commands, used specifically when migrate a clients inputs.
class MigrateNetworkInputVector final
class NetworkInputMigrationVector final
{
public:
static constexpr uint32_t MaxElements = 90; // Never try to migrate a list larger than this amount, bumped up to handle DTLS connection time
MigrateNetworkInputVector();
MigrateNetworkInputVector(const ConstNetworkEntityHandle& entityHandle);
virtual ~MigrateNetworkInputVector() = default;
NetworkInputMigrationVector();
NetworkInputMigrationVector(const ConstNetworkEntityHandle& entityHandle);
virtual ~NetworkInputMigrationVector() = default;
uint32_t GetSize() const;
NetworkInput& operator[](uint32_t index);
@@ -1,114 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Time/ITime.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
namespace Multiplayer
{
//! This is a strong typedef for representing the number of application frames since application start.
AZ_TYPE_SAFE_INTEGRAL(ApplicationFrameId, uint32_t);
static constexpr ApplicationFrameId InvalidApplicationFrameId = ApplicationFrameId{0xFFFFFFFF};
//! @class INetworkTime
//! @brief This is an AZ::Interface<> for managing multiplayer specific time related operations.
class INetworkTime
{
public:
AZ_RTTI(INetworkTime, "{7D468063-255B-4FEE-86E1-6D750EEDD42A}");
INetworkTime() = default;
virtual ~INetworkTime() = default;
//! Converts from an ApplicationFrameId to a corresponding TimeMs.
//! @param frameId the ApplicationFrameId to convert to a TimeMs
//! @return the TimeMs that corresponds to the provided ApplicationFrameId
virtual AZ::TimeMs ConvertFrameIdToTimeMs(ApplicationFrameId frameId) const = 0;
//! Converts from a TimeMs to an ApplicationFrameId.
//! @param timeMs the TimeMs to convert to an ApplicationFrameId
//! @return the ApplicationFrameId that corresponds to the provided TimeMs
virtual ApplicationFrameId ConvertTimeMsToFrameId(AZ::TimeMs timeMs) const = 0;
//! Returns true if the application frameId has been temporarily altered.
//! @return true if the application frameId has been altered, false otherwise
virtual bool IsApplicationFrameIdRewound() const = 0;
//! Retrieves the applications current frameId (may be rewound on the server during backward reconciliation).
//! @return the applications current frameId
virtual ApplicationFrameId GetApplicationFrameId() const = 0;
//! Retrieves the unaltered applications current frameId.
//! @return the applications current frameId, unaltered by any scoped time instance
virtual ApplicationFrameId GetUnalteredApplicationFrameId() const = 0;
//! Increments the applications current frameId.
virtual void IncrementApplicationFrameId() = 0;
//! Synchronizes rewindable entity state for the current application time.
virtual void SyncRewindableEntityState() = 0;
//! Get the controlling connection that may be currently altering global game time.
//! Note this abstraction is required at a relatively high level to allow for 'don't rewind the shooter' semantics
//! @return the ConnectionId of the connection requesting the rewind operation
virtual AzNetworking::ConnectionId GetRewindingConnectionId() const = 0;
//! Get the controlling connection that may be currently altering global game time.
//! Note this abstraction is required at a relatively high level to allow for 'don't rewind the shooter' semantics
//! @param rewindConnectionId if this parameter matches the current rewindConnectionId, it will return the unaltered applicationFrameId
//! @return the ApplicationFrameId taking into account the provided rewinding connectionId
virtual ApplicationFrameId GetApplicationFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const = 0;
//! Alters the current ApplicationFrameId and binds that alteration to the provided ConnectionId.
//! @param frameId the new ApplicationFrameId to use
//! @param rewindConnectionId the rewinding ConnectionId
virtual void AlterApplicationFrameId(ApplicationFrameId frameId, AzNetworking::ConnectionId rewindConnectionId) = 0;
AZ_DISABLE_COPY_MOVE(INetworkTime);
};
// EBus wrapper for ScriptCanvas
class INetworkTimeRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using INetworkTimeRequestBus = AZ::EBus<INetworkTime, INetworkTimeRequests>;
//! @class ScopedAlterTime
//! @brief This is a wrapper that temporarily adjusts global program time for backward reconciliation purposes.
class ScopedAlterTime final
{
public:
inline ScopedAlterTime(ApplicationFrameId frameId, AzNetworking::ConnectionId connectionId)
{
INetworkTime* time = AZ::Interface<INetworkTime>::Get();
m_previousApplicationFrameId = time->GetApplicationFrameId();
m_previousRewindConnectionId = time->GetRewindingConnectionId();
time->AlterApplicationFrameId(frameId, connectionId);
}
inline ~ScopedAlterTime()
{
INetworkTime* time = AZ::Interface<INetworkTime>::Get();
time->AlterApplicationFrameId(m_previousApplicationFrameId, m_previousRewindConnectionId);
}
private:
ApplicationFrameId m_previousApplicationFrameId = InvalidApplicationFrameId;
AzNetworking::ConnectionId m_previousRewindConnectionId = AzNetworking::InvalidConnectionId;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ApplicationFrameId);
@@ -24,36 +24,31 @@ namespace Multiplayer
AZ::Interface<INetworkTime>::Unregister(this);
}
AZ::TimeMs NetworkTime::ConvertFrameIdToTimeMs([[maybe_unused]] ApplicationFrameId frameId) const
{
return AZ::TimeMs{0};
}
ApplicationFrameId NetworkTime::ConvertTimeMsToFrameId([[maybe_unused]] AZ::TimeMs timeMs) const
{
return ApplicationFrameId{0};
}
bool NetworkTime::IsApplicationFrameIdRewound() const
bool NetworkTime::IsTimeRewound() const
{
return m_rewindingConnectionId != AzNetworking::InvalidConnectionId;
}
ApplicationFrameId NetworkTime::GetApplicationFrameId() const
HostFrameId NetworkTime::GetHostFrameId() const
{
return m_applicationFrameId;
return m_hostFrameId;
}
ApplicationFrameId NetworkTime::GetUnalteredApplicationFrameId() const
HostFrameId NetworkTime::GetUnalteredHostFrameId() const
{
return m_unalteredFrameId;
}
void NetworkTime::IncrementApplicationFrameId()
void NetworkTime::IncrementHostFrameId()
{
AZ_Assert(!IsApplicationFrameIdRewound(), "Incrementing the global application frameId is unsupported under a rewound time scope");
AZ_Assert(!IsTimeRewound(), "Incrementing the global application frameId is unsupported under a rewound time scope");
++m_unalteredFrameId;
m_applicationFrameId = m_unalteredFrameId;
m_hostFrameId = m_unalteredFrameId;
}
AZ::TimeMs NetworkTime::GetHostTimeMs() const
{
return m_hostTimeMs;
}
void NetworkTime::SyncRewindableEntityState()
@@ -66,14 +61,15 @@ namespace Multiplayer
return m_rewindingConnectionId;
}
ApplicationFrameId NetworkTime::GetApplicationFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const
HostFrameId NetworkTime::GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const
{
return (IsApplicationFrameIdRewound() && (rewindConnectionId == m_rewindingConnectionId)) ? m_unalteredFrameId : m_applicationFrameId;
return (IsTimeRewound() && (rewindConnectionId == m_rewindingConnectionId)) ? m_unalteredFrameId : m_hostFrameId;
}
void NetworkTime::AlterApplicationFrameId(ApplicationFrameId frameId, AzNetworking::ConnectionId rewindConnectionId)
void NetworkTime::AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId)
{
m_applicationFrameId = frameId;
m_hostFrameId = frameId;
m_hostTimeMs = timeMs;
m_rewindingConnectionId = rewindConnectionId;
}
}
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkTime/INetworkTime.h>
#include <Include/INetworkTime.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Console/IConsole.h>
@@ -28,22 +28,22 @@ namespace Multiplayer
//! INetworkTime overrides.
//! @{
AZ::TimeMs ConvertFrameIdToTimeMs(ApplicationFrameId frameId) const override;
ApplicationFrameId ConvertTimeMsToFrameId(AZ::TimeMs timeMs) const override;
bool IsApplicationFrameIdRewound() const override;
ApplicationFrameId GetApplicationFrameId() const override;
ApplicationFrameId GetUnalteredApplicationFrameId() const override;
void IncrementApplicationFrameId() override;
bool IsTimeRewound() const override;
HostFrameId GetHostFrameId() const override;
HostFrameId GetUnalteredHostFrameId() const override;
void IncrementHostFrameId() override;
AZ::TimeMs GetHostTimeMs() const override;
void SyncRewindableEntityState() override;
AzNetworking::ConnectionId GetRewindingConnectionId() const override;
ApplicationFrameId GetApplicationFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override;
void AlterApplicationFrameId(ApplicationFrameId frameId, AzNetworking::ConnectionId rewindConnectionId) override;
HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override;
void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) override;
//! @}
private:
ApplicationFrameId m_applicationFrameId = ApplicationFrameId{0};
ApplicationFrameId m_unalteredFrameId = ApplicationFrameId{0};
HostFrameId m_hostFrameId = HostFrameId{ 0 };
HostFrameId m_unalteredFrameId = HostFrameId{ 0 };
AZ::TimeMs m_hostTimeMs = AZ::TimeMs{ 0 };
AzNetworking::ConnectionId m_rewindingConnectionId = AzNetworking::InvalidConnectionId;
};
}
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkTime/INetworkTime.h>
#include <Include/INetworkTime.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
@@ -87,25 +87,25 @@ namespace Multiplayer
//! Returns what the appropriate current time is for this rewindable property.
//! @return the appropriate current time is for this rewindable property
ApplicationFrameId GetCurrentTimeForProperty() const;
HostFrameId GetCurrentTimeForProperty() const;
//! Updates the latest value for this object instance, if frameTime represents a current or future time.
//! Any attempts to set old values on the object will fail
//! @param value the new value to set in the object history
//! @param frameTime the time to set the value for
void SetValueForTime(const BASE_TYPE& value, ApplicationFrameId frameTime);
void SetValueForTime(const BASE_TYPE& value, HostFrameId frameTime);
//! Const value accessor, returns the correct value for the provided input time.
//! @param frameTime the frame time to return the associated value for
//! @return value given the current input time
const BASE_TYPE& GetValueForTime(ApplicationFrameId frameTime) const;
const BASE_TYPE& GetValueForTime(HostFrameId frameTime) const;
//! Helper method to compute clamped array index values accounting for the offset head index.
AZStd::size_t GetOffsetIndex(AZStd::size_t absoluteIndex) const;
AZStd::array<BASE_TYPE, REWIND_SIZE> m_history;
AzNetworking::ConnectionId m_owningConnectionId = AzNetworking::InvalidConnectionId;
ApplicationFrameId m_headTime = ApplicationFrameId{0};
HostFrameId m_headTime = HostFrameId{0};
uint32_t m_headIndex = 0;
};
}
@@ -48,7 +48,7 @@ namespace Multiplayer
inline RewindableObject<BASE_TYPE, REWIND_SIZE> &RewindableObject<BASE_TYPE, REWIND_SIZE>::operator =(const RewindableObject<BASE_TYPE, REWIND_SIZE>& rhs)
{
INetworkTime* networkTime = AZ::Interface<INetworkTime>::Get();
SetValueForTime(rhs.GetValueForTime(networkTime->GetApplicationFrameId()), GetCurrentTimeForProperty());
SetValueForTime(rhs.GetValueForTime(networkTime->GetHostFrameId()), GetCurrentTimeForProperty());
return *this;
}
@@ -73,7 +73,7 @@ namespace Multiplayer
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline BASE_TYPE& RewindableObject<BASE_TYPE, REWIND_SIZE>::Modify()
{
const ApplicationFrameId frameTime = GetCurrentTimeForProperty();
const HostFrameId frameTime = GetCurrentTimeForProperty();
if (frameTime < m_headTime)
{
AZ_Assert(false, "Trying to mutate a rewindable in the past");
@@ -103,7 +103,7 @@ namespace Multiplayer
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline bool RewindableObject<BASE_TYPE, REWIND_SIZE>::Serialize(AzNetworking::ISerializer& serializer)
{
const ApplicationFrameId frameTime = GetCurrentTimeForProperty();
const HostFrameId frameTime = GetCurrentTimeForProperty();
BASE_TYPE value = GetValueForTime(frameTime);
if (serializer.Serialize(value, "Element") && (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject))
{
@@ -113,14 +113,14 @@ namespace Multiplayer
}
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline ApplicationFrameId RewindableObject<BASE_TYPE, REWIND_SIZE>::GetCurrentTimeForProperty() const
inline HostFrameId RewindableObject<BASE_TYPE, REWIND_SIZE>::GetCurrentTimeForProperty() const
{
INetworkTime* networkTime = AZ::Interface<INetworkTime>::Get();
return networkTime->GetApplicationFrameIdForRewindingConnection(m_owningConnectionId);
return networkTime->GetHostFrameIdForRewindingConnection(m_owningConnectionId);
}
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline void RewindableObject<BASE_TYPE, REWIND_SIZE>::SetValueForTime(const BASE_TYPE& value, ApplicationFrameId frameTime)
inline void RewindableObject<BASE_TYPE, REWIND_SIZE>::SetValueForTime(const BASE_TYPE& value, HostFrameId frameTime)
{
if (frameTime < m_headTime)
{
@@ -155,7 +155,7 @@ namespace Multiplayer
}
template <typename BASE_TYPE, AZStd::size_t REWIND_SIZE>
inline const BASE_TYPE &RewindableObject<BASE_TYPE, REWIND_SIZE>::GetValueForTime(ApplicationFrameId frameTime) const
inline const BASE_TYPE &RewindableObject<BASE_TYPE, REWIND_SIZE>::GetValueForTime(HostFrameId frameTime) const
{
if (frameTime > m_headTime)
{
@@ -1,42 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <AzCore/std/containers/unordered_map.h>
namespace Multiplayer
{
struct EntityReplicationData
{
EntityReplicationData() = default;
NetEntityRole m_netEntityRole = NetEntityRole::InvalidRole;
float m_priority = 0.0f;
};
using ReplicationSet = AZStd::unordered_map<ConstNetworkEntityHandle, EntityReplicationData>;
class IReplicationWindow
{
public:
virtual ~IReplicationWindow() = default;
virtual bool ReplicationSetUpdateReady() = 0;
virtual const ReplicationSet& GetReplicationSet() const = 0;
//! Max number of entities we can send updates for in one frame
virtual uint32_t GetMaxEntityReplicatorSendCount() const = 0;
virtual bool IsInWindow(const ConstNetworkEntityHandle& entityPtr, NetEntityRole& outNetworkRole) const = 0;
virtual void UpdateWindow() = 0;
virtual void DebugDraw() const = 0;
};
}
@@ -10,7 +10,7 @@
*
*/
#include "NullReplicationWindow.h"
#include <Source/ReplicationWindows/NullReplicationWindow.h>
namespace Multiplayer
{
@@ -12,7 +12,7 @@
#pragma once
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Include/IReplicationWindow.h>
namespace Multiplayer
{
@@ -56,6 +56,8 @@ namespace Multiplayer
ServerToClientReplicationWindow::ServerToClientReplicationWindow(NetworkEntityHandle controlledEntity, const AzNetworking::IConnection* connection)
: m_controlledEntity(controlledEntity)
, m_entityActivatedEventHandler([this](AZ::Entity* entity) { OnEntityActivated(entity); })
, m_entityDeactivatedEventHandler([this](AZ::Entity* entity) { OnEntityDeactivated(entity); })
, m_connection(connection)
, m_lastCheckedSentPackets(connection->GetMetrics().m_packetsSent)
, m_lastCheckedLostPackets(connection->GetMetrics().m_packetsLost)
@@ -74,7 +76,9 @@ namespace Multiplayer
//}
m_updateWindowEvent.Enqueue(sv_ClientReplicationWindowUpdateMs, true);
AZ::EntitySystemBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Get()->RegisterEntityActivatedEventHandler(m_entityActivatedEventHandler);
AZ::Interface<AZ::ComponentApplicationRequests>::Get()->RegisterEntityDeactivatedEventHandler(m_entityDeactivatedEventHandler);
}
bool ServerToClientReplicationWindow::ReplicationSetUpdateReady()
@@ -146,24 +150,29 @@ namespace Multiplayer
}
);
NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker();
// Add all the neighbors
for (AzFramework::VisibilityEntry* visEntry : gatheredEntries)
{
// TODO: Discard entities that don't have a NetBindComponent
//if (mp_ControlledFilteredEntityComponent && mp_ControlledFilteredEntityComponent->IsEntityFiltered(iterator.Get()))
//{
// continue;
//}
// We want to find the closest extent to the player and prioritize using that distance
const AZ::Vector3 supportNormal = controlledEntityPosition - visEntry->m_boundingVolume.GetCenter();
const AZ::Vector3 closestPosition = visEntry->m_boundingVolume.GetSupport(supportNormal);
const float gatherDistanceSquared = controlledEntityPosition.GetDistanceSq(closestPosition);
const float priority = (gatherDistanceSquared > 0.0f) ? 1.0f / gatherDistanceSquared : 0.0f;
AZ::Entity* entity = static_cast<AZ::Entity*>(visEntry->m_userData);
NetworkEntityHandle entityHandle(entity, GetNetworkEntityTracker());
AddEntityToReplicationSet(entityHandle, priority, gatherDistanceSquared);
NetBindComponent* entryNetBindComponent = entity->template FindComponent<NetBindComponent>();
if (entryNetBindComponent != nullptr)
{
const AZ::Vector3 supportNormal = controlledEntityPosition - visEntry->m_boundingVolume.GetCenter();
const AZ::Vector3 closestPosition = visEntry->m_boundingVolume.GetSupport(supportNormal);
const float gatherDistanceSquared = controlledEntityPosition.GetDistanceSq(closestPosition);
const float priority = (gatherDistanceSquared > 0.0f) ? 1.0f / gatherDistanceSquared : 0.0f;
NetworkEntityHandle entityHandle(entryNetBindComponent, networkEntityTracker);
AddEntityToReplicationSet(entityHandle, priority, gatherDistanceSquared);
}
}
// Add in Autonomous Entities
@@ -200,10 +209,8 @@ namespace Multiplayer
//}
}
void ServerToClientReplicationWindow::OnEntityActivated(const AZ::EntityId& entityId)
void ServerToClientReplicationWindow::OnEntityActivated(AZ::Entity* entity)
{
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
ConstNetworkEntityHandle entityHandle(entity, GetNetworkEntityTracker());
NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent();
if (netBindComponent != nullptr)
@@ -231,10 +238,8 @@ namespace Multiplayer
}
}
void ServerToClientReplicationWindow::OnEntityDeactivated(const AZ::EntityId& entityId)
void ServerToClientReplicationWindow::OnEntityDeactivated(AZ::Entity* entity)
{
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
ConstNetworkEntityHandle entityHandle(entity, GetNetworkEntityTracker());
NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent();
if (netBindComponent != nullptr)
@@ -12,9 +12,9 @@
#pragma once
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
#include <Include/IReplicationWindow.h>
#include <Include/NetworkEntityHandle.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzCore/Component/EntityBus.h>
#include <AzCore/EBus/ScheduledEvent.h>
@@ -27,7 +27,6 @@ namespace Multiplayer
class ServerToClientReplicationWindow
: public IReplicationWindow
, public AZ::EntitySystemBus::Handler
{
public:
@@ -56,11 +55,8 @@ namespace Multiplayer
//! @}
private:
//! EntitySystemBus interface
//! @{
void OnEntityActivated(const AZ::EntityId&) override;
void OnEntityDeactivated(const AZ::EntityId&) override;
//! @}
void OnEntityActivated(AZ::Entity* entity);
void OnEntityDeactivated(AZ::Entity* entity);
//void CollectControlledEntitiesRecursive(ReplicationSet& replicationSet, EntityHierarchyComponent::Authority& hierarchyController);
//void OnAddFilteredEntity(NetEntityId filteredEntityId);
@@ -79,6 +75,9 @@ namespace Multiplayer
NetworkEntityHandle m_controlledEntity;
AZ::TransformInterface* m_controlledEntityTransform = nullptr;
AZ::EntityActivatedEvent::Handler m_entityActivatedEventHandler;
AZ::EntityDeactivatedEvent::Handler m_entityDeactivatedEventHandler;
//FilteredEntityComponent::Authority* m_controlledFilteredEntityComponent = nullptr;
//NetBindComponent* m_controlledNetBindComponent = nullptr;