diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp index e8c30d0816..70000dc9a8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp @@ -176,7 +176,7 @@ namespace AzNetworking if (::bind(aznumeric_cast(m_socketFd), (const sockaddr*)&hints, sizeof(hints)) != 0) { const int32_t error = GetLastNetworkError(); - AZLOG_ERROR("Failed to bind socket (%d:%s)", error, GetNetworkErrorDesc(error)); + AZLOG_ERROR("Failed to bind TCP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); return false; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index 870545e6c8..5676d48150 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -162,7 +162,7 @@ namespace AzNetworking const UdpReaderThread::ReceivedPackets* packets = m_readerThread.GetReceivedPackets(m_socket.get()); if (packets == nullptr) { - AZ_Assert(false, "nullptr was retrieved for the received packet buffer, check that the socket has been registered with the reader thread"); + // Socket is not yet registered with the reader thread and is likely still pending, try again later return; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp index e642c87623..cbb5f8e6c0 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp @@ -82,7 +82,7 @@ namespace AzNetworking if (::bind(static_cast(m_socketFd), (const sockaddr *)&hints, sizeof(hints)) != 0) { const int32_t error = GetLastNetworkError(); - AZLOG_ERROR("Failed to bind socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); + AZLOG_ERROR("Failed to bind UDP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); return false; } } diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 46f56ef315..3f0c2936b7 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -77,12 +77,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzToolsFramework Gem::Multiplayer.Static ) - + ly_add_target( - NAME Multiplayer.Editor.Static STATIC + NAME Multiplayer.Editor GEM_MODULE NAMESPACE Gem FILES_CMAKE - multiplayer_editor_files.cmake + multiplayer_editor_shared_files.cmake COMPILE_DEFINITIONS PUBLIC MULTIPLAYER_EDITOR @@ -94,7 +94,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) PUBLIC Include BUILD_DEPENDENCIES - PUBLIC + PRIVATE Legacy::CryCommon Legacy::Editor.Headers AZ::AzCore @@ -102,23 +102,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzNetworking AZ::AzToolsFramework Gem::Multiplayer.Static - ) - - ly_add_target( - NAME Multiplayer.Editor GEM_MODULE - NAMESPACE Gem - FILES_CMAKE - multiplayer_editor_shared_files.cmake - INCLUDE_DIRECTORIES - PRIVATE - . - Source - ${pal_source_dir} - PUBLIC - Include - BUILD_DEPENDENCIES - PRIVATE - Gem::Multiplayer.Editor.Static Gem::Multiplayer.Tools ) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 84a7207f0c..633218c215 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -59,10 +59,5 @@ - - - - - - + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml new file mode 100644 index 0000000000..986860e822 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp new file mode 100644 index 0000000000..dcf9c134da --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -0,0 +1,162 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace AzNetworking; + + static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface"); + static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); + static constexpr AZStd::string_view DefaultEditorIp = "127.0.0.1"; + static constexpr uint16_t DefaultServerPort = 30090; + static constexpr uint16_t DefaultServerEditorPort = 30091; + + static AZStd::vector buffer; + static AZ::IO::ByteContainerStream> s_byteStream(&buffer); + + 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_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( + AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); + if (editorsv_isDedicated) + { + m_networkEditorInterface->Listen(DefaultServerEditorPort); + } + } + + 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 + s_byteStream.Write(TcpPacketEncodingBuffer::GetCapacity(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); + } + else + { + // This is the last expected packet, flush it to the buffer + s_byteStream.Write(packet.GetAssetData().GetSize(), reinterpret_cast(packet.ModifyAssetData().GetBuffer())); + + // Read all assets out of the buffer + s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + AZStd::vector> assetData; + while (s_byteStream.GetCurPos() < s_byteStream.GetLength()) + { + AZ::Data::AssetLoadBehavior assetLoadBehavior; + s_byteStream.Read(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); + + AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(s_byteStream, nullptr); + AZ::Data::Asset asset = AZ::Data::Asset(assetDatum, assetLoadBehavior); + + /* + // Register Asset to AssetManager + */ + + assetData.push_back(asset); + } + + // Now that we've deserialized, clear the byte stream + s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + s_byteStream.Truncate(); + + /* + // Hand-off our resultant assets + */ + + AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); + if (connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady())) + { + // Setup the normal multiplayer connection + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + networkInterface->Listen(DefaultServerPort); + + return true; + } + else + { + return false; + } + } + + 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); + + // Connect the Editor to the local server for Multiplayer simulation + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); + INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); + const IpAddress ipAddress(DefaultEditorIp.data(), DefaultServerEditorPort, 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) + { + ; + } +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h new file mode 100644 index 0000000000..3621e3aee6 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -0,0 +1,55 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +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; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp deleted file mode 100644 index 470aa61cd0..0000000000 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp +++ /dev/null @@ -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 - -namespace Multiplayer -{ - MultiplayerEditorDispatcher::MultiplayerEditorDispatcher() - { - ; - } -} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h deleted file mode 100644 index c1058dc8a0..0000000000 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#pragma once - -#include - -#include -#include -#include -#include - -#include - - -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: - }; -} diff --git a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp similarity index 97% rename from Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp rename to Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp index 97c5e4d105..ae38ef1d6d 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/MultiplayerEditorGem.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.h similarity index 100% rename from Gems/Multiplayer/Code/Source/MultiplayerEditorGem.h rename to Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorGem.h diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 15f00bdf80..4c3e8200a1 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -26,16 +26,16 @@ namespace Multiplayer { - static const AZStd::string_view s_networkInterfaceName("MultiplayerEditorServerInterface"); + static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); using namespace AzNetworking; - AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + AZ_CVAR(bool, editorsv_enabled, true, 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"); + AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); + AZ_CVAR(uint16_t, editorsv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -70,16 +70,6 @@ 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() @@ -109,18 +99,16 @@ 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; } - if (m_editorNetworkInterface) + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkEditorInterfaceName)); + if (editorNetworkInterface) { - // Disconnect the interface, connection management will clean it up - m_editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByUser); - m_editorConnId = AzNetworking::InvalidConnectionId; + editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient); } break; } @@ -133,116 +121,95 @@ namespace Multiplayer { 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(); + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + + AZStd::vector buffer; + AZ::IO::ByteContainerStream byteStream(&buffer); - if (assetData.size() > 0) + // Serialize Asset information and AssetData into a potentially large buffer + for (auto asset : assetData) { - // 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; + AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior(); + byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); + + AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + } - m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( - processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + // 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"; } - } - // 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)); + 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; + } - // 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) + if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION)) { - readSize = byteStreamSize; + // Add this platform's exe extension if it's not specified + serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION); } - outBuffer.Resize(readSize); - byteStream.Read(readSize, outBuffer.GetBuffer()); + // 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; - // If we've run out of buffer, mark that we're done - if (byteStream.GetCurPos() == byteStream.GetLength()) + // Launch the Server and give it a few seconds to boot up + m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( + processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000)); + + // Now that the server has launched, attempt to connect the NetworkInterface + const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkEditorInterfaceName)); + m_editorConnId = editorNetworkInterface->Connect( + AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_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()) { - packet.SetLastUpdate(true); - } - m_editorNetworkInterface->SendReliablePacket(m_editorConnId, packet); - } + 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; + } - void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) - { + 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); + } + } - int MultiplayerEditorSystemComponent::GetTickOrder() - { - // Tick immediately after the network system component - return AZ::TICK_PLACEMENT + 1; } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index d43d8747b9..569092e981 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -14,6 +14,8 @@ #include +#include + #include #include #include @@ -34,7 +36,6 @@ 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 @@ -61,14 +62,7 @@ namespace Multiplayer void NotifyRegisterViews() override; //! @} - private: - - //! AZ::TickBus::Handler overrides. - //! @{ - void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; - int GetTickOrder() override; - //! @} - + private: //! EditorEvents::Handler overrides //! @{ void OnEditorNotifyEvent(EEditorNotifyEvent event) override; @@ -82,6 +76,5 @@ 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 5dfdd86607..e277b394ff 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include namespace AZ::ConsoleTypeHelpers { @@ -60,6 +63,8 @@ namespace Multiplayer static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); static constexpr uint16_t DefaultServerPort = 30090; static constexpr uint16_t DefaultServerEditorPort = 30091; + //static AZStd::vector buffer; + //static AZ::IO::ByteContainerStream> s_byteStream(&buffer); 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"); @@ -107,7 +112,7 @@ namespace Multiplayer AZ::ConsoleInvokedFrom invokedFrom ) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); }) { - ; + } void MultiplayerSystemComponent::Activate() @@ -401,19 +406,6 @@ 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, @@ -447,13 +439,19 @@ namespace Multiplayer // TODO: This needs to be set to the players autonomous proxy ------------v NetworkEntityHandle controlledEntity = GetNetworkEntityTracker()->Get(NetEntityId{ 0 }); - if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so + if (controlledEntity.GetEntity() != nullptr) { - connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); - } + if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so + { + connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); + } - AZStd::unique_ptr window = AZStd::make_unique(controlledEntity, connection); - reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); + AZStd::unique_ptr window = + AZStd::make_unique(controlledEntity, connection); + reinterpret_cast(connection->GetUserData()) + ->GetReplicationManager() + .SetReplicationWindow(AZStd::move(window)); + } } else { @@ -509,12 +507,6 @@ 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 af5791e5aa..c49e5367e6 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -16,10 +16,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -72,7 +74,7 @@ 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); + //bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet); //! IConnectionListener interface //! @{ @@ -125,5 +127,9 @@ namespace Multiplayer AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId; + +#if !defined(_RELEASE) + MultiplayerEditorConnection m_editorConnectionListener; +#endif }; } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index c2b0c07976..bc6e5710fc 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -35,6 +35,10 @@ namespace Multiplayer context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) { ProcessPrefab(context, prefabName, prefab); }); + if (context.GetProcessedObjects().size() > 0) + { + mpTools->SetDidProcessNetworkPrefabs(true); + } } void NetworkPrefabProcessor::Reflect(AZ::ReflectContext* context) diff --git a/Gems/Multiplayer/Code/multiplayer_editor_files.cmake b/Gems/Multiplayer/Code/multiplayer_editor_files.cmake index ce3e3227e0..5714be5dfb 100644 --- a/Gems/Multiplayer/Code/multiplayer_editor_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_editor_files.cmake @@ -10,6 +10,4 @@ # set(FILES - Source/Editor/MultiplayerEditorDispatcher.cpp - Source/Editor/MultiplayerEditorDispatcher.h ) diff --git a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake index 2d5611d4b6..3fb76061b8 100644 --- a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake @@ -12,8 +12,8 @@ set(FILES Source/MultiplayerGem.cpp Source/MultiplayerGem.h - Source/MultiplayerEditorGem.cpp - Source/MultiplayerEditorGem.h + Source/Editor/MultiplayerEditorGem.cpp + Source/Editor/MultiplayerEditorGem.h Source/Editor/MultiplayerEditorSystemComponent.cpp Source/Editor/MultiplayerEditorSystemComponent.h ) diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 1f4e57ae43..b8fd842426 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -33,6 +33,7 @@ set(FILES Source/AutoGen/AutoComponentTypes_Source.jinja Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml + Source/AutoGen/MultiplayerEditor.AutoPackets.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml Source/Components/LocalPredictionPlayerInputComponent.cpp Source/Components/LocalPredictionPlayerInputComponent.h @@ -52,6 +53,8 @@ set(FILES Source/ConnectionData/ServerToClientConnectionData.cpp Source/ConnectionData/ServerToClientConnectionData.h Source/ConnectionData/ServerToClientConnectionData.inl + Source/Editor/MultiplayerEditorConnection.cpp + Source/Editor/MultiplayerEditorConnection.h Source/EntityDomains/FullOwnershipEntityDomain.cpp Source/EntityDomains/FullOwnershipEntityDomain.h Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp