diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp
index 78020cb09d..e8c30d0816 100644
--- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp
+++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp
@@ -116,8 +116,8 @@ namespace AzNetworking
int32_t TcpSocket::Receive(uint8_t* outData, uint32_t size) const
{
- AZ_Assert(size > 0, "Invalid data size for send");
- AZ_Assert(outData != nullptr, "NULL data pointer passed to send");
+ AZ_Assert(size > 0, "Invalid data size for receive");
+ AZ_Assert(outData != nullptr, "NULL data pointer passed to receive");
if (!IsOpen())
{
return SocketOpResultErrorNotOpen;
diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml
index 5de466899c..21faefa880 100644
--- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml
+++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml
@@ -57,4 +57,10 @@
+
+
+
+
+
+
diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp
index 0850b858a2..15f00bdf80 100644
--- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp
+++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp
@@ -10,23 +10,32 @@
*
*/
+#include
#include
+#include
+#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#include
namespace Multiplayer
{
+ static const AZStd::string_view s_networkInterfaceName("MultiplayerEditorServerInterface");
+
using namespace AzNetworking;
AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"Whether Editor launching a local server to connect to is supported");
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, sv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to");
+ AZ_CVAR(uint16_t, sv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic");
void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context)
{
@@ -61,6 +70,16 @@ namespace Multiplayer
{
AzFramework::GameEntityContextEventBus::Handler::BusConnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
+
+ // Setup a network interface handled by MultiplayerSystemComponent
+ if (m_editorNetworkInterface == nullptr)
+ {
+ AZ::Entity* systemEntity = this->GetEntity();
+ MultiplayerSystemComponent* mpSysComponent = systemEntity->FindComponent();
+
+ m_editorNetworkInterface = AZ::Interface::Get()->CreateNetworkInterface(
+ AZ::Name(s_networkInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *mpSysComponent);
+ }
}
void MultiplayerEditorSystemComponent::Deactivate()
@@ -97,25 +116,52 @@ namespace Multiplayer
m_serverProcess->TerminateProcess(0);
m_serverProcess = nullptr;
}
+ if (m_editorNetworkInterface)
+ {
+ // Disconnect the interface, connection management will clean it up
+ m_editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByUser);
+ m_editorConnId = AzNetworking::InvalidConnectionId;
+ }
break;
}
}
void MultiplayerEditorSystemComponent::OnGameEntitiesStarted()
{
+ auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get();
+ if (!prefabEditorEntityOwnershipInterface)
+ {
+ AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable");
+ }
+ const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
+
+ AZStd::vector buffer;
+ AZ::IO::ByteContainerStream byteStream(&buffer);
+
+ // Serialize Asset information and AssetData into a potentially large buffer
+ for (auto asset : assetData)
+ {
+ AZ::Data::AssetId assetId = asset.GetId();
+ AZ::Data::AssetType assetType = asset.GetType();
+ const AZStd::string& assetHint = asset.GetHint();
+ AZ::IO::SizeType assetHintSize = assetHint.size();
+ AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior();
+
+ byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId));
+ byteStream.Write(sizeof(AZ::Data::AssetType), reinterpret_cast(&assetType));
+ byteStream.Write(sizeof(assetHintSize), reinterpret_cast(&assetHintSize));
+ byteStream.Write(assetHint.size(), assetHint.c_str());
+ byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior));
+
+ AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
+ }
+
// BeginGameMode and Prefab Processing have completed at this point
IMultiplayerTools* mpTools = AZ::Interface::Get();
if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs())
{
AZ::TickBus::Handler::BusConnect();
- auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get();
- if (!prefabEditorEntityOwnershipInterface)
- {
- AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable");
- }
- const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
-
if (assetData.size() > 0)
{
// Assemble the server's path
@@ -154,6 +200,39 @@ namespace Multiplayer
processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE);
}
}
+
+ // Now that the server has launched, attempt to connect the NetworkInterface
+ const AZ::CVarFixedString remoteAddress = sv_serveraddr;
+ m_editorConnId = m_editorNetworkInterface->Connect(
+ AzNetworking::IpAddress(remoteAddress.c_str(), sv_port, AzNetworking::ProtocolType::Tcp));
+
+ // 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())
+ {
+ MultiplayerPackets::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);
+ }
+ m_editorNetworkInterface->SendReliablePacket(m_editorConnId, packet);
+ }
+
}
void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h
index 31ecdf83a3..d43d8747b9 100644
--- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h
+++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h
@@ -81,5 +81,7 @@ namespace Multiplayer
IEditor* m_editor = nullptr;
AzFramework::ProcessWatcher* m_serverProcess = nullptr;
+ AzNetworking::ConnectionId m_editorConnId;
+ AzNetworking::INetworkInterface* m_editorNetworkInterface = nullptr;
};
}
diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp
index 03c661ab03..3c1e142d96 100644
--- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp
+++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp
@@ -57,7 +57,9 @@ namespace Multiplayer
using namespace AzNetworking;
static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface");
+ static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface");
static constexpr uint16_t DefaultServerPort = 30090;
+ static constexpr uint16_t DefaultServerEditorPort = 30091;
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");
@@ -397,6 +399,19 @@ namespace Multiplayer
return false;
}
+ bool MultiplayerSystemComponent::HandleRequest
+ (
+ [[maybe_unused]] AzNetworking::IConnection* connection,
+ [[maybe_unused]] const IPacketHeader& packetHeader,
+ [[maybe_unused]] MultiplayerPackets::EditorServerInit& packet
+ )
+ {
+#if !defined(_RELEASE)
+ // Support Editor Server Init for all non-release targets
+#endif
+ return true;
+ }
+
ConnectResult MultiplayerSystemComponent::ValidateConnect
(
[[maybe_unused]] const IpAddress& remoteAddress,
@@ -492,6 +507,12 @@ namespace Multiplayer
{
if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer)
{
+#if !defined(_RELEASE)
+ m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface(
+ AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
+ m_networkEditorInterface->Listen(DefaultServerEditorPort);
+#endif
+
m_initEvent.Signal(m_networkInterface);
const AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-16384.0f), AZ::Vector3(16384.0f));
diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h
index f25e530b61..e77fa129b0 100644
--- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h
+++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h
@@ -72,7 +72,8 @@ namespace Multiplayer
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::NotifyClientMigration& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityMigration& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet);
-
+ bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet);
+
//! IConnectionListener interface
//! @{
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
@@ -109,6 +110,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 m_cvarCommands;
diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp
index a5df3c1dc5..ae91999a0c 100644
--- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp
+++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp
@@ -24,6 +24,16 @@ namespace Multiplayer
NetworkPrefabProcessor::Reflect(context);
}
+ void MultiplayerToolsSystemComponent::Activate()
+ {
+ AZ::Interface::Register(this);
+ }
+
+ void MultiplayerToolsSystemComponent::Deactivate()
+ {
+ AZ::Interface::Unregister(this);
+ }
+
bool MultiplayerToolsSystemComponent::DidProcessNetworkPrefabs()
{
return m_didProcessNetPrefabs;
diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h
index 82d0415c5a..181a971150 100644
--- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h
+++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h
@@ -31,9 +31,8 @@ namespace Multiplayer
~MultiplayerToolsSystemComponent() override = default;
/// AZ::Component overrides.
- void Activate() override {};
-
- void Deactivate() override {};
+ void Activate() override;
+ void Deactivate() override;
bool DidProcessNetworkPrefabs() override;