From e6650f1ff4723c236f4693e37488e12fdf046db2 Mon Sep 17 00:00:00 2001 From: Gene Walters <32776221+AMZN-Gene@users.noreply.github.com> Date: Wed, 27 Oct 2021 10:38:09 -0700 Subject: [PATCH] LYN-7655 Fix Race Condition When Launching at Editor-Server (#4946) * Fix a race condition where the editor tries to connect to the editor-server before the editor-server is ready (originally discovered on lower-spec Jenkin machines). Change editor-server so that editor waits to receive a EditorServerReadyForInit before trying to send all the level data. * The editor might not be the connector so make sure to connect to the actual MP simulation even if the editor isn't the editor-server connect (if editorsv_launch=true then the editor-server will connect to the editor) * Adding warnings if MPEditorConnection cannot find certain cvars Signed-off-by: Gene Walters --- .../Multiplayer/MultiplayerEditorServerBus.h | 27 ++++ .../AutoGen/MultiplayerEditor.AutoPackets.xml | 8 +- .../Editor/MultiplayerEditorConnection.cpp | 112 +++++++------- .../Editor/MultiplayerEditorConnection.h | 9 +- .../MultiplayerEditorSystemComponent.cpp | 140 +++++++++++++----- .../Editor/MultiplayerEditorSystemComponent.h | 8 +- Gems/Multiplayer/Code/multiplayer_files.cmake | 1 + 7 files changed, 205 insertions(+), 100 deletions(-) create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerEditorServerBus.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerEditorServerBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerEditorServerBus.h new file mode 100644 index 0000000000..1c6d2152bb --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerEditorServerBus.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace Multiplayer +{ + class MultiplayerEditorServerRequests : public AZ::EBusTraits + { + public: + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + + //! Sends a packet that initializes a local server launched from the editor. + //! The editor will package the data required for loading the current editor level on the editor-server; data includes entities and asset data. + //! @param connection The connection to the editor-server + virtual void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) = 0; + }; + using MultiplayerEditorServerRequestBus = AZ::EBus; +} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml index dd553a2413..b8b880d03c 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/MultiplayerEditor.AutoPackets.xml @@ -4,13 +4,15 @@ - - + + + + - + diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index a30ab207b4..d2b0ca7095 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -17,7 +18,6 @@ #include #include #include -#include #include #include @@ -35,13 +35,28 @@ namespace Multiplayer m_networkEditorInterface->SetTimeoutMs(AZ::TimeMs{ 0 }); // Disable timeouts on this network interface if (editorsv_isDedicated) { - uint16_t editorServerPort = DefaultServerEditorPort; - if (auto console = AZ::Interface::Get(); console) + uint16_t editorsv_port = DefaultServerEditorPort; + const auto console = AZ::Interface::Get(); + if (console->GetCvarValue("editorsv_port", editorsv_port) != AZ::GetValueResult::Success) { - console->GetCvarValue("editorsv_port", editorServerPort); + AZ_Assert( false, + "MultiplayerEditorConnection failed! Could not find the editorsv_port cvar; we may not be able to connect to the editor's port! Please update this code to use a valid cvar!") + } + + AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.") + + // Check if there's already an Editor out there waiting to connect + const ConnectionId editorServerToEditorConnectionId = m_networkEditorInterface->Connect(IpAddress(LocalHost.data(), editorsv_port, ProtocolType::Tcp)); + + // If there wasn't an Editor waiting for this server to start, then assume this is an editor-server launched by hand... listen and wait for the editor to request a connection + if (editorServerToEditorConnectionId == InvalidConnectionId) + { + m_networkEditorInterface->Listen(editorsv_port); + } + else + { + m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForLevelData()); } - AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening."); - m_networkEditorInterface->Listen(editorServerPort); } } @@ -49,7 +64,7 @@ namespace Multiplayer ( [[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] const IPacketHeader& packetHeader, - [[maybe_unused]] MultiplayerEditorPackets::EditorServerInit& packet + [[maybe_unused]] MultiplayerEditorPackets::EditorServerLevelData& packet ) { // Editor Server Init is intended for non-release targets @@ -76,7 +91,7 @@ namespace Multiplayer AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream(m_byteStream, nullptr); if (!assetDatum) { - AZLOG_ERROR("EditorServerInit packet contains no asset data. Asset: %s", assetHint.c_str()); + AZLOG_ERROR("EditorServerLevelData packet contains no asset data. Asset: %s", assetHint.c_str()) return false; } assetSize = m_byteStream.GetCurPos() - assetSize; @@ -105,18 +120,21 @@ namespace Multiplayer // Load the level via the root spawnable that was registered const AZ::CVarFixedString loadLevelString = "LoadLevel Root.spawnable"; - AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + const auto console = AZ::Interface::Get(); + console->PerformCommand(loadLevelString.c_str()); // Setup the normal multiplayer connection AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpNetworkInterfaceName)); - uint16_t serverPort = DefaultServerPort; - if (auto console = AZ::Interface::Get(); console) + uint16_t sv_port = DefaultServerPort; + if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success) { - console->GetCvarValue("sv_port", serverPort); + AZ_Assert(false, + "MultiplayerEditorConnection::HandleRequest for EditorServerLevelData failed! Could not find the sv_port cvar; we won't be able to listen on the correct port for incoming network messages! Please update this code to use a valid cvar!") } - networkInterface->Listen(serverPort); + + networkInterface->Listen(sv_port); AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); return connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady()); @@ -125,6 +143,15 @@ namespace Multiplayer return true; } + bool MultiplayerEditorConnection::HandleRequest( + [[maybe_unused]] AzNetworking::IConnection* connection, + [[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader, + [[maybe_unused]] MultiplayerEditorPackets::EditorServerReadyForLevelData& packet) + { + MultiplayerEditorServerRequestBus::Broadcast(&MultiplayerEditorServerRequestBus::Events::SendEditorServerLevelDataPacket, connection); + return true; + } + bool MultiplayerEditorConnection::HandleRequest ( [[maybe_unused]] AzNetworking::IConnection* connection, @@ -132,23 +159,29 @@ namespace Multiplayer [[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); + // Receiving this packet means Editor sync is done, disconnect + connection->Disconnect(AzNetworking::DisconnectReason::TerminatedByClient, AzNetworking::TerminationEndpoint::Local); + const auto console = AZ::Interface::Get(); + AZ::CVarFixedString editorsv_serveraddr = AZ::CVarFixedString(LocalHost); + uint16_t sv_port = DefaultServerEditorPort; - if (auto console = AZ::Interface::Get(); console) - { - AZ::CVarFixedString remoteAddress; - uint16_t remotePort; - if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound && - console->GetCvarValue("sv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound) - { - // Connect the Editor to the editor server for Multiplayer simulation - AZ::Interface::Get()->Connect(remoteAddress.c_str(), remotePort); - } - } + if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success) + { + AZ_Assert(false, + "MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the sv_port cvar; we may not be able to " + "connect to the correct port for incoming network messages! Please update this code to use a valid cvar!") } + + if (console->GetCvarValue("editorsv_serveraddr", editorsv_serveraddr) != AZ::GetValueResult::Success) + { + AZ_Assert(false, + "MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the editorsv_serveraddr cvar; we may not be able to " + "connect to the correct port for incoming network messages! Please update this code to use a valid cvar!") + } + + // Connect the Editor to the editor server for Multiplayer simulation + AZ::Interface::Get()->Connect(editorsv_serveraddr.c_str(), sv_port); + return true; } @@ -171,26 +204,5 @@ namespace Multiplayer { 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) - { - bool editorLaunch = false; - if (auto console = AZ::Interface::Get(); console) - { - console->GetCvarValue("editorsv_launch", editorLaunch); - } - - if (editorsv_isDedicated && editorLaunch && m_networkEditorInterface->GetConnectionSet().GetConnectionCount() == 1) - { - if (m_networkEditorInterface->GetPort() != 0) - { - m_networkEditorInterface->StopListening(); - } - } - } + } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h index b93c830cd0..3828d751f0 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -10,8 +10,6 @@ #include -#include -#include #include #include #include @@ -33,7 +31,8 @@ namespace Multiplayer 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::EditorServerReadyForLevelData& packet); + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerLevelData& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet); //! IConnectionListener interface @@ -41,8 +40,8 @@ namespace Multiplayer AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; void OnConnect(AzNetworking::IConnection* connection) override; AzNetworking::PacketDispatchResult 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; + void OnPacketLost([[maybe_unused]]AzNetworking::IConnection* connection, [[maybe_unused]]AzNetworking::PacketId packetId) override {} + void OnDisconnect([[maybe_unused]]AzNetworking::IConnection* connection, [[maybe_unused]]AzNetworking::DisconnectReason reason, [[maybe_unused]]AzNetworking::TerminationEndpoint endpoint) override {} //! @} private: diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 55a05e33a7..2651981bce 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -71,6 +71,7 @@ namespace Multiplayer { AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + MultiplayerEditorServerRequestBus::Handler::BusConnect(); AZ::Interface::Get()->AddServerAcceptanceReceivedHandler(m_serverAcceptanceReceivedHandler); } @@ -78,6 +79,7 @@ namespace Multiplayer { AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); AzFramework::GameEntityContextEventBus::Handler::BusDisconnect(); + MultiplayerEditorServerRequestBus::Handler::BusDisconnect(); } void MultiplayerEditorSystemComponent::NotifyRegisterViews() @@ -107,8 +109,8 @@ namespace Multiplayer m_serverProcess->TerminateProcess(0); m_serverProcess = nullptr; } - INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); - if (editorNetworkInterface) + + if (INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName))) { editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient); } @@ -191,53 +193,49 @@ namespace Multiplayer } const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; - if (editorsv_launch && LocalHost == remoteAddress) + if (editorsv_launch) { + if (LocalHost != remoteAddress) + { + AZ_Warning( + "MultiplayerEditor", false, + "Launching EditorServer skipped because incompatible cvars. editorsv_launch=true, meaning you want to launch an editor-server on this machine, but the editorsv_serveraddr is %s instead of the local address (127.0.0.1). " + "Please either set editorsv_launch=false and keep the remote editor-server, or set editorsv_launch=true and editorsv_serveraddr=127.0.0.1.", + remoteAddress.c_str()) + return; + } + + // Begin listening for MPEditor packets before we launch the editor-server. + // The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data. + INetworkInterface* editorNetworkInterface = + AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); + AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect."); + editorNetworkInterface->Listen(editorsv_port); + + // Launch the editor-server m_serverProcess = LaunchEditorServer(); } - - // Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets - AZ::Interface::Get()->BuildSpawnablesList(); - - // Now that the server has launched, attempt to connect the NetworkInterface - INetworkInterface* editorNetworkInterface = AZ::Interface::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) + else { - 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; - } + // Editorsv_launch=false, so we're expecting an editor-server already exists. + // Connect to the editor-server and then send the EditorServerLevelData packet. + INetworkInterface* editorNetworkInterface = AZ::Interface::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)); - // 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; - auto& outBuffer = packet.ModifyAssetData(); - - // Size the packet's buffer appropriately - size_t readSize = outBuffer.GetCapacity(); - size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos(); - if (byteStreamSize < readSize) + if (m_editorConnId == AzNetworking::InvalidConnectionId) { - readSize = byteStreamSize; + AZ_Warning( + "MultiplayerEditor", false, + "Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). " + "Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.", + remoteAddress.c_str(), + static_cast < uint16_t>(editorsv_port)) + return; } - 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); + SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId)); } } } @@ -253,4 +251,64 @@ namespace Multiplayer // but since we're in Editor, we're already in the level. AZ::Interface::Get()->SendReadyForEntityUpdates(true); } + + void MultiplayerEditorSystemComponent::SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) + { + const auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (!prefabEditorEntityOwnershipInterface) + { + AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable") + return; + } + + 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 (const auto& asset : assetData) + { + AZ::Data::AssetId assetId = asset.GetId(); + AZStd::string assetHint = asset.GetHint(); + auto hintSize = aznumeric_cast(assetHint.size()); + + byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); + byteStream.Write(sizeof(uint32_t), reinterpret_cast(&hintSize)); + byteStream.Write(assetHint.size(), assetHint.data()); + AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + } + + // Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets + AZ::Interface::Get()->BuildSpawnablesList(); + + // Read the buffer into EditorServerLevelData packets until we've flushed the whole thing + byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + + while (byteStream.GetCurPos() < byteStream.GetLength()) + { + MultiplayerEditorPackets::EditorServerLevelData editorServerLevelDataPacket; + auto& outBuffer = editorServerLevelDataPacket.ModifyAssetData(); + + // Size the packet's buffer appropriately + size_t readSize = outBuffer.GetCapacity(); + const 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()) + { + editorServerLevelDataPacket.SetLastUpdate(true); + } + + connection->SendReliablePacket(editorServerLevelDataPacket); + } + } + } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 4e4c6b677f..a008afc873 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -9,7 +9,7 @@ #pragma once #include - +#include #include #include @@ -35,6 +35,7 @@ namespace Multiplayer , private AzFramework::GameEntityContextEventBus::Handler , private AzToolsFramework::EditorEvents::Bus::Handler , private IEditorNotifyListener + , private MultiplayerEditorServerRequestBus::Handler { public: AZ_COMPONENT(MultiplayerEditorSystemComponent, "{9F335CC0-5574-4AD3-A2D8-2FAEF356946C}"); @@ -73,6 +74,11 @@ namespace Multiplayer void OnGameEntitiesReset() override; //! @} + //! MultiplayerEditorServerRequestBus::Handler + //! @{ + void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) override; + //! @} + IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; AzNetworking::ConnectionId m_editorConnId; diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index a799278203..1376083443 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -13,6 +13,7 @@ set(FILES Include/Multiplayer/MultiplayerConstants.h Include/Multiplayer/MultiplayerStats.h Include/Multiplayer/MultiplayerTypes.h + Include/Multiplayer/MultiplayerEditorServerBus.h Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h Include/Multiplayer/Components/MultiplayerComponent.h Include/Multiplayer/Components/MultiplayerComponentRegistry.h