MultiplayerEditorSystemComponent.cpp 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Multiplayer/IMultiplayer.h>
  9. #include <Multiplayer/IMultiplayerTools.h>
  10. #include <Multiplayer/INetworkSpawnableLibrary.h>
  11. #include <Multiplayer/MultiplayerConstants.h>
  12. #include <Multiplayer/Components/NetBindComponent.h>
  13. #include <Multiplayer/Components/NetworkTransformComponent.h>
  14. #include <MultiplayerSystemComponent.h>
  15. #include <PythonEditorEventsBus.h>
  16. #include <Editor/MultiplayerEditorAutomation.h>
  17. #include <Editor/MultiplayerEditorSystemComponent.h>
  18. #include <Editor/EditorViewportSettings.h>
  19. #include <Editor/Viewport.h>
  20. #include <Editor/ViewManager.h>
  21. #include <AzCore/Console/IConsole.h>
  22. #include <AzCore/Interface/Interface.h>
  23. #include <AzCore/Serialization/SerializeContext.h>
  24. #include <AzCore/std/chrono/chrono.h>
  25. #include <AzCore/Utils/Utils.h>
  26. #include <AzFramework/Process/ProcessUtils.h>
  27. #include <AzNetworking/Framework/INetworking.h>
  28. #include <AzToolsFramework/ActionManager/Action/ActionManagerInterface.h>
  29. #include <AzToolsFramework/ActionManager/HotKey/HotKeyManagerInterface.h>
  30. #include <AzToolsFramework/ActionManager/Menu/MenuManagerInterface.h>
  31. #include <AzToolsFramework/API/EntityCompositionRequestBus.h>
  32. #include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
  33. #include <AzToolsFramework/Editor/ActionManagerIdentifiers/EditorActionUpdaterIdentifiers.h>
  34. #include <AzToolsFramework/Editor/ActionManagerIdentifiers/EditorContextIdentifiers.h>
  35. #include <AzToolsFramework/Editor/ActionManagerIdentifiers/EditorMenuIdentifiers.h>
  36. #include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
  37. #include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h>
  38. #include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
  39. #include <AzToolsFramework/Viewport/ViewportMessages.h>
  40. #include <AzToolsFramework/ViewportSelection/EditorHelpers.h>
  41. #include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
  42. #include <Atom/RPI.Public/RPISystemInterface.h>
  43. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  44. #include <QMenu>
  45. #include <QAction>
  46. namespace Multiplayer
  47. {
  48. using namespace AzNetworking;
  49. AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  50. "Whether Editor launching a local server to connect to is supported");
  51. AZ_CVAR(bool, editorsv_clientserver, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  52. "If true, the editor will act as both the server and a client. No dedicated server will be launched.");
  53. AZ_CVAR(bool, editorsv_launch, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  54. "Whether Editor should launch a server when the server address is localhost");
  55. AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  56. "The server executable that should be run. Empty to use the current project's ServerLauncher");
  57. AZ_CVAR(bool, editorsv_hidden, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  58. "The server executable launches hidden without a window. Best used with editorsv_rhi_override set to null.");
  59. AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to");
  60. AZ_CVAR(AZ::CVarFixedString, editorsv_rhi_override, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  61. "Override the default rendering hardware interface (rhi) when launching the Editor server. For example, you may be running an Editor using 'dx12', but want to launch a headless server using 'null'. If empty the server will launch using the same rhi as the Editor.");
  62. AZ_CVAR(uint16_t, editorsv_max_connection_attempts, 5, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  63. "The maximum times the editor will attempt to connect to the server. Time between attempts is increased based on the number of failed attempts.");
  64. AZ_CVAR(bool, editorsv_print_server_logs, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  65. "Whether Editor should print its server's logs to the Editor console. Useful for seeing server prints, warnings, and errors without having to open up the server console or server.log file. Note: Must be set before entering the editor play mode.");
  66. AZ_CVAR_EXTERNED(uint16_t, editorsv_port);
  67. AZ_CVAR_EXTERNED(bool, bg_enableNetworkingMetrics);
  68. //////////////////////////////////////////////////////////////////////////
  69. void PyEnterGameMode()
  70. {
  71. editorsv_enabled = true;
  72. editorsv_launch = true;
  73. AzToolsFramework::EditorLayerPythonRequestBus::Broadcast(&AzToolsFramework::EditorLayerPythonRequestBus::Events::EnterGameMode);
  74. }
  75. bool PyIsInGameMode()
  76. {
  77. // If the network entity manager is tracking at least 1 entity then the editor has connected and the autonomous player exists and is being replicated.
  78. if (const INetworkEntityManager* networkEntityManager = AZ::Interface<INetworkEntityManager>::Get())
  79. {
  80. return networkEntityManager->GetEntityCount() > 0;
  81. }
  82. AZ_Warning("MultiplayerEditorSystemComponent", false, "PyIsInGameMode returning false; NetworkEntityManager has not been created yet.")
  83. return false;
  84. }
  85. void PythonEditorFuncs::Reflect(AZ::ReflectContext* context)
  86. {
  87. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  88. {
  89. serialize->Class<PythonEditorFuncs, AZ::Component>()
  90. ->Version(0);
  91. }
  92. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  93. {
  94. // This will create static python methods in the 'azlmbr.multiplayer' module
  95. // Note: The methods will be prefixed with the class name, PythonEditorFuncs
  96. // Example Hydra Python: azlmbr.multiplayer.PythonEditorFuncs_enter_game_mode()
  97. behaviorContext->Class<PythonEditorFuncs>()
  98. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  99. ->Attribute(AZ::Script::Attributes::Module, "multiplayer")
  100. ->Method("enter_game_mode", PyEnterGameMode, nullptr, "Enters the editor game mode and launches/connects to the server launcher.")
  101. ->Method("is_in_game_mode", PyIsInGameMode, nullptr, "Queries if it's in the game mode and the server has finished connecting and the default network player has spawned.")
  102. ;
  103. }
  104. }
  105. void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context)
  106. {
  107. Automation::MultiplayerEditorAutomationHandler::Reflect(context);
  108. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  109. {
  110. serializeContext->Class<MultiplayerEditorSystemComponent, AZ::Component>()
  111. ->Version(1);
  112. }
  113. // Reflect Python Editor Functions
  114. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  115. {
  116. // This will add the MultiplayerPythonEditorBus into the 'azlmbr.multiplayer' module
  117. behaviorContext->EBus<MultiplayerEditorLayerPythonRequestBus>("MultiplayerPythonEditorBus")
  118. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  119. ->Attribute(AZ::Script::Attributes::Module, "multiplayer")
  120. ->Event("EnterGameMode", &MultiplayerEditorLayerPythonRequestBus::Events::EnterGameMode)
  121. ->Event("IsInGameMode", &MultiplayerEditorLayerPythonRequestBus::Events::IsInGameMode)
  122. ;
  123. }
  124. }
  125. void MultiplayerEditorSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  126. {
  127. required.push_back(AZ_CRC_CE("MultiplayerService"));
  128. }
  129. void MultiplayerEditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  130. {
  131. provided.push_back(AZ_CRC_CE("MultiplayerEditorService"));
  132. }
  133. void MultiplayerEditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  134. {
  135. incompatible.push_back(AZ_CRC_CE("MultiplayerEditorService"));
  136. }
  137. MultiplayerEditorSystemComponent::MultiplayerEditorSystemComponent()
  138. : m_serverAcceptanceReceivedHandler([this](){OnServerAcceptanceReceived();})
  139. {
  140. ;
  141. }
  142. void MultiplayerEditorSystemComponent::Activate()
  143. {
  144. AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
  145. MultiplayerEditorServerRequestBus::Handler::BusConnect();
  146. AZ::Interface<IMultiplayer>::Get()->AddServerAcceptanceReceivedHandler(m_serverAcceptanceReceivedHandler);
  147. AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
  148. AzToolsFramework::ActionManagerRegistrationNotificationBus::Handler::BusConnect();
  149. }
  150. void MultiplayerEditorSystemComponent::Deactivate()
  151. {
  152. AzToolsFramework::ActionManagerRegistrationNotificationBus::Handler::BusDisconnect();
  153. AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
  154. MultiplayerEditorServerRequestBus::Handler::BusDisconnect();
  155. AZ::TickBus::Handler::BusDisconnect();
  156. AzToolsFramework::Prefab::PrefabToInMemorySpawnableNotificationBus::Handler::BusDisconnect();
  157. AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect();
  158. ResetLevelSendData();
  159. }
  160. void MultiplayerEditorSystemComponent::NotifyRegisterViews()
  161. {
  162. AZ_Assert(m_editor == nullptr, "NotifyRegisterViews occurred twice!");
  163. m_editor = nullptr;
  164. AzToolsFramework::EditorRequests::Bus::BroadcastResult(m_editor, &AzToolsFramework::EditorRequests::GetEditor);
  165. if(m_editor)
  166. {
  167. m_editor->RegisterNotifyListener(this);
  168. }
  169. }
  170. void MultiplayerEditorSystemComponent::ResetLevelSendData()
  171. {
  172. // Clear out the temporary buffer so that it doesn't consume any memory when not in use.
  173. m_levelSendData = {};
  174. }
  175. void MultiplayerEditorSystemComponent::OnEditorNotifyEvent(EEditorNotifyEvent event)
  176. {
  177. switch (event)
  178. {
  179. case eNotify_OnQuit:
  180. AZ_Warning("Multiplayer Editor", m_editor != nullptr, "Multiplayer Editor received On Quit without an Editor pointer.");
  181. if (m_editor)
  182. {
  183. m_editor->UnregisterNotifyListener(this);
  184. m_editor = nullptr;
  185. }
  186. [[fallthrough]];
  187. case eNotify_OnEndGameMode:
  188. // Kill the configured server if it's active
  189. AZ::TickBus::Handler::BusDisconnect();
  190. m_connectionEvent.RemoveFromQueue();
  191. ResetLevelSendData();
  192. if (m_serverProcessWatcher)
  193. {
  194. m_serverProcessWatcher->TerminateProcess(0);
  195. // The TracePrinter hangs onto a pointer to an object that is owned by
  196. // the ProcessWatcher. Make sure to destroy the TracePrinter first, before ProcessWatcher.
  197. m_serverProcessTracePrinter = nullptr;
  198. m_serverProcessWatcher = nullptr;
  199. }
  200. const AZ::Name editorInterfaceName = AZ::Name(MpEditorInterfaceName);
  201. if (INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(editorInterfaceName))
  202. {
  203. editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient);
  204. }
  205. if (const auto console = AZ::Interface<AZ::IConsole>::Get())
  206. {
  207. console->PerformCommand("disconnect");
  208. }
  209. // SpawnableAssetEventsBus would already be disconnected once OnStartPlayInEditor happens, but it's possible to
  210. // exit gamemode before the OnStartPlayInEditor is called if the user hits CTRL+G and then ESC really fast.
  211. AzToolsFramework::Prefab::PrefabToInMemorySpawnableNotificationBus::Handler::BusDisconnect();
  212. // Rebuild the library to clear temporary in-memory spawnable assets
  213. AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
  214. // Delete the spawnables we've stored for the server
  215. m_preAliasedSpawnablesForServer.clear();
  216. // Turn off debug messaging: we've exiting playmode and intentionally disconnected from the server.
  217. MultiplayerEditorServerNotificationBus::Broadcast(&MultiplayerEditorServerNotificationBus::Events::OnPlayModeEnd);
  218. break;
  219. }
  220. }
  221. bool MultiplayerEditorSystemComponent::FindServerLauncher(AZ::IO::FixedMaxPath& serverPath)
  222. {
  223. serverPath.clear();
  224. // 1. Try the path from `editorsv_process` cvar.
  225. const AZ::IO::FixedMaxPath serverPathFromCvar{ AZ::CVarFixedString(editorsv_process).c_str()};
  226. if (AZ::IO::SystemFile::Exists(serverPathFromCvar.c_str()))
  227. {
  228. serverPath = serverPathFromCvar;
  229. return true;
  230. }
  231. // 2. Try from the executable folder where the Editor was launched from.
  232. AZ::IO::FixedMaxPath serverPathFromEditorLocation = AZ::Utils::GetExecutableDirectory();
  233. serverPathFromEditorLocation /= AZStd::string_view(AZ::Utils::GetProjectName() + ".ServerLauncher" + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
  234. if (AZ::IO::SystemFile::Exists(serverPathFromEditorLocation.c_str()))
  235. {
  236. serverPath = serverPathFromEditorLocation;
  237. return true;
  238. }
  239. // 3. Try from the project's build folder.
  240. AZ::IO::FixedMaxPath serverPathFromProjectBin;
  241. if (const auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  242. {
  243. if (AZ::IO::FixedMaxPath projectModulePath;
  244. settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
  245. {
  246. serverPathFromProjectBin /= projectModulePath;
  247. serverPathFromProjectBin /= AZStd::string_view(AZ::Utils::GetProjectName() + ".ServerLauncher" + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
  248. if (AZ::IO::SystemFile::Exists(serverPathFromProjectBin.c_str()))
  249. {
  250. serverPath = serverPathFromProjectBin;
  251. return true;
  252. }
  253. }
  254. }
  255. AZ_Error(
  256. "MultiplayerEditor", false,
  257. "The ServerLauncher binary is missing! Attempted to find ServerLauncher in the editorsv_process path:\"%s\", relative to "
  258. "editor:\"%s\" and relative to the current project:\"%s\". Please build ServerLauncher or specify its location using editorsv_process.",
  259. serverPathFromCvar.c_str(),
  260. serverPathFromEditorLocation.c_str(),
  261. serverPathFromProjectBin.c_str());
  262. return false;
  263. }
  264. bool MultiplayerEditorSystemComponent::LaunchEditorServer()
  265. {
  266. // Assemble the server's path
  267. AZ::IO::FixedMaxPath serverPath;
  268. if (!FindServerLauncher(serverPath))
  269. {
  270. return false;
  271. }
  272. // Start the configured server if it's available
  273. AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
  274. // Open the server launcher using the same rhi as the editor (or launch with the override rhi)
  275. AZ::Name server_rhi = AZ::RPI::RPISystemInterface::Get()->GetRenderApiName();
  276. if (!static_cast<AZ::CVarFixedString>(editorsv_rhi_override).empty())
  277. {
  278. server_rhi = static_cast<AZ::CVarFixedString>(editorsv_rhi_override);
  279. }
  280. processLaunchInfo.m_commandlineParameters = AZStd::string::format(
  281. R"("%s" --project-path "%s" --editorsv_isDedicated true --bg_ConnectToAssetProcessor false --rhi "%s" --editorsv_port %i --bg_enableNetworkingMetrics %i --sv_dedicated_host_onstartup false)",
  282. serverPath.c_str(),
  283. AZ::Utils::GetProjectPath().c_str(),
  284. server_rhi.GetCStr(),
  285. static_cast<uint16_t>(editorsv_port),
  286. (bg_enableNetworkingMetrics ? 1 : 0)
  287. );
  288. processLaunchInfo.m_showWindow = !editorsv_hidden;
  289. processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL;
  290. processLaunchInfo.m_tetherLifetime = true;
  291. // Launch the Server
  292. const AzFramework::ProcessCommunicationType communicationType = editorsv_print_server_logs
  293. ? AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT
  294. : AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE;
  295. AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, communicationType);
  296. if (outProcess)
  297. {
  298. MultiplayerEditorServerNotificationBus::Broadcast(&MultiplayerEditorServerNotificationBus::Events::OnServerLaunched);
  299. // Stop the previous server if one exists
  300. if (m_serverProcessWatcher)
  301. {
  302. AZ::TickBus::Handler::BusDisconnect();
  303. m_serverProcessWatcher->TerminateProcess(0);
  304. }
  305. m_serverProcessWatcher.reset(outProcess);
  306. if (editorsv_print_server_logs)
  307. {
  308. // Create a threaded trace printer so that it will keep the output pipes flowing smoothly even while sending the
  309. // editor data over to the server.
  310. m_serverProcessTracePrinter = AZStd::make_unique<ProcessCommunicatorTracePrinter>(
  311. m_serverProcessWatcher->GetCommunicator(), "EditorServer", ProcessCommunicatorTracePrinter::TraceProcessing::Threaded);
  312. }
  313. // Connect to the tick bus to listen for unexpected server process disconnections
  314. AZ::TickBus::Handler::BusConnect();
  315. }
  316. else
  317. {
  318. AZ_Error("MultiplayerEditor", outProcess, "LaunchEditorServer failed! Unable to create AzFramework::ProcessWatcher.");
  319. return false;
  320. }
  321. return true;
  322. }
  323. void MultiplayerEditorSystemComponent::OnServerAcceptanceReceived()
  324. {
  325. // We're now accepting the connection to the EditorServer.
  326. // In normal game clients SendReadyForEntityUpdates will be enabled once the appropriate level's root spawnable is loaded,
  327. // but since we're in Editor, we're already in the level.
  328. AZ::Interface<IMultiplayer>::Get()->SendReadyForEntityUpdates(true);
  329. }
  330. void MultiplayerEditorSystemComponent::SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection)
  331. {
  332. const auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
  333. if (!prefabEditorEntityOwnershipInterface)
  334. {
  335. AZ_Error("MultiplayerEditor", false, "PrefabEditorEntityOwnershipInterface could not find PrefabEditorEntityOwnershipInterface!");
  336. return;
  337. }
  338. AZ_TracePrintf("MultiplayerEditor", "Editor is sending the editor-server the level data packet.")
  339. m_levelSendData.m_sendConnection = connection;
  340. m_levelSendData.m_byteStream =
  341. AZStd::make_unique<AZ::IO::ByteContainerStream<AZStd::vector<uint8_t>>>(&m_levelSendData.m_sendBuffer);
  342. // Serialize Asset information and AssetData into a potentially large buffer
  343. for (const auto& preAliasedSpawnableData : m_preAliasedSpawnablesForServer)
  344. {
  345. // This is an un-aliased level spawnable (example: Root.spawnable and Root.network.spawnable) which we'll send to the server
  346. auto hintSize = aznumeric_cast<uint32_t>(preAliasedSpawnableData.assetHint.size());
  347. m_levelSendData.m_byteStream->Write(sizeof(AZ::Data::AssetId), reinterpret_cast<const void*>(&preAliasedSpawnableData.assetId));
  348. m_levelSendData.m_byteStream->Write(sizeof(uint32_t), reinterpret_cast<void*>(&hintSize));
  349. m_levelSendData.m_byteStream->Write(preAliasedSpawnableData.assetHint.size(), preAliasedSpawnableData.assetHint.data());
  350. AZ::Utils::SaveObjectToStream(
  351. *m_levelSendData.m_byteStream,
  352. AZ::DataStream::ST_BINARY,
  353. preAliasedSpawnableData.spawnable.get(),
  354. preAliasedSpawnableData.spawnable->GetType());
  355. }
  356. // Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets
  357. AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
  358. // Read the buffer into EditorServerLevelData packets until we've flushed the whole thing
  359. m_levelSendData.m_byteStream->Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
  360. // Send an initial notification showing how much data will be sent.
  361. MultiplayerEditorServerNotificationBus::Broadcast(
  362. &MultiplayerEditorServerNotificationBus::Events::OnEditorSendingLevelData,
  363. 0,
  364. aznumeric_cast<uint32_t>(m_levelSendData.m_byteStream->GetLength()));
  365. // The actual data will get sent "asynchronously" during the OnTick callback over multiple frames.
  366. }
  367. void MultiplayerEditorSystemComponent::SendLevelDataToServer()
  368. {
  369. // This controls the maximum time slice to use for sending packets. Lower numbers will make the total send time take longer,
  370. // but will give the Editor more time to do other work. Larger numbers will make the total send time faster, but will starve
  371. // the Editor. The current value attempts to balance between the two.
  372. static constexpr AZ::TimeMs MaxSendTimeMs = AZ::TimeMs{ 5 };
  373. // These control how many retries and how to space them out for packet send failures.
  374. static constexpr int MaxRetries = 20;
  375. static constexpr AZ::TimeMs InitialMsDelayPerRetry = AZ::TimeMs { 10 };
  376. static constexpr AZ::TimeMs MaxMsDelayPerRetry = AZ::TimeMs { 1000 };
  377. // If there's no data left to send, exit.
  378. if (!m_levelSendData.m_byteStream)
  379. {
  380. return;
  381. }
  382. bool updateFinished = false;
  383. bool updateSuccessful = true;
  384. AZ::TimeMs startTime = AZ::GetElapsedTimeMs();
  385. // Loop and send packets until we've reached our max send time slice for this frame.
  386. while (!updateFinished && ((AZ::GetElapsedTimeMs() - startTime) < MaxSendTimeMs))
  387. {
  388. MultiplayerEditorPackets::EditorServerLevelData editorServerLevelDataPacket;
  389. auto& outBuffer = editorServerLevelDataPacket.ModifyAssetData();
  390. // Size the packet's buffer appropriately
  391. size_t readSize = outBuffer.GetCapacity();
  392. const size_t byteStreamSize = m_levelSendData.m_byteStream->GetLength() - m_levelSendData.m_byteStream->GetCurPos();
  393. if (byteStreamSize < readSize)
  394. {
  395. readSize = byteStreamSize;
  396. }
  397. outBuffer.Resize(readSize);
  398. m_levelSendData.m_byteStream->Read(readSize, outBuffer.GetBuffer());
  399. // If we've run out of buffer, mark that we're done
  400. if (m_levelSendData.m_byteStream->GetCurPos() == m_levelSendData.m_byteStream->GetLength())
  401. {
  402. editorServerLevelDataPacket.SetLastUpdate(true);
  403. updateFinished = true;
  404. }
  405. // Try to send the packet to the Editor server. Retry if necessary.
  406. bool packetSent = false;
  407. AZ::TimeMs millisecondDelayPerRetry = InitialMsDelayPerRetry;
  408. int numRetries = 0;
  409. while (!packetSent && (numRetries < MaxRetries))
  410. {
  411. packetSent = m_levelSendData.m_sendConnection->SendReliablePacket(editorServerLevelDataPacket);
  412. if (!packetSent)
  413. {
  414. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(aznumeric_cast<int>(millisecondDelayPerRetry)));
  415. numRetries++;
  416. // Keep doubling the time between retries up to the max amount, then clamp it there.
  417. millisecondDelayPerRetry = AZStd::min(millisecondDelayPerRetry * AZ::TimeMs{ 2 }, MaxMsDelayPerRetry);
  418. // Force the networking buffers to try and flush before sending the packet again.
  419. AZ::Interface<AzNetworking::INetworking>::Get()->ForceUpdate();
  420. }
  421. }
  422. if (packetSent)
  423. {
  424. // Update our information to track the current amount of data sent.
  425. MultiplayerEditorServerNotificationBus::Broadcast(
  426. &MultiplayerEditorServerNotificationBus::Events::OnEditorSendingLevelData,
  427. aznumeric_cast<uint32_t>(m_levelSendData.m_byteStream->GetCurPos()),
  428. aznumeric_cast<uint32_t>(m_levelSendData.m_byteStream->GetLength()));
  429. }
  430. else
  431. {
  432. updateFinished = true;
  433. updateSuccessful = false;
  434. }
  435. }
  436. if (updateFinished)
  437. {
  438. // After we're done sending the level data, clear out our temporary buffer.
  439. ResetLevelSendData();
  440. if (updateSuccessful)
  441. {
  442. // Notify that the level has successfully been sent.
  443. MultiplayerEditorServerNotificationBus::Broadcast(
  444. &MultiplayerEditorServerNotificationBus::Events::OnEditorSendingLevelDataSuccess);
  445. }
  446. else
  447. {
  448. // Notify that the level send failed.
  449. MultiplayerEditorServerNotificationBus::Broadcast(
  450. &MultiplayerEditorServerNotificationBus::Events::OnEditorSendingLevelDataFailed);
  451. }
  452. }
  453. }
  454. void MultiplayerEditorSystemComponent::EnterGameMode()
  455. {
  456. PyEnterGameMode();
  457. }
  458. bool MultiplayerEditorSystemComponent::IsInGameMode()
  459. {
  460. return PyIsInGameMode();
  461. }
  462. void MultiplayerEditorSystemComponent::OnTick(float, AZ::ScriptTimePoint)
  463. {
  464. if (m_serverProcessWatcher && !m_serverProcessWatcher->IsProcessRunning())
  465. {
  466. AZ::TickBus::Handler::BusDisconnect();
  467. MultiplayerEditorServerNotificationBus::Broadcast(&MultiplayerEditorServerNotificationBus::Events::OnEditorServerProcessStoppedUnexpectedly);
  468. AZ_Warning("MultiplayerEditorSystemComponent", false, "The editor server process has unexpectedly stopped running. Did it crash or get accidentally closed?")
  469. }
  470. // Continue sending the level data to the server if any more data exists that needs to be sent.
  471. SendLevelDataToServer();
  472. }
  473. void MultiplayerEditorSystemComponent::Connect()
  474. {
  475. ++m_connectionAttempts;
  476. if (m_connectionAttempts > editorsv_max_connection_attempts)
  477. {
  478. m_connectionEvent.RemoveFromQueue();
  479. MultiplayerEditorServerNotificationBus::Broadcast(&MultiplayerEditorServerNotificationBus::Events::OnEditorConnectionAttemptsFailed, editorsv_max_connection_attempts);
  480. return;
  481. }
  482. MultiplayerEditorServerNotificationBus::Broadcast(&MultiplayerEditorServerNotificationBus::Events::OnEditorConnectionAttempt, m_connectionAttempts, editorsv_max_connection_attempts);
  483. AZ_TracePrintf("MultiplayerEditor", "Editor TCP connection attempt #%i.", m_connectionAttempts)
  484. const AZ::Name editorInterfaceName = AZ::Name(MpEditorInterfaceName);
  485. INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(editorInterfaceName);
  486. AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.")
  487. const AZ::CVarFixedString remoteAddress = editorsv_serveraddr;
  488. m_editorConnId = editorNetworkInterface->Connect(AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp));
  489. if (m_editorConnId != AzNetworking::InvalidConnectionId)
  490. {
  491. AZ_TracePrintf("MultiplayerEditor", "Editor has connected to the editor-server.")
  492. m_connectionEvent.RemoveFromQueue();
  493. SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId));
  494. }
  495. else
  496. {
  497. // Increase the wait time based on the number of connection attempts.
  498. const double retrySeconds = m_connectionAttempts;
  499. constexpr bool autoRequeue = false;
  500. m_connectionEvent.Enqueue(AZ::SecondsToTimeMs(retrySeconds), autoRequeue);
  501. }
  502. }
  503. void MultiplayerEditorSystemComponent::OnPreparingInMemorySpawnableFromPrefab(
  504. const AzFramework::Spawnable& spawnable, const AZStd::string& assetHint)
  505. {
  506. // Only grab the level (Root.spawnable or Root.network.spawnable)
  507. // We'll receive OnPreparingSpawnable for other spawnables that are referenced by components in the level,
  508. // but these spawnables are already available for the server inside the asset cache.
  509. if (!assetHint.starts_with(AzFramework::Spawnable::DefaultMainSpawnableName))
  510. {
  511. return;
  512. }
  513. AZ::SerializeContext* serializeContext = nullptr;
  514. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  515. AZ_Assert(serializeContext, "Failed to retrieve application serialization context.")
  516. AZ_Assert(!assetHint.empty(), "Asset hint is empty!")
  517. // Store a clone of this spawnable for the server; we make a clone now before the spawnable is modified by aliasing.
  518. // Aliasing for this editor (client) is different from aliasing that will happen on the server.
  519. // For example, resolving alias on the client disables auto-spawning of network entities, and will instead wait for a message from the server before updating the net-entities.
  520. AZStd::unique_ptr<AzFramework::Spawnable> preAliasedSpawnableClone(serializeContext->CloneObject(&spawnable));
  521. m_preAliasedSpawnablesForServer.push_back({ AZStd::move(preAliasedSpawnableClone), assetHint, spawnable.GetId() });
  522. }
  523. void MultiplayerEditorSystemComponent::OnStartPlayInEditorBegin()
  524. {
  525. IMultiplayerTools* mpTools = AZ::Interface<IMultiplayerTools>::Get();
  526. if (!editorsv_enabled || !mpTools)
  527. {
  528. // Early out if Editor server is not enabled.
  529. return;
  530. }
  531. if (editorsv_clientserver)
  532. {
  533. // Start hosting as a client-server
  534. const bool isDedicated = false;
  535. AZ::Interface<IMultiplayer>::Get()->StartHosting(editorsv_port, isDedicated);
  536. return;
  537. }
  538. AZ_Assert(m_preAliasedSpawnablesForServer.empty(), "MultiplayerEditorSystemComponent already has pre-aliased spawnables! Please update code to clean-up the table between entering and existing play mode.")
  539. AzToolsFramework::Prefab::PrefabToInMemorySpawnableNotificationBus::Handler::BusConnect();
  540. }
  541. void MultiplayerEditorSystemComponent::OnStartPlayInEditor()
  542. {
  543. IMultiplayerTools* mpTools = AZ::Interface<IMultiplayerTools>::Get();
  544. if (!editorsv_enabled || !mpTools)
  545. {
  546. // Early out if Editor server is not enabled.
  547. return;
  548. }
  549. if (editorsv_clientserver)
  550. {
  551. return;
  552. }
  553. AzToolsFramework::Prefab::PrefabToInMemorySpawnableNotificationBus::Handler::BusDisconnect();
  554. if (editorsv_launch)
  555. {
  556. const AZ::CVarFixedString remoteAddress = editorsv_serveraddr;
  557. if (LocalHost != remoteAddress)
  558. {
  559. AZ_Warning(
  560. "MultiplayerEditor", false,
  561. "Launching editor server skipped because of incompatible settings. "
  562. "When using editorsv_launch=true editorsv_serveraddr must be set to local address (127.0.0.1) instead %s",
  563. remoteAddress.c_str()) return;
  564. }
  565. // Find any existing server launchers before launching a new one.
  566. // It's possible for a rogue server launcher to exist if the Editor shutdown unexpectedly while running a previous multiplayer session.
  567. // It's also common to open ServerLaunchers by hand for testing, but then to forget to shut it down before starting the editor play mode.
  568. const AZStd::string serverExeFilename(AZ::Utils::GetProjectName() + ".ServerLauncher" + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
  569. int existingServers = AzFramework::ProcessUtils::ProcessCount(serverExeFilename);
  570. if (existingServers > 0)
  571. {
  572. AZ_Warning("MultiplayerEditorSystemComponent", false,
  573. "There are already existing servers opened (x%i: %s); please terminate as your Editor may connect to the wrong server! "
  574. "If your intention was to connect to this server instead of automatically launching one from the Editor set editorsv_launch = false.",
  575. existingServers, serverExeFilename.c_str());
  576. }
  577. AZ_Printf("MultiplayerEditor", "Editor is listening for the editor-server...\n");
  578. // Launch the editor-server
  579. if (!LaunchEditorServer())
  580. {
  581. MultiplayerEditorServerNotificationBus::Broadcast(&MultiplayerEditorServerNotificationBus::Events::OnServerLaunchFail);
  582. return;
  583. }
  584. }
  585. // Keep trying to connect until the port is finally available.
  586. m_connectionAttempts = 0;
  587. constexpr double retrySeconds = 1.0;
  588. constexpr bool autoRequeue = false;
  589. m_connectionEvent.Enqueue(AZ::SecondsToTimeMs(retrySeconds), autoRequeue);
  590. }
  591. void MultiplayerEditorSystemComponent::OnActionRegistrationHook()
  592. {
  593. auto actionManagerInterface = AZ::Interface<AzToolsFramework::ActionManagerInterface>::Get();
  594. auto hotKeyManagerInterface = AZ::Interface<AzToolsFramework::HotKeyManagerInterface>::Get();
  595. auto readOnlyEntityPublicInterface = AZ::Interface<AzToolsFramework::ReadOnlyEntityPublicInterface>::Get();
  596. if (!actionManagerInterface || !hotKeyManagerInterface || !readOnlyEntityPublicInterface)
  597. {
  598. return;
  599. }
  600. // Create Multiplayer Entity
  601. {
  602. constexpr AZStd::string_view actionIdentifier = "o3de.action.multiplayer.createMultiplayerEntity";
  603. AzToolsFramework::ActionProperties actionProperties;
  604. actionProperties.m_name = "Create multiplayer entity";
  605. actionProperties.m_description = "Create a multiplayer entity.";
  606. actionProperties.m_category = "Entity";
  607. actionManagerInterface->RegisterAction(
  608. EditorIdentifiers::MainWindowActionContextIdentifier,
  609. actionIdentifier,
  610. actionProperties,
  611. [this, readOnlyEntityPublicInterface]
  612. {
  613. AzToolsFramework::EntityIdList selectedEntities;
  614. AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
  615. selectedEntities, &AzToolsFramework::ToolsApplicationRequests::Bus::Events::GetSelectedEntities);
  616. // when nothing is selected, entity is created at root level.
  617. if (selectedEntities.empty())
  618. {
  619. ContextMenu_NewMultiplayerEntity(AZ::EntityId(), AZ::Vector3::CreateZero());
  620. }
  621. // when a single entity is selected, entity is created as its child.
  622. else if (selectedEntities.size() == 1)
  623. {
  624. AZ::EntityId selectedEntityId = selectedEntities.front();
  625. bool selectedEntityIsReadOnly = readOnlyEntityPublicInterface->IsReadOnly(selectedEntityId);
  626. auto containerEntityInterface = AZ::Interface<AzToolsFramework::ContainerEntityInterface>::Get();
  627. if (containerEntityInterface && containerEntityInterface->IsContainerOpen(selectedEntityId) && !selectedEntityIsReadOnly)
  628. {
  629. ContextMenu_NewMultiplayerEntity(selectedEntityId, AZ::Vector3::CreateZero());
  630. }
  631. }
  632. }
  633. );
  634. actionManagerInterface->InstallEnabledStateCallback(
  635. actionIdentifier,
  636. [readOnlyEntityPublicInterface]()
  637. {
  638. AzToolsFramework::EntityIdList selectedEntities;
  639. AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
  640. selectedEntities, &AzToolsFramework::ToolsApplicationRequests::Bus::Events::GetSelectedEntities);
  641. if (selectedEntities.size() == 0)
  642. {
  643. return true;
  644. }
  645. else if (selectedEntities.size() == 1)
  646. {
  647. AZ::EntityId selectedEntityId = selectedEntities.front();
  648. bool selectedEntityIsReadOnly = readOnlyEntityPublicInterface->IsReadOnly(selectedEntityId);
  649. auto containerEntityInterface = AZ::Interface<AzToolsFramework::ContainerEntityInterface>::Get();
  650. return (containerEntityInterface && containerEntityInterface->IsContainerOpen(selectedEntityId) && !selectedEntityIsReadOnly);
  651. }
  652. return false;
  653. }
  654. );
  655. actionManagerInterface->AddActionToUpdater(EditorIdentifiers::EntitySelectionChangedUpdaterIdentifier, actionIdentifier);
  656. hotKeyManagerInterface->SetActionHotKey(actionIdentifier, "Ctrl+Alt+M");
  657. }
  658. }
  659. void MultiplayerEditorSystemComponent::OnMenuBindingHook()
  660. {
  661. auto menuManagerInterface = AZ::Interface<AzToolsFramework::MenuManagerInterface>::Get();
  662. if (!menuManagerInterface)
  663. {
  664. return;
  665. }
  666. menuManagerInterface->AddActionToMenu(EditorIdentifiers::EntityCreationMenuIdentifier, "o3de.action.multiplayer.createMultiplayerEntity", 1000);
  667. }
  668. void MultiplayerEditorSystemComponent::ContextMenu_NewMultiplayerEntity(AZ::EntityId parentEntityId, const AZ::Vector3& worldPosition)
  669. {
  670. auto prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get();
  671. AZ::EntityId newEntityId = prefabIntegrationInterface->CreateNewEntityAtPosition(worldPosition, parentEntityId);
  672. AzToolsFramework::EntityCompositionRequestBus::Broadcast
  673. (
  674. &AzToolsFramework::EntityCompositionRequests::AddComponentsToEntities,
  675. AzToolsFramework::EntityIdList{ newEntityId },
  676. AZ::ComponentTypeList{ azrtti_typeid<NetBindComponent>(), azrtti_typeid<NetworkTransformComponent>() }
  677. );
  678. }
  679. void MultiplayerEditorSystemComponent::OnStopPlayInEditorBegin()
  680. {
  681. if (GetMultiplayer()->GetAgentType() != MultiplayerAgentType::ClientServer || !editorsv_clientserver)
  682. {
  683. return;
  684. }
  685. // Make sure the client-server stops before the editor leaves play mode.
  686. // Otherwise network entities will be left hanging around.
  687. AZ::Interface<IMultiplayer>::Get()->Terminate(DisconnectReason::TerminatedByUser);
  688. }
  689. } // namespace Multiplayer