Added delta replication and stat tracking to vector and array network property container types, + some minor cleanup I ran into when looking for the best hook points for this functionality

Signed-off-by: kberg-amzn <karlberg@amazon.com>
This commit is contained in:
kberg-amzn
2021-08-30 12:05:53 -07:00
parent 86770deefa
commit 99d71cfbc5
8 changed files with 144 additions and 52 deletions
@@ -102,11 +102,34 @@ namespace Multiplayer
return GetEntity()->FindComponent<ComponentType>();
}
inline void UpdateComponentMetrics
(
bool modifyRecord,
uint32_t prevSerializerSize,
uint32_t currSerializerSize,
NetComponentId componentId,
PropertyIndex propertyIndex,
MultiplayerStats& stats
)
{
const uint32_t updateSize = (currSerializerSize - prevSerializerSize);
if (updateSize > 0)
{
if (modifyRecord)
{
stats.RecordPropertyReceived(componentId, propertyIndex, updateSize);
}
else
{
stats.RecordPropertySent(componentId, propertyIndex, updateSize);
}
}
}
template <typename TYPE>
inline void SerializeNetworkPropertyHelper
(
AzNetworking::ISerializer& serializer,
bool modifyRecord,
AzNetworking::FixedSizeBitsetView& bitset,
int32_t bitIndex,
TYPE& value,
@@ -118,6 +141,7 @@ namespace Multiplayer
{
if (bitset.GetBit(bitIndex))
{
const bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject;
const uint32_t prevUpdateSize = serializer.GetSize();
serializer.ClearTrackedChangesFlag();
serializer.Serialize(value, name);
@@ -127,19 +151,78 @@ namespace Multiplayer
bitset.SetBit(bitIndex, false);
}
const uint32_t postUpdateSize = serializer.GetSize();
// Network Property metrics
const uint32_t updateSize = (postUpdateSize - prevUpdateSize);
if (updateSize > 0)
UpdateComponentMetrics(modifyRecord, prevUpdateSize, postUpdateSize, componentId, propertyIndex, stats);
}
}
template <typename TYPE, AZStd::size_t SIZE>
inline void SerializeNetworkPropertyHelperArray
(
AzNetworking::ISerializer& serializer,
AzNetworking::FixedSizeBitsetView& bitset,
AZStd::array<TYPE, SIZE>& value,
NetComponentId componentId,
PropertyIndex propertyIndex,
MultiplayerStats& stats
)
{
const bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject;
const uint32_t prevUpdateSize = serializer.GetSize();
for (uint32_t i = 0; i < SIZE; ++i)
{
if (bitset.GetBit(i))
{
if (modifyRecord)
serializer.ClearTrackedChangesFlag();
serializer.Serialize(value[i], "Element");
if (modifyRecord && !serializer.GetTrackedChangesFlag())
{
stats.RecordPropertyReceived(componentId, propertyIndex, updateSize);
}
else
{
stats.RecordPropertySent(componentId, propertyIndex, updateSize);
bitset.SetBit(i, false);
}
}
}
const uint32_t postUpdateSize = serializer.GetSize();
UpdateComponentMetrics(modifyRecord, prevUpdateSize, postUpdateSize, componentId, propertyIndex, stats);
}
template <typename TYPE, AZStd::size_t SIZE>
inline void SerializeNetworkPropertyHelperVector
(
AzNetworking::ISerializer& serializer,
AzNetworking::FixedSizeBitsetView& bitset,
AZStd::fixed_vector<TYPE, SIZE>& value,
NetComponentId componentId,
PropertyIndex propertyIndex,
MultiplayerStats& stats
)
{
const bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject;
const uint32_t prevUpdateSize = serializer.GetSize();
if (bitset.GetBit(SIZE))
{
using SizeType = typename AZ::SizeType<AZ::RequiredBytesForValue<SIZE>(), false>::Type;
SizeType origSize = aznumeric_cast<SizeType>(value.size());
SizeType newSize = origSize;
serializer.Serialize(newSize, "Size");
value.resize(newSize);
if (modifyRecord && origSize == newSize)
{
bitset.SetBit(SIZE, false);
}
}
for (uint32_t i = 0; i < value.size(); ++i)
{
if (bitset.GetBit(i))
{
serializer.ClearTrackedChangesFlag();
serializer.Serialize(value[i], "Element");
if (modifyRecord && !serializer.GetTrackedChangesFlag())
{
bitset.SetBit(i, false);
}
}
}
const uint32_t postUpdateSize = serializer.GetSize();
UpdateComponentMetrics(modifyRecord, prevUpdateSize, postUpdateSize, componentId, propertyIndex, stats);
}
}
@@ -17,12 +17,10 @@
namespace Multiplayer
{
constexpr AZStd::string_view MPNetworkInterfaceName("MultiplayerNetworkInterface");
constexpr AZStd::string_view MPEditorInterfaceName("MultiplayerEditorNetworkInterface");
constexpr AZStd::string_view MpNetworkInterfaceName("MultiplayerNetworkInterface");
constexpr AZStd::string_view MpEditorInterfaceName("MultiplayerEditorNetworkInterface");
constexpr AZStd::string_view LocalHost("127.0.0.1");
constexpr uint16_t DefaultServerPort = 33450;
constexpr uint16_t DefaultServerEditorPort = 33451;
}
@@ -86,7 +86,7 @@ namespace Multiplayer
Activate
};
// Structure for identifying a specific entity within a spawnable
//! Structure for identifying a specific entity within a spawnable.
struct PrefabEntityId
{
AZ_TYPE_INFO(PrefabEntityId, "{EFD37465-CCAC-4E87-A825-41B4010A2C75}");
@@ -97,29 +97,10 @@ namespace Multiplayer
uint32_t m_entityOffset = AllIndices;
PrefabEntityId() = default;
explicit PrefabEntityId(AZ::Name name, uint32_t entityOffset = AllIndices)
: m_prefabName(name)
, m_entityOffset(entityOffset)
{
}
bool operator==(const PrefabEntityId& rhs) const
{
return m_prefabName == rhs.m_prefabName && m_entityOffset == rhs.m_entityOffset;
}
bool operator!=(const PrefabEntityId& rhs) const
{
return !(*this == rhs);
}
bool Serialize(AzNetworking::ISerializer& serializer)
{
serializer.Serialize(m_prefabName, "prefabName");
serializer.Serialize(m_entityOffset, "entityOffset");
return serializer.IsValid();
}
explicit PrefabEntityId(AZ::Name name, uint32_t entityOffset = AllIndices);
bool operator==(const PrefabEntityId& rhs) const;
bool operator!=(const PrefabEntityId& rhs) const;
bool Serialize(AzNetworking::ISerializer& serializer);
};
struct EntityMigrationMessage
@@ -128,6 +109,30 @@ namespace Multiplayer
PrefabEntityId m_prefabEntityId;
AzNetworking::PacketEncodingBuffer m_propertyUpdateData;
};
inline PrefabEntityId::PrefabEntityId(AZ::Name name, uint32_t entityOffset)
: m_prefabName(name)
, m_entityOffset(entityOffset)
{
;
}
inline bool PrefabEntityId::operator==(const PrefabEntityId& rhs) const
{
return m_prefabName == rhs.m_prefabName && m_entityOffset == rhs.m_entityOffset;
}
inline bool PrefabEntityId::operator!=(const PrefabEntityId& rhs) const
{
return !(*this == rhs);
}
inline bool PrefabEntityId::Serialize(AzNetworking::ISerializer& serializer)
{
serializer.Serialize(m_prefabName, "prefabName");
serializer.Serialize(m_entityOffset, "entityOffset");
return serializer.IsValid();
}
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::HostId);
@@ -5,12 +5,12 @@
#}
{%- macro ParseRpcParams(property, outNames, outTypes, outDefines, use_default_value=False) -%}
{%- for Param in property.iter('Param') -%}
{%- do outNames.append(Param.attrib['Name']) -%}
{%- do outNames.append(LowerFirst(Param.attrib['Name'])) -%}
{%- do outTypes.append(Param.attrib['Type']) -%}
{%- if use_default_value and Param.attrib['DefaultValue'] -%}
{%- do outDefines.append('const ' ~ Param.attrib['Type'] ~ '& ' + Param.attrib['Name'] + ' = ' + Param.attrib['DefaultValue']) -%}
{%- do outDefines.append('const ' ~ Param.attrib['Type'] ~ '& ' + LowerFirst(Param.attrib['Name']) + ' = ' + Param.attrib['DefaultValue']) -%}
{%- else -%}
{%- do outDefines.append('const ' ~ Param.attrib['Type'] ~ '& ' ~ Param.attrib['Name']) -%}
{%- do outDefines.append('const ' ~ Param.attrib['Type'] ~ '& ' ~ LowerFirst(Param.attrib['Name'])) -%}
{%- endif -%}
{%- endfor -%}
{%- endmacro -%}
@@ -636,7 +636,6 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
{% if networkPropertyCount.value > 0 %}
[[maybe_unused]] Multiplayer::MultiplayerStats& stats = Multiplayer::GetMultiplayer()->GetStats();
// We modify the record if we are writing an update so that we don't notify for a change that really didn't change the value (just a duplicated send from the server)
[[maybe_unused]] bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject;
{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %}
{% if Property.attrib['Container'] != 'None' and Property.attrib['Container'] != 'Object' %}
{ // Serialization for Vector and Array Network Properties
@@ -651,17 +650,24 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
if (deltaRecord.AnySet())
{
{% if Property.attrib['Container'] == 'Vector' %}
serializer.Serialize<AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>>(m_{{ LowerFirst(Property.attrib['Name']) }}, "{{ LowerFirst(Property.attrib['Name']) }}");
Multiplayer::SerializeNetworkPropertyHelperVector
{% elif Property.attrib['Container'] == 'Array' %}
serializer.Serialize<AZStd::array<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>>(m_{{ LowerFirst(Property.attrib['Name']) }}, "{{ LowerFirst(Property.attrib['Name']) }}");
Multiplayer::SerializeNetworkPropertyHelperArray
{% endif %}
(
serializer,
deltaRecord,
m_{{ LowerFirst(Property.attrib['Name']) }},
GetNetComponentId(),
static_cast<Multiplayer::PropertyIndex>({{ UpperFirst(Component.attrib['Name']) }}Internal::NetworkProperties::{{ UpperFirst(Property.attrib['Name']) }}),
stats
);
}
}
{% else %}
Multiplayer::SerializeNetworkPropertyHelper
(
serializer,
modifyRecord,
replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }},
static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }}),
m_{{ LowerFirst(Property.attrib['Name']) }},
@@ -31,7 +31,7 @@ namespace Multiplayer
: m_byteStream(&m_buffer)
{
m_networkEditorInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
AZ::Name(MpEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
m_networkEditorInterface->SetTimeoutEnabled(false);
if (editorsv_isDedicated)
{
@@ -109,7 +109,7 @@ namespace Multiplayer
// Setup the normal multiplayer connection
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer);
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName));
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpNetworkInterfaceName));
uint16_t serverPort = DefaultServerPort;
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console)
@@ -105,7 +105,7 @@ namespace Multiplayer
m_serverProcess->TerminateProcess(0);
m_serverProcess = nullptr;
}
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName));
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
if (editorNetworkInterface)
{
editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient);
@@ -194,7 +194,7 @@ namespace Multiplayer
AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
// Now that the server has launched, attempt to connect the NetworkInterface
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName));
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.");
m_editorConnId = editorNetworkInterface->Connect(
AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp));
@@ -180,7 +180,7 @@ namespace Multiplayer
{
AZ::TickBus::Handler::BusConnect();
AzFramework::SessionNotificationBus::Handler::BusConnect();
m_networkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(AZ::Name(MPNetworkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this);
m_networkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(AZ::Name(MpNetworkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this);
if (AZ::Interface<AZ::IConsole>::Get())
{
m_consoleCommandHandler.Connect(AZ::Interface<AZ::IConsole>::Get()->GetConsoleCommandInvokedEvent());
@@ -197,7 +197,7 @@ namespace Multiplayer
AZ::Interface<AzFramework::ISessionHandlingClientRequests>::Unregister(this);
AZ::Interface<IMultiplayer>::Unregister(this);
m_consoleCommandHandler.Disconnect();
AZ::Interface<INetworking>::Get()->DestroyNetworkInterface(AZ::Name(MPNetworkInterfaceName));
AZ::Interface<INetworking>::Get()->DestroyNetworkInterface(AZ::Name(MpNetworkInterfaceName));
AzFramework::SessionNotificationBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
}