MultiplayerSystemComponent.cpp 91 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991
  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/MultiplayerConstants.h>
  9. #include <Multiplayer/Components/MultiplayerComponent.h>
  10. #include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
  11. #include <Multiplayer/IMultiplayerSpawner.h>
  12. #include <MultiplayerSystemComponent.h>
  13. #include <ConnectionData/ClientToServerConnectionData.h>
  14. #include <ConnectionData/ServerToClientConnectionData.h>
  15. #include <EntityDomains/FullOwnershipEntityDomain.h>
  16. #include <EntityDomains/NullEntityDomain.h>
  17. #include <ReplicationWindows/NullReplicationWindow.h>
  18. #include <ReplicationWindows/ServerToClientReplicationWindow.h>
  19. #include <Source/AutoGen/AutoComponentTypes.h>
  20. #include <Multiplayer/Session/ISessionRequests.h>
  21. #include <Multiplayer/Session/SessionConfig.h>
  22. #include <Multiplayer/MultiplayerPerformanceStats.h>
  23. #include <Multiplayer/MultiplayerMetrics.h>
  24. #include <AzCore/Debug/Profiler.h>
  25. #include <AzCore/Serialization/SerializeContext.h>
  26. #include <AzCore/Serialization/Utils.h>
  27. #include <AzCore/Interface/Interface.h>
  28. #include <AzCore/Component/ComponentApplicationLifecycle.h>
  29. #include <AzCore/Component/TransformBus.h>
  30. #include <AzCore/Console/IConsole.h>
  31. #include <AzCore/Console/ILogger.h>
  32. #include <AzCore/Asset/AssetCommon.h>
  33. #include <AzCore/Utils/Utils.h>
  34. #include <AzCore/RTTI/BehaviorContext.h>
  35. #include <AzFramework/Components/CameraBus.h>
  36. #include <AzFramework/Visibility/IVisibilitySystem.h>
  37. #include <AzNetworking/Framework/INetworking.h>
  38. #include <AzFramework/Process/ProcessWatcher.h>
  39. #include <cmath>
  40. #include <AzCore/Jobs/JobCompletion.h>
  41. #include <AzCore/Jobs/JobFunction.h>
  42. #include <System/PhysXSystem.h>
  43. #include <AzCore/Jobs/JobCompletion.h>
  44. #include <AzCore/Jobs/JobFunction.h>
  45. AZ_DEFINE_BUDGET(MULTIPLAYER);
  46. namespace AZ
  47. {
  48. AZ_TYPE_INFO_SPECIALIZE(Multiplayer::MultiplayerAgentType, "{53EA1938-5FFB-4305-B50A-D20730E8639B}");
  49. }
  50. namespace AZ::ConsoleTypeHelpers
  51. {
  52. inline CVarFixedString ValueToString(const AzNetworking::ProtocolType& value)
  53. {
  54. return (value == AzNetworking::ProtocolType::Tcp) ? "tcp" : "udp";
  55. }
  56. inline bool StringSetToValue(AzNetworking::ProtocolType& outValue, const ConsoleCommandContainer& arguments)
  57. {
  58. if (!arguments.empty())
  59. {
  60. if (arguments.front() == "tcp")
  61. {
  62. outValue = AzNetworking::ProtocolType::Tcp;
  63. return true;
  64. }
  65. else if (arguments.front() == "udp")
  66. {
  67. outValue = AzNetworking::ProtocolType::Udp;
  68. return true;
  69. }
  70. }
  71. return false;
  72. }
  73. }
  74. namespace Multiplayer
  75. {
  76. using namespace AzNetworking;
  77. AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  78. "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port");
  79. AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  80. "The address of the remote server or host to connect to");
  81. AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic");
  82. AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic");
  83. AZ_CVAR(uint16_t, sv_portRange, 999, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The range of ports the host will incrementally attempt to bind to when initializing");
  84. AZ_CVAR(AZ::CVarFixedString, sv_map, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The map the server should load");
  85. AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking");
  86. AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server");
  87. AZ_CVAR(bool, sv_isTransient, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "[DEPRECATED: use sv_terminateOnPlayerExit instead] Whether a dedicated server shuts down if all existing connections disconnect.");
  88. AZ_CVAR(bool, sv_terminateOnPlayerExit, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether a dedicated server shuts down if all existing connections disconnect.");
  89. AZ_CVAR(AZ::TimeMs, sv_serverSendRateMs, AZ::TimeMs{ 50 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of milliseconds between each network update");
  90. AZ_CVAR(float, cl_renderTickBlendBase, 0.15f, nullptr, AZ::ConsoleFunctorFlags::Null,
  91. "The base used for blending between network updates, 0.1 will be quite linear, 0.2 or 0.3 will "
  92. "slow down quicker and may be better suited to connections with highly variable latency");
  93. AZ_CVAR(bool, bg_multiplayerDebugDraw, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables debug draw for the multiplayer gem");
  94. AZ_CVAR(bool, sv_dedicated_host_onstartup, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether dedicated servers will begin hosting on app startup.");
  95. AZ_CVAR(bool, cl_connect_onstartup, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "[DEPRECATED: use connect instead] Whether to call connect as soon as the Multiplayer SystemComponent is activated.");
  96. AZ_CVAR(bool, sv_versionMismatch_autoDisconnect, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  97. "Should the server automatically disconnect a client that is attempting connect who is running a build containing different/modified multiplayer components.");
  98. AZ_CVAR(bool, sv_versionMismatch_sendManifestToClient, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  99. "Should the server send all its individual multiplayer component version information to the client when there's a mismatch? "
  100. "Upon receiving the information, the client will print the mismatch information to the game log. "
  101. "Provided for debugging during development, but you may want to mark false for release builds.");
  102. AZ_CVAR(
  103. bool,
  104. sv_versionMismatch_check_enabled,
  105. true,
  106. nullptr,
  107. AZ::ConsoleFunctorFlags::DontReplicate,
  108. "If true, the server will check that client version of multiplayer component matches the server's.");
  109. AZ_CVAR(bool, bg_capturePhysicsTickMetric, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  110. "Should the Multiplayer gem record average physics tick time?");
  111. AZ_CVAR(bool, bg_captureTransportMetrics, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  112. "Should the Multiplayer gem record transport metrics?");
  113. AZ_CVAR(AzNetworking::ProtocolType, bg_captureTransportType, AzNetworking::ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  114. "Capture either UDP or TCP transport metrics.");
  115. AZ_CVAR(AZ::TimeMs, bg_captureTransportPeriod, AZ::TimeMs{1000}, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  116. "How often in milliseconds to record transport metrics.");
  117. AZ_CVAR(bool, sv_multithreadedConnectionUpdates, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  118. "If true, the server will send updates to clients on different threads, which improves performance with large number of clients");
  119. AZ_CVAR(bool, bg_parallelNotifyPreRender, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  120. "If true, OnPreRender events will be sent in parallel from job threads. Please make sure the handlers of the event are thread safe.");
  121. void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context)
  122. {
  123. NetworkSpawnable::Reflect(context);
  124. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  125. {
  126. serializeContext->Class<MultiplayerSystemComponent, AZ::Component>()
  127. ->Version(1);
  128. serializeContext->Class<NetEntityId>()
  129. ->Version(1);
  130. serializeContext->Class<NetComponentId>()
  131. ->Version(1);
  132. serializeContext->Class<PropertyIndex>()
  133. ->Version(1);
  134. serializeContext->Class<RpcIndex>()
  135. ->Version(1);
  136. serializeContext->Class<ClientInputId>()
  137. ->Version(1);
  138. serializeContext->Class<HostFrameId>()
  139. ->Version(1);
  140. }
  141. else if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  142. {
  143. behaviorContext->Class<NetEntityId>();
  144. behaviorContext->Class<NetComponentId>();
  145. behaviorContext->Class<PropertyIndex>();
  146. behaviorContext->Class<RpcIndex>();
  147. behaviorContext->Class<ClientInputId>();
  148. behaviorContext->Class<HostFrameId>();
  149. behaviorContext->Enum<static_cast<int>(MultiplayerAgentType::Uninitialized)>("MultiplayerAgentType_Uninitialized")
  150. ->Enum<static_cast<int>(MultiplayerAgentType::Client)>("MultiplayerAgentType_Client")
  151. ->Enum<static_cast<int>(MultiplayerAgentType::ClientServer)>("MultiplayerAgentType_ClientServer")
  152. ->Enum<static_cast<int>(MultiplayerAgentType::DedicatedServer)>("MultiplayerAgentType_DedicatedServer");
  153. behaviorContext->Class<MultiplayerSystemComponent>("MultiplayerSystemComponent")
  154. ->Attribute(AZ::Script::Attributes::Module, "multiplayer")
  155. ->Attribute(AZ::Script::Attributes::Category, "Multiplayer")
  156. ->Method("GetOnEndpointDisconnectedEvent", []() -> EndpointDisconnectedEvent*
  157. {
  158. const auto mpComponent = static_cast<MultiplayerSystemComponent*>(AZ::Interface<IMultiplayer>::Get());
  159. if (!mpComponent)
  160. {
  161. AZ_Assert(false, "GetOnEndpointDisconnectedEvent failed to find the multiplayer system component. Please update behavior context to properly retrieve the event.");
  162. return nullptr;
  163. }
  164. return &mpComponent->m_endpointDisconnectedEvent;
  165. })
  166. ->Attribute(
  167. AZ::Script::Attributes::AzEventDescription,
  168. AZ::BehaviorAzEventDescription{ "On Endpoint Disconnected Event", { "Type of Multiplayer Agent that disconnected" } })
  169. ->Method("ClearAllEntities", []()
  170. {
  171. const auto mpComponent = static_cast<MultiplayerSystemComponent*>(AZ::Interface<IMultiplayer>::Get());
  172. if (!mpComponent)
  173. {
  174. AZ_Assert( false, "ClearAllEntities failed to find the multiplayer system component. Please update behavior context to properly clear all entities.");
  175. return;
  176. }
  177. mpComponent->GetNetworkEntityManager()->ClearAllEntities();
  178. })
  179. ->Method("GetCurrentBlendFactor", []()
  180. {
  181. if (const IMultiplayer* multiplayerSystem = GetMultiplayer())
  182. {
  183. return multiplayerSystem->GetCurrentBlendFactor();
  184. }
  185. return 0.f;
  186. })
  187. ;
  188. }
  189. MultiplayerComponent::Reflect(context);
  190. NetworkTime::Reflect(context);
  191. }
  192. void MultiplayerSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  193. {
  194. required.push_back(AZ_CRC_CE("NetworkingService"));
  195. required.push_back(AZ_CRC_CE("MultiplayerStatSystemComponent"));
  196. }
  197. void MultiplayerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  198. {
  199. provided.push_back(AZ_CRC_CE("MultiplayerService"));
  200. }
  201. void MultiplayerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  202. {
  203. incompatible.push_back(AZ_CRC_CE("MultiplayerService"));
  204. }
  205. MultiplayerSystemComponent::MultiplayerSystemComponent()
  206. : m_consoleCommandHandler([this]
  207. (
  208. AZStd::string_view command,
  209. const AZ::ConsoleCommandContainer& args,
  210. AZ::ConsoleFunctorFlags flags,
  211. AZ::ConsoleInvokedFrom invokedFrom
  212. ) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); })
  213. , m_autonomousEntityReplicatorCreatedHandler([this]([[maybe_unused]] NetEntityId netEntityId) { OnAutonomousEntityReplicatorCreated(); })
  214. {
  215. AZ::Interface<IMultiplayer>::Register(this);
  216. }
  217. MultiplayerSystemComponent::~MultiplayerSystemComponent()
  218. {
  219. AZ::Interface<IMultiplayer>::Unregister(this);
  220. }
  221. void MultiplayerSystemComponent::Activate()
  222. {
  223. #if (O3DE_EDITOR_CONNECTION_LISTENER_ENABLE)
  224. m_editorConnectionListener = AZStd::make_unique<MultiplayerEditorConnection>();
  225. #endif
  226. RegisterMetrics();
  227. AzFramework::RootSpawnableNotificationBus::Handler::BusConnect();
  228. AZ::TickBus::Handler::BusConnect();
  229. SessionNotificationBus::Handler::BusConnect();
  230. AzFramework::LevelLoadBlockerBus::Handler::BusConnect();
  231. const AZ::Name interfaceName = AZ::Name(MpNetworkInterfaceName);
  232. m_networkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(interfaceName, sv_protocol, TrustZone::ExternalClientToServer, *this);
  233. AZ::Interface<ISessionHandlingClientRequests>::Register(this);
  234. //! Register our gems multiplayer components to assign NetComponentIds
  235. RegisterMultiplayerComponents();
  236. if (auto console = AZ::Interface<AZ::IConsole>::Get())
  237. {
  238. m_consoleCommandHandler.Connect(console->GetConsoleCommandInvokedEvent());
  239. }
  240. if (bg_captureTransportMetrics)
  241. {
  242. m_metricsEvent.Enqueue(bg_captureTransportPeriod, true);
  243. }
  244. // Wait for all systems to activate because allowing this server or client to host or connect.
  245. // Connecting too soon causes a "version mismatch" because all of the system components haven't registered their multiplayer components.
  246. if (const auto settingsRegistry = AZ::SettingsRegistry::Get())
  247. {
  248. AZ::ComponentApplicationLifecycle::RegisterHandler(
  249. *settingsRegistry,
  250. m_componentApplicationLifecycleHandler,
  251. [this](const AZ::SettingsRegistryInterface::NotifyEventArgs&)
  252. {
  253. const auto console = AZ::Interface<AZ::IConsole>::Get();
  254. if (!console)
  255. {
  256. AZ_Assert(false, "Multiplayer system is attempting to register console commands before AZ::Console is available.");
  257. return;
  258. }
  259. // It's now safe to register and execute the "host" and "connect" commands
  260. m_hostConsoleCommand = AZStd::make_unique<AZ::ConsoleFunctor<MultiplayerSystemComponent, false>>(
  261. "host",
  262. "Opens a multiplayer connection as a host for other clients to connect to",
  263. AZ::ConsoleFunctorFlags::DontReplicate | AZ::ConsoleFunctorFlags::DontDuplicate,
  264. AZ::TypeId{},
  265. *this,
  266. &MultiplayerSystemComponent::HostConsoleCommand);
  267. m_connectConsoleCommand = AZStd::make_unique<AZ::ConsoleFunctor<MultiplayerSystemComponent, false>>(
  268. "connect",
  269. "Opens a multiplayer connection to a remote host",
  270. AZ::ConsoleFunctorFlags::DontReplicate | AZ::ConsoleFunctorFlags::DontDuplicate,
  271. AZ::TypeId{},
  272. *this,
  273. &MultiplayerSystemComponent::ConnectConsoleCommand);
  274. // ExecuteDeferredConsoleCommands will execute any previously deferred "host" or "connect" commands now that they have been registered with the AZ Console
  275. console->ExecuteDeferredConsoleCommands();
  276. // Don't access cvars directly (their values might be stale https://github.com/o3de/o3de/issues/5537)
  277. bool isDedicatedServer = false;
  278. bool dedicatedServerHostOnStartup = false;
  279. if (console->GetCvarValue("sv_isDedicated", isDedicatedServer) != AZ::GetValueResult::Success)
  280. {
  281. AZLOG_WARN("Multiplayer system failed to access cvar on startup (sv_isDedicated).")
  282. return;
  283. }
  284. if (console->GetCvarValue("sv_dedicated_host_onstartup", dedicatedServerHostOnStartup) != AZ::GetValueResult::Success)
  285. {
  286. AZLOG_WARN("Multiplayer system failed to access cvar on startup (sv_dedicated_host_onstartup).")
  287. return;
  288. }
  289. // Dedicated servers will automatically begin hosting
  290. if (isDedicatedServer && dedicatedServerHostOnStartup)
  291. {
  292. this->StartHosting(sv_port, /*is dedicated*/ true);
  293. }
  294. },
  295. "SystemComponentsActivated",
  296. /*autoRegisterEvent*/ true);
  297. }
  298. }
  299. void MultiplayerSystemComponent::RegisterMetrics()
  300. {
  301. DECLARE_PERFORMANCE_STAT_GROUP(MultiplayerGroup_Networking, "Networking");
  302. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_EntityCount, "NumEntities");
  303. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_FrameTimeUs, "FrameTimeUs");
  304. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_ClientConnectionCount, "ClientConnections");
  305. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_ApplicationFrameTimeUs, "AppFrameTimeUs");
  306. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_DesyncCorrections, "DesyncCorrections");
  307. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalTimeSpentUpdatingMs, "TotalTimeSpentUpdatingMs");
  308. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalSendTimeMs, "TotalSendTimeMs");
  309. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalSentPackets, "TotalSentPackets");
  310. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalSentBytesAfterCompression, "TotalSentBytesAfterCompression");
  311. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalSentBytesBeforeCompression, "TotalSentBytesBeforeCompression");
  312. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalResentPacketsDueToPacketLoss, "TotalResentPacketsDueToPacketLoss");
  313. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalReceiveTimeInMs, "TotalReceiveTimeInMs");
  314. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalReceivedPackets, "TotalReceivedPackets");
  315. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalReceivedBytesAfterCompression, "TotalReceivedBytesAfterCompression");
  316. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalReceivedBytesBeforeCompression, "TotalReceivedBytesBeforeCompression");
  317. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_TotalPacketsDiscardedDueToLoad, "TotalPacketsDiscardedDueToLoad");
  318. DECLARE_PERFORMANCE_STAT(MultiplayerGroup_Networking, MultiplayerStat_PhysicsFrameTimeUs, "PhysicsFrameTimeUs");
  319. }
  320. void MultiplayerSystemComponent::Deactivate()
  321. {
  322. m_hostConsoleCommand.reset();
  323. m_preSimulateHandler.Disconnect();
  324. m_postSimulateHandler.Disconnect();
  325. m_metricsEvent.RemoveFromQueue();
  326. AZ::Interface<ISessionHandlingClientRequests>::Unregister(this);
  327. m_consoleCommandHandler.Disconnect();
  328. const AZ::Name interfaceName = AZ::Name(MpNetworkInterfaceName);
  329. AZ::Interface<INetworking>::Get()->DestroyNetworkInterface(interfaceName);
  330. AzFramework::LevelLoadBlockerBus::Handler::BusDisconnect();
  331. SessionNotificationBus::Handler::BusDisconnect();
  332. AZ::TickBus::Handler::BusDisconnect();
  333. AzFramework::RootSpawnableNotificationBus::Handler::BusDisconnect();
  334. m_networkEntityManager.Reset();
  335. #if (O3DE_EDITOR_CONNECTION_LISTENER_ENABLE)
  336. m_editorConnectionListener.reset();
  337. #endif
  338. }
  339. bool MultiplayerSystemComponent::StartHosting(uint16_t port, bool isDedicated)
  340. {
  341. if (IsHosting())
  342. {
  343. AZLOG_WARN("Already hosting on port %u, new host request ignored (request is for port %u).",
  344. m_networkInterface->GetPort(), static_cast<uint32_t>(sv_port));
  345. return false;
  346. }
  347. if (port == UseDefaultHostPort)
  348. {
  349. port = sv_port;
  350. }
  351. if (port != sv_port)
  352. {
  353. sv_port = port;
  354. }
  355. const uint16_t maxPort = sv_port + sv_portRange;
  356. while (sv_port <= maxPort)
  357. {
  358. if (m_networkInterface->Listen(sv_port))
  359. {
  360. InitializeMultiplayer(isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer);
  361. return true;
  362. }
  363. AZLOG_WARN("Failed to start listening on port %u, port is in use?", static_cast<uint32_t>(sv_port));
  364. sv_port = sv_port + 1;
  365. }
  366. return false;
  367. }
  368. bool MultiplayerSystemComponent::Connect(const AZStd::string& remoteAddress, uint16_t port)
  369. {
  370. InitializeMultiplayer(MultiplayerAgentType::Client);
  371. const IpAddress address(remoteAddress.c_str(), port, m_networkInterface->GetType());
  372. return m_networkInterface->Connect(address, cl_clientport) != InvalidConnectionId;
  373. }
  374. void MultiplayerSystemComponent::Terminate(AzNetworking::DisconnectReason reason)
  375. {
  376. // Cleanup connections, fire events and uninitialize state
  377. auto visitor = [reason](IConnection& connection) { connection.Disconnect(reason, TerminationEndpoint::Local); };
  378. m_networkInterface->GetConnectionSet().VisitConnections(visitor);
  379. MultiplayerAgentType agentType = GetAgentType();
  380. if (agentType == MultiplayerAgentType::DedicatedServer || agentType == MultiplayerAgentType::ClientServer)
  381. {
  382. m_networkInterface->StopListening();
  383. }
  384. // Clear out all the registered network entities
  385. GetNetworkEntityManager()->ClearAllEntities();
  386. InitializeMultiplayer(MultiplayerAgentType::Uninitialized);
  387. // Signal session management, do this after uninitializing state
  388. if (agentType == MultiplayerAgentType::DedicatedServer || agentType == MultiplayerAgentType::ClientServer)
  389. {
  390. if (AZ::Interface<ISessionHandlingProviderRequests>::Get() != nullptr)
  391. {
  392. AZ::Interface<ISessionHandlingProviderRequests>::Get()->HandleDestroySession();
  393. }
  394. }
  395. }
  396. bool MultiplayerSystemComponent::RequestPlayerJoinSession(const SessionConnectionConfig& config)
  397. {
  398. m_pendingConnectionTickets.push(config.m_playerSessionId);
  399. AZStd::string hostname = config.m_dnsName.empty() ? config.m_ipAddress : config.m_dnsName;
  400. Connect(hostname.c_str(), config.m_port);
  401. return true;
  402. }
  403. void MultiplayerSystemComponent::RequestPlayerLeaveSession()
  404. {
  405. if (GetAgentType() == MultiplayerAgentType::Client)
  406. {
  407. Terminate(DisconnectReason::TerminatedByUser);
  408. }
  409. }
  410. bool MultiplayerSystemComponent::OnSessionHealthCheck()
  411. {
  412. return true;
  413. }
  414. bool MultiplayerSystemComponent::IsHosting() const
  415. {
  416. return (GetAgentType() == MultiplayerAgentType::ClientServer) || (GetAgentType() == MultiplayerAgentType::DedicatedServer);
  417. }
  418. bool MultiplayerSystemComponent::OnCreateSessionBegin(const SessionConfig& sessionConfig)
  419. {
  420. // Check if session manager has a certificate for us and pass it along if so
  421. auto console = AZ::Interface<AZ::IConsole>::Get();
  422. if (console != nullptr)
  423. {
  424. bool tcpUseEncryption = false;
  425. console->GetCvarValue("net_TcpUseEncryption", tcpUseEncryption);
  426. bool udpUseEncryption = false;
  427. console->GetCvarValue("net_UdpUseEncryption", udpUseEncryption);
  428. auto sessionProviderHandler = AZ::Interface<ISessionHandlingProviderRequests>::Get();
  429. if ((tcpUseEncryption || udpUseEncryption) && sessionProviderHandler != nullptr)
  430. {
  431. AZ::CVarFixedString externalCertPath = AZ::CVarFixedString(sessionProviderHandler->GetExternalSessionCertificate().c_str());
  432. if (!externalCertPath.empty())
  433. {
  434. AZ::CVarFixedString commandString = "net_SslExternalCertificateFile " + externalCertPath;
  435. console->PerformCommand(commandString.c_str());
  436. }
  437. }
  438. }
  439. Multiplayer::MultiplayerAgentType serverType = sv_isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer;
  440. InitializeMultiplayer(serverType);
  441. // Load a multiplayer level if there's a session property called the "level"...
  442. if (const auto& levelName = sessionConfig.m_sessionProperties.find("level");
  443. console != nullptr && levelName != sessionConfig.m_sessionProperties.end())
  444. {
  445. AZStd::string loadLevelCommand = "loadlevel " + levelName->second;
  446. console->PerformCommand(loadLevelCommand.c_str());
  447. }
  448. return m_networkInterface->Listen(sessionConfig.m_port);
  449. }
  450. void MultiplayerSystemComponent::OnCreateSessionEnd()
  451. {
  452. }
  453. bool MultiplayerSystemComponent::OnDestroySessionBegin()
  454. {
  455. // This can be triggered external from Multiplayer so only run if we are in an Initialized state
  456. if (GetAgentType() == MultiplayerAgentType::Uninitialized)
  457. {
  458. return true;
  459. }
  460. auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByServer, TerminationEndpoint::Local); };
  461. m_networkInterface->GetConnectionSet().VisitConnections(visitor);
  462. if (GetAgentType() == MultiplayerAgentType::DedicatedServer || GetAgentType() == MultiplayerAgentType::ClientServer)
  463. {
  464. m_networkInterface->StopListening();
  465. }
  466. InitializeMultiplayer(MultiplayerAgentType::Uninitialized);
  467. return true;
  468. }
  469. void MultiplayerSystemComponent::OnDestroySessionEnd()
  470. {
  471. }
  472. void MultiplayerSystemComponent::OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason)
  473. {
  474. AZ_UNUSED(sessionConfig);
  475. AZ_UNUSED(updateReason);
  476. }
  477. void MultiplayerSystemComponent::OnUpdateSessionEnd()
  478. {
  479. }
  480. void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
  481. {
  482. AZ_PROFILE_SCOPE(MULTIPLAYER, "MultiplayerSystemComponent: OnTick");
  483. SET_PERFORMANCE_STAT(MultiplayerStat_ApplicationFrameTimeUs, AZ::SecondsToTimeUs(deltaTime));
  484. const AZStd::chrono::steady_clock::time_point startMultiplayerTickTime = AZStd::chrono::steady_clock::now();
  485. if (bg_multiplayerDebugDraw)
  486. {
  487. m_networkEntityManager.DebugDraw();
  488. }
  489. const AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
  490. const AZ::TimeMs serverRateMs = static_cast<AZ::TimeMs>(sv_serverSendRateMs);
  491. const float serverRateSeconds = static_cast<float>(serverRateMs) / 1000.0f;
  492. TickVisibleNetworkEntities(deltaTime, serverRateSeconds);
  493. if (GetAgentType() == MultiplayerAgentType::ClientServer
  494. || GetAgentType() == MultiplayerAgentType::DedicatedServer)
  495. {
  496. m_serverSendAccumulator += deltaTime;
  497. if (m_serverSendAccumulator < serverRateSeconds)
  498. {
  499. return;
  500. }
  501. m_serverSendAccumulator -= serverRateSeconds;
  502. m_networkTime.IncrementHostFrameId();
  503. }
  504. // Handle deferred local rpc messages that were generated during the updates
  505. m_networkEntityManager.DispatchLocalDeferredRpcMessages();
  506. // INetworking ticks immediately before IMultiplayer, so all our pending RPC's and network property updates have now been processed
  507. // Restore any entities that were rewound during input processing so that normal gameplay updates have the correct state
  508. Multiplayer::GetNetworkTime()->ClearRewoundEntities();
  509. // Let the network system know the frame is done and we can collect dirty bits
  510. m_networkEntityManager.NotifyEntitiesChanged();
  511. m_networkEntityManager.NotifyEntitiesDirtied();
  512. MultiplayerStats& stats = GetStats();
  513. stats.TickStats(deltaTimeMs);
  514. stats.m_entityCount = GetNetworkEntityManager()->GetEntityCount();
  515. stats.m_serverConnectionCount = 0;
  516. stats.m_clientConnectionCount = 0;
  517. // Metrics calculation, as update calls are threaded.
  518. UpdatedMetricsConnectionCount();
  519. // Send out the game state update to all connections
  520. UpdateConnections();
  521. MultiplayerPackets::SyncConsole packet;
  522. AZ::ThreadSafeDeque<AZStd::string>::DequeType cvarUpdates;
  523. m_cvarCommands.Swap(cvarUpdates);
  524. auto visitor = [&packet](IConnection& connection)
  525. {
  526. if (connection.GetConnectionRole() == ConnectionRole::Acceptor)
  527. {
  528. connection.SendReliablePacket(packet);
  529. }
  530. };
  531. while (cvarUpdates.size() > 0)
  532. {
  533. packet.ModifyCommandSet().emplace_back(cvarUpdates.front());
  534. if (packet.GetCommandSet().full())
  535. {
  536. m_networkInterface->GetConnectionSet().VisitConnections(visitor);
  537. packet.ModifyCommandSet().clear();
  538. }
  539. cvarUpdates.pop_front();
  540. }
  541. if (!packet.GetCommandSet().empty())
  542. {
  543. AZ_PROFILE_SCOPE(MULTIPLAYER, "MultiplayerSystemComponent: OnTick - SendReliablePackets");
  544. m_networkInterface->GetConnectionSet().VisitConnections(visitor);
  545. }
  546. const auto duration =
  547. AZStd::chrono::duration_cast<AZStd::chrono::microseconds>(AZStd::chrono::steady_clock::now() - startMultiplayerTickTime);
  548. stats.RecordFrameTime(AZ::TimeUs{ duration.count() });
  549. }
  550. void MultiplayerSystemComponent::UpdatedMetricsConnectionCount()
  551. {
  552. MultiplayerStats& stats = GetStats();
  553. auto updateMetrics = [&stats](IConnection& connection)
  554. {
  555. if (connection.GetUserData() != nullptr)
  556. {
  557. IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection.GetUserData());
  558. if (connectionData->GetConnectionDataType() == ConnectionDataType::ServerToClient)
  559. {
  560. stats.m_clientConnectionCount++;
  561. }
  562. else
  563. {
  564. stats.m_serverConnectionCount++;
  565. }
  566. }
  567. };
  568. m_networkInterface->GetConnectionSet().VisitConnections(updateMetrics);
  569. }
  570. void MultiplayerSystemComponent::UpdateConnections()
  571. {
  572. if (sv_multithreadedConnectionUpdates && (GetAgentType() == MultiplayerAgentType::ClientServer ||
  573. GetAgentType() == MultiplayerAgentType::DedicatedServer))
  574. {
  575. // Threaded update calls.
  576. AZ_PROFILE_SCOPE(MULTIPLAYER, "MultiplayerSystemComponent: UpdateConnections");
  577. AZ::JobCompletion jobCompletion;
  578. auto sendNetworkUpdates = [&jobCompletion](IConnection& connection)
  579. {
  580. AZ::Job* job = AZ::CreateJobFunction([&connection]()
  581. {
  582. if (connection.GetUserData() != nullptr)
  583. {
  584. IConnectionData* connectionData = static_cast<IConnectionData*>(connection.GetUserData());
  585. connectionData->Update();
  586. }
  587. }, true /*auto delete*/, nullptr);
  588. job->SetDependent(&jobCompletion);
  589. job->Start();
  590. };
  591. m_networkInterface->GetConnectionSet().VisitConnections(sendNetworkUpdates);
  592. jobCompletion.StartAndWaitForCompletion();
  593. }
  594. else // On clients (including the Editor) run in a single threaded mode to avoid issues in UI asset loading
  595. {
  596. AZ_PROFILE_SCOPE(MULTIPLAYER, "MultiplayerSystemComponent: OnTick - SendOutGameStateUpdate");
  597. auto sendNetworkUpdates = [](IConnection& connection)
  598. {
  599. if (connection.GetUserData() != nullptr)
  600. {
  601. IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection.GetUserData());
  602. connectionData->Update();
  603. }
  604. };
  605. m_networkInterface->GetConnectionSet().VisitConnections(sendNetworkUpdates);
  606. }
  607. }
  608. int MultiplayerSystemComponent::GetTickOrder()
  609. {
  610. // Tick immediately after the network system component
  611. return AZ::TICK_PLACEMENT + 1;
  612. }
  613. struct ConsoleReplicator
  614. {
  615. ConsoleReplicator(IConnection* connection)
  616. : m_connection(connection)
  617. {
  618. ;
  619. }
  620. virtual ~ConsoleReplicator()
  621. {
  622. if (!m_syncPacket.GetCommandSet().empty())
  623. {
  624. m_connection->SendReliablePacket(m_syncPacket);
  625. }
  626. }
  627. void Visit(AZ::ConsoleFunctorBase* functor)
  628. {
  629. if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::DontReplicate) == AZ::ConsoleFunctorFlags::DontReplicate)
  630. {
  631. // If the cvar is marked don't replicate, don't send it at all
  632. return;
  633. }
  634. AZ::CVarFixedString replicateValue;
  635. if (functor->GetReplicationString(replicateValue))
  636. {
  637. m_syncPacket.ModifyCommandSet().emplace_back(AZStd::move(replicateValue));
  638. if (m_syncPacket.GetCommandSet().full())
  639. {
  640. m_connection->SendReliablePacket(m_syncPacket);
  641. m_syncPacket.ModifyCommandSet().clear();
  642. }
  643. }
  644. }
  645. IConnection* m_connection;
  646. MultiplayerPackets::SyncConsole m_syncPacket;
  647. };
  648. bool MultiplayerSystemComponent::IsHandshakeComplete(AzNetworking::IConnection* connection) const
  649. {
  650. return reinterpret_cast<IConnectionData*>(connection->GetUserData())->DidHandshake();
  651. }
  652. bool MultiplayerSystemComponent::AttemptPlayerConnect(AzNetworking::IConnection* connection, MultiplayerPackets::Connect& packet)
  653. {
  654. reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetProviderTicket(packet.GetTicket().c_str());
  655. const char* levelName = AZ::Interface<AzFramework::ILevelSystemLifecycle>::Get()->GetCurrentLevelName();
  656. if (!levelName || *levelName == '\0')
  657. {
  658. AZLOG_WARN(
  659. "Server does not have a multiplayer level loaded! Make sure the server has a level loaded before accepting clients.");
  660. m_noServerLevelLoadedEvent.Signal();
  661. connection->Disconnect(DisconnectReason::ServerNoLevelLoaded, TerminationEndpoint::Local);
  662. return true;
  663. }
  664. // Hosts will handle spawning for a player on connect
  665. if (GetAgentType() == MultiplayerAgentType::ClientServer
  666. || GetAgentType() == MultiplayerAgentType::DedicatedServer)
  667. {
  668. // We use a temporary userId over the clients address so we can maintain client lookups even in the event of wifi handoff
  669. IMultiplayerSpawner* spawner = AZ::Interface<IMultiplayerSpawner>::Get();
  670. NetworkEntityHandle controlledEntity;
  671. // Check rejoin data first
  672. const auto node = m_playerRejoinData.find(packet.GetTemporaryUserId());
  673. if (node != m_playerRejoinData.end())
  674. {
  675. controlledEntity = m_networkEntityManager.GetNetworkEntityTracker()->Get(node->second);
  676. }
  677. else if (spawner)
  678. {
  679. // Route to spawner implementation
  680. MultiplayerAgentDatum datum;
  681. datum.m_agentType = MultiplayerAgentType::Client;
  682. datum.m_id = connection->GetConnectionId();
  683. const uint64_t userId = packet.GetTemporaryUserId();
  684. controlledEntity = spawner->OnPlayerJoin(userId, datum);
  685. if (controlledEntity.Exists())
  686. {
  687. EnableAutonomousControl(controlledEntity, connection->GetConnectionId());
  688. StartServerToClientReplication(userId, controlledEntity, connection);
  689. }
  690. else
  691. {
  692. // If there wasn't a player entity available, wait until a level loads and check again.
  693. // This can happen if IMultiplayerSpawn depends on a level being loaded, but the client connects to the server before the server has started a level.
  694. m_playersWaitingToBeSpawned.emplace_back(userId, datum, connection);
  695. }
  696. }
  697. else
  698. {
  699. // There's no player spawner, maybe the level's entities aren't finished activating
  700. if (!m_levelEntitiesActivated)
  701. {
  702. // Remember this player, and spawn it once the level entities finish activating
  703. MultiplayerAgentDatum datum;
  704. datum.m_agentType = MultiplayerAgentType::Client;
  705. datum.m_id = connection->GetConnectionId();
  706. const uint64_t userId = packet.GetTemporaryUserId();
  707. m_playersWaitingToBeSpawned.emplace_back(userId, datum, connection);
  708. }
  709. else
  710. {
  711. AZLOG_ERROR("No IMultiplayerSpawner was available. Ensure that one is registered for usage on PlayerJoin.");
  712. }
  713. }
  714. }
  715. if (connection->SendReliablePacket(MultiplayerPackets::Accept(levelName)))
  716. {
  717. reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetDidHandshake(true);
  718. if (packet.GetTemporaryUserId() == 0)
  719. {
  720. // Sync our console
  721. ConsoleReplicator consoleReplicator(connection);
  722. AZ::Interface<AZ::IConsole>::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); });
  723. }
  724. return true;
  725. }
  726. return false;
  727. }
  728. bool MultiplayerSystemComponent::HandleRequest
  729. (
  730. AzNetworking::IConnection* connection,
  731. [[maybe_unused]] const IPacketHeader& packetHeader,
  732. MultiplayerPackets::Connect& packet
  733. )
  734. {
  735. PlayerConnectionConfig config;
  736. config.m_playerConnectionId = aznumeric_cast<uint32_t>(connection->GetConnectionId());
  737. config.m_playerSessionId = packet.GetTicket();
  738. // Validate our session with the provider if any
  739. ISessionHandlingProviderRequests* sessionRequests = AZ::Interface<ISessionHandlingProviderRequests>::Get();
  740. if (sessionRequests != nullptr)
  741. {
  742. if (!sessionRequests->ValidatePlayerJoinSession(config))
  743. {
  744. auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); };
  745. m_networkInterface->GetConnectionSet().VisitConnections(visitor);
  746. return true;
  747. }
  748. }
  749. // Make sure the client that's trying to connect has the same multiplayer components
  750. if (sv_versionMismatch_check_enabled && GetMultiplayerComponentRegistry()->GetSystemVersionHash() != packet.GetSystemVersionHash())
  751. {
  752. // There's a multiplayer component mismatch. Send the server's component information back to the client so they can compare.
  753. if (sv_versionMismatch_sendManifestToClient)
  754. {
  755. MultiplayerPackets::VersionMismatch versionMismatchPacket(GetMultiplayerComponentRegistry()->GetMultiplayerComponentVersionHashes());
  756. connection->SendReliablePacket(versionMismatchPacket);
  757. }
  758. else
  759. {
  760. // sv_versionMismatch_sendManifestToClient is false; don't send any individual components, just let the client know there was a mismatch.
  761. MultiplayerPackets::VersionMismatch versionMismatchPacket;
  762. connection->SendReliablePacket(versionMismatchPacket);
  763. }
  764. m_originalConnectPackets[connection->GetConnectionId()] = packet;
  765. return true;
  766. }
  767. return AttemptPlayerConnect(connection, packet);
  768. }
  769. bool MultiplayerSystemComponent::HandleRequest
  770. (
  771. [[maybe_unused]] AzNetworking::IConnection* connection,
  772. [[maybe_unused]] const IPacketHeader& packetHeader,
  773. [[maybe_unused]] MultiplayerPackets::Accept& packet
  774. )
  775. {
  776. reinterpret_cast<IConnectionData*>(connection->GetUserData())->SetDidHandshake(true);
  777. if (m_temporaryUserIdentifier == 0)
  778. {
  779. sv_map = packet.GetMap().c_str();
  780. AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap();
  781. m_blockClientLoadLevel = false;
  782. AZ::Interface<AZ::IConsole>::Get()->PerformCommand(loadLevelString.c_str());
  783. m_blockClientLoadLevel = true;
  784. }
  785. else
  786. {
  787. // Bypass map loading and immediately ready the connection for updates
  788. IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection->GetUserData());
  789. if (connectionData)
  790. {
  791. connectionData->SetCanSendUpdates(true);
  792. // @nt: TODO - delete once dropped RPC problem fixed
  793. // Connection has migrated, we are now waiting for the autonomous entity replicator to be created
  794. connectionData->GetReplicationManager().AddAutonomousEntityReplicatorCreatedHandler(m_autonomousEntityReplicatorCreatedHandler);
  795. }
  796. }
  797. m_serverAcceptanceReceivedEvent.Signal();
  798. return true;
  799. }
  800. bool MultiplayerSystemComponent::HandleRequest
  801. (
  802. AzNetworking::IConnection* connection,
  803. [[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader,
  804. MultiplayerPackets::ReadyForEntityUpdates& packet
  805. )
  806. {
  807. IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection->GetUserData());
  808. if (connectionData)
  809. {
  810. connectionData->SetCanSendUpdates(packet.GetReadyForEntityUpdates());
  811. return true;
  812. }
  813. return false;
  814. }
  815. bool MultiplayerSystemComponent::HandleRequest
  816. (
  817. [[maybe_unused]] AzNetworking::IConnection* connection,
  818. [[maybe_unused]] const IPacketHeader& packetHeader,
  819. [[maybe_unused]] MultiplayerPackets::SyncConsole& packet
  820. )
  821. {
  822. if (GetAgentType() != MultiplayerAgentType::Client)
  823. {
  824. return false;
  825. }
  826. ExecuteConsoleCommandList(connection, packet.GetCommandSet());
  827. return true;
  828. }
  829. bool MultiplayerSystemComponent::HandleRequest
  830. (
  831. [[maybe_unused]] AzNetworking::IConnection* connection,
  832. [[maybe_unused]] const IPacketHeader& packetHeader,
  833. [[maybe_unused]] MultiplayerPackets::ConsoleCommand& packet
  834. )
  835. {
  836. const bool isClient = (GetAgentType() == MultiplayerAgentType::Client);
  837. const AZ::ConsoleFunctorFlags requiredSet = isClient ? AZ::ConsoleFunctorFlags::Null : AZ::ConsoleFunctorFlags::AllowClientSet;
  838. AZ::Interface<AZ::IConsole>::Get()->PerformCommand(packet.GetCommand().c_str(), AZ::ConsoleSilentMode::NotSilent, AZ::ConsoleInvokedFrom::AzNetworking, requiredSet);
  839. return true;
  840. }
  841. bool MultiplayerSystemComponent::HandleRequest
  842. (
  843. [[maybe_unused]] AzNetworking::IConnection* connection,
  844. [[maybe_unused]] const IPacketHeader& packetHeader,
  845. [[maybe_unused]] MultiplayerPackets::EntityUpdates& packet
  846. )
  847. {
  848. bool handledAll = true;
  849. if (connection->GetUserData() == nullptr)
  850. {
  851. AZLOG_WARN("Missing connection data, likely due to a connection in the process of closing, entity updates size %u", aznumeric_cast<uint32_t>(packet.GetEntityMessages().size()));
  852. return handledAll;
  853. }
  854. EntityReplicationManager& replicationManager = reinterpret_cast<IConnectionData*>(connection->GetUserData())->GetReplicationManager();
  855. if ((GetAgentType() == MultiplayerAgentType::Client) && (packet.GetHostFrameId() > m_lastReplicatedHostFrameId))
  856. {
  857. // Update client to latest server time
  858. m_tickFactor = 0.0f;
  859. m_lastReplicatedHostTimeMs = packet.GetHostTimeMs();
  860. m_lastReplicatedHostFrameId = packet.GetHostFrameId();
  861. m_networkTime.ForceSetTime(m_lastReplicatedHostFrameId, m_lastReplicatedHostTimeMs);
  862. }
  863. for (AZStd::size_t i = 0; i < packet.GetEntityMessages().size(); ++i)
  864. {
  865. const NetworkEntityUpdateMessage& updateMessage = packet.GetEntityMessages()[i];
  866. handledAll &= replicationManager.HandleEntityUpdateMessage(connection, packetHeader, updateMessage);
  867. AZ_Assert(handledAll, "EntityUpdates did not handle all update messages");
  868. }
  869. return handledAll;
  870. }
  871. bool MultiplayerSystemComponent::HandleRequest
  872. (
  873. [[maybe_unused]] AzNetworking::IConnection* connection,
  874. [[maybe_unused]] const IPacketHeader& packetHeader,
  875. [[maybe_unused]] MultiplayerPackets::EntityRpcs& packet
  876. )
  877. {
  878. if (connection->GetUserData() == nullptr)
  879. {
  880. AZLOG_WARN("Missing connection data, likely due to a connection in the process of closing, entity updates size %u", aznumeric_cast<uint32_t>(packet.GetEntityRpcs().size()));
  881. return true;
  882. }
  883. EntityReplicationManager& replicationManager = reinterpret_cast<IConnectionData*>(connection->GetUserData())->GetReplicationManager();
  884. return replicationManager.HandleEntityRpcMessages(connection, packet.ModifyEntityRpcs());
  885. }
  886. bool MultiplayerSystemComponent::HandleRequest
  887. (
  888. [[maybe_unused]] AzNetworking::IConnection* connection,
  889. [[maybe_unused]] const IPacketHeader& packetHeader,
  890. [[maybe_unused]] MultiplayerPackets::RequestReplicatorReset& packet
  891. )
  892. {
  893. if (connection->GetUserData() == nullptr)
  894. {
  895. AZLOG_WARN("Missing connection data, likely due to a connection in the process of closing");
  896. return true;
  897. }
  898. EntityReplicationManager& replicationManager = reinterpret_cast<IConnectionData*>(connection->GetUserData())->GetReplicationManager();
  899. return replicationManager.HandleEntityResetMessages(connection, packet.GetEntityIds());
  900. }
  901. bool MultiplayerSystemComponent::HandleRequest
  902. (
  903. [[maybe_unused]] AzNetworking::IConnection* connection,
  904. [[maybe_unused]] const IPacketHeader& packetHeader,
  905. [[maybe_unused]] MultiplayerPackets::ClientMigration& packet
  906. )
  907. {
  908. if (GetAgentType() != MultiplayerAgentType::Client)
  909. {
  910. // Only clients are allowed to migrate from one server to another
  911. return false;
  912. }
  913. // Store the temporary user identifier so we can transmit it with our next Connect packet
  914. // The new server will use this to re-attach our set of autonomous entities
  915. m_temporaryUserIdentifier = packet.GetTemporaryUserIdentifier();
  916. // Disconnect our existing server connection
  917. auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::ClientMigrated, TerminationEndpoint::Local); };
  918. m_networkInterface->GetConnectionSet().VisitConnections(visitor);
  919. AZLOG_INFO("Migrating to new server shard");
  920. m_clientMigrationStartEvent.Signal(packet.GetLastClientInputId());
  921. if (m_networkInterface->Connect(packet.GetRemoteServerAddress()) == AzNetworking::InvalidConnectionId)
  922. {
  923. AZLOG_ERROR("Failed to connect to new host during client migration event");
  924. }
  925. return true;
  926. }
  927. bool MultiplayerSystemComponent::HandleRequest(
  928. IConnection* connection,
  929. [[maybe_unused]] const IPacketHeader& packetHeader,
  930. MultiplayerPackets::VersionMismatch& packet)
  931. {
  932. // Iterate over each component and see what's been added, missing, or modified
  933. for (const auto& [theirComponentName, theirComponentHash] : packet.GetComponentVersions())
  934. {
  935. // Check for modified components
  936. AZ::HashValue64 localComponentHash;
  937. if (GetMultiplayerComponentRegistry()->FindComponentVersionHashByName(theirComponentName, localComponentHash))
  938. {
  939. if (theirComponentHash != localComponentHash)
  940. {
  941. AZLOG_ERROR(
  942. "Multiplayer component mismatch! %s has a different version hash. Please make sure both client and server have "
  943. "matching multiplayer components.",
  944. theirComponentName.GetCStr());
  945. }
  946. }
  947. else
  948. {
  949. // Connected application is using a multiplayer component that doesn't exist in this application
  950. AZLOG_ERROR(
  951. "Multiplayer component mismatch! This application is missing a component with version hash 0x%llx. "
  952. "Because this component is missing, the name isn't available, only its hash. "
  953. "To find the missing component go to the other machine and search for 's_versionHash = AZ::HashValue64{ 0x%llx }' "
  954. "inside the generated multiplayer auto-component build folder.",
  955. static_cast<AZ::u64>(theirComponentHash),
  956. static_cast<AZ::u64>(theirComponentHash));
  957. }
  958. }
  959. // One last iteration over our components this time to check if we have a component the connected app is missing.
  960. if (!packet.GetComponentVersions().empty())
  961. {
  962. for (const auto& ourComponent : GetMultiplayerComponentRegistry()->GetMultiplayerComponentVersionHashes())
  963. {
  964. AZ::Name ourComponentName = ourComponent.first;
  965. bool theyHaveComponent = false;
  966. for (const auto& theirComponent : packet.GetComponentVersions())
  967. {
  968. if (ourComponentName == theirComponent.first)
  969. {
  970. theyHaveComponent = true;
  971. break;
  972. }
  973. }
  974. if (!theyHaveComponent)
  975. {
  976. AZLOG_ERROR(
  977. "Multiplayer component mismatch! This application has a component named %s which the connected application is missing!",
  978. ourComponentName.GetCStr());
  979. }
  980. }
  981. }
  982. // The client receives this packet first from the server, and then the client sends a packet back
  983. if (connection->GetConnectionRole() == ConnectionRole::Connector)
  984. {
  985. // If this is the connector (client), send all our component information back to the acceptor (server).
  986. MultiplayerPackets::VersionMismatch versionMismatchPacket(GetMultiplayerComponentRegistry()->GetMultiplayerComponentVersionHashes());
  987. connection->SendReliablePacket(versionMismatchPacket);
  988. }
  989. else if (connection->GetConnectionRole() == ConnectionRole::Acceptor)
  990. {
  991. // If this is the server, that means the client has also received all the component version information by this time.
  992. // Now either disconnect, or accept the connection even though there's a mismatch.
  993. if (sv_versionMismatch_autoDisconnect)
  994. {
  995. // Disconnect from the connector
  996. connection->Disconnect(DisconnectReason::VersionMismatch, TerminationEndpoint::Local);
  997. }
  998. else
  999. {
  1000. if (m_originalConnectPackets.contains(connection->GetConnectionId()))
  1001. {
  1002. // DANGER: Accepting the player connection even though there's a component mismatch
  1003. AZLOG_WARN("Multiplayer component mismatch was found. Server configured to allow the player to connect anyways. Please set "
  1004. "sv_versionMismatch_autoDisconnect=true if this is undesired behavior!");
  1005. AttemptPlayerConnect(connection, m_originalConnectPackets[connection->GetConnectionId()]);
  1006. m_originalConnectPackets.erase(connection->GetConnectionId());
  1007. }
  1008. else
  1009. {
  1010. AZ_Assert(false, "Multiplayer component mismatch finished comparing components; "
  1011. "failed to accept connection because the original connection packet is missing. This should not happen.");
  1012. }
  1013. }
  1014. }
  1015. m_versionMismatchEvent.Signal();
  1016. return true;
  1017. }
  1018. ConnectResult MultiplayerSystemComponent::ValidateConnect
  1019. (
  1020. [[maybe_unused]] const IpAddress& remoteAddress,
  1021. [[maybe_unused]] const IPacketHeader& packetHeader,
  1022. [[maybe_unused]] ISerializer& serializer
  1023. )
  1024. {
  1025. return ConnectResult::Accepted;
  1026. }
  1027. void MultiplayerSystemComponent::OnConnect(AzNetworking::IConnection* connection)
  1028. {
  1029. AZStd::string providerTicket;
  1030. if (connection->GetConnectionRole() == ConnectionRole::Connector)
  1031. {
  1032. AZLOG_INFO("New outgoing connection to remote address: %s", connection->GetRemoteAddress().GetString().c_str());
  1033. if (!m_pendingConnectionTickets.empty())
  1034. {
  1035. providerTicket = m_pendingConnectionTickets.front();
  1036. m_pendingConnectionTickets.pop();
  1037. }
  1038. connection->SendReliablePacket(MultiplayerPackets::Connect(
  1039. 0,
  1040. m_temporaryUserIdentifier,
  1041. providerTicket.c_str(),
  1042. GetMultiplayerComponentRegistry()->GetSystemVersionHash()));
  1043. }
  1044. else
  1045. {
  1046. AZLOG_INFO("New incoming connection from remote address: %s", connection->GetRemoteAddress().GetString().c_str())
  1047. MultiplayerAgentDatum datum;
  1048. datum.m_id = connection->GetConnectionId();
  1049. datum.m_isInvited = false;
  1050. datum.m_agentType = MultiplayerAgentType::Client;
  1051. m_connectionAcquiredEvent.Signal(datum);
  1052. }
  1053. if (GetAgentType() == MultiplayerAgentType::ClientServer
  1054. || GetAgentType() == MultiplayerAgentType::DedicatedServer)
  1055. {
  1056. connection->SetUserData(new ServerToClientConnectionData(connection, *this));
  1057. }
  1058. else
  1059. {
  1060. connection->SetUserData(new ClientToServerConnectionData(connection, *this, providerTicket));
  1061. AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<NullReplicationWindow>(connection);
  1062. reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window));
  1063. }
  1064. }
  1065. AzNetworking::PacketDispatchResult MultiplayerSystemComponent::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer)
  1066. {
  1067. return MultiplayerPackets::DispatchPacket(connection, packetHeader, serializer, *this);
  1068. }
  1069. void MultiplayerSystemComponent::OnPacketLost([[maybe_unused]] IConnection* connection, [[maybe_unused]] PacketId packetId)
  1070. {
  1071. ;
  1072. }
  1073. void MultiplayerSystemComponent::OnDisconnect(AzNetworking::IConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint)
  1074. {
  1075. const char* endpointString = (endpoint == TerminationEndpoint::Local) ? "Disconnecting" : "Remotely disconnected";
  1076. const AZStd::string reasonString = ToString(reason);
  1077. AZLOG_INFO("%s from remote address %s due to %s", endpointString, connection->GetRemoteAddress().GetString().c_str(), reasonString.c_str());
  1078. // The client is disconnecting
  1079. if (m_agentType == MultiplayerAgentType::Client)
  1080. {
  1081. AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Connector, "Client connection role should only ever be Connector");
  1082. if (reason == DisconnectReason::ServerNoLevelLoaded)
  1083. {
  1084. AZLOG_WARN("Server did not provide a valid level to load! Make sure the server has a level loaded before connecting.");
  1085. m_noServerLevelLoadedEvent.Signal();
  1086. }
  1087. }
  1088. else if (m_agentType == MultiplayerAgentType::DedicatedServer || m_agentType == MultiplayerAgentType::ClientServer)
  1089. {
  1090. // Signal to session management that a user has left the server
  1091. if (connection->GetConnectionRole() == ConnectionRole::Acceptor)
  1092. {
  1093. IMultiplayerSpawner* spawner = AZ::Interface<IMultiplayerSpawner>::Get();
  1094. if (spawner)
  1095. {
  1096. // Check if this disconnected player was waiting to be spawned, and therefore, doesn't have a controlled player entity yet.
  1097. bool playerSpawned = true;
  1098. for (auto it = m_playersWaitingToBeSpawned.begin(); it != m_playersWaitingToBeSpawned.end(); ++it)
  1099. {
  1100. if (it->connection && it->connection->GetConnectionId() == connection->GetConnectionId())
  1101. {
  1102. m_playersWaitingToBeSpawned.erase(it);
  1103. playerSpawned = false;
  1104. break;
  1105. }
  1106. }
  1107. // Alert IMultiplayerSpawner that our spawned player has left.
  1108. if (playerSpawned)
  1109. {
  1110. if (auto connectionData = reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData()))
  1111. {
  1112. if (IReplicationWindow* replicationWindow = connectionData->GetReplicationManager().GetReplicationWindow())
  1113. {
  1114. const ReplicationSet& replicationSet = replicationWindow->GetReplicationSet();
  1115. spawner->OnPlayerLeave(connectionData->GetPrimaryPlayerEntity(), replicationSet, reason);
  1116. }
  1117. else
  1118. {
  1119. AZLOG_ERROR("No IReplicationWindow found OnPlayerDisconnect.");
  1120. }
  1121. }
  1122. else
  1123. {
  1124. AZLOG_ERROR("No ServerToClientConnectionData found OnPlayerDisconnect.");
  1125. }
  1126. }
  1127. }
  1128. else
  1129. {
  1130. AZLOG_ERROR("No IMultiplayerSpawner found OnPlayerDisconnect. Ensure one is registered.");
  1131. }
  1132. if (AZ::Interface<ISessionHandlingProviderRequests>::Get() != nullptr)
  1133. {
  1134. PlayerConnectionConfig config;
  1135. config.m_playerConnectionId = aznumeric_cast<uint32_t>(connection->GetConnectionId());
  1136. config.m_playerSessionId =
  1137. reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->GetProviderTicket();
  1138. AZ::Interface<ISessionHandlingProviderRequests>::Get()->HandlePlayerLeaveSession(config);
  1139. }
  1140. }
  1141. }
  1142. m_endpointDisconnectedEvent.Signal(m_agentType);
  1143. // Clean up any multiplayer connection data we've bound to this connection instance
  1144. if (connection->GetUserData() != nullptr)
  1145. {
  1146. auto connectionData = reinterpret_cast<IConnectionData*>(connection->GetUserData());
  1147. delete connectionData;
  1148. connection->SetUserData(nullptr);
  1149. }
  1150. // Signal to session management when there are no remaining players in a dedicated server for potential cleanup
  1151. // We avoid this for client server as the host itself is a user and dedicated servers that do not terminate when all players have exited
  1152. if (sv_terminateOnPlayerExit && m_agentType == MultiplayerAgentType::DedicatedServer && connection->GetConnectionRole() == ConnectionRole::Acceptor)
  1153. {
  1154. if (m_networkInterface->GetConnectionSet().GetActiveConnectionCount() == 0)
  1155. {
  1156. AZLOG_INFO("Server exiting due to zero active connections (sv_terminateOnPlayerExit=true)");
  1157. Terminate(DisconnectReason::TerminatedByServer);
  1158. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::ExitMainLoop);
  1159. }
  1160. }
  1161. }
  1162. MultiplayerAgentType MultiplayerSystemComponent::GetAgentType() const
  1163. {
  1164. return m_agentType;
  1165. }
  1166. void MultiplayerSystemComponent::InitializeMultiplayer(MultiplayerAgentType multiplayerType)
  1167. {
  1168. bool sessionStarted = false;
  1169. if (bg_capturePhysicsTickMetric)
  1170. {
  1171. if (auto* physXSystem = PhysX::GetPhysXSystem())
  1172. {
  1173. m_preSimulateHandler.Disconnect();
  1174. physXSystem->RegisterPreSimulateEvent(m_preSimulateHandler);
  1175. m_postSimulateHandler.Disconnect();
  1176. physXSystem->RegisterPostSimulateEvent(m_postSimulateHandler);
  1177. }
  1178. }
  1179. m_lastReplicatedHostFrameId = HostFrameId{0};
  1180. if (m_agentType == multiplayerType)
  1181. {
  1182. return;
  1183. }
  1184. m_playersWaitingToBeSpawned.clear();
  1185. if (m_agentType != MultiplayerAgentType::Uninitialized && multiplayerType != MultiplayerAgentType::Uninitialized)
  1186. {
  1187. AZLOG_WARN("Attemping to InitializeMultiplayer from one initialized type to another. Your session may not have been properly torn down. Please call the 'disconnect' console command to terminated the current multiplayer simulation before switching to a new multiplayer role.");
  1188. }
  1189. if (m_agentType == MultiplayerAgentType::Uninitialized)
  1190. {
  1191. m_spawnNetboundEntities = false;
  1192. if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer)
  1193. {
  1194. sessionStarted = true;
  1195. m_spawnNetboundEntities = true;
  1196. if (!m_networkEntityManager.IsInitialized())
  1197. {
  1198. const AZ::CVarFixedString serverAddr = cl_serveraddr;
  1199. const uint16_t serverPort = cl_serverport;
  1200. const AzNetworking::ProtocolType serverProtocol = sv_protocol;
  1201. const AzNetworking::IpAddress hostId = AzNetworking::IpAddress(serverAddr.c_str(), serverPort, serverProtocol);
  1202. // Set up a full ownership domain if we didn't construct a domain during the initialize event
  1203. m_networkEntityManager.Initialize(hostId, AZStd::make_unique<FullOwnershipEntityDomain>());
  1204. }
  1205. }
  1206. else if (multiplayerType == MultiplayerAgentType::Client)
  1207. {
  1208. m_networkEntityManager.Initialize(AzNetworking::IpAddress(), AZStd::make_unique<NullEntityDomain>());
  1209. }
  1210. }
  1211. m_agentType = multiplayerType;
  1212. // Spawn the default player for this host since the host is also a player (not a dedicated server)
  1213. if (m_agentType == MultiplayerAgentType::ClientServer)
  1214. {
  1215. MultiplayerAgentDatum datum;
  1216. datum.m_agentType = MultiplayerAgentType::ClientServer;
  1217. datum.m_id = InvalidConnectionId; //< no network connection: the client is hosting itself.
  1218. constexpr uint64_t userId = 0; //< user id 0: the client hosting in client-server is always the first player.
  1219. NetworkEntityHandle controlledEntity;
  1220. if (IMultiplayerSpawner* spawner = AZ::Interface<IMultiplayerSpawner>::Get())
  1221. {
  1222. // Route to spawner implementation
  1223. controlledEntity = spawner->OnPlayerJoin(userId, datum);
  1224. }
  1225. // A controlled player entity likely doesn't exist at this time.
  1226. // Unless IMultiplayerSpawner has a way to return a player without being inside a level (for example using a system component), the client-server's player won't be
  1227. // spawned until the level is loaded.
  1228. if (controlledEntity.Exists())
  1229. {
  1230. EnableAutonomousControl(controlledEntity, InvalidConnectionId);
  1231. }
  1232. else
  1233. {
  1234. // If there wasn't any player entity, wait until a level loads and check again
  1235. m_playersWaitingToBeSpawned.emplace_back(userId, datum, nullptr);
  1236. }
  1237. }
  1238. AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType));
  1239. if (auto* statSystem = AZ::Interface<IMultiplayerStatSystem>::Get())
  1240. {
  1241. statSystem->Register();
  1242. }
  1243. if (sessionStarted)
  1244. {
  1245. m_networkInitEvent.Signal(m_networkInterface);
  1246. }
  1247. }
  1248. void MultiplayerSystemComponent::AddClientMigrationStartEventHandler(ClientMigrationStartEvent::Handler& handler)
  1249. {
  1250. handler.Connect(m_clientMigrationStartEvent);
  1251. }
  1252. void MultiplayerSystemComponent::AddClientMigrationEndEventHandler(ClientMigrationEndEvent::Handler& handler)
  1253. {
  1254. handler.Connect(m_clientMigrationEndEvent);
  1255. }
  1256. void MultiplayerSystemComponent::AddEndpointDisconnectedHandler(EndpointDisconnectedEvent::Handler& handler)
  1257. {
  1258. handler.Connect(m_endpointDisconnectedEvent);
  1259. }
  1260. void MultiplayerSystemComponent::AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler)
  1261. {
  1262. handler.Connect(m_notifyClientMigrationEvent);
  1263. }
  1264. void MultiplayerSystemComponent::AddNotifyEntityMigrationEventHandler(NotifyEntityMigrationEvent::Handler& handler)
  1265. {
  1266. handler.Connect(m_notifyEntityMigrationEvent);
  1267. }
  1268. void MultiplayerSystemComponent::AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler)
  1269. {
  1270. handler.Connect(m_connectionAcquiredEvent);
  1271. }
  1272. void MultiplayerSystemComponent::AddNetworkInitHandler(NetworkInitEvent::Handler& handler)
  1273. {
  1274. handler.Connect(m_networkInitEvent);
  1275. }
  1276. void MultiplayerSystemComponent::AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler)
  1277. {
  1278. handler.Connect(m_serverAcceptanceReceivedEvent);
  1279. }
  1280. void MultiplayerSystemComponent::AddLevelLoadBlockedHandler(LevelLoadBlockedEvent::Handler& handler)
  1281. {
  1282. handler.Connect(m_levelLoadBlockedEvent);
  1283. }
  1284. void MultiplayerSystemComponent::AddNoServerLevelLoadedHandler(NoServerLevelLoadedEvent::Handler& handler)
  1285. {
  1286. handler.Connect(m_noServerLevelLoadedEvent);
  1287. }
  1288. void MultiplayerSystemComponent::AddVersionMismatchHandler(NoServerLevelLoadedEvent::Handler& handler)
  1289. {
  1290. handler.Connect(m_versionMismatchEvent);
  1291. }
  1292. void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId)
  1293. {
  1294. m_notifyClientMigrationEvent.Signal(connectionId, hostId, userIdentifier, lastClientInputId, controlledEntityId);
  1295. }
  1296. void MultiplayerSystemComponent::SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId)
  1297. {
  1298. m_notifyEntityMigrationEvent.Signal(entityHandle, remoteHostId);
  1299. }
  1300. void MultiplayerSystemComponent::SendReadyForEntityUpdates(bool readyForEntityUpdates)
  1301. {
  1302. IConnectionSet& connectionSet = m_networkInterface->GetConnectionSet();
  1303. connectionSet.VisitConnections([readyForEntityUpdates](IConnection& connection)
  1304. {
  1305. connection.SendReliablePacket(MultiplayerPackets::ReadyForEntityUpdates(readyForEntityUpdates));
  1306. });
  1307. }
  1308. AZ::TimeMs MultiplayerSystemComponent::GetCurrentHostTimeMs() const
  1309. {
  1310. if (GetAgentType() == MultiplayerAgentType::Client)
  1311. {
  1312. return m_lastReplicatedHostTimeMs;
  1313. }
  1314. else // ClientServer or DedicatedServer
  1315. {
  1316. return m_networkTime.GetHostTimeMs();
  1317. }
  1318. }
  1319. float MultiplayerSystemComponent::GetCurrentBlendFactor() const
  1320. {
  1321. return m_renderBlendFactor;
  1322. }
  1323. INetworkTime* MultiplayerSystemComponent::GetNetworkTime()
  1324. {
  1325. return &m_networkTime;
  1326. }
  1327. INetworkEntityManager* MultiplayerSystemComponent::GetNetworkEntityManager()
  1328. {
  1329. return &m_networkEntityManager;
  1330. }
  1331. void MultiplayerSystemComponent::RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId)
  1332. {
  1333. m_playerRejoinData[temporaryUserIdentifier] = controlledEntityId;
  1334. }
  1335. void MultiplayerSystemComponent::CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId)
  1336. {
  1337. IConnection* connection = m_networkInterface->GetConnectionSet().GetConnection(connectionId);
  1338. if (connection != nullptr) // Make sure the player has not disconnected since the start of migration
  1339. {
  1340. // Tell the client who to join
  1341. MultiplayerPackets::ClientMigration clientMigration(publicHostId, temporaryUserIdentifier, migratedClientInputId);
  1342. connection->SendReliablePacket(clientMigration);
  1343. }
  1344. }
  1345. void MultiplayerSystemComponent::SetShouldSpawnNetworkEntities(bool value)
  1346. {
  1347. m_spawnNetboundEntities = value;
  1348. }
  1349. bool MultiplayerSystemComponent::GetShouldSpawnNetworkEntities() const
  1350. {
  1351. return m_spawnNetboundEntities;
  1352. }
  1353. void MultiplayerSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
  1354. {
  1355. const MultiplayerStats& stats = GetStats();
  1356. AZLOG_INFO("Total networked entities: %llu", aznumeric_cast<AZ::u64>(stats.m_entityCount));
  1357. AZLOG_INFO("Total client connections: %llu", aznumeric_cast<AZ::u64>(stats.m_clientConnectionCount));
  1358. AZLOG_INFO("Total server connections: %llu", aznumeric_cast<AZ::u64>(stats.m_serverConnectionCount));
  1359. const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateTotalPropertyUpdateSentMetrics();
  1360. const MultiplayerStats::Metric propertyUpdatesRecv = stats.CalculateTotalPropertyUpdateRecvMetrics();
  1361. const MultiplayerStats::Metric rpcsSent = stats.CalculateTotalRpcsSentMetrics();
  1362. const MultiplayerStats::Metric rpcsRecv = stats.CalculateTotalRpcsRecvMetrics();
  1363. AZLOG_INFO("Total property updates sent: %llu", aznumeric_cast<AZ::u64>(propertyUpdatesSent.m_totalCalls));
  1364. AZLOG_INFO("Total property updates sent bytes: %llu", aznumeric_cast<AZ::u64>(propertyUpdatesSent.m_totalBytes));
  1365. AZLOG_INFO("Total property updates received: %llu", aznumeric_cast<AZ::u64>(propertyUpdatesRecv.m_totalCalls));
  1366. AZLOG_INFO("Total property updates received bytes: %llu", aznumeric_cast<AZ::u64>(propertyUpdatesRecv.m_totalBytes));
  1367. AZLOG_INFO("Total RPCs sent: %llu", aznumeric_cast<AZ::u64>(rpcsSent.m_totalCalls));
  1368. AZLOG_INFO("Total RPCs sent bytes: %llu", aznumeric_cast<AZ::u64>(rpcsSent.m_totalBytes));
  1369. AZLOG_INFO("Total RPCs received: %llu", aznumeric_cast<AZ::u64>(rpcsRecv.m_totalCalls));
  1370. AZLOG_INFO("Total RPCs received bytes: %llu", aznumeric_cast<AZ::u64>(rpcsRecv.m_totalBytes));
  1371. }
  1372. void MultiplayerSystemComponent::TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds)
  1373. {
  1374. AZ_PROFILE_SCOPE(MULTIPLAYER, "MultiplayerSystemComponent: TickVisibleNetworkEntities");
  1375. m_tickFactor += deltaTime / serverRateSeconds;
  1376. // Linear close to the origin, but asymptote at y = 1
  1377. m_renderBlendFactor = AZStd::clamp(1.0f - (std::pow(cl_renderTickBlendBase, m_tickFactor)), 0.0f, m_tickFactor);
  1378. AZLOG
  1379. (
  1380. NET_Blending,
  1381. "Computed blend factor of %0.3f using a tick factor of %0.3f, a frametime of %0.3f and a serverTickRate of %0.3f",
  1382. m_renderBlendFactor,
  1383. m_tickFactor,
  1384. deltaTime,
  1385. serverRateSeconds
  1386. );
  1387. #if AZ_TRAIT_CLIENT
  1388. if (Camera::ActiveCameraRequestBus::HasHandlers())
  1389. {
  1390. // If there's a camera, update only what's visible
  1391. AZ::Transform activeCameraTransform;
  1392. Camera::Configuration activeCameraConfiguration;
  1393. Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraTransform, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraTransform);
  1394. Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraConfiguration, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraConfiguration);
  1395. const AZ::ViewFrustumAttributes frustumAttributes
  1396. (
  1397. activeCameraTransform,
  1398. activeCameraConfiguration.m_frustumHeight / activeCameraConfiguration.m_frustumWidth,
  1399. activeCameraConfiguration.m_fovRadians,
  1400. activeCameraConfiguration.m_nearClipDistance,
  1401. activeCameraConfiguration.m_farClipDistance
  1402. );
  1403. const AZ::Frustum viewFrustum = AZ::Frustum(frustumAttributes);
  1404. // Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system
  1405. AZStd::vector<NetBindComponent*> gatheredEntities;
  1406. AZ::Interface<AzFramework::IVisibilitySystem>::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum,
  1407. [this, &gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData)
  1408. {
  1409. gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size());
  1410. for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries)
  1411. {
  1412. if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity)
  1413. {
  1414. AZ::Entity* entity = static_cast<AZ::Entity*>(visEntry->m_userData);
  1415. NetBindComponent* netBindComponent = m_networkEntityManager.GetNetworkEntityTracker()->GetNetBindComponent(entity);
  1416. if (netBindComponent != nullptr)
  1417. {
  1418. AZ_Assert(netBindComponent->GetEntity() != nullptr, "Null entity for this component");
  1419. gatheredEntities.push_back(netBindComponent);
  1420. }
  1421. }
  1422. }
  1423. });
  1424. if (bg_parallelNotifyPreRender)
  1425. {
  1426. AZ::JobCompletion jobCompletion;
  1427. for (NetBindComponent* netBindComponent : gatheredEntities)
  1428. {
  1429. AZ::Job* job = AZ::CreateJobFunction([netBindComponent = netBindComponent, deltaTime]()
  1430. {
  1431. AZ_PROFILE_SCOPE(AzCore, "OnPreRenderJob");
  1432. netBindComponent->NotifyPreRender(deltaTime);
  1433. }, true, nullptr);
  1434. job->SetDependent(&jobCompletion);
  1435. job->Start();
  1436. }
  1437. jobCompletion.StartAndWaitForCompletion();
  1438. }
  1439. else
  1440. {
  1441. for (NetBindComponent* netBindComponent : gatheredEntities)
  1442. {
  1443. netBindComponent->NotifyPreRender(deltaTime);
  1444. }
  1445. }
  1446. }
  1447. else
  1448. #endif // on servers update all net entities
  1449. {
  1450. // If there's no camera, fall back to updating all net entities
  1451. for (auto& iter : *(m_networkEntityManager.GetNetworkEntityTracker()))
  1452. {
  1453. AZ::Entity* entity = iter.second;
  1454. NetBindComponent* netBindComponent = m_networkEntityManager.GetNetworkEntityTracker()->GetNetBindComponent(entity);
  1455. if (netBindComponent != nullptr)
  1456. {
  1457. netBindComponent->NotifyPreRender(deltaTime);
  1458. }
  1459. }
  1460. }
  1461. }
  1462. void MultiplayerSystemComponent::OnConsoleCommandInvoked
  1463. (
  1464. AZStd::string_view command,
  1465. const AZ::ConsoleCommandContainer& args,
  1466. AZ::ConsoleFunctorFlags flags,
  1467. AZ::ConsoleInvokedFrom invokedFrom
  1468. )
  1469. {
  1470. if (invokedFrom == AZ::ConsoleInvokedFrom::AzNetworking)
  1471. {
  1472. return;
  1473. }
  1474. if ((flags & AZ::ConsoleFunctorFlags::DontReplicate) == AZ::ConsoleFunctorFlags::DontReplicate)
  1475. {
  1476. // If the cvar is marked don't replicate, don't send it at all
  1477. return;
  1478. }
  1479. AZStd::string replicateString = AZStd::string(command) + " ";
  1480. AZ::StringFunc::Join(replicateString, args.begin(), args.end(), " ");
  1481. m_cvarCommands.PushBackItem(AZStd::move(replicateString));
  1482. }
  1483. void MultiplayerSystemComponent::OnAutonomousEntityReplicatorCreated()
  1484. {
  1485. m_autonomousEntityReplicatorCreatedHandler.Disconnect();
  1486. m_clientMigrationEndEvent.Signal();
  1487. }
  1488. void MultiplayerSystemComponent::ExecuteConsoleCommandList(IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands)
  1489. {
  1490. AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get();
  1491. const bool isAcceptor = (connection->GetConnectionRole() == ConnectionRole::Acceptor); // We're hosting if we accepted the connection
  1492. const AZ::ConsoleFunctorFlags requiredSet = isAcceptor ? AZ::ConsoleFunctorFlags::AllowClientSet : AZ::ConsoleFunctorFlags::Null;
  1493. for (auto& command : commands)
  1494. {
  1495. console->PerformCommand(command.c_str(), AZ::ConsoleSilentMode::NotSilent, AZ::ConsoleInvokedFrom::AzNetworking, requiredSet);
  1496. }
  1497. }
  1498. void MultiplayerSystemComponent::EnableAutonomousControl(NetworkEntityHandle entityHandle, AzNetworking::ConnectionId ownerConnectionId)
  1499. {
  1500. if (!entityHandle.Exists())
  1501. {
  1502. AZLOG_WARN("Attempting to enable autonomous control for an invalid multiplayer entity");
  1503. return;
  1504. }
  1505. entityHandle.GetNetBindComponent()->SetOwningConnectionId(ownerConnectionId);
  1506. // An invalid connection id means this player is controlled by us (the host); not controlled by some connected client.
  1507. if (ownerConnectionId == InvalidConnectionId)
  1508. {
  1509. entityHandle.GetNetBindComponent()->EnablePlayerHostAutonomy(true);
  1510. }
  1511. if (auto* hierarchyComponent = entityHandle.FindComponent<NetworkHierarchyRootComponent>())
  1512. {
  1513. for (AZ::Entity* subEntity : hierarchyComponent->GetHierarchicalEntities())
  1514. {
  1515. NetworkEntityHandle subEntityHandle = NetworkEntityHandle(subEntity);
  1516. NetBindComponent* subEntityNetBindComponent = subEntityHandle.GetNetBindComponent();
  1517. if (subEntityNetBindComponent != nullptr)
  1518. {
  1519. subEntityNetBindComponent->SetOwningConnectionId(ownerConnectionId);
  1520. // An invalid connection id means this player is controlled by us (the host); not controlled by some connected client.
  1521. if (ownerConnectionId == InvalidConnectionId)
  1522. {
  1523. subEntityNetBindComponent->EnablePlayerHostAutonomy(true);
  1524. }
  1525. }
  1526. }
  1527. }
  1528. }
  1529. void MultiplayerSystemComponent::OnRootSpawnableAssigned(
  1530. [[maybe_unused]] AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
  1531. {
  1532. m_levelEntitiesActivated = false;
  1533. }
  1534. void MultiplayerSystemComponent::OnRootSpawnableReady(
  1535. [[maybe_unused]] AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
  1536. {
  1537. m_levelEntitiesActivated = true;
  1538. // Ignore level loads if not in multiplayer mode
  1539. if (m_agentType == MultiplayerAgentType::Uninitialized)
  1540. {
  1541. return;
  1542. }
  1543. // Spawn players waiting to be spawned. This can happen when a player connects before a level is loaded, so there isn't any player spawner components registered
  1544. IMultiplayerSpawner* spawner = AZ::Interface<IMultiplayerSpawner>::Get();
  1545. if (!spawner)
  1546. {
  1547. AZLOG_ERROR("Attempting to spawn players on level load failed. No IMultiplayerSpawner found. Ensure one is registered.");
  1548. return;
  1549. }
  1550. for (const auto& playerWaitingToBeSpawned : m_playersWaitingToBeSpawned)
  1551. {
  1552. NetworkEntityHandle controlledEntity = spawner->OnPlayerJoin(playerWaitingToBeSpawned.userId, playerWaitingToBeSpawned.agent);
  1553. if (controlledEntity.Exists())
  1554. {
  1555. EnableAutonomousControl(controlledEntity, playerWaitingToBeSpawned.agent.m_id);
  1556. }
  1557. else
  1558. {
  1559. AZLOG_WARN("Attempting to spawn network player on level load failed. IMultiplayerSpawner did not return a controlled entity.");
  1560. return;
  1561. }
  1562. if ((GetAgentType() == MultiplayerAgentType::ClientServer || GetAgentType() == MultiplayerAgentType::DedicatedServer)
  1563. && playerWaitingToBeSpawned.agent.m_agentType == MultiplayerAgentType::Client)
  1564. {
  1565. StartServerToClientReplication(playerWaitingToBeSpawned.userId, controlledEntity, playerWaitingToBeSpawned.connection);
  1566. }
  1567. }
  1568. m_playersWaitingToBeSpawned.clear();
  1569. }
  1570. void MultiplayerSystemComponent::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
  1571. {
  1572. m_levelEntitiesActivated = false;
  1573. }
  1574. bool MultiplayerSystemComponent::ShouldBlockLevelLoading(const char* levelName)
  1575. {
  1576. bool blockLevelLoad = false;
  1577. switch (m_agentType)
  1578. {
  1579. case MultiplayerAgentType::Uninitialized:
  1580. {
  1581. // replace .spawnable with .network.spawnable
  1582. AZStd::string networkSpawnablePath(levelName);
  1583. networkSpawnablePath.erase(networkSpawnablePath.size() - strlen(AzFramework::Spawnable::DotFileExtension));
  1584. networkSpawnablePath += NetworkSpawnableFileExtension;
  1585. AZ::Data::AssetId networkSpawnableAssetId;
  1586. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  1587. networkSpawnableAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, networkSpawnablePath.c_str(), azrtti_typeid<AzFramework::Spawnable>(), false);
  1588. if (networkSpawnableAssetId.IsValid())
  1589. {
  1590. AZLOG_WARN("MultiplayerSystemComponent blocked loading a network level. Your multiplayer agent is uninitialized; did you forget to host before loading a network level?")
  1591. blockLevelLoad = true;
  1592. }
  1593. break;
  1594. }
  1595. case MultiplayerAgentType::Client:
  1596. if (m_blockClientLoadLevel)
  1597. {
  1598. AZLOG_WARN("MultiplayerSystemComponent blocked this client from loading a new level. Clients should only attempt to load level when instructed by their server. Disconnect from server before calling LoadLevel.")
  1599. blockLevelLoad = true;
  1600. }
  1601. break;
  1602. case MultiplayerAgentType::ClientServer:
  1603. if (m_playersWaitingToBeSpawned.empty())
  1604. {
  1605. AZLOG_WARN("MultiplayerSystemComponent blocked this host from loading a new level because you already have a player. Loading a new level could destroy the existing network player entity. Disconnect from the multiplayer simulation before changing levels.")
  1606. blockLevelLoad = true;
  1607. }
  1608. break;
  1609. case MultiplayerAgentType::DedicatedServer:
  1610. if (m_networkInterface->GetConnectionSet().GetConnectionCount() > 0)
  1611. {
  1612. AZLOG_WARN("MultiplayerSystemComponent blocked this host from loading a new level because clients are connected. Loading a new level would destroy the existing clients' network player entity.")
  1613. blockLevelLoad = true;
  1614. }
  1615. break;
  1616. default:
  1617. AZLOG_WARN("MultiplayerSystemComponent::ShouldBlockLevelLoading called with unsupported agent type. Please update code to support agent type: %s.", GetEnumString(m_agentType));
  1618. }
  1619. if (blockLevelLoad)
  1620. {
  1621. m_levelLoadBlockedEvent.Signal();
  1622. }
  1623. return blockLevelLoad;
  1624. }
  1625. void MultiplayerSystemComponent::StartServerToClientReplication(uint64_t userId, NetworkEntityHandle controlledEntity, IConnection* connection)
  1626. {
  1627. if (auto connectionData = reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData()))
  1628. {
  1629. AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<ServerToClientReplicationWindow>(controlledEntity, connection);
  1630. connectionData->GetReplicationManager().SetReplicationWindow(AZStd::move(window));
  1631. connectionData->SetControlledEntity(controlledEntity);
  1632. // If this is a migrate or rejoin, immediately ready the connection for updates
  1633. if (userId != 0)
  1634. {
  1635. connectionData->SetCanSendUpdates(true);
  1636. }
  1637. }
  1638. }
  1639. void MultiplayerSystemComponent::MetricsEvent()
  1640. {
  1641. const auto& networkInterfaces = AZ::Interface<AzNetworking::INetworking>::Get()->GetNetworkInterfaces();
  1642. for (const auto& networkInterface : networkInterfaces)
  1643. {
  1644. if (networkInterface.second->GetType() != bg_captureTransportType)
  1645. {
  1646. continue;
  1647. }
  1648. if (networkInterface.second->GetTrustZone() != TrustZone::ExternalClientToServer)
  1649. {
  1650. continue;
  1651. }
  1652. const NetworkInterfaceMetrics& metrics = networkInterface.second->GetMetrics();
  1653. SET_PERFORMANCE_STAT(MultiplayerStat_TotalTimeSpentUpdatingMs, metrics.m_updateTimeMs);
  1654. SET_PERFORMANCE_STAT(MultiplayerStat_TotalSendTimeMs, metrics.m_sendTimeMs);
  1655. SET_PERFORMANCE_STAT(MultiplayerStat_TotalSentPackets, metrics.m_sendPackets);
  1656. SET_PERFORMANCE_STAT(MultiplayerStat_TotalSentBytesAfterCompression, metrics.m_sendBytes);
  1657. SET_PERFORMANCE_STAT(MultiplayerStat_TotalSentBytesBeforeCompression, metrics.m_sendBytesUncompressed);
  1658. SET_PERFORMANCE_STAT(MultiplayerStat_TotalResentPacketsDueToPacketLoss, metrics.m_resentPackets);
  1659. SET_PERFORMANCE_STAT(MultiplayerStat_TotalReceiveTimeInMs, metrics.m_recvTimeMs);
  1660. SET_PERFORMANCE_STAT(MultiplayerStat_TotalReceivedPackets, metrics.m_recvPackets);
  1661. SET_PERFORMANCE_STAT(MultiplayerStat_TotalReceivedBytesAfterCompression, metrics.m_recvBytes);
  1662. SET_PERFORMANCE_STAT(MultiplayerStat_TotalReceivedBytesBeforeCompression, metrics.m_recvBytesUncompressed);
  1663. SET_PERFORMANCE_STAT(MultiplayerStat_TotalPacketsDiscardedDueToLoad, metrics.m_discardedPackets);
  1664. break; // Assuming there is only one network interface for communicating with clients
  1665. }
  1666. }
  1667. void MultiplayerSystemComponent::OnPhysicsPreSimulate([[maybe_unused]] float dt)
  1668. {
  1669. m_startPhysicsTickTime = AZStd::chrono::steady_clock::now();
  1670. }
  1671. void MultiplayerSystemComponent::OnPhysicsPostSimulate([[maybe_unused]] float dt)
  1672. {
  1673. const auto duration = AZStd::chrono::duration_cast<AZStd::chrono::microseconds>(
  1674. AZStd::chrono::steady_clock::now() - m_startPhysicsTickTime);
  1675. SET_PERFORMANCE_STAT(MultiplayerStat_PhysicsFrameTimeUs, AZ::TimeUs{ duration.count() });
  1676. }
  1677. void MultiplayerSystemComponent::HostConsoleCommand([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
  1678. {
  1679. StartHosting(sv_port, sv_isDedicated);
  1680. }
  1681. void sv_launch_local_client([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
  1682. {
  1683. // Try finding the game launcher from the executable folder where this server was launched from.
  1684. AZ::IO::FixedMaxPath gameLauncherPath = AZ::Utils::GetExecutableDirectory();
  1685. gameLauncherPath /= AZStd::string_view(AZ::Utils::GetProjectName() + ".GameLauncher" + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
  1686. if (!AZ::IO::SystemFile::Exists(gameLauncherPath.c_str()))
  1687. {
  1688. AZLOG_ERROR("Could not find GameLauncher executable (%s)", gameLauncherPath.c_str());
  1689. return;
  1690. }
  1691. const auto multiplayerInterface = AZ::Interface<IMultiplayer>::Get();
  1692. if (!multiplayerInterface)
  1693. {
  1694. AZLOG_ERROR("Sv_launch_local_client failed. MultiplayerSystemComponent hasn't been constructed yet.");
  1695. return;
  1696. }
  1697. // Only allow hosts to launch a client, otherwise there's nothing for the client to connect to.
  1698. if (multiplayerInterface->GetAgentType() != MultiplayerAgentType::DedicatedServer &&
  1699. multiplayerInterface->GetAgentType() != MultiplayerAgentType::ClientServer)
  1700. {
  1701. AZLOG_ERROR("Cannot sv_launch_local_client. This program isn't hosting, please call 'host' command.");
  1702. return;
  1703. }
  1704. AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
  1705. processLaunchInfo.m_commandlineParameters = AZStd::string::format("%s +connect", gameLauncherPath.c_str());
  1706. processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL;
  1707. // Launch GameLauncher and connect to this server
  1708. const bool launchSuccess = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
  1709. if (!launchSuccess)
  1710. {
  1711. AZLOG_ERROR("Failed to launch the local client process.");
  1712. return;
  1713. }
  1714. }
  1715. AZ_CONSOLEFREEFUNC(sv_launch_local_client, AZ::ConsoleFunctorFlags::DontReplicate, "Launches a local client and connects to this host server (only works if currently hosting)");
  1716. void MultiplayerSystemComponent::ConnectConsoleCommand(const AZ::ConsoleCommandContainer& arguments)
  1717. {
  1718. if (arguments.size() < 1)
  1719. {
  1720. const AZ::CVarFixedString remoteAddress = cl_serveraddr;
  1721. Connect(remoteAddress.c_str(), cl_serverport);
  1722. }
  1723. else
  1724. {
  1725. AZ::CVarFixedString remoteAddress{ arguments.front() };
  1726. const AZStd::size_t portSeparator = remoteAddress.find_first_of(':');
  1727. if (portSeparator == AZStd::string::npos)
  1728. {
  1729. Connect(remoteAddress.c_str(), cl_serverport);
  1730. }
  1731. else
  1732. {
  1733. char* mutableAddress = remoteAddress.data();
  1734. mutableAddress[portSeparator] = '\0';
  1735. const char* addressStr = mutableAddress;
  1736. const char* portStr = &(mutableAddress[portSeparator + 1]);
  1737. const uint16_t portNumber = aznumeric_cast<uint16_t>(atol(portStr));
  1738. Connect(addressStr, portNumber);
  1739. }
  1740. }
  1741. }
  1742. void disconnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
  1743. {
  1744. AZ::Interface<IMultiplayer>::Get()->Terminate(DisconnectReason::TerminatedByUser);
  1745. }
  1746. AZ_CONSOLEFREEFUNC(disconnect, AZ::ConsoleFunctorFlags::DontReplicate, "Disconnects any open multiplayer connections");
  1747. } // namespace Multiplayer