MultiplayerEditorConnection.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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 <AzCore/Console/IConsole.h>
  9. #include <AzCore/Component/ComponentApplicationLifecycle.h>
  10. #include <AzCore/Interface/Interface.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Serialization/Utils.h>
  13. #include <AzCore/Utils/Utils.h>
  14. #include <AzFramework/Spawnable/InMemorySpawnableAssetContainer.h>
  15. #include <AzNetworking/ConnectionLayer/IConnection.h>
  16. #include <AzNetworking/Framework/INetworking.h>
  17. #include <Editor/MultiplayerEditorConnection.h>
  18. #include <Multiplayer/IMultiplayer.h>
  19. #include <Multiplayer/INetworkSpawnableLibrary.h>
  20. #include <Multiplayer/MultiplayerConstants.h>
  21. #include <Multiplayer/MultiplayerEditorServerBus.h>
  22. #include <Source/AutoGen/AutoComponentTypes.h>
  23. namespace Multiplayer
  24. {
  25. using namespace AzNetworking;
  26. 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.");
  27. AZ_CVAR(uint16_t, editorsv_port, DefaultServerEditorPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic.");
  28. MultiplayerEditorConnection::MultiplayerEditorConnection()
  29. : m_byteStream(&m_buffer)
  30. {
  31. const AZ::Name editorInterfaceName = AZ::Name(MpEditorInterfaceName);
  32. m_networkEditorInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
  33. editorInterfaceName, ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
  34. m_networkEditorInterface->SetTimeoutMs(AZ::Time::ZeroTimeMs); // Disable timeouts on this network interface
  35. // Wait to activate the editor-server until:
  36. // - LegacySystemInterfaceCreated is signaled, so that the logging system is ready. Automated testing listens for these logs.
  37. // - LegacyCommandLineProcessed is signaled, so that everything has initialized and finished their blocking loads, so that it
  38. // should be relatively safe to start receiving packets without as much fear of too much time passing between system ticks.
  39. if (editorsv_isDedicated)
  40. {
  41. // Server logs will be piped to the editor so turn off buffering,
  42. // otherwise it'll take a lot of logs to fill up the buffer before stdout is finally flushed.
  43. // This isn't optimal, but will only affect editor-servers (used when testing multiplayer levels in Editor gameplay mode) and not production servers.
  44. // Note: _IOLBF (flush on newlines) won't work for Automated Testing which uses a headless server app and will fall back to _IOFBF (full buffering)
  45. setvbuf(stdout, NULL, _IONBF, 0);
  46. // If the settings registry is not available at this point,
  47. // then something catastrophic has happened in the application startup.
  48. // That should have been caught and messaged out earlier in startup.
  49. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  50. {
  51. AZ::ComponentApplicationLifecycle::RegisterHandler(
  52. *settingsRegistry, m_componentApplicationLifecycleHandler,
  53. [this](const AZ::SettingsRegistryInterface::NotifyEventArgs&)
  54. {
  55. ActivateDedicatedEditorServer();
  56. },
  57. "LegacyCommandLineProcessed");
  58. }
  59. }
  60. }
  61. MultiplayerEditorConnection::~MultiplayerEditorConnection()
  62. {
  63. const AZ::Name editorInterfaceName = AZ::Name(MpEditorInterfaceName);
  64. AZ::Interface<INetworking>::Get()->DestroyNetworkInterface(editorInterfaceName);
  65. if (m_inMemorySpawnableAssetContainer != nullptr)
  66. {
  67. m_inMemorySpawnableAssetContainer->ClearAllInMemorySpawnableAssets();
  68. }
  69. }
  70. void MultiplayerEditorConnection::ActivateDedicatedEditorServer() const
  71. {
  72. if (m_isActivated || !editorsv_isDedicated)
  73. {
  74. return;
  75. }
  76. m_isActivated = true;
  77. AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.")
  78. m_networkEditorInterface->Listen(editorsv_port);
  79. }
  80. bool MultiplayerEditorConnection::HandleRequest
  81. (
  82. [[maybe_unused]] AzNetworking::IConnection* connection,
  83. [[maybe_unused]] const IPacketHeader& packetHeader,
  84. MultiplayerEditorPackets::EditorServerLevelData& packet
  85. )
  86. {
  87. // Editor Server Init is intended for non-release targets
  88. m_byteStream.Write(packet.GetAssetData().GetSize(), packet.ModifyAssetData().GetBuffer());
  89. // In case if this is the last update, process the byteStream buffer. Otherwise more packets are expected
  90. if (!packet.GetLastUpdate())
  91. {
  92. return true;
  93. }
  94. // This is the last expected packet
  95. // Read all assets out of the buffer
  96. // Create in-memory spawnables for the level, root.spawnable and root.network.spawnable (if level contains network entities)
  97. if (m_inMemorySpawnableAssetContainer != nullptr)
  98. {
  99. m_inMemorySpawnableAssetContainer->ClearAllInMemorySpawnableAssets();
  100. }
  101. m_inMemorySpawnableAssetContainer = AZStd::make_unique<AzFramework::InMemorySpawnableAssetContainer>();
  102. AzFramework::InMemorySpawnableAssetContainer::AssetDataInfoContainer rootSpawnableAssetDataInfoContainer;
  103. m_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
  104. AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> assetData;
  105. while (m_byteStream.GetCurPos() < m_byteStream.GetLength())
  106. {
  107. AZ::Data::AssetId assetId;
  108. uint32_t hintSize;
  109. AZStd::string assetHint;
  110. m_byteStream.Read(sizeof(AZ::Data::AssetId), &assetId);
  111. m_byteStream.Read(sizeof(uint32_t), &hintSize);
  112. assetHint.resize(hintSize);
  113. m_byteStream.Read(hintSize, assetHint.data());
  114. size_t assetSize = m_byteStream.GetCurPos();
  115. // Load spawnable from stream without loading any asset references
  116. AzFramework::Spawnable* spawnable = AZ::Utils::LoadObjectFromStream<AzFramework::Spawnable>(
  117. m_byteStream, nullptr, AZ::ObjectStream::FilterDescriptor(AZ::Data::AssetFilterNoAssetLoading));
  118. if (!spawnable)
  119. {
  120. AZLOG_ERROR("EditorServerLevelData packet contains no asset data. Asset: %s", assetHint.c_str())
  121. return false;
  122. }
  123. // We only care about Root.spawnable and Root.network.spawnable
  124. AZ_Assert(assetHint.starts_with(AzFramework::Spawnable::DefaultMainSpawnableName), "Editor sent the server more than just the root (level) spawnable. Ensure the editor code only sends Root.");
  125. AZ::Data::AssetInfo spawnableAssetInfo;
  126. spawnableAssetInfo.m_sizeBytes = m_byteStream.GetCurPos() - assetSize;
  127. spawnableAssetInfo.m_assetId = assetId;
  128. spawnableAssetInfo.m_assetType = spawnable->GetType();
  129. spawnableAssetInfo.m_relativePath = assetHint;
  130. rootSpawnableAssetDataInfoContainer.emplace_back(spawnable, spawnableAssetInfo);
  131. }
  132. // Now that we've deserialized, clear the byte stream
  133. m_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
  134. m_byteStream.Truncate();
  135. if (rootSpawnableAssetDataInfoContainer.empty())
  136. {
  137. AZ_Assert(false, "MultiplayerEditorConnection failed to create level spawnable. Editor never sent the Root.spawnable; ensure the Editor sends the current Root.spawnable (the level).");
  138. return false;
  139. }
  140. // Setup the normal multiplayer connection.
  141. // This needs to be done before in-memory spawnable creation and level loading
  142. // because the entity alias resolution is dependent on connection type
  143. AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer);
  144. INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpNetworkInterfaceName));
  145. // Create in-memory spawnables and load dependent assets. This ensures dependent spawnables are loaded
  146. // when the level is loaded by path. Otherwise the dependent spawnables may not load because they will
  147. // already have a "Ready" status from the AssignAssetData call in InMemorySpawnableAssetContainer
  148. // (ex. root.spawnable depends on the in-memory root.network.spawnable)
  149. constexpr bool loadDependentAssets = true;
  150. AzFramework::InMemorySpawnableAssetContainer::CreateSpawnableResult createSpawnableResult =
  151. m_inMemorySpawnableAssetContainer->CreateInMemorySpawnableAsset(rootSpawnableAssetDataInfoContainer, loadDependentAssets, "Root");
  152. if (!createSpawnableResult.IsSuccess())
  153. {
  154. AZ_Assert(false, "MultiplayerEditorConnection failed to create level spawnable. Error result: %s", createSpawnableResult.GetError().c_str())
  155. }
  156. // Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets
  157. AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
  158. // Load the level via the root spawnable that was registered
  159. const auto console = AZ::Interface<AZ::IConsole>::Get();
  160. AZStd::string loadRootSpawnableCommand = AZStd::string::format("LoadLevel %s%s", AzFramework::Spawnable::DefaultMainSpawnableName, AzFramework::Spawnable::DotFileExtension);
  161. console->PerformCommand(loadRootSpawnableCommand.c_str());
  162. uint16_t sv_port = DefaultServerPort;
  163. if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success)
  164. {
  165. AZ_Assert(false,
  166. "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!")
  167. }
  168. networkInterface->Listen(sv_port);
  169. AZLOG_INFO("Editor Server completed receiving the editor's level assets, responding to Editor...");
  170. return connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady());
  171. }
  172. bool MultiplayerEditorConnection::HandleRequest(
  173. [[maybe_unused]] AzNetworking::IConnection* connection,
  174. [[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader,
  175. [[maybe_unused]] MultiplayerEditorPackets::EditorServerReadyForLevelData& packet)
  176. {
  177. MultiplayerEditorServerRequestBus::Broadcast(&MultiplayerEditorServerRequestBus::Events::SendEditorServerLevelDataPacket, connection);
  178. return true;
  179. }
  180. bool MultiplayerEditorConnection::HandleRequest
  181. (
  182. [[maybe_unused]] AzNetworking::IConnection* connection,
  183. [[maybe_unused]] const IPacketHeader& packetHeader,
  184. [[maybe_unused]] MultiplayerEditorPackets::EditorServerReady& packet
  185. )
  186. {
  187. // Receiving this packet means Editor sync is done, disconnect
  188. connection->Disconnect(AzNetworking::DisconnectReason::TerminatedByClient, AzNetworking::TerminationEndpoint::Local);
  189. const auto console = AZ::Interface<AZ::IConsole>::Get();
  190. AZ::CVarFixedString editorsv_serveraddr = AZ::CVarFixedString(LocalHost);
  191. uint16_t sv_port = DefaultServerEditorPort;
  192. if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success)
  193. {
  194. AZ_Assert(false,
  195. "MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the sv_port cvar; we may not be able to "
  196. "connect to the correct port for incoming network messages! Please update this code to use a valid cvar!")
  197. }
  198. if (console->GetCvarValue("editorsv_serveraddr", editorsv_serveraddr) != AZ::GetValueResult::Success)
  199. {
  200. AZ_Assert(false,
  201. "MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the editorsv_serveraddr cvar; we may not be able to "
  202. "connect to the correct port for incoming network messages! Please update this code to use a valid cvar!")
  203. }
  204. // Connect the Editor to the editor server for Multiplayer simulation
  205. if (AZ::Interface<IMultiplayer>::Get()->Connect(editorsv_serveraddr.c_str(), sv_port))
  206. {
  207. AZ_Printf("MultiplayerEditorConnection", "Editor-server ready. Editor has successfully connected to the editor-server's network simulation.")
  208. MultiplayerEditorServerNotificationBus::Broadcast(&MultiplayerEditorServerNotificationBus::Events::OnConnectToSimulationSuccess);
  209. }
  210. else
  211. {
  212. MultiplayerEditorServerNotificationBus::Broadcast(&MultiplayerEditorServerNotificationBus::Events::OnConnectToSimulationFail, sv_port);
  213. }
  214. return true;
  215. }
  216. ConnectResult MultiplayerEditorConnection::ValidateConnect
  217. (
  218. [[maybe_unused]] const IpAddress& remoteAddress,
  219. [[maybe_unused]] const IPacketHeader& packetHeader,
  220. [[maybe_unused]] ISerializer& serializer
  221. )
  222. {
  223. return ConnectResult::Accepted;
  224. }
  225. void MultiplayerEditorConnection::OnConnect([[maybe_unused]] AzNetworking::IConnection* connection)
  226. {
  227. ;
  228. }
  229. AzNetworking::PacketDispatchResult MultiplayerEditorConnection::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer)
  230. {
  231. return MultiplayerEditorPackets::DispatchPacket(connection, packetHeader, serializer, *this);
  232. }
  233. } // namespace Multiplayer