From 73891c71b777ed1e8e1402ffdbc7463bff6670a8 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Sun, 3 Oct 2021 22:49:13 -0700 Subject: [PATCH 1/5] Fix EditorServer connection by ensuring that SendReadyForEntityUpdates is only called after a handshake is finished and the editor has accepted the editorserver's connection. Also allow engine-centric projects to use editor-server by passing in the project-path when launching the editor-server. Also allow devs to set sv_defaultPlayerSpawnAsset from inside the editor and it'll be used by the editor-server Signed-off-by: Gene Walters --- .../Editor/MultiplayerEditorConnection.cpp | 1 - .../MultiplayerEditorSystemComponent.cpp | 8 ++++++- .../Source/MultiplayerSystemComponent.cpp | 22 ++++++++++++++----- .../Code/Source/MultiplayerSystemComponent.h | 2 ++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index 3ca1af8b66..24858e6412 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -146,7 +146,6 @@ namespace Multiplayer { // Connect the Editor to the editor server for Multiplayer simulation AZ::Interface::Get()->Connect(remoteAddress.c_str(), remotePort); - AZ::Interface::Get()->SendReadyForEntityUpdates(true); } } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 377fb1eb56..d13c6538db 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -142,8 +142,14 @@ namespace Multiplayer } // Start the configured server if it's available + AZStd::string projectPath(AZ::Utils::GetProjectPath().c_str()); + AZStd::replace(projectPath.begin(), projectPath.end(), AZ::IO::WindowsPathSeparator, AZ::IO::PosixPathSeparator); AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str()); + processLaunchInfo.m_commandlineParameters = AZStd::string::format( + R"("%s" --project-path "%s" --editorsv_isDedicated true --sv_defaultPlayerSpawnAsset "%s")", + serverPath.c_str(), + projectPath.c_str(), + static_cast(sv_defaultPlayerSpawnAsset).c_str()); processLaunchInfo.m_showWindow = true; processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index e66eb8d3a3..9b04cd98d4 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -489,11 +489,23 @@ namespace Multiplayer { m_didHandshake = true; - AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); - AZ::Interface::Get()->PerformCommand(commandString.c_str()); + // If this is an Editor then we're now accepting the connection to the EditorServer. + // In normal game clients SendReadyForEntityUpdates will be enabled once the appropriate level's root spawnable is loaded, + // but since we're in Editor, we're already in the level. + AZ::ApplicationTypeQuery applicationType; + AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::QueryApplicationType, applicationType); + if (applicationType.IsEditor()) + { + SendReadyForEntityUpdates(true); + } + else + { + AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(commandString.c_str()); - AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); - AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + } return true; } @@ -981,7 +993,7 @@ namespace Multiplayer INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate); NetworkEntityHandle controlledEntity; - if (entityList.size() > 0) + if (!entityList.empty()) { controlledEntity = entityList[0]; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index c467ed9ad9..ba014f814a 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -37,6 +37,8 @@ namespace AzNetworking namespace Multiplayer { + AZ_CVAR_EXTERNED(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset); + //! Multiplayer system component wraps the bridging logic between the game and transport layer. class MultiplayerSystemComponent final : public AZ::Component From d36041d1b74060b9cd69a74e0d880e3c624791db Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 6 Oct 2021 15:48:54 -0700 Subject: [PATCH 2/5] Update based on feedback. Adding event to MPSystemComponent for receiving a server acceptance packet. The editor system component subscribes to this event and tells the server we're ready for entity updates Signed-off-by: Gene Walters --- .../Code/Include/Multiplayer/IMultiplayer.h | 5 ++++ .../MultiplayerEditorSystemComponent.cpp | 10 +++++++ .../Editor/MultiplayerEditorSystemComponent.h | 7 +++++ .../Source/MultiplayerSystemComponent.cpp | 27 ++++++++----------- .../Code/Source/MultiplayerSystemComponent.h | 2 ++ 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index e32b6188eb..e32a5bd0f3 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -44,6 +44,7 @@ namespace Multiplayer using ClientDisconnectedEvent = AZ::Event<>; using ConnectionAcquiredEvent = AZ::Event; + using ServerAcceptanceReceivedEvent = AZ::Event<>; using SessionInitEvent = AZ::Event; using SessionShutdownEvent = AZ::Event; @@ -102,6 +103,10 @@ namespace Multiplayer //! @param handler The ConnectionAcquiredEvent Handler to add virtual void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) = 0; + //! Adds a ServerAcceptanceReceived Handler which is invoked when the client receives the accept packet from the server. + //! @param handler The ServerAcceptanceReceived Handler to add + virtual void AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) = 0; + //! Adds a SessionInitEvent Handler which is invoked when a new network session starts. //! @param handler The SessionInitEvent Handler to add virtual void AddSessionInitHandler(SessionInitEvent::Handler& handler) = 0; diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index d13c6538db..3a48135bc3 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -62,6 +62,7 @@ namespace Multiplayer } MultiplayerEditorSystemComponent::MultiplayerEditorSystemComponent() + : m_serverAcceptanceReceivedHandler([this](){OnServerAcceptanceReceived();}) { ; } @@ -70,6 +71,7 @@ namespace Multiplayer { AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + AZ::Interface::Get()->AddServerAcceptanceReceivedHandler(m_serverAcceptanceReceivedHandler); } void MultiplayerEditorSystemComponent::Deactivate() @@ -245,4 +247,12 @@ namespace Multiplayer void MultiplayerEditorSystemComponent::OnGameEntitiesReset() { } + + void MultiplayerEditorSystemComponent::OnServerAcceptanceReceived() + { + // We're now accepting the connection to the EditorServer. + // In normal game clients SendReadyForEntityUpdates will be enabled once the appropriate level's root spawnable is loaded, + // but since we're in Editor, we're already in the level. + AZ::Interface::Get()->SendReadyForEntityUpdates(true); + } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 6a9e6a79b6..b1e9335697 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -8,6 +8,8 @@ #pragma once +#include "Multiplayer/IMultiplayer.h" + #include #include @@ -45,6 +47,9 @@ namespace Multiplayer MultiplayerEditorSystemComponent(); ~MultiplayerEditorSystemComponent() override = default; + //! Called once the editor receives the server's accept packet + void OnServerAcceptanceReceived(); + //! AZ::Component overrides. //! @{ void Activate() override; @@ -71,5 +76,7 @@ namespace Multiplayer IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; AzNetworking::ConnectionId m_editorConnId; + + ServerAcceptanceReceivedEvent::Handler m_serverAcceptanceReceivedHandler; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 9b04cd98d4..d05139cb41 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -489,23 +489,13 @@ namespace Multiplayer { m_didHandshake = true; - // If this is an Editor then we're now accepting the connection to the EditorServer. - // In normal game clients SendReadyForEntityUpdates will be enabled once the appropriate level's root spawnable is loaded, - // but since we're in Editor, we're already in the level. - AZ::ApplicationTypeQuery applicationType; - AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::QueryApplicationType, applicationType); - if (applicationType.IsEditor()) - { - SendReadyForEntityUpdates(true); - } - else - { - AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); - AZ::Interface::Get()->PerformCommand(commandString.c_str()); + AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(commandString.c_str()); - AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); - AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); - } + AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + + m_serverAcceptanceReceivedEvent.Signal(); return true; } @@ -800,6 +790,11 @@ namespace Multiplayer handler.Connect(m_connAcquiredEvent); } + void MultiplayerSystemComponent::AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) + { + handler.Connect(m_serverAcceptanceReceivedEvent); + } + void MultiplayerSystemComponent::AddSessionInitHandler(SessionInitEvent::Handler& handler) { handler.Connect(m_initEvent); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index ba014f814a..4a4cdd4265 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -111,6 +111,7 @@ namespace Multiplayer void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; + void AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) override; bool StartHosting(uint16_t port, bool isDedicated = true) override; bool Connect(AZStd::string remoteAddress, uint16_t port) override; void Terminate(AzNetworking::DisconnectReason reason) override; @@ -151,6 +152,7 @@ namespace Multiplayer SessionInitEvent m_initEvent; SessionShutdownEvent m_shutdownEvent; ConnectionAcquiredEvent m_connAcquiredEvent; + ServerAcceptanceReceivedEvent m_serverAcceptanceReceivedEvent; ClientDisconnectedEvent m_clientDisconnectedEvent; AZStd::queue m_pendingConnectionTickets; From 50fe8bc04dcc652cb5bec4334d6c740400acfa55 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 6 Oct 2021 16:35:21 -0700 Subject: [PATCH 3/5] pass in AZ::Utils::GetProjectPath().c_str() directly since converting projectpath is not needed Signed-off-by: Gene Walters --- .../Code/Source/Editor/MultiplayerEditorSystemComponent.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 3a48135bc3..55a05e33a7 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -144,13 +144,11 @@ namespace Multiplayer } // Start the configured server if it's available - AZStd::string projectPath(AZ::Utils::GetProjectPath().c_str()); - AZStd::replace(projectPath.begin(), projectPath.end(), AZ::IO::WindowsPathSeparator, AZ::IO::PosixPathSeparator); AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; processLaunchInfo.m_commandlineParameters = AZStd::string::format( R"("%s" --project-path "%s" --editorsv_isDedicated true --sv_defaultPlayerSpawnAsset "%s")", serverPath.c_str(), - projectPath.c_str(), + AZ::Utils::GetProjectPath().c_str(), static_cast(sv_defaultPlayerSpawnAsset).c_str()); processLaunchInfo.m_showWindow = true; processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; From 7e6d35b046fc640ea95ccec04448510b8c99fb09 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 6 Oct 2021 20:11:29 -0700 Subject: [PATCH 4/5] small fix to include using <> Signed-off-by: Gene Walters --- .../Code/Source/Editor/MultiplayerEditorSystemComponent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index b1e9335697..4e4c6b677f 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -8,7 +8,7 @@ #pragma once -#include "Multiplayer/IMultiplayer.h" +#include #include From 9b86749a838334e5065e9a4bcd574d8edd6a12ef Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Thu, 7 Oct 2021 13:01:05 -0700 Subject: [PATCH 5/5] Making sure unit tests using iMultiplayers implement the new AddServerAcceptance events; misc cleanup Signed-off-by: Gene Walters --- Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h | 1 + Gems/Multiplayer/Code/Tests/MockInterfaces.h | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h index 7f98d22702..5a528ed497 100644 --- a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h @@ -328,6 +328,7 @@ namespace Multiplayer void Terminate([[maybe_unused]] AzNetworking::DisconnectReason reason) override {} void AddClientDisconnectedHandler([[maybe_unused]] ClientDisconnectedEvent::Handler& handler) override {} void AddConnectionAcquiredHandler([[maybe_unused]] ConnectionAcquiredEvent::Handler& handler) override {} + void AddServerAcceptanceReceivedHandler([[maybe_unused]] ServerAcceptanceReceivedEvent::Handler& handler) override {} void AddSessionInitHandler([[maybe_unused]] SessionInitEvent::Handler& handler) override {} void AddSessionShutdownHandler([[maybe_unused]] SessionShutdownEvent::Handler& handler) override {} void SendReadyForEntityUpdates([[maybe_unused]] bool readyForEntityUpdates) override {} diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h index 44024f67ab..527aeb51bc 100644 --- a/Gems/Multiplayer/Code/Tests/MockInterfaces.h +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -29,9 +29,10 @@ namespace UnitTest MOCK_METHOD1(AddClientDisconnectedHandler, void(AZ::Event<>::Handler&)); MOCK_METHOD1(AddNotifyClientMigrationHandler, void(Multiplayer::NotifyClientMigrationEvent::Handler&)); MOCK_METHOD1(AddNotifyEntityMigrationEventHandler, void(Multiplayer::NotifyEntityMigrationEvent::Handler&)); - MOCK_METHOD1(AddConnectionAcquiredHandler, void(AZ::Event::Handler&)); - MOCK_METHOD1(AddSessionInitHandler, void(AZ::Event::Handler&)); - MOCK_METHOD1(AddSessionShutdownHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddConnectionAcquiredHandler, void(Multiplayer::ConnectionAcquiredEvent::Handler&)); + MOCK_METHOD1(AddServerAcceptanceReceivedHandler, void(Multiplayer::ServerAcceptanceReceivedEvent::Handler&)); + MOCK_METHOD1(AddSessionInitHandler, void(Multiplayer::SessionInitEvent::Handler&)); + MOCK_METHOD1(AddSessionShutdownHandler, void(Multiplayer::SessionShutdownEvent::Handler&)); MOCK_METHOD3(SendNotifyClientMigrationEvent, void(const Multiplayer::HostId&, uint64_t, Multiplayer::ClientInputId)); MOCK_METHOD2(SendNotifyEntityMigrationEvent, void(const Multiplayer::ConstNetworkEntityHandle&, const Multiplayer::HostId&)); MOCK_METHOD1(SendReadyForEntityUpdates, void(bool));