Merging latest main

This commit is contained in:
karlberg
2021-05-19 13:37:45 -07:00
934 changed files with 12574 additions and 31465 deletions
@@ -40,12 +40,12 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}AddEvent(AZ::Even
{% endif %}
const {{ Property.attrib['Type'] }}& {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}GetBack() const
{
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.GetBack();
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.back();
}
uint32_t {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}GetSize() const
{
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.GetSize();
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.size();
}
{% if Property.attrib['GenerateEventBindings']|booleanTrue %}
@@ -110,8 +110,8 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index
bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const {{ Property.attrib['Type'] }} &value)
{
int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.GetSize();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.PushBack(value);
int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.size();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.push_back(value);
int32_t bitIndex = indexToSet + static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }});
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true);
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true);
@@ -121,15 +121,20 @@ bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const {{
bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PopBack(const Multiplayer::NetworkInput&)
{
if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.empty())
{
return false;
}
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true);
GetParent().MarkDirty();
return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.PopBack();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.pop_back();
return true;
}
void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}Clear(const Multiplayer::NetworkInput&)
{
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true);
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.Clear();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.clear();
GetParent().MarkDirty();
}
@@ -202,26 +207,31 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index
bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const {{ Property.attrib['Type'] }} &value)
{
int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.GetSize();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.PushBack(value);
int32_t bitIndex = indexToSet + static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }});
uint32_t indexToSet = aznumeric_cast<uint32_t>(GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.size());
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.push_back(value);
uint32_t bitIndex = indexToSet + aznumeric_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }});
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true);
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true);
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(aznumeric_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true);
GetParent().MarkDirty();
return true;
}
bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PopBack()
{
if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.empty())
{
return false;
}
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true);
GetParent().MarkDirty();
return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.PopBack();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.pop_back();
return true;
}
void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}Clear()
{
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true);
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.Clear();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.clear();
GetParent().MarkDirty();
}
@@ -481,13 +491,14 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
[[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' %}
{
const uint32_t firstBit = static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateFrom, ReplicateTo, Property, 'Start') }});
{ /* @todo Implement serialization for Vector and Array Network Properties
const uint32_t firstBit = static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }});
{% if Property.attrib['Container'] == 'Vector' %}
const uint32_t lastBit = static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateFrom, ReplicateTo, Property, 'Size') }});
const uint32_t lastBit = static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }});
{% else %}
const uint32_t lastBit = static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateFrom, ReplicateTo, Property, 'End') }});
const uint32_t lastBit = static_cast<uint32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'End') }});
{% endif %}
AzNetworking::BitsetView deltaRecord(replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}, firstBit, lastBit - firstBit + 1);
if (deltaRecord.AnySet())
{
@@ -497,7 +508,8 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
Multiplayer::SerializableFixedSizeArrayDeltaStruct<{% if Property.attrib['IsRewindable']|booleanTrue %}Multiplayer::RewindableObject<{% endif %}{{ Property.attrib['Type'] }}{% if Property.attrib['IsRewindable']|booleanTrue %}, Multiplayer::RewindHistorySize>{% endif %}, {{ Property.attrib['Count'] }}> deltaStruct(m_{{ Property.attrib['Name'] }}, deltaRecord);
{% endif %}
serializer.Serialize(deltaStruct, "{{ UpperFirst(Property.attrib['Name']) }}");
}
}
*/
}
{% else %}
Multiplayer::SerializeNetworkPropertyHelper
@@ -527,7 +539,8 @@ void {{ ClassName }}::NotifyChanges{{ AutoComponentMacros.GetNetPropertiesSetNam
{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %}
{% if (Property.attrib['GenerateEventBindings']|booleanTrue) %}
{% if Property.attrib['Container'] != 'None' and Property.attrib['Container'] != 'Object' %}
for (uint32_t bitIndex = static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateFrom, ReplicateTo, Property, 'Start') }}), elementIndex = 0; bitIndex <= static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component, ReplicateFrom, ReplicateFrom, ReplicateTo, Property, 'End') }}); ++bitIndex, ++elementIndex)
/* todo Implement NotifyChangesAuthorityToClientProperties for Arrays and Vectors
for (uint32_t bitIndex = static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}), elementIndex = 0; bitIndex <= static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component, ReplicateFrom, ReplicateTo, Property, 'End') }}); ++bitIndex, ++elementIndex)
{
if (replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.GetBit(bitIndex){% if Property.attrib['Container'] == 'Vector' %} && elementIndex < m_{{ Property.attrib['Name'] }}.GetSize(){% endif %})
{
@@ -537,8 +550,8 @@ void {{ ClassName }}::NotifyChanges{{ AutoComponentMacros.GetNetPropertiesSetNam
{% if Property.attrib['Container'] == 'Vector' %}
if (replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.GetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }})))
{
m_{{ LowerFirst(Property.attrib['Name']) }}SizeChangedEvent.Signal(m_{{ LowerFirst(Property.attrib['Name']) }}.GetSize());
}
m_{{ LowerFirst(Property.attrib['Name']) }}SizeChangedEvent.Signal(m_{{ LowerFirst(Property.attrib['Name']) }}.size());
} */
{% endif %}
{% else %}
if (replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.GetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }})))
@@ -579,12 +592,12 @@ const {{ Property.attrib['Type'] }}& {{ ClassName }}::Get{{ UpperFirst(Property.
const {{ Property.attrib['Type'] }}& {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}GetBack() const
{
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.GetBack();
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.back();
}
uint32_t {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}GetSize() const
{
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.GetSize();
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.size();
}
{% else %}
@@ -665,7 +678,11 @@ enum class NetworkProperties
{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %}
{% if (Property.attrib['IsPublic'] | booleanTrue == true) and (Property.attrib['GenerateEventBindings'] | booleanTrue == true) -%}
// {{ UpperFirst(Property.attrib['Name']) }}: Replicate from {{ ReplicateFrom }} to {{ ReplicateTo }}
{% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %}
->Method("Get{{ UpperFirst(Property.attrib['Name']) }}", [](AZ::EntityId id, int32_t index) -> {{ Property.attrib['Type'] }}
{% else %}
->Method("Get{{ UpperFirst(Property.attrib['Name']) }}", [](AZ::EntityId id) -> {{ Property.attrib['Type'] }}
{% endif %}
{
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(id);
if (!entity)
@@ -681,9 +698,17 @@ enum class NetworkProperties
return {{ Property.attrib['Type'] }}();
}
{% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %}
return networkComponent->Get{{ UpperFirst(Property.attrib['Name']) }}(index);
{% else %}
return networkComponent->Get{{ UpperFirst(Property.attrib['Name']) }}();
{% endif %}
})
{% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %}
->Method("Set{{ UpperFirst(Property.attrib['Name']) }}", [](AZ::EntityId id, int32_t index, const {{ Property.attrib['Type'] }}& value) -> void
{% else %}
->Method("Set{{ UpperFirst(Property.attrib['Name']) }}", [](AZ::EntityId id, const {{ Property.attrib['Type'] }}& {{ LowerFirst(Property.attrib['Name']) }}) -> void
{% endif %}
{
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(id);
if (!entity)
@@ -706,9 +731,17 @@ enum class NetworkProperties
return;
}
{% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' -%}
controller->Set{{ UpperFirst(Property.attrib['Name']) }}(index, value);
{% else %}
controller->Set{{ UpperFirst(Property.attrib['Name']) }}({{ LowerFirst(Property.attrib['Name']) }});
{% endif %}
})
{% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' -%}
->Method("GetOn{{ UpperFirst(Property.attrib['Name']) }}ChangedEvent", [](AZ::EntityId id) -> AZ::Event<int32_t, {{ Property.attrib['Type'] }}>*
{% else %}
->Method("GetOn{{ UpperFirst(Property.attrib['Name']) }}ChangedEvent", [](AZ::EntityId id) -> AZ::Event<{{ Property.attrib['Type'] }}>*
{% endif %}
{
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(id);
if (!entity)
@@ -726,7 +759,11 @@ enum class NetworkProperties
return &networkComponent->m_{{ LowerFirst(Property.attrib['Name']) }}Event;
})
{% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' -%}
->Attribute(AZ::Script::Attributes::AzEventDescription, AZ::BehaviorAzEventDescription{ "On {{ UpperFirst(Property.attrib['Name']) }} Changed Event", {"Index", "New {{ Property.attrib['Type'] }}"} })
{% else %}
->Attribute(AZ::Script::Attributes::AzEventDescription, AZ::BehaviorAzEventDescription{ "On {{ UpperFirst(Property.attrib['Name']) }} Changed Event", {"New {{ Property.attrib['Type'] }}"} })
{% endif %}
{% endif %}
{% endcall %}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<PacketGroup Name="MultiplayerEditorPackets" PacketStart="CorePackets::PacketType::MAX">
<Include File="AzNetworking/AutoGen/CorePackets.AutoPackets.h" />
<Include File="Multiplayer/MultiplayerTypes.h" />
<Include File="Multiplayer/NetworkTime/INetworkTime.h" />
<Packet Name="EditorServerInit" Desc="A packet that initializes a local server launched from the editor">
<Member Type="bool" Name="lastUpdate" Init="false"/>
<Member Type="AzNetworking::TcpPacketEncodingBuffer" Name="assetData"/>
</Packet>
<Packet Name="EditorServerReady" Desc="A response packet the local server should send when ready for traffic"/>
</PacketGroup>
@@ -23,7 +23,7 @@ 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
#ifndef AZ_RELEASE_BUILD
AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat");
#endif
@@ -477,7 +477,7 @@ namespace Multiplayer
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
#ifndef AZ_RELEASE_BUILD
m_moveAccumulator += deltaTime * cl_DebugHackTimeMultiplier;
#else
m_moveAccumulator += deltaTime;
@@ -0,0 +1,187 @@
/*
* 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 <Multiplayer/IMultiplayer.h>
#include <Multiplayer/MultiplayerConstants.h>
#include <Editor/MultiplayerEditorConnection.h>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/Framework/INetworking.h>
namespace Multiplayer
{
using namespace AzNetworking;
AZ_CVAR(bool, editorsv_isDedicated, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether to init as a server expecting data from an Editor. Do not modify unless you're sure of what you're doing.");
MultiplayerEditorConnection::MultiplayerEditorConnection()
: m_byteStream(&m_buffer)
{
m_networkEditorInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
if (editorsv_isDedicated)
{
uint16_t editorServerPort = DefaultServerEditorPort;
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console)
{
console->GetCvarValue("editorsv_port", editorServerPort);
}
AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.");
m_networkEditorInterface->Listen(editorServerPort);
}
}
bool MultiplayerEditorConnection::HandleRequest
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] const IPacketHeader& packetHeader,
[[maybe_unused]] MultiplayerEditorPackets::EditorServerInit& packet
)
{
// Editor Server Init is intended for non-release targets
if (!packet.GetLastUpdate())
{
// More packets are expected, flush this to the buffer
m_byteStream.Write(TcpPacketEncodingBuffer::GetCapacity(), reinterpret_cast<void*>(packet.ModifyAssetData().GetBuffer()));
}
else
{
// This is the last expected packet, flush it to the buffer
m_byteStream.Write(packet.GetAssetData().GetSize(), reinterpret_cast<void*>(packet.ModifyAssetData().GetBuffer()));
// Read all assets out of the buffer
m_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> assetData;
while (m_byteStream.GetCurPos() < m_byteStream.GetLength())
{
AZ::Data::AssetId assetId;
uint32_t hintSize;
AZStd::string assetHint;
m_byteStream.Read(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
m_byteStream.Read(sizeof(uint32_t), reinterpret_cast<void*>(&hintSize));
assetHint.resize(hintSize);
m_byteStream.Read(hintSize, assetHint.data());
size_t assetSize = m_byteStream.GetCurPos();
AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream<AZ::Data::AssetData>(m_byteStream, nullptr);
assetSize = m_byteStream.GetCurPos() - assetSize;
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::Asset<AZ::Data::AssetData>(assetId, assetDatum, AZ::Data::AssetLoadBehavior::NoLoad);
asset.SetHint(assetHint);
AZ::Data::AssetInfo assetInfo;
assetInfo.m_assetId = asset.GetId();
assetInfo.m_assetType = asset.GetType();
assetInfo.m_relativePath = asset.GetHint();
assetInfo.m_sizeBytes = assetSize;
// Register Asset to AssetManager
AZ::Data::AssetManager::Instance().AssignAssetData(asset);
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::RegisterAsset, asset.GetId(), assetInfo);
assetData.push_back(asset);
}
// Now that we've deserialized, clear the byte stream
m_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
m_byteStream.Truncate();
// Load the level via the root spawnable that was registered
const AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable";
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(loadLevelString.c_str());
// Setup the normal multiplayer connection
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer);
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName));
uint16_t serverPort = DefaultServerPort;
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console)
{
console->GetCvarValue("sv_port", serverPort);
}
networkInterface->Listen(serverPort);
AZLOG_INFO("Editor Server completed asset receive, responding to Editor...");
return connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady());
}
return true;
}
bool MultiplayerEditorConnection::HandleRequest
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] const IPacketHeader& packetHeader,
[[maybe_unused]] MultiplayerEditorPackets::EditorServerReady& packet
)
{
if (connection->GetConnectionRole() == ConnectionRole::Connector)
{
// Receiving this packet means Editor sync is done, disconnect
connection->Disconnect(AzNetworking::DisconnectReason::TerminatedByClient, AzNetworking::TerminationEndpoint::Local);
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console)
{
AZ::CVarFixedString remoteAddress;
uint16_t remotePort;
if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound &&
console->GetCvarValue("editorsv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound)
{
// Connect the Editor to the editor server for Multiplayer simulation
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::Client);
INetworkInterface* networkInterface =
AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName));
const IpAddress ipAddress(remoteAddress.c_str(), remotePort, networkInterface->GetType());
networkInterface->Connect(ipAddress);
}
}
}
return true;
}
ConnectResult MultiplayerEditorConnection::ValidateConnect
(
[[maybe_unused]] const IpAddress& remoteAddress,
[[maybe_unused]] const IPacketHeader& packetHeader,
[[maybe_unused]] ISerializer& serializer
)
{
return ConnectResult::Accepted;
}
void MultiplayerEditorConnection::OnConnect([[maybe_unused]] AzNetworking::IConnection* connection)
{
;
}
bool MultiplayerEditorConnection::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer)
{
return MultiplayerEditorPackets::DispatchPacket(connection, packetHeader, serializer, *this);
}
void MultiplayerEditorConnection::OnPacketLost([[maybe_unused]] IConnection* connection, [[maybe_unused]] PacketId packetId)
{
;
}
void MultiplayerEditorConnection::OnDisconnect([[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint)
{
;
}
}
@@ -0,0 +1,58 @@
/*
* 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/AutoGen/MultiplayerEditor.AutoPacketDispatcher.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/std/string/string.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
namespace AzNetworking
{
class INetworkInterface;
}
namespace Multiplayer
{
//! MultiplayerEditorConnection is a connection listener to synchronize the Editor and a local server it launches
class MultiplayerEditorConnection final
: public AzNetworking::IConnectionListener
{
public:
MultiplayerEditorConnection();
~MultiplayerEditorConnection() = default;
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet);
//! IConnectionListener interface
//! @{
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
void OnConnect(AzNetworking::IConnection* connection) override;
bool OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
void OnPacketLost(AzNetworking::IConnection* connection, AzNetworking::PacketId packetId) override;
void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override;
//! @}
private:
AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr;
AZStd::vector<uint8_t> m_buffer;
AZ::IO::ByteContainerStream<AZStd::vector<uint8_t>> m_byteStream;
};
}
@@ -1,21 +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.
*
*/
#include <Source/Editor/MultiplayerEditorDispatcher.h>
namespace Multiplayer
{
MultiplayerEditorDispatcher::MultiplayerEditorDispatcher()
{
;
}
}
@@ -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 <IEditor.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
namespace Multiplayer
{
//! MultiplayerEditorDispatcher is responsible for dispatching delta from the Editor to an Editor launched local server
class MultiplayerEditorDispatcher final
{
public:
MultiplayerEditorDispatcher();
~MultiplayerEditorDispatcher() = default;
private:
};
}
@@ -10,13 +10,13 @@
*
*/
#include <Source/Multiplayer_precompiled.h>
#include <Source/MultiplayerGem.h>
#include <Source/MultiplayerSystemComponent.h>
#include <Source/MultiplayerEditorGem.h>
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
#include <Multiplayer_precompiled.h>
#include <MultiplayerGem.h>
#include <MultiplayerSystemComponent.h>
#include <Editor/MultiplayerEditorGem.h>
#include <Editor/MultiplayerEditorSystemComponent.h>
#include <Source/Editor/MultiplayerEditorSystemComponent.h>
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
namespace Multiplayer
{
@@ -10,12 +10,22 @@
*
*/
#include <Source/Editor/MultiplayerEditorSystemComponent.h>
#include <AzCore/Interface/Interface.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/IMultiplayerTools.h>
#include <Multiplayer/MultiplayerConstants.h>
#include <MultiplayerSystemComponent.h>
#include <Editor/MultiplayerEditorSystemComponent.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Utils/Utils.h>
#include <AzNetworking/Framework/INetworking.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
namespace Multiplayer
{
@@ -23,8 +33,12 @@ namespace Multiplayer
AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"Whether Editor launching a local server to connect to is supported");
AZ_CVAR(bool, editorsv_launch, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"Whether Editor should launch a server when the server address is localhost");
AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"The server executable that should be run. Empty to use the current project's ServerLauncher");
AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to");
AZ_CVAR(uint16_t, editorsv_port, DefaultServerEditorPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic");
void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context)
{
@@ -57,12 +71,14 @@ namespace Multiplayer
void MultiplayerEditorSystemComponent::Activate()
{
AzFramework::GameEntityContextEventBus::Handler::BusConnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
}
void MultiplayerEditorSystemComponent::Deactivate()
{
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
AzFramework::GameEntityContextEventBus::Handler::BusDisconnect();
}
void MultiplayerEditorSystemComponent::NotifyRegisterViews()
@@ -77,50 +93,6 @@ namespace Multiplayer
{
switch (event)
{
case eNotify_OnBeginGameMode:
{
AZ::TickBus::Handler::BusConnect();
if (editorsv_enabled)
{
// Assemble the server's path
AZ::CVarFixedString serverProcess = editorsv_process;
if (serverProcess.empty())
{
// If enabled but no process name is supplied, try this project's ServerLauncher
serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher";
}
AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory();
if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR))
{
// If only the process name is specified, append that as well
serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess);
}
else
{
// If any path was already specified, then simply assign
serverPath = serverProcess;
}
if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION))
{
// Add this platform's exe extension if it's not specified
serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
}
// Start the configured server if it's available
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters =
AZStd::string::format("\"%s\"", serverPath.c_str());
processLaunchInfo.m_showWindow = true;
processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL;
m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess(
processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE);
}
break;
}
case eNotify_OnQuit:
AZ_Warning("Multiplayer Editor", m_editor != nullptr, "Multiplayer Editor received On Quit without an Editor pointer.");
if (m_editor)
@@ -130,25 +102,132 @@ namespace Multiplayer
}
[[fallthrough]];
case eNotify_OnEndGameMode:
AZ::TickBus::Handler::BusDisconnect();
// Kill the configured server if it's active
if (m_serverProcess)
{
m_serverProcess->TerminateProcess(0);
m_serverProcess = nullptr;
}
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPEditorInterfaceName));
if (editorNetworkInterface)
{
editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient);
}
break;
}
}
void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
AzFramework::ProcessWatcher* LaunchEditorServer()
{
// Assemble the server's path
AZ::CVarFixedString serverProcess = editorsv_process;
AZ::IO::FixedMaxPath serverPath;
if (serverProcess.empty())
{
// If enabled but no process name is supplied, try this project's ServerLauncher
serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher";
serverPath = AZ::Utils::GetExecutableDirectory();
serverPath /= serverProcess + AZ_TRAIT_OS_EXECUTABLE_EXTENSION;
}
else
{
serverPath = serverProcess;
}
// Start the configured server if it's available
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str());
processLaunchInfo.m_showWindow = true;
processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL;
// Launch the Server and give it a few seconds to boot up
AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess(
processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE);
if (outProcess)
{
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000));
}
return outProcess;
}
int MultiplayerEditorSystemComponent::GetTickOrder()
void MultiplayerEditorSystemComponent::OnGameEntitiesStarted()
{
// Tick immediately after the network system component
return AZ::TICK_PLACEMENT + 1;
auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable");
}
// BeginGameMode and Prefab Processing have completed at this point
IMultiplayerTools* mpTools = AZ::Interface<IMultiplayerTools>::Get();
if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs())
{
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
AZStd::vector<uint8_t> buffer;
AZ::IO::ByteContainerStream byteStream(&buffer);
// Serialize Asset information and AssetData into a potentially large buffer
for (const auto& asset : assetData)
{
AZ::Data::AssetId assetId = asset.GetId();
AZStd::string assetHint = asset.GetHint();
uint32_t hintSize = aznumeric_cast<uint32_t>(assetHint.size());
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
byteStream.Write(sizeof(uint32_t), reinterpret_cast<void*>(&hintSize));
byteStream.Write(assetHint.size(), assetHint.data());
AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
}
const AZ::CVarFixedString remoteAddress = editorsv_serveraddr;
if (editorsv_launch && LocalHost == remoteAddress)
{
m_serverProcess = LaunchEditorServer();
}
// Now that the server has launched, attempt to connect the NetworkInterface
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));
if (m_editorConnId == AzNetworking::InvalidConnectionId)
{
AZ_Warning(
"MultiplayerEditor", false,
"Could not connect to server targeted by Editor. If using a local server, check that it's built and editorsv_launch is true.");
return;
}
// Read the buffer into EditorServerInit packets until we've flushed the whole thing
byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
while (byteStream.GetCurPos() < byteStream.GetLength())
{
MultiplayerEditorPackets::EditorServerInit packet;
AzNetworking::TcpPacketEncodingBuffer& outBuffer = packet.ModifyAssetData();
// Size the packet's buffer appropriately
size_t readSize = TcpPacketEncodingBuffer::GetCapacity();
size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos();
if (byteStreamSize < readSize)
{
readSize = byteStreamSize;
}
outBuffer.Resize(readSize);
byteStream.Read(readSize, outBuffer.GetBuffer());
// If we've run out of buffer, mark that we're done
if (byteStream.GetCurPos() == byteStream.GetLength())
{
packet.SetLastUpdate(true);
}
editorNetworkInterface->SendReliablePacket(m_editorConnId, packet);
}
}
}
}
@@ -14,15 +14,16 @@
#include <IEditor.h>
#include <Editor/MultiplayerEditorConnection.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
namespace AzNetworking
{
class INetworkInterface;
@@ -33,7 +34,7 @@ namespace Multiplayer
//! Multiplayer system component wraps the bridging logic between the game and transport layer.
class MultiplayerEditorSystemComponent final
: public AZ::Component
, private AZ::TickBus::Handler
, private AzFramework::GameEntityContextEventBus::Handler
, private AzToolsFramework::EditorEvents::Bus::Handler
, private IEditorNotifyListener
{
@@ -59,17 +60,19 @@ namespace Multiplayer
void NotifyRegisterViews() override;
//! @}
private:
//! AZ::TickBus::Handler overrides.
private:
//! EditorEvents::Handler overrides
//! @{
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
//! @}
//!
void OnEditorNotifyEvent(EEditorNotifyEvent event) override;
//! @}
//! GameEntityContextEventBus::Handler overrides
//! @{
void OnGameEntitiesStarted() override;
//! @}
IEditor* m_editor = nullptr;
AzFramework::ProcessWatcher* m_serverProcess = nullptr;
AzNetworking::ConnectionId m_editorConnId;
};
}
@@ -10,22 +10,27 @@
*
*/
#include <Source/MultiplayerSystemComponent.h>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <Source/ConnectionData/ClientToServerConnectionData.h>
#include <Source/ConnectionData/ServerToClientConnectionData.h>
#include <Source/ReplicationWindows/NullReplicationWindow.h>
#include <Source/ReplicationWindows/ServerToClientReplicationWindow.h>
#include <Source/EntityDomains/FullOwnershipEntityDomain.h>
#include <Multiplayer/MultiplayerConstants.h>
#include <Multiplayer/Components/MultiplayerComponent.h>
#include <AzNetworking/Framework/INetworking.h>
#include <MultiplayerSystemComponent.h>
#include <ConnectionData/ClientToServerConnectionData.h>
#include <ConnectionData/ServerToClientConnectionData.h>
#include <EntityDomains/FullOwnershipEntityDomain.h>
#include <ReplicationWindows/NullReplicationWindow.h>
#include <ReplicationWindows/ServerToClientReplicationWindow.h>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzNetworking/Framework/INetworking.h>
namespace AZ::ConsoleTypeHelpers
{
@@ -59,11 +64,8 @@ namespace Multiplayer
{
using namespace AzNetworking;
static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface");
static constexpr uint16_t DefaultServerPort = 30090;
AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port");
AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to");
AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to");
AZ_CVAR(AZ::CVarFixedString, cl_serverpassword, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Optional server password");
AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic");
AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic");
@@ -140,7 +142,7 @@ namespace Multiplayer
void MultiplayerSystemComponent::Activate()
{
AZ::TickBus::Handler::BusConnect();
m_networkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(AZ::Name(s_networkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this);
m_networkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(AZ::Name(MPNetworkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this);
m_consoleCommandHandler.Connect(AZ::Interface<AZ::IConsole>::Get()->GetConsoleCommandInvokedEvent());
AZ::Interface<IMultiplayer>::Register(this);
@@ -631,7 +633,7 @@ namespace Multiplayer
{
Multiplayer::MultiplayerAgentType serverType = sv_isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer;
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(serverType);
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName));
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName));
networkInterface->Listen(sv_port);
}
AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to");
@@ -639,7 +641,7 @@ namespace Multiplayer
void connect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::Client);
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName));
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName));
if (arguments.size() < 1)
{
@@ -669,7 +671,7 @@ namespace Multiplayer
void disconnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::Uninitialized);
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName));
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName));
auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); };
networkInterface->GetConnectionSet().VisitConnections(visitor);
}
@@ -12,17 +12,20 @@
#pragma once
#include <Multiplayer/IMultiplayer.h>
#include <Editor/MultiplayerEditorConnection.h>
#include <NetworkTime/NetworkTime.h>
#include <NetworkEntity/NetworkEntityManager.h>
#include <Source/AutoGen/Multiplayer.AutoPacketDispatcher.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/Threading/ThreadSafeDeque.h>
#include <AzCore/std/string/string.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <Multiplayer/IMultiplayer.h>
#include <Source/NetworkTime/NetworkTime.h>
#include <Source/NetworkEntity/NetworkEntityManager.h>
#include <Source/AutoGen/Multiplayer.AutoPacketDispatcher.h>
namespace AzNetworking
{
@@ -69,7 +72,7 @@ namespace Multiplayer
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityUpdates& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityRpcs& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ClientMigration& packet);
//! IConnectionListener interface
//! @{
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
@@ -105,6 +108,7 @@ namespace Multiplayer
AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session");
AzNetworking::INetworkInterface* m_networkInterface = nullptr;
AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr;
AZ::ConsoleCommandInvokedEvent::Handler m_consoleCommandHandler;
AZ::ThreadSafeDeque<AZStd::string> m_cvarCommands;
@@ -118,5 +122,9 @@ namespace Multiplayer
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId;
#if !defined(AZ_RELEASE_BUILD)
MultiplayerEditorConnection m_editorConnectionListener;
#endif
};
}
@@ -10,40 +10,40 @@
*
*/
#include <Source/Multiplayer_precompiled.h>
#include <Source/MultiplayerToolsModule.h>
#include <Multiplayer_precompiled.h>
#include <MultiplayerToolsModule.h>
#include <Pipeline/NetworkPrefabProcessor.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <Prefab/Instance/InstanceSerializer.h>
namespace Multiplayer
{
//! Multiplayer Tools system component provides serialize context reflection for tools-only systems.
class MultiplayerToolsSystemComponent final
: public AZ::Component
void MultiplayerToolsSystemComponent::Reflect(AZ::ReflectContext* context)
{
public:
AZ_COMPONENT(MultiplayerToolsSystemComponent, "{65AF5342-0ECE-423B-B646-AF55A122F72B}");
NetworkPrefabProcessor::Reflect(context);
}
static void Reflect(AZ::ReflectContext* context)
{
NetworkPrefabProcessor::Reflect(context);
}
void MultiplayerToolsSystemComponent::Activate()
{
AZ::Interface<IMultiplayerTools>::Register(this);
}
MultiplayerToolsSystemComponent() = default;
~MultiplayerToolsSystemComponent() override = default;
void MultiplayerToolsSystemComponent::Deactivate()
{
AZ::Interface<IMultiplayerTools>::Unregister(this);
}
/// AZ::Component overrides.
void Activate() override
{
bool MultiplayerToolsSystemComponent::DidProcessNetworkPrefabs()
{
return m_didProcessNetPrefabs;
}
}
void Deactivate() override
{
}
};
void MultiplayerToolsSystemComponent::SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs)
{
m_didProcessNetPrefabs = didProcessNetPrefabs;
}
MultiplayerToolsModule::MultiplayerToolsModule()
: AZ::Module()
@@ -12,10 +12,36 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Module/Module.h>
#include <Multiplayer/IMultiplayerTools.h>
namespace Multiplayer
{
class MultiplayerToolsSystemComponent final
: public AZ::Component
, public IMultiplayerTools
{
public:
AZ_COMPONENT(MultiplayerToolsSystemComponent, "{65AF5342-0ECE-423B-B646-AF55A122F72B}");
static void Reflect(AZ::ReflectContext* context);
MultiplayerToolsSystemComponent() = default;
~MultiplayerToolsSystemComponent() override = default;
/// AZ::Component overrides.
void Activate() override;
void Deactivate() override;
bool DidProcessNetworkPrefabs() override;
private:
void SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) override;
bool m_didProcessNetPrefabs = false;
};
class MultiplayerToolsModule
: public AZ::Module
{
@@ -555,10 +555,10 @@ namespace Multiplayer
{
replicatorEntity = entityList[0];
}
AZ_Assert(replicatorEntity != nullptr, "Failed to create entity from prefab %s", prefabEntityId.m_prefabName.GetCStr());
if (replicatorEntity == nullptr)
else
{
AZ_Assert(false, "There should be exactly one created entity out of prefab %s, index %d. Got: %d",
prefabEntityId.m_prefabName.GetCStr(), prefabEntityId.m_entityOffset, entityList.size());
return false;
}
}
@@ -34,18 +34,15 @@ namespace Multiplayer
: m_networkEntityAuthorityTracker(*this)
, m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event"))
, m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event"))
, m_onSpawnedHandler([this](AZ::Data::Asset<AzFramework::Spawnable> spawnable) { this->OnSpawned(spawnable); })
, m_onDespawnedHandler([this](AZ::Data::Asset<AzFramework::Spawnable> spawnable) { this->OnDespawned(spawnable); })
{
AZ::Interface<INetworkEntityManager>::Register(this);
AzFramework::RootSpawnableNotificationBus::Handler::BusConnect();
AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler);
AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler);
}
NetworkEntityManager::~NetworkEntityManager()
{
AzFramework::RootSpawnableNotificationBus::Handler::BusDisconnect();
AZ::Interface<INetworkEntityManager>::Unregister(this);
}
void NetworkEntityManager::Initialize(HostId hostId, AZStd::unique_ptr<IEntityDomain> entityDomain)
@@ -331,17 +328,41 @@ namespace Multiplayer
const AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
size_t entitiesSize = entities.size();
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
EntityIdMap originalToCloneIdMap;
for (size_t i = 0; i < entitiesSize; ++i)
{
AZ::Entity* clone = serializeContext->CloneObject(entities[i].get());
AZ::Entity* originalEntity = entities[i].get();
AZ::Entity* clone = serializeContext->CloneObject(originalEntity);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
clone->SetId(AZ::Entity::MakeId());
originalToCloneIdMap[originalEntity->GetId()] = clone->GetId();
NetBindComponent* netBindComponent = clone->FindComponent<NetBindComponent>();
if (netBindComponent != nullptr)
{
// Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here.
auto* transformComponent = clone->FindComponent<AzFramework::TransformComponent>();
AZ::EntityId parentId = transformComponent->GetParentId();
if (parentId.IsValid())
{
auto it = originalToCloneIdMap.find(parentId);
if (it != originalToCloneIdMap.end())
{
transformComponent->SetParentRelative(it->second);
}
else
{
AZ_Warning("NetworkEntityManager", false, "Entity %s doesn't have the parent entity %s present in network.spawnable",
clone->GetName().c_str(), parentId.ToString().data());
}
}
PrefabEntityId prefabEntityId;
prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetPrefabNameFromAssetId(spawnable.GetId());
prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetSpawnableNameFromAssetId(spawnable.GetId());
prefabEntityId.m_entityOffset = aznumeric_cast<uint32_t>(i);
const NetEntityId netEntityId = NextId();
@@ -469,57 +490,19 @@ namespace Multiplayer
}
}
void NetworkEntityManager::OnSpawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable)
void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole)
{
AzFramework::Spawnable* spawnableData = spawnable.GetAs<AzFramework::Spawnable>();
const auto& entityList = spawnableData->GetEntities();
if (entityList.size() == 0)
auto* netBindComponent = netEntity->FindComponent<NetBindComponent>();
if (netBindComponent)
{
AZ_Error("NetworkEntityManager", false, "OnSpawned: Spawnable %s doesn't have any entities.",
spawnable.GetHint().c_str());
return;
const NetEntityId netEntityId = NextId();
netBindComponent->PreInit(netEntity, prefabEntityId, netEntityId, netEntityRole);
}
const auto& rootEntity = entityList[0];
auto* spawnableHolder = rootEntity->FindComponent<NetworkSpawnableHolderComponent>();
if (!spawnableHolder)
else
{
// Root entity doesn't have NetworkSpawnableHolderComponent. It means there's no corresponding network spawnable.
return;
AZ_Error("NetworkEntityManager", false, "SetupNetEntity called for an entity with no NetBindComponent. Entity: %s",
netEntity->GetName().c_str());
}
AZ::Data::Asset<AzFramework::Spawnable> netSpawnableAsset = spawnableHolder->GetNetworkSpawnableAsset();
AzFramework::Spawnable* netSpawnable = netSpawnableAsset.GetAs<AzFramework::Spawnable>();
if (!netSpawnable)
{
// TODO: Temp sync load until JsonSerialization of loadBehavior is fixed.
netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(
netSpawnableAsset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(netSpawnableAsset);
netSpawnable = netSpawnableAsset.GetAs<AzFramework::Spawnable>();
}
if (!netSpawnable)
{
AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Net spawnable doesn't have any data.");
return;
}
auto* multiplayer = GetMultiplayer();
const auto agentType = multiplayer->GetAgentType();
const bool spawnImmediately =
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
if (spawnImmediately)
{
CreateEntitiesImmediate(*netSpawnable, NetEntityRole::Authority);
}
}
void NetworkEntityManager::OnDespawned([[maybe_unused]]AZ::Data::Asset<AzFramework::Spawnable> spawnable)
{
// TODO: Remove entities instantiated from the spawnable
}
}
@@ -62,6 +62,8 @@ namespace Multiplayer
const AZ::Transform& transform
) override;
void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override;
uint32_t GetEntityCount() const override;
NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) override;
void MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) override;
@@ -93,9 +95,6 @@ namespace Multiplayer
void RemoveEntities();
NetEntityId NextId();
void OnSpawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable);
void OnDespawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable);
NetworkEntityTracker m_networkEntityTracker;
NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker;
MultiplayerComponentRegistry m_multiplayerComponentRegistry;
@@ -123,8 +122,5 @@ namespace Multiplayer
DeferredRpcMessages m_localDeferredRpcMessages;
NetworkSpawnableLibrary m_networkPrefabLibrary;
AZ::Event<AZ::Data::Asset<AzFramework::Spawnable>>::Handler m_onSpawnedHandler;
AZ::Event<AZ::Data::Asset<AzFramework::Spawnable>>::Handler m_onDespawnedHandler;
};
}
@@ -14,20 +14,23 @@
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Interface/Interface.h>
namespace Multiplayer
{
NetworkSpawnableLibrary::NetworkSpawnableLibrary()
{
AZ::Interface<INetworkSpawnableLibrary>::Register(this);
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
}
NetworkSpawnableLibrary::~NetworkSpawnableLibrary()
{
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
AZ::Interface<INetworkSpawnableLibrary>::Unregister(this);
}
void NetworkSpawnableLibrary::BuildPrefabsList()
void NetworkSpawnableLibrary::BuildSpawnablesList()
{
auto enumerateCallback = [this](const AZ::Data::AssetId id, const AZ::Data::AssetInfo& info)
{
@@ -50,10 +53,10 @@ namespace Multiplayer
void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
{
BuildPrefabsList();
BuildSpawnablesList();
}
AZ::Name NetworkSpawnableLibrary::GetPrefabNameFromAssetId(AZ::Data::AssetId assetId)
AZ::Name NetworkSpawnableLibrary::GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId)
{
if (assetId.IsValid())
{
@@ -12,30 +12,31 @@
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/string/string.h>
#include <Multiplayer/INetworkSpawnableLibrary.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzCore/Name/Name.h>
namespace Multiplayer
{
/// Implementation of the network prefab library interface.
class NetworkSpawnableLibrary final
: private AzFramework::AssetCatalogEventBus::Handler
: public INetworkSpawnableLibrary
, private AzFramework::AssetCatalogEventBus::Handler
{
public:
AZ_RTTI(NetworkSpawnableLibrary, "{65E15F33-E893-49C2-A8E2-B6A8A6EF31E0}", INetworkSpawnableLibrary);
NetworkSpawnableLibrary();
~NetworkSpawnableLibrary();
void BuildPrefabsList();
void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id);
/// INetworkSpawnableLibrary overrides.
void BuildSpawnablesList() override;
void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) override;
AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) override;
AZ::Data::AssetId GetAssetIdByName(AZ::Name name) override;
/// AssetCatalogEventBus overrides.
void OnCatalogLoaded(const char* catalogFile) override;
AZ::Name GetPrefabNameFromAssetId(AZ::Data::AssetId assetId);
AZ::Data::AssetId GetAssetIdByName(AZ::Name name);
private:
AZStd::unordered_map<AZ::Name, AZ::Data::AssetId> m_spawnables;
AZStd::unordered_map<AZ::Data::AssetId, AZ::Name> m_spawnablesReverseLookup;
@@ -11,7 +11,12 @@
*/
#include <Source/Pipeline/NetBindMarkerComponent.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/INetworkSpawnableLibrary.h>
#include <AzCore/Component/TransformBus.h>
#include <AzFramework/Components/TransformComponent.h>
namespace Multiplayer
{
@@ -21,15 +26,91 @@ namespace Multiplayer
if (serializeContext)
{
serializeContext->Class<NetBindMarkerComponent, AZ::Component>()
->Version(1);
->Version(1)
->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex)
->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset);
}
}
AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset<AzFramework::Spawnable>& asset)
{
AzFramework::Spawnable* spawnable = asset.GetAs<AzFramework::Spawnable>();
if (!spawnable)
{
asset =
AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset);
spawnable = asset.GetAs<AzFramework::Spawnable>();
}
return spawnable;
}
void NetBindMarkerComponent::Activate()
{
const auto agentType = AZ::Interface<IMultiplayer>::Get()->GetAgentType();
const bool spawnImmediately =
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid())
{
AZ::Transform worldTm = GetEntity()->FindComponent<AzFramework::TransformComponent>()->GetWorldTM();
auto preInsertionCallback =
[worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()]
(AzFramework::EntitySpawnTicket&, AzFramework::SpawnableEntityContainerView entities)
{
if (entities.size() == 1)
{
AZ::Entity* netEntity = *entities.begin();
auto* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
transformComponent->SetWorldTM(worldTm);
AZ::Name spawnableName = AZ::Interface<INetworkSpawnableLibrary>::Get()->GetSpawnableNameFromAssetId(spawnableAssetId);
PrefabEntityId prefabEntityId;
prefabEntityId.m_prefabName = spawnableName;
prefabEntityId.m_entityOffset = netEntityIndex;
AZ::Interface<INetworkEntityManager>::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority);
}
else
{
AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size());
}
};
m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset);
AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(m_netSpawnTicket, {m_netEntityIndex}, preInsertionCallback);
}
}
void NetBindMarkerComponent::Deactivate()
{
if(m_netSpawnTicket.IsValid())
{
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket);
}
}
size_t NetBindMarkerComponent::GetNetEntityIndex() const
{
return m_netEntityIndex;
}
void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex)
{
m_netEntityIndex = netEntityIndex;
}
void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset)
{
m_networkSpawnableAsset = networkSpawnableAsset;
}
AZ::Data::Asset<AzFramework::Spawnable> NetBindMarkerComponent::GetNetworkSpawnableAsset() const
{
return m_networkSpawnableAsset;
}
}
@@ -13,6 +13,9 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
namespace Multiplayer
{
@@ -34,6 +37,15 @@ namespace Multiplayer
void Deactivate() override;
//! @}
size_t GetNetEntityIndex() const;
void SetNetEntityIndex(size_t val);
void SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset);
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset() const;
private:
AZ::Data::Asset<AzFramework::Spawnable> m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad};
size_t m_netEntityIndex = 0;
AzFramework::EntitySpawnTicket m_netSpawnTicket;
};
} // namespace Multiplayer
@@ -10,7 +10,11 @@
*
*/
#include <Source/Pipeline/NetworkPrefabProcessor.h>
#include <Multiplayer/IMultiplayerTools.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Pipeline/NetBindMarkerComponent.h>
#include <Pipeline/NetworkPrefabProcessor.h>
#include <Pipeline/NetworkSpawnableHolderComponent.h>
#include <AzCore/Serialization/Utils.h>
#include <AzFramework/Components/TransformComponent.h>
@@ -18,9 +22,6 @@
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <Prefab/Spawnable/SpawnableUtils.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Source/Pipeline/NetBindMarkerComponent.h>
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
namespace Multiplayer
{
@@ -29,9 +30,20 @@ namespace Multiplayer
void NetworkPrefabProcessor::Process(PrefabProcessorContext& context)
{
IMultiplayerTools* mpTools = AZ::Interface<IMultiplayerTools>::Get();
if (mpTools)
{
mpTools->SetDidProcessNetworkPrefabs(false);
}
context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) {
ProcessPrefab(context, prefabName, prefab);
});
if (mpTools && !context.GetProcessedObjects().empty())
{
mpTools->SetDidProcessNetworkPrefabs(true);
}
}
void NetworkPrefabProcessor::Reflect(AZ::ReflectContext* context)
@@ -59,6 +71,7 @@ namespace Multiplayer
return result;
}
void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab)
{
using namespace AzToolsFramework::Prefab;
@@ -113,29 +126,35 @@ namespace Multiplayer
AZStd::unique_ptr<Instance> networkInstance(aznew Instance());
for (auto entityId : networkedEntityIds)
{
AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release();
AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset;
networkSpawnableAsset.Create(networkSpawnable->GetId());
networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
for (size_t entityIndex = 0; entityIndex < networkedEntityIds.size(); ++entityIndex)
{
AZ::EntityId entityId = networkedEntityIds[entityIndex];
AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release();
// Net entity will need a new ID to avoid IDs collision
netEntity->SetId(AZ::Entity::MakeId());
networkInstance->AddEntity(*netEntity);
AZ::Entity* breadcrumbEntity = aznew AZ::Entity(netEntity->GetName());
// Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable
AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName());
breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault());
breadcrumbEntity->CreateComponent<NetBindMarkerComponent>();
NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent<NetBindMarkerComponent>();
// Each spawnable has a root meta-data entity at position 0, so starting net indices from 1
netBindMarkerComponent->SetNetEntityIndex(entityIndex + 1);
netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset);
AzFramework::TransformComponent* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
breadcrumbEntity->CreateComponent<AzFramework::TransformComponent>(*transformComponent);
// TODO: Configure NetBindMarkerComponent to refer to the net entity
sourceInstance->AddEntity(*breadcrumbEntity);
}
// Add net spawnable asset holder
{
AZ::Data::AssetId assetId = networkSpawnable->GetId();
AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset;
networkSpawnableAsset.Create(assetId);
networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity();
if (containerEntityRef.has_value())
{
@@ -175,6 +194,9 @@ namespace Multiplayer
(*it)->InvalidateDependencies();
(*it)->EvaluateDependencies();
}
SpawnableUtils::SortEntitiesByTransformHierarchy(*networkSpawnable);
context.GetProcessedObjects().push_back(AZStd::move(object));
}
else