NetworkClient.cs 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Mirror.RemoteCalls;
  5. using UnityEngine;
  6. namespace Mirror
  7. {
  8. public enum ConnectState
  9. {
  10. None,
  11. // connecting between Connect() and OnTransportConnected()
  12. Connecting,
  13. Connected,
  14. // disconnecting between Disconnect() and OnTransportDisconnected()
  15. Disconnecting,
  16. Disconnected
  17. }
  18. /// <summary>NetworkClient with connection to server.</summary>
  19. public static class NetworkClient
  20. {
  21. // message handlers by messageId
  22. internal static readonly Dictionary<ushort, NetworkMessageDelegate> handlers =
  23. new Dictionary<ushort, NetworkMessageDelegate>();
  24. /// <summary>All spawned NetworkIdentities by netId.</summary>
  25. // client sees OBSERVED spawned ones.
  26. public static readonly Dictionary<uint, NetworkIdentity> spawned =
  27. new Dictionary<uint, NetworkIdentity>();
  28. /// <summary>Client's NetworkConnection to server.</summary>
  29. public static NetworkConnection connection { get; internal set; }
  30. /// <summary>True if client is ready (= joined world).</summary>
  31. // TODO redundant state. point it to .connection.isReady instead (& test)
  32. // TODO OR remove NetworkConnection.isReady? unless it's used on server
  33. //
  34. // TODO maybe ClientState.Connected/Ready/AddedPlayer/etc.?
  35. // way better for security if we can check states in callbacks
  36. public static bool ready;
  37. /// <summary>NetworkIdentity of the localPlayer </summary>
  38. public static NetworkIdentity localPlayer { get; internal set; }
  39. // NetworkClient state
  40. internal static ConnectState connectState = ConnectState.None;
  41. /// <summary>IP address of the connection to server.</summary>
  42. // empty if the client has not connected yet.
  43. public static string serverIp => connection.address;
  44. /// <summary>active is true while a client is connecting/connected</summary>
  45. // (= while the network is active)
  46. public static bool active => connectState == ConnectState.Connecting ||
  47. connectState == ConnectState.Connected;
  48. /// <summary>Check if client is connecting (before connected).</summary>
  49. public static bool isConnecting => connectState == ConnectState.Connecting;
  50. /// <summary>Check if client is connected (after connecting).</summary>
  51. public static bool isConnected => connectState == ConnectState.Connected;
  52. /// <summary>True if client is running in host mode.</summary>
  53. public static bool isHostClient => connection is LocalConnectionToServer;
  54. // OnConnected / OnDisconnected used to be NetworkMessages that were
  55. // invoked. this introduced a bug where external clients could send
  56. // Connected/Disconnected messages over the network causing undefined
  57. // behaviour.
  58. // => public so that custom NetworkManagers can hook into it
  59. public static Action OnConnectedEvent;
  60. public static Action OnDisconnectedEvent;
  61. public static Action<Exception> OnErrorEvent;
  62. /// <summary>Registered spawnable prefabs by assetId.</summary>
  63. public static readonly Dictionary<Guid, GameObject> prefabs =
  64. new Dictionary<Guid, GameObject>();
  65. // custom spawn / unspawn handlers.
  66. // useful to support prefab pooling etc.:
  67. // https://mirror-networking.gitbook.io/docs/guides/gameobjects/custom-spawnfunctions
  68. internal static readonly Dictionary<Guid, SpawnHandlerDelegate> spawnHandlers =
  69. new Dictionary<Guid, SpawnHandlerDelegate>();
  70. internal static readonly Dictionary<Guid, UnSpawnDelegate> unspawnHandlers =
  71. new Dictionary<Guid, UnSpawnDelegate>();
  72. // spawning
  73. // internal for tests
  74. internal static bool isSpawnFinished;
  75. // Disabled scene objects that can be spawned again, by sceneId.
  76. internal static readonly Dictionary<ulong, NetworkIdentity> spawnableObjects =
  77. new Dictionary<ulong, NetworkIdentity>();
  78. static Unbatcher unbatcher = new Unbatcher();
  79. // interest management component (optional)
  80. // only needed for SetHostVisibility
  81. public static InterestManagement aoi;
  82. // scene loading
  83. public static bool isLoadingScene;
  84. // initialization //////////////////////////////////////////////////////
  85. static void AddTransportHandlers()
  86. {
  87. // += so that other systems can also hook into it (i.e. statistics)
  88. Transport.activeTransport.OnClientConnected += OnTransportConnected;
  89. Transport.activeTransport.OnClientDataReceived += OnTransportData;
  90. Transport.activeTransport.OnClientDisconnected += OnTransportDisconnected;
  91. Transport.activeTransport.OnClientError += OnError;
  92. }
  93. static void RemoveTransportHandlers()
  94. {
  95. // -= so that other systems can also hook into it (i.e. statistics)
  96. Transport.activeTransport.OnClientConnected -= OnTransportConnected;
  97. Transport.activeTransport.OnClientDataReceived -= OnTransportData;
  98. Transport.activeTransport.OnClientDisconnected -= OnTransportDisconnected;
  99. Transport.activeTransport.OnClientError -= OnError;
  100. }
  101. internal static void RegisterSystemHandlers(bool hostMode)
  102. {
  103. // host mode client / remote client react to some messages differently.
  104. // but we still need to add handlers for all of them to avoid
  105. // 'message id not found' errors.
  106. if (hostMode)
  107. {
  108. RegisterHandler<ObjectDestroyMessage>(OnHostClientObjectDestroy);
  109. RegisterHandler<ObjectHideMessage>(OnHostClientObjectHide);
  110. RegisterHandler<NetworkPongMessage>(_ => {}, false);
  111. RegisterHandler<SpawnMessage>(OnHostClientSpawn);
  112. // host mode doesn't need spawning
  113. RegisterHandler<ObjectSpawnStartedMessage>(_ => {});
  114. // host mode doesn't need spawning
  115. RegisterHandler<ObjectSpawnFinishedMessage>(_ => {});
  116. // host mode doesn't need state updates
  117. RegisterHandler<EntityStateMessage>(_ => {});
  118. }
  119. else
  120. {
  121. RegisterHandler<ObjectDestroyMessage>(OnObjectDestroy);
  122. RegisterHandler<ObjectHideMessage>(OnObjectHide);
  123. RegisterHandler<NetworkPongMessage>(NetworkTime.OnClientPong, false);
  124. RegisterHandler<SpawnMessage>(OnSpawn);
  125. RegisterHandler<ObjectSpawnStartedMessage>(OnObjectSpawnStarted);
  126. RegisterHandler<ObjectSpawnFinishedMessage>(OnObjectSpawnFinished);
  127. RegisterHandler<EntityStateMessage>(OnEntityStateMessage);
  128. }
  129. // These handlers are the same for host and remote clients
  130. RegisterHandler<ChangeOwnerMessage>(OnChangeOwner);
  131. RegisterHandler<RpcMessage>(OnRPCMessage);
  132. }
  133. // connect /////////////////////////////////////////////////////////////
  134. /// <summary>Connect client to a NetworkServer by address.</summary>
  135. public static void Connect(string address)
  136. {
  137. // Debug.Log($"Client Connect: {address}");
  138. Debug.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkClient.Connect, If you are calling Connect manually then make sure to set 'Transport.activeTransport' first");
  139. RegisterSystemHandlers(false);
  140. Transport.activeTransport.enabled = true;
  141. AddTransportHandlers();
  142. connectState = ConnectState.Connecting;
  143. Transport.activeTransport.ClientConnect(address);
  144. connection = new NetworkConnectionToServer();
  145. }
  146. /// <summary>Connect client to a NetworkServer by Uri.</summary>
  147. public static void Connect(Uri uri)
  148. {
  149. // Debug.Log($"Client Connect: {uri}");
  150. Debug.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkClient.Connect, If you are calling Connect manually then make sure to set 'Transport.activeTransport' first");
  151. RegisterSystemHandlers(false);
  152. Transport.activeTransport.enabled = true;
  153. AddTransportHandlers();
  154. connectState = ConnectState.Connecting;
  155. Transport.activeTransport.ClientConnect(uri);
  156. connection = new NetworkConnectionToServer();
  157. }
  158. // TODO why are there two connect host methods?
  159. // called from NetworkManager.FinishStartHost()
  160. public static void ConnectHost()
  161. {
  162. //Debug.Log("Client Connect Host to Server");
  163. RegisterSystemHandlers(true);
  164. connectState = ConnectState.Connected;
  165. // create local connection objects and connect them
  166. LocalConnectionToServer connectionToServer = new LocalConnectionToServer();
  167. LocalConnectionToClient connectionToClient = new LocalConnectionToClient();
  168. connectionToServer.connectionToClient = connectionToClient;
  169. connectionToClient.connectionToServer = connectionToServer;
  170. connection = connectionToServer;
  171. // create server connection to local client
  172. NetworkServer.SetLocalConnection(connectionToClient);
  173. }
  174. /// <summary>Connect host mode</summary>
  175. // called from NetworkManager.StartHostClient
  176. // TODO why are there two connect host methods?
  177. public static void ConnectLocalServer()
  178. {
  179. // call server OnConnected with server's connection to client
  180. NetworkServer.OnConnected(NetworkServer.localConnection);
  181. // call client OnConnected with client's connection to server
  182. // => previously we used to send a ConnectMessage to
  183. // NetworkServer.localConnection. this would queue the message
  184. // until NetworkClient.Update processes it.
  185. // => invoking the client's OnConnected event directly here makes
  186. // tests fail. so let's do it exactly the same order as before by
  187. // queueing the event for next Update!
  188. //OnConnectedEvent?.Invoke(connection);
  189. ((LocalConnectionToServer)connection).QueueConnectedEvent();
  190. }
  191. // disconnect //////////////////////////////////////////////////////////
  192. /// <summary>Disconnect from server.</summary>
  193. public static void Disconnect()
  194. {
  195. // only if connected or connecting.
  196. // don't disconnect() again if already in the process of
  197. // disconnecting or fully disconnected.
  198. if (connectState != ConnectState.Connecting &&
  199. connectState != ConnectState.Connected)
  200. return;
  201. // we are disconnecting until OnTransportDisconnected is called.
  202. // setting state to Disconnected would stop OnTransportDisconnected
  203. // from calling cleanup code because it would think we are already
  204. // disconnected fully.
  205. // TODO move to 'cleanup' code below if safe
  206. connectState = ConnectState.Disconnecting;
  207. ready = false;
  208. // call Disconnect on the NetworkConnection
  209. connection?.Disconnect();
  210. // IMPORTANT: do NOT clear connection here yet.
  211. // we still need it in OnTransportDisconnected for callbacks.
  212. // connection = null;
  213. }
  214. // transport events ////////////////////////////////////////////////////
  215. // called by Transport
  216. static void OnTransportConnected()
  217. {
  218. if (connection != null)
  219. {
  220. // reset network time stats
  221. NetworkTime.ResetStatics();
  222. // reset unbatcher in case any batches from last session remain.
  223. unbatcher = new Unbatcher();
  224. // the handler may want to send messages to the client
  225. // thus we should set the connected state before calling the handler
  226. connectState = ConnectState.Connected;
  227. NetworkTime.UpdateClient();
  228. OnConnectedEvent?.Invoke();
  229. }
  230. else Debug.LogError("Skipped Connect message handling because connection is null.");
  231. }
  232. // helper function
  233. static bool UnpackAndInvoke(NetworkReader reader, int channelId)
  234. {
  235. if (MessagePacking.Unpack(reader, out ushort msgType))
  236. {
  237. // try to invoke the handler for that message
  238. if (handlers.TryGetValue(msgType, out NetworkMessageDelegate handler))
  239. {
  240. handler.Invoke(connection, reader, channelId);
  241. // message handler may disconnect client, making connection = null
  242. // therefore must check for null to avoid NRE.
  243. if (connection != null)
  244. connection.lastMessageTime = Time.time;
  245. return true;
  246. }
  247. else
  248. {
  249. // message in a batch are NOT length prefixed to save bandwidth.
  250. // every message needs to be handled and read until the end.
  251. // otherwise it would overlap into the next message.
  252. // => need to warn and disconnect to avoid undefined behaviour.
  253. // => WARNING, not error. can happen if attacker sends random data.
  254. Debug.LogWarning($"Unknown message id: {msgType}. This can happen if no handler was registered for this message.");
  255. // simply return false. caller is responsible for disconnecting.
  256. //connection.Disconnect();
  257. return false;
  258. }
  259. }
  260. else
  261. {
  262. // => WARNING, not error. can happen if attacker sends random data.
  263. Debug.LogWarning("Invalid message header.");
  264. // simply return false. caller is responsible for disconnecting.
  265. //connection.Disconnect();
  266. return false;
  267. }
  268. }
  269. // called by Transport
  270. internal static void OnTransportData(ArraySegment<byte> data, int channelId)
  271. {
  272. if (connection != null)
  273. {
  274. // server might batch multiple messages into one packet.
  275. // feed it to the Unbatcher.
  276. // NOTE: we don't need to associate a channelId because we
  277. // always process all messages in the batch.
  278. if (!unbatcher.AddBatch(data))
  279. {
  280. Debug.LogWarning($"NetworkClient: failed to add batch, disconnecting.");
  281. connection.Disconnect();
  282. return;
  283. }
  284. // process all messages in the batch.
  285. // only while NOT loading a scene.
  286. // if we get a scene change message, then we need to stop
  287. // processing. otherwise we might apply them to the old scene.
  288. // => fixes https://github.com/vis2k/Mirror/issues/2651
  289. //
  290. // NOTE: is scene starts loading, then the rest of the batch
  291. // would only be processed when OnTransportData is called
  292. // the next time.
  293. // => consider moving processing to NetworkEarlyUpdate.
  294. while (!isLoadingScene &&
  295. unbatcher.GetNextMessage(out NetworkReader reader, out double remoteTimestamp))
  296. {
  297. // enough to read at least header size?
  298. if (reader.Remaining >= MessagePacking.HeaderSize)
  299. {
  300. // make remoteTimeStamp available to the user
  301. connection.remoteTimeStamp = remoteTimestamp;
  302. // handle message
  303. if (!UnpackAndInvoke(reader, channelId))
  304. {
  305. // warn, disconnect and return if failed
  306. // -> warning because attackers might send random data
  307. // -> messages in a batch aren't length prefixed.
  308. // failing to read one would cause undefined
  309. // behaviour for every message afterwards.
  310. // so we need to disconnect.
  311. // -> return to avoid the below unbatches.count error.
  312. // we already disconnected and handled it.
  313. Debug.LogWarning($"NetworkClient: failed to unpack and invoke message. Disconnecting.");
  314. connection.Disconnect();
  315. return;
  316. }
  317. }
  318. // otherwise disconnect
  319. else
  320. {
  321. // WARNING, not error. can happen if attacker sends random data.
  322. Debug.LogWarning($"NetworkClient: received Message was too short (messages should start with message id)");
  323. connection.Disconnect();
  324. return;
  325. }
  326. }
  327. // if we weren't interrupted by a scene change,
  328. // then all batched messages should have been processed now.
  329. // if not, we need to log an error to avoid debugging hell.
  330. // otherwise batches would silently grow.
  331. // we need to log an error to avoid debugging hell.
  332. //
  333. // EXAMPLE: https://github.com/vis2k/Mirror/issues/2882
  334. // -> UnpackAndInvoke silently returned because no handler for id
  335. // -> Reader would never be read past the end
  336. // -> Batch would never be retired because end is never reached
  337. //
  338. // NOTE: prefixing every message in a batch with a length would
  339. // avoid ever not reading to the end. for extra bandwidth.
  340. //
  341. // IMPORTANT: always keep this check to detect memory leaks.
  342. // this took half a day to debug last time.
  343. if (!isLoadingScene && unbatcher.BatchesCount > 0)
  344. {
  345. Debug.LogError($"Still had {unbatcher.BatchesCount} batches remaining after processing, even though processing was not interrupted by a scene change. This should never happen, as it would cause ever growing batches.\nPossible reasons:\n* A message didn't deserialize as much as it serialized\n*There was no message handler for a message id, so the reader wasn't read until the end.");
  346. }
  347. }
  348. else Debug.LogError("Skipped Data message handling because connection is null.");
  349. }
  350. // called by Transport
  351. // IMPORTANT: often times when disconnecting, we call this from Mirror
  352. // too because we want to remove the connection and handle
  353. // the disconnect immediately.
  354. // => which is fine as long as we guarantee it only runs once
  355. // => which we do by setting the state to Disconnected!
  356. internal static void OnTransportDisconnected()
  357. {
  358. // StopClient called from user code triggers Disconnected event
  359. // from transport which calls StopClient again, so check here
  360. // and short circuit running the Shutdown process twice.
  361. if (connectState == ConnectState.Disconnected) return;
  362. // Raise the event before changing ConnectState
  363. // because 'active' depends on this during shutdown
  364. if (connection != null) OnDisconnectedEvent?.Invoke();
  365. connectState = ConnectState.Disconnected;
  366. ready = false;
  367. // now that everything was handled, clear the connection.
  368. // previously this was done in Disconnect() already, but we still
  369. // need it for the above OnDisconnectedEvent.
  370. connection = null;
  371. // transport handlers are only added when connecting.
  372. // so only remove when actually disconnecting.
  373. RemoveTransportHandlers();
  374. }
  375. static void OnError(Exception exception)
  376. {
  377. Debug.LogException(exception);
  378. OnErrorEvent?.Invoke(exception);
  379. }
  380. // send ////////////////////////////////////////////////////////////////
  381. /// <summary>Send a NetworkMessage to the server over the given channel.</summary>
  382. public static void Send<T>(T message, int channelId = Channels.Reliable)
  383. where T : struct, NetworkMessage
  384. {
  385. if (connection != null)
  386. {
  387. if (connectState == ConnectState.Connected)
  388. {
  389. connection.Send(message, channelId);
  390. }
  391. else Debug.LogError("NetworkClient Send when not connected to a server");
  392. }
  393. else Debug.LogError("NetworkClient Send with no connection");
  394. }
  395. // message handlers ////////////////////////////////////////////////////
  396. /// <summary>Register a handler for a message type T. Most should require authentication.</summary>
  397. public static void RegisterHandler<T>(Action<T> handler, bool requireAuthentication = true)
  398. where T : struct, NetworkMessage
  399. {
  400. ushort msgType = MessagePacking.GetId<T>();
  401. if (handlers.ContainsKey(msgType))
  402. {
  403. Debug.LogWarning($"NetworkClient.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
  404. }
  405. // we use the same WrapHandler function for server and client.
  406. // so let's wrap it to ignore the NetworkConnection parameter.
  407. // it's not needed on client. it's always NetworkClient.connection.
  408. void HandlerWrapped(NetworkConnection _, T value) => handler(value);
  409. handlers[msgType] = MessagePacking.WrapHandler((Action<NetworkConnection, T>) HandlerWrapped, requireAuthentication);
  410. }
  411. /// <summary>Replace a handler for a particular message type. Should require authentication by default.</summary>
  412. // RegisterHandler throws a warning (as it should) if a handler is assigned twice
  413. // Use of ReplaceHandler makes it clear the user intended to replace the handler
  414. public static void ReplaceHandler<T>(Action<NetworkConnection, T> handler, bool requireAuthentication = true)
  415. where T : struct, NetworkMessage
  416. {
  417. ushort msgType = MessagePacking.GetId<T>();
  418. handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
  419. }
  420. /// <summary>Replace a handler for a particular message type. Should require authentication by default.</summary>
  421. // RegisterHandler throws a warning (as it should) if a handler is assigned twice
  422. // Use of ReplaceHandler makes it clear the user intended to replace the handler
  423. public static void ReplaceHandler<T>(Action<T> handler, bool requireAuthentication = true)
  424. where T : struct, NetworkMessage
  425. {
  426. ReplaceHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication);
  427. }
  428. /// <summary>Unregister a message handler of type T.</summary>
  429. public static bool UnregisterHandler<T>()
  430. where T : struct, NetworkMessage
  431. {
  432. // use int to minimize collisions
  433. ushort msgType = MessagePacking.GetId<T>();
  434. return handlers.Remove(msgType);
  435. }
  436. // spawnable prefabs ///////////////////////////////////////////////////
  437. /// <summary>Find the registered prefab for this asset id.</summary>
  438. // Useful for debuggers
  439. public static bool GetPrefab(Guid assetId, out GameObject prefab)
  440. {
  441. prefab = null;
  442. return assetId != Guid.Empty &&
  443. prefabs.TryGetValue(assetId, out prefab) && prefab != null;
  444. }
  445. /// <summary>Validates Prefab then adds it to prefabs dictionary.</summary>
  446. static void RegisterPrefabIdentity(NetworkIdentity prefab)
  447. {
  448. if (prefab.assetId == Guid.Empty)
  449. {
  450. Debug.LogError($"Can not Register '{prefab.name}' because it had empty assetid. If this is a scene Object use RegisterSpawnHandler instead");
  451. return;
  452. }
  453. if (prefab.sceneId != 0)
  454. {
  455. Debug.LogError($"Can not Register '{prefab.name}' because it has a sceneId, make sure you are passing in the original prefab and not an instance in the scene.");
  456. return;
  457. }
  458. NetworkIdentity[] identities = prefab.GetComponentsInChildren<NetworkIdentity>();
  459. if (identities.Length > 1)
  460. {
  461. Debug.LogError($"Prefab '{prefab.name}' has multiple NetworkIdentity components. There should only be one NetworkIdentity on a prefab, and it must be on the root object.");
  462. }
  463. if (prefabs.ContainsKey(prefab.assetId))
  464. {
  465. GameObject existingPrefab = prefabs[prefab.assetId];
  466. Debug.LogWarning($"Replacing existing prefab with assetId '{prefab.assetId}'. Old prefab '{existingPrefab.name}', New prefab '{prefab.name}'");
  467. }
  468. if (spawnHandlers.ContainsKey(prefab.assetId) || unspawnHandlers.ContainsKey(prefab.assetId))
  469. {
  470. Debug.LogWarning($"Adding prefab '{prefab.name}' with assetId '{prefab.assetId}' when spawnHandlers with same assetId already exists. If you want to use custom spawn handling, then remove the prefab from NetworkManager's registered prefabs first.");
  471. }
  472. // Debug.Log($"Registering prefab '{prefab.name}' as asset:{prefab.assetId}");
  473. prefabs[prefab.assetId] = prefab.gameObject;
  474. }
  475. /// <summary>Register spawnable prefab with custom assetId.</summary>
  476. // Note: newAssetId can not be set on GameObjects that already have an assetId
  477. // Note: registering with assetId is useful for assetbundles etc. a lot
  478. // of people use this.
  479. public static void RegisterPrefab(GameObject prefab, Guid newAssetId)
  480. {
  481. if (prefab == null)
  482. {
  483. Debug.LogError("Could not register prefab because it was null");
  484. return;
  485. }
  486. if (newAssetId == Guid.Empty)
  487. {
  488. Debug.LogError($"Could not register '{prefab.name}' with new assetId because the new assetId was empty");
  489. return;
  490. }
  491. NetworkIdentity identity = prefab.GetComponent<NetworkIdentity>();
  492. if (identity == null)
  493. {
  494. Debug.LogError($"Could not register '{prefab.name}' since it contains no NetworkIdentity component");
  495. return;
  496. }
  497. if (identity.assetId != Guid.Empty && identity.assetId != newAssetId)
  498. {
  499. Debug.LogError($"Could not register '{prefab.name}' to {newAssetId} because it already had an AssetId, Existing assetId {identity.assetId}");
  500. return;
  501. }
  502. identity.assetId = newAssetId;
  503. RegisterPrefabIdentity(identity);
  504. }
  505. /// <summary>Register spawnable prefab.</summary>
  506. public static void RegisterPrefab(GameObject prefab)
  507. {
  508. if (prefab == null)
  509. {
  510. Debug.LogError("Could not register prefab because it was null");
  511. return;
  512. }
  513. NetworkIdentity identity = prefab.GetComponent<NetworkIdentity>();
  514. if (identity == null)
  515. {
  516. Debug.LogError($"Could not register '{prefab.name}' since it contains no NetworkIdentity component");
  517. return;
  518. }
  519. RegisterPrefabIdentity(identity);
  520. }
  521. /// <summary>Register a spawnable prefab with custom assetId and custom spawn/unspawn handlers.</summary>
  522. // Note: newAssetId can not be set on GameObjects that already have an assetId
  523. // Note: registering with assetId is useful for assetbundles etc. a lot
  524. // of people use this.
  525. // TODO why do we have one with SpawnDelegate and one with SpawnHandlerDelegate?
  526. public static void RegisterPrefab(GameObject prefab, Guid newAssetId, SpawnDelegate spawnHandler, UnSpawnDelegate unspawnHandler)
  527. {
  528. // We need this check here because we don't want a null handler in the lambda expression below
  529. if (spawnHandler == null)
  530. {
  531. Debug.LogError($"Can not Register null SpawnHandler for {newAssetId}");
  532. return;
  533. }
  534. RegisterPrefab(prefab, newAssetId, msg => spawnHandler(msg.position, msg.assetId), unspawnHandler);
  535. }
  536. /// <summary>Register a spawnable prefab with custom spawn/unspawn handlers.</summary>
  537. // TODO why do we have one with SpawnDelegate and one with SpawnHandlerDelegate?
  538. public static void RegisterPrefab(GameObject prefab, SpawnDelegate spawnHandler, UnSpawnDelegate unspawnHandler)
  539. {
  540. if (prefab == null)
  541. {
  542. Debug.LogError("Could not register handler for prefab because the prefab was null");
  543. return;
  544. }
  545. NetworkIdentity identity = prefab.GetComponent<NetworkIdentity>();
  546. if (identity == null)
  547. {
  548. Debug.LogError($"Could not register handler for '{prefab.name}' since it contains no NetworkIdentity component");
  549. return;
  550. }
  551. if (identity.sceneId != 0)
  552. {
  553. Debug.LogError($"Can not Register '{prefab.name}' because it has a sceneId, make sure you are passing in the original prefab and not an instance in the scene.");
  554. return;
  555. }
  556. Guid assetId = identity.assetId;
  557. if (assetId == Guid.Empty)
  558. {
  559. Debug.LogError($"Can not Register handler for '{prefab.name}' because it had empty assetid. If this is a scene Object use RegisterSpawnHandler instead");
  560. return;
  561. }
  562. // We need this check here because we don't want a null handler in the lambda expression below
  563. if (spawnHandler == null)
  564. {
  565. Debug.LogError($"Can not Register null SpawnHandler for {assetId}");
  566. return;
  567. }
  568. RegisterPrefab(prefab, msg => spawnHandler(msg.position, msg.assetId), unspawnHandler);
  569. }
  570. /// <summary>Register a spawnable prefab with custom assetId and custom spawn/unspawn handlers.</summary>
  571. // Note: newAssetId can not be set on GameObjects that already have an assetId
  572. // Note: registering with assetId is useful for assetbundles etc. a lot
  573. // of people use this.
  574. // TODO why do we have one with SpawnDelegate and one with SpawnHandlerDelegate?
  575. public static void RegisterPrefab(GameObject prefab, Guid newAssetId, SpawnHandlerDelegate spawnHandler, UnSpawnDelegate unspawnHandler)
  576. {
  577. if (newAssetId == Guid.Empty)
  578. {
  579. Debug.LogError($"Could not register handler for '{prefab.name}' with new assetId because the new assetId was empty");
  580. return;
  581. }
  582. if (prefab == null)
  583. {
  584. Debug.LogError("Could not register handler for prefab because the prefab was null");
  585. return;
  586. }
  587. NetworkIdentity identity = prefab.GetComponent<NetworkIdentity>();
  588. if (identity == null)
  589. {
  590. Debug.LogError($"Could not register handler for '{prefab.name}' since it contains no NetworkIdentity component");
  591. return;
  592. }
  593. if (identity.assetId != Guid.Empty && identity.assetId != newAssetId)
  594. {
  595. Debug.LogError($"Could not register Handler for '{prefab.name}' to {newAssetId} because it already had an AssetId, Existing assetId {identity.assetId}");
  596. return;
  597. }
  598. if (identity.sceneId != 0)
  599. {
  600. Debug.LogError($"Can not Register '{prefab.name}' because it has a sceneId, make sure you are passing in the original prefab and not an instance in the scene.");
  601. return;
  602. }
  603. identity.assetId = newAssetId;
  604. Guid assetId = identity.assetId;
  605. if (spawnHandler == null)
  606. {
  607. Debug.LogError($"Can not Register null SpawnHandler for {assetId}");
  608. return;
  609. }
  610. if (unspawnHandler == null)
  611. {
  612. Debug.LogError($"Can not Register null UnSpawnHandler for {assetId}");
  613. return;
  614. }
  615. if (spawnHandlers.ContainsKey(assetId) || unspawnHandlers.ContainsKey(assetId))
  616. {
  617. Debug.LogWarning($"Replacing existing spawnHandlers for prefab '{prefab.name}' with assetId '{assetId}'");
  618. }
  619. if (prefabs.ContainsKey(assetId))
  620. {
  621. // this is error because SpawnPrefab checks prefabs before handler
  622. Debug.LogError($"assetId '{assetId}' is already used by prefab '{prefabs[assetId].name}', unregister the prefab first before trying to add handler");
  623. }
  624. NetworkIdentity[] identities = prefab.GetComponentsInChildren<NetworkIdentity>();
  625. if (identities.Length > 1)
  626. {
  627. Debug.LogError($"Prefab '{prefab.name}' has multiple NetworkIdentity components. There should only be one NetworkIdentity on a prefab, and it must be on the root object.");
  628. }
  629. //Debug.Log($"Registering custom prefab {prefab.name} as asset:{assetId} {spawnHandler.GetMethodName()}/{unspawnHandler.GetMethodName()}");
  630. spawnHandlers[assetId] = spawnHandler;
  631. unspawnHandlers[assetId] = unspawnHandler;
  632. }
  633. /// <summary>Register a spawnable prefab with custom spawn/unspawn handlers.</summary>
  634. // TODO why do we have one with SpawnDelegate and one with SpawnHandlerDelegate?
  635. public static void RegisterPrefab(GameObject prefab, SpawnHandlerDelegate spawnHandler, UnSpawnDelegate unspawnHandler)
  636. {
  637. if (prefab == null)
  638. {
  639. Debug.LogError("Could not register handler for prefab because the prefab was null");
  640. return;
  641. }
  642. NetworkIdentity identity = prefab.GetComponent<NetworkIdentity>();
  643. if (identity == null)
  644. {
  645. Debug.LogError($"Could not register handler for '{prefab.name}' since it contains no NetworkIdentity component");
  646. return;
  647. }
  648. if (identity.sceneId != 0)
  649. {
  650. Debug.LogError($"Can not Register '{prefab.name}' because it has a sceneId, make sure you are passing in the original prefab and not an instance in the scene.");
  651. return;
  652. }
  653. Guid assetId = identity.assetId;
  654. if (assetId == Guid.Empty)
  655. {
  656. Debug.LogError($"Can not Register handler for '{prefab.name}' because it had empty assetid. If this is a scene Object use RegisterSpawnHandler instead");
  657. return;
  658. }
  659. if (spawnHandler == null)
  660. {
  661. Debug.LogError($"Can not Register null SpawnHandler for {assetId}");
  662. return;
  663. }
  664. if (unspawnHandler == null)
  665. {
  666. Debug.LogError($"Can not Register null UnSpawnHandler for {assetId}");
  667. return;
  668. }
  669. if (spawnHandlers.ContainsKey(assetId) || unspawnHandlers.ContainsKey(assetId))
  670. {
  671. Debug.LogWarning($"Replacing existing spawnHandlers for prefab '{prefab.name}' with assetId '{assetId}'");
  672. }
  673. if (prefabs.ContainsKey(assetId))
  674. {
  675. // this is error because SpawnPrefab checks prefabs before handler
  676. Debug.LogError($"assetId '{assetId}' is already used by prefab '{prefabs[assetId].name}', unregister the prefab first before trying to add handler");
  677. }
  678. NetworkIdentity[] identities = prefab.GetComponentsInChildren<NetworkIdentity>();
  679. if (identities.Length > 1)
  680. {
  681. Debug.LogError($"Prefab '{prefab.name}' has multiple NetworkIdentity components. There should only be one NetworkIdentity on a prefab, and it must be on the root object.");
  682. }
  683. //Debug.Log($"Registering custom prefab {prefab.name} as asset:{assetId} {spawnHandler.GetMethodName()}/{unspawnHandler.GetMethodName()}");
  684. spawnHandlers[assetId] = spawnHandler;
  685. unspawnHandlers[assetId] = unspawnHandler;
  686. }
  687. /// <summary>Removes a registered spawn prefab that was setup with NetworkClient.RegisterPrefab.</summary>
  688. public static void UnregisterPrefab(GameObject prefab)
  689. {
  690. if (prefab == null)
  691. {
  692. Debug.LogError("Could not unregister prefab because it was null");
  693. return;
  694. }
  695. NetworkIdentity identity = prefab.GetComponent<NetworkIdentity>();
  696. if (identity == null)
  697. {
  698. Debug.LogError($"Could not unregister '{prefab.name}' since it contains no NetworkIdentity component");
  699. return;
  700. }
  701. Guid assetId = identity.assetId;
  702. prefabs.Remove(assetId);
  703. spawnHandlers.Remove(assetId);
  704. unspawnHandlers.Remove(assetId);
  705. }
  706. // spawn handlers //////////////////////////////////////////////////////
  707. /// <summary>This is an advanced spawning function that registers a custom assetId with the spawning system.</summary>
  708. // This can be used to register custom spawning methods for an assetId -
  709. // instead of the usual method of registering spawning methods for a
  710. // prefab. This should be used when no prefab exists for the spawned
  711. // objects - such as when they are constructed dynamically at runtime
  712. // from configuration data.
  713. public static void RegisterSpawnHandler(Guid assetId, SpawnDelegate spawnHandler, UnSpawnDelegate unspawnHandler)
  714. {
  715. // We need this check here because we don't want a null handler in the lambda expression below
  716. if (spawnHandler == null)
  717. {
  718. Debug.LogError($"Can not Register null SpawnHandler for {assetId}");
  719. return;
  720. }
  721. RegisterSpawnHandler(assetId, msg => spawnHandler(msg.position, msg.assetId), unspawnHandler);
  722. }
  723. /// <summary>This is an advanced spawning function that registers a custom assetId with the spawning system.</summary>
  724. // This can be used to register custom spawning methods for an assetId -
  725. // instead of the usual method of registering spawning methods for a
  726. // prefab. This should be used when no prefab exists for the spawned
  727. // objects - such as when they are constructed dynamically at runtime
  728. // from configuration data.
  729. public static void RegisterSpawnHandler(Guid assetId, SpawnHandlerDelegate spawnHandler, UnSpawnDelegate unspawnHandler)
  730. {
  731. if (spawnHandler == null)
  732. {
  733. Debug.LogError($"Can not Register null SpawnHandler for {assetId}");
  734. return;
  735. }
  736. if (unspawnHandler == null)
  737. {
  738. Debug.LogError($"Can not Register null UnSpawnHandler for {assetId}");
  739. return;
  740. }
  741. if (assetId == Guid.Empty)
  742. {
  743. Debug.LogError("Can not Register SpawnHandler for empty Guid");
  744. return;
  745. }
  746. if (spawnHandlers.ContainsKey(assetId) || unspawnHandlers.ContainsKey(assetId))
  747. {
  748. Debug.LogWarning($"Replacing existing spawnHandlers for {assetId}");
  749. }
  750. if (prefabs.ContainsKey(assetId))
  751. {
  752. // this is error because SpawnPrefab checks prefabs before handler
  753. Debug.LogError($"assetId '{assetId}' is already used by prefab '{prefabs[assetId].name}'");
  754. }
  755. // Debug.Log("RegisterSpawnHandler asset {assetId} {spawnHandler.GetMethodName()}/{unspawnHandler.GetMethodName()}");
  756. spawnHandlers[assetId] = spawnHandler;
  757. unspawnHandlers[assetId] = unspawnHandler;
  758. }
  759. /// <summary> Removes a registered spawn handler function that was registered with NetworkClient.RegisterHandler().</summary>
  760. public static void UnregisterSpawnHandler(Guid assetId)
  761. {
  762. spawnHandlers.Remove(assetId);
  763. unspawnHandlers.Remove(assetId);
  764. }
  765. /// <summary>This clears the registered spawn prefabs and spawn handler functions for this client.</summary>
  766. public static void ClearSpawners()
  767. {
  768. prefabs.Clear();
  769. spawnHandlers.Clear();
  770. unspawnHandlers.Clear();
  771. }
  772. internal static bool InvokeUnSpawnHandler(Guid assetId, GameObject obj)
  773. {
  774. if (unspawnHandlers.TryGetValue(assetId, out UnSpawnDelegate handler) && handler != null)
  775. {
  776. handler(obj);
  777. return true;
  778. }
  779. return false;
  780. }
  781. // ready ///////////////////////////////////////////////////////////////
  782. /// <summary>Sends Ready message to server, indicating that we loaded the scene, ready to enter the game.</summary>
  783. // This could be for example when a client enters an ongoing game and
  784. // has finished loading the current scene. The server should respond to
  785. // the SYSTEM_READY event with an appropriate handler which instantiates
  786. // the players object for example.
  787. public static bool Ready()
  788. {
  789. // Debug.Log($"NetworkClient.Ready() called with connection {conn}");
  790. if (ready)
  791. {
  792. Debug.LogError("NetworkClient is already ready. It shouldn't be called twice.");
  793. return false;
  794. }
  795. // need a valid connection to become ready
  796. if (connection == null)
  797. {
  798. Debug.LogError("Ready() called with invalid connection object: conn=null");
  799. return false;
  800. }
  801. // Set these before sending the ReadyMessage, otherwise host client
  802. // will fail in InternalAddPlayer with null readyConnection.
  803. // TODO this is redundant. have one source of truth for .ready
  804. ready = true;
  805. connection.isReady = true;
  806. // Tell server we're ready to have a player object spawned
  807. connection.Send(new ReadyMessage());
  808. return true;
  809. }
  810. // add player //////////////////////////////////////////////////////////
  811. // called from message handler for Owner message
  812. internal static void InternalAddPlayer(NetworkIdentity identity)
  813. {
  814. //Debug.Log("NetworkClient.InternalAddPlayer");
  815. // NOTE: It can be "normal" when changing scenes for the player to be destroyed and recreated.
  816. // But, the player structures are not cleaned up, we'll just replace the old player
  817. localPlayer = identity;
  818. // NOTE: we DONT need to set isClient=true here, because OnStartClient
  819. // is called before OnStartLocalPlayer, hence it's already set.
  820. // localPlayer.isClient = true;
  821. // TODO this check might not be necessary
  822. //if (readyConnection != null)
  823. if (ready && connection != null)
  824. {
  825. connection.identity = identity;
  826. }
  827. else Debug.LogWarning("No ready connection found for setting player controller during InternalAddPlayer");
  828. }
  829. /// <summary>Sends AddPlayer message to the server, indicating that we want to join the world.</summary>
  830. public static bool AddPlayer()
  831. {
  832. // ensure valid ready connection
  833. if (connection == null)
  834. {
  835. Debug.LogError("AddPlayer requires a valid NetworkClient.connection.");
  836. return false;
  837. }
  838. // UNET checked 'if readyConnection != null'.
  839. // in other words, we need a connection and we need to be ready.
  840. if (!ready)
  841. {
  842. Debug.LogError("AddPlayer requires a ready NetworkClient.");
  843. return false;
  844. }
  845. if (connection.identity != null)
  846. {
  847. Debug.LogError("NetworkClient.AddPlayer: a PlayerController was already added. Did you call AddPlayer twice?");
  848. return false;
  849. }
  850. // Debug.Log($"NetworkClient.AddPlayer() called with connection {readyConnection}");
  851. connection.Send(new AddPlayerMessage());
  852. return true;
  853. }
  854. // spawning ////////////////////////////////////////////////////////////
  855. internal static void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage message)
  856. {
  857. if (message.assetId != Guid.Empty)
  858. identity.assetId = message.assetId;
  859. if (!identity.gameObject.activeSelf)
  860. {
  861. identity.gameObject.SetActive(true);
  862. }
  863. // apply local values for VR support
  864. identity.transform.localPosition = message.position;
  865. identity.transform.localRotation = message.rotation;
  866. identity.transform.localScale = message.scale;
  867. identity.hasAuthority = message.isOwner;
  868. identity.netId = message.netId;
  869. if (message.isLocalPlayer)
  870. InternalAddPlayer(identity);
  871. // deserialize components if any payload
  872. // (Count is 0 if there were no components)
  873. if (message.payload.Count > 0)
  874. {
  875. using (NetworkReaderPooled payloadReader = NetworkReaderPool.Get(message.payload))
  876. {
  877. identity.OnDeserializeAllSafely(payloadReader, true);
  878. }
  879. }
  880. spawned[message.netId] = identity;
  881. // the initial spawn with OnObjectSpawnStarted/Finished calls all
  882. // object's OnStartClient/OnStartLocalPlayer after they were all
  883. // spawned.
  884. // this only happens once though.
  885. // for all future spawns, we need to call OnStartClient/LocalPlayer
  886. // here immediately since there won't be another OnObjectSpawnFinished.
  887. if (isSpawnFinished)
  888. {
  889. identity.NotifyAuthority();
  890. identity.OnStartClient();
  891. CheckForLocalPlayer(identity);
  892. }
  893. }
  894. // Finds Existing Object with NetId or spawns a new one using AssetId or sceneId
  895. internal static bool FindOrSpawnObject(SpawnMessage message, out NetworkIdentity identity)
  896. {
  897. // was the object already spawned?
  898. identity = GetExistingObject(message.netId);
  899. // if found, return early
  900. if (identity != null)
  901. {
  902. return true;
  903. }
  904. if (message.assetId == Guid.Empty && message.sceneId == 0)
  905. {
  906. Debug.LogError($"OnSpawn message with netId '{message.netId}' has no AssetId or sceneId");
  907. return false;
  908. }
  909. identity = message.sceneId == 0 ? SpawnPrefab(message) : SpawnSceneObject(message.sceneId);
  910. if (identity == null)
  911. {
  912. Debug.LogError($"Could not spawn assetId={message.assetId} scene={message.sceneId:X} netId={message.netId}");
  913. return false;
  914. }
  915. return true;
  916. }
  917. static NetworkIdentity GetExistingObject(uint netid)
  918. {
  919. spawned.TryGetValue(netid, out NetworkIdentity localObject);
  920. return localObject;
  921. }
  922. static NetworkIdentity SpawnPrefab(SpawnMessage message)
  923. {
  924. // custom spawn handler for this prefab? (for prefab pools etc.)
  925. //
  926. // IMPORTANT: look for spawn handlers BEFORE looking for registered
  927. // prefabs. Unspawning also looks for unspawn handlers
  928. // before falling back to regular Destroy. this needs to
  929. // be consistent.
  930. // https://github.com/vis2k/Mirror/issues/2705
  931. if (spawnHandlers.TryGetValue(message.assetId, out SpawnHandlerDelegate handler))
  932. {
  933. GameObject obj = handler(message);
  934. if (obj == null)
  935. {
  936. Debug.LogError($"Spawn Handler returned null, Handler assetId '{message.assetId}'");
  937. return null;
  938. }
  939. NetworkIdentity identity = obj.GetComponent<NetworkIdentity>();
  940. if (identity == null)
  941. {
  942. Debug.LogError($"Object Spawned by handler did not have a NetworkIdentity, Handler assetId '{message.assetId}'");
  943. return null;
  944. }
  945. return identity;
  946. }
  947. // otherwise look in NetworkManager registered prefabs
  948. if (GetPrefab(message.assetId, out GameObject prefab))
  949. {
  950. GameObject obj = GameObject.Instantiate(prefab, message.position, message.rotation);
  951. //Debug.Log($"Client spawn handler instantiating [netId{message.netId} asset ID:{message.assetId} pos:{message.position} rotation:{message.rotation}]");
  952. return obj.GetComponent<NetworkIdentity>();
  953. }
  954. Debug.LogError($"Failed to spawn server object, did you forget to add it to the NetworkManager? assetId={message.assetId} netId={message.netId}");
  955. return null;
  956. }
  957. static NetworkIdentity SpawnSceneObject(ulong sceneId)
  958. {
  959. NetworkIdentity identity = GetAndRemoveSceneObject(sceneId);
  960. if (identity == null)
  961. {
  962. Debug.LogError($"Spawn scene object not found for {sceneId:X}. Make sure that client and server use exactly the same project. This only happens if the hierarchy gets out of sync.");
  963. // dump the whole spawnable objects dict for easier debugging
  964. //foreach (KeyValuePair<ulong, NetworkIdentity> kvp in spawnableObjects)
  965. // Debug.Log($"Spawnable: SceneId={kvp.Key:X} name={kvp.Value.name}");
  966. }
  967. //else Debug.Log($"Client spawn for [netId:{msg.netId}] [sceneId:{msg.sceneId:X}] obj:{identity}");
  968. return identity;
  969. }
  970. static NetworkIdentity GetAndRemoveSceneObject(ulong sceneId)
  971. {
  972. if (spawnableObjects.TryGetValue(sceneId, out NetworkIdentity identity))
  973. {
  974. spawnableObjects.Remove(sceneId);
  975. return identity;
  976. }
  977. return null;
  978. }
  979. // Checks if identity is not spawned yet, not hidden and has sceneId
  980. static bool ConsiderForSpawning(NetworkIdentity identity)
  981. {
  982. // not spawned yet, not hidden, etc.?
  983. return !identity.gameObject.activeSelf &&
  984. identity.gameObject.hideFlags != HideFlags.NotEditable &&
  985. identity.gameObject.hideFlags != HideFlags.HideAndDontSave &&
  986. identity.sceneId != 0;
  987. }
  988. /// <summary>Call this after loading/unloading a scene in the client after connection to register the spawnable objects</summary>
  989. public static void PrepareToSpawnSceneObjects()
  990. {
  991. // remove existing items, they will be re-added below
  992. spawnableObjects.Clear();
  993. // finds all NetworkIdentity currently loaded by unity (includes disabled objects)
  994. NetworkIdentity[] allIdentities = Resources.FindObjectsOfTypeAll<NetworkIdentity>();
  995. foreach (NetworkIdentity identity in allIdentities)
  996. {
  997. // add all unspawned NetworkIdentities to spawnable objects
  998. if (ConsiderForSpawning(identity))
  999. {
  1000. spawnableObjects.Add(identity.sceneId, identity);
  1001. }
  1002. }
  1003. }
  1004. internal static void OnObjectSpawnStarted(ObjectSpawnStartedMessage _)
  1005. {
  1006. // Debug.Log("SpawnStarted");
  1007. PrepareToSpawnSceneObjects();
  1008. isSpawnFinished = false;
  1009. }
  1010. internal static void OnObjectSpawnFinished(ObjectSpawnFinishedMessage _)
  1011. {
  1012. //Debug.Log("SpawnFinished");
  1013. ClearNullFromSpawned();
  1014. // paul: Initialize the objects in the same order as they were
  1015. // initialized in the server. This is important if spawned objects
  1016. // use data from scene objects
  1017. foreach (NetworkIdentity identity in spawned.Values.OrderBy(uv => uv.netId))
  1018. {
  1019. identity.NotifyAuthority();
  1020. identity.OnStartClient();
  1021. CheckForLocalPlayer(identity);
  1022. }
  1023. isSpawnFinished = true;
  1024. }
  1025. static readonly List<uint> removeFromSpawned = new List<uint>();
  1026. static void ClearNullFromSpawned()
  1027. {
  1028. // spawned has null objects after changing scenes on client using
  1029. // NetworkManager.ServerChangeScene remove them here so that 2nd
  1030. // loop below does not get NullReferenceException
  1031. // see https://github.com/vis2k/Mirror/pull/2240
  1032. // TODO fix scene logic so that client scene doesn't have null objects
  1033. foreach (KeyValuePair<uint, NetworkIdentity> kvp in spawned)
  1034. {
  1035. if (kvp.Value == null)
  1036. {
  1037. removeFromSpawned.Add(kvp.Key);
  1038. }
  1039. }
  1040. // can't modify NetworkIdentity.spawned inside foreach so need 2nd loop to remove
  1041. foreach (uint id in removeFromSpawned)
  1042. {
  1043. spawned.Remove(id);
  1044. }
  1045. removeFromSpawned.Clear();
  1046. }
  1047. // host mode callbacks /////////////////////////////////////////////////
  1048. static void OnHostClientObjectDestroy(ObjectDestroyMessage message)
  1049. {
  1050. //Debug.Log($"NetworkClient.OnLocalObjectObjDestroy netId:{message.netId}");
  1051. spawned.Remove(message.netId);
  1052. }
  1053. static void OnHostClientObjectHide(ObjectHideMessage message)
  1054. {
  1055. //Debug.Log($"ClientScene::OnLocalObjectObjHide netId:{message.netId}");
  1056. if (spawned.TryGetValue(message.netId, out NetworkIdentity localObject) &&
  1057. localObject != null)
  1058. {
  1059. if (aoi != null)
  1060. aoi.SetHostVisibility(localObject, false);
  1061. }
  1062. }
  1063. internal static void OnHostClientSpawn(SpawnMessage message)
  1064. {
  1065. // on host mode, the object already exist in NetworkServer.spawned.
  1066. // simply add it to NetworkClient.spawned too.
  1067. if (NetworkServer.spawned.TryGetValue(message.netId, out NetworkIdentity localObject) && localObject != null)
  1068. {
  1069. spawned[message.netId] = localObject;
  1070. // now do the actual 'spawning' on host mode
  1071. if (message.isLocalPlayer)
  1072. InternalAddPlayer(localObject);
  1073. localObject.hasAuthority = message.isOwner;
  1074. localObject.NotifyAuthority();
  1075. localObject.OnStartClient();
  1076. if (aoi != null)
  1077. aoi.SetHostVisibility(localObject, true);
  1078. CheckForLocalPlayer(localObject);
  1079. }
  1080. }
  1081. // client-only mode callbacks //////////////////////////////////////////
  1082. static void OnEntityStateMessage(EntityStateMessage message)
  1083. {
  1084. // Debug.Log($"NetworkClient.OnUpdateVarsMessage {msg.netId}");
  1085. if (spawned.TryGetValue(message.netId, out NetworkIdentity localObject) && localObject != null)
  1086. {
  1087. using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(message.payload))
  1088. localObject.OnDeserializeAllSafely(networkReader, false);
  1089. }
  1090. else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message.");
  1091. }
  1092. static void OnRPCMessage(RpcMessage message)
  1093. {
  1094. // Debug.Log($"NetworkClient.OnRPCMessage hash:{msg.functionHash} netId:{msg.netId}");
  1095. if (spawned.TryGetValue(message.netId, out NetworkIdentity identity))
  1096. {
  1097. using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(message.payload))
  1098. identity.HandleRemoteCall(message.componentIndex, message.functionHash, RemoteCallType.ClientRpc, networkReader);
  1099. }
  1100. }
  1101. static void OnObjectHide(ObjectHideMessage message) => DestroyObject(message.netId);
  1102. internal static void OnObjectDestroy(ObjectDestroyMessage message) => DestroyObject(message.netId);
  1103. internal static void OnSpawn(SpawnMessage message)
  1104. {
  1105. // Debug.Log($"Client spawn handler instantiating netId={msg.netId} assetID={msg.assetId} sceneId={msg.sceneId:X} pos={msg.position}");
  1106. if (FindOrSpawnObject(message, out NetworkIdentity identity))
  1107. {
  1108. ApplySpawnPayload(identity, message);
  1109. }
  1110. }
  1111. internal static void OnChangeOwner(ChangeOwnerMessage message)
  1112. {
  1113. NetworkIdentity identity = GetExistingObject(message.netId);
  1114. if (identity != null)
  1115. ChangeOwner(identity, message);
  1116. else
  1117. Debug.LogError($"OnChangeOwner: Could not find object with netId {message.netId}");
  1118. }
  1119. // ChangeOwnerMessage contains new 'owned' and new 'localPlayer'
  1120. // that we need to apply to the identity.
  1121. internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage message)
  1122. {
  1123. // local player before, but not anymore?
  1124. // call OnStopLocalPlayer before setting new values.
  1125. if (identity.isLocalPlayer && !message.isLocalPlayer)
  1126. {
  1127. identity.OnStopLocalPlayer();
  1128. }
  1129. // set ownership flag (aka authority)
  1130. identity.hasAuthority = message.isOwner;
  1131. identity.NotifyAuthority();
  1132. // set localPlayer flag
  1133. identity.isLocalPlayer = message.isLocalPlayer;
  1134. // identity is now local player. set our static helper field to it.
  1135. if (identity.isLocalPlayer)
  1136. {
  1137. localPlayer = identity;
  1138. }
  1139. // identity's isLocalPlayer was set to false.
  1140. // clear our static localPlayer IF (and only IF) it was that one before.
  1141. else if (localPlayer == identity)
  1142. {
  1143. localPlayer = null;
  1144. }
  1145. // call OnStartLocalPlayer if it's the local player now.
  1146. CheckForLocalPlayer(identity);
  1147. }
  1148. internal static void CheckForLocalPlayer(NetworkIdentity identity)
  1149. {
  1150. if (identity == localPlayer)
  1151. {
  1152. // Set isLocalPlayer to true on this NetworkIdentity and trigger
  1153. // OnStartLocalPlayer in all scripts on the same GO
  1154. identity.connectionToServer = connection;
  1155. identity.OnStartLocalPlayer();
  1156. // Debug.Log($"NetworkClient.OnOwnerMessage player:{identity.name}");
  1157. }
  1158. }
  1159. // destroy /////////////////////////////////////////////////////////////
  1160. static void DestroyObject(uint netId)
  1161. {
  1162. // Debug.Log($"NetworkClient.OnObjDestroy netId: {netId}");
  1163. if (spawned.TryGetValue(netId, out NetworkIdentity localObject) && localObject != null)
  1164. {
  1165. if (localObject.isLocalPlayer)
  1166. localObject.OnStopLocalPlayer();
  1167. localObject.OnStopClient();
  1168. // custom unspawn handler for this prefab? (for prefab pools etc.)
  1169. if (InvokeUnSpawnHandler(localObject.assetId, localObject.gameObject))
  1170. {
  1171. // reset object after user's handler
  1172. localObject.Reset();
  1173. }
  1174. // otherwise fall back to default Destroy
  1175. else if (localObject.sceneId == 0)
  1176. {
  1177. // don't call reset before destroy so that values are still set in OnDestroy
  1178. GameObject.Destroy(localObject.gameObject);
  1179. }
  1180. // scene object.. disable it in scene instead of destroying
  1181. else
  1182. {
  1183. localObject.gameObject.SetActive(false);
  1184. spawnableObjects[localObject.sceneId] = localObject;
  1185. // reset for scene objects
  1186. localObject.Reset();
  1187. }
  1188. // remove from dictionary no matter how it is unspawned
  1189. spawned.Remove(netId);
  1190. }
  1191. //else Debug.LogWarning($"Did not find target for destroy message for {netId}");
  1192. }
  1193. // update //////////////////////////////////////////////////////////////
  1194. // NetworkEarlyUpdate called before any Update/FixedUpdate
  1195. // (we add this to the UnityEngine in NetworkLoop)
  1196. internal static void NetworkEarlyUpdate()
  1197. {
  1198. // process all incoming messages first before updating the world
  1199. if (Transport.activeTransport != null)
  1200. Transport.activeTransport.ClientEarlyUpdate();
  1201. }
  1202. // NetworkLateUpdate called after any Update/FixedUpdate/LateUpdate
  1203. // (we add this to the UnityEngine in NetworkLoop)
  1204. internal static void NetworkLateUpdate()
  1205. {
  1206. // local connection?
  1207. if (connection is LocalConnectionToServer localConnection)
  1208. {
  1209. localConnection.Update();
  1210. }
  1211. // remote connection?
  1212. else if (connection is NetworkConnectionToServer remoteConnection)
  1213. {
  1214. // only update things while connected
  1215. if (active && connectState == ConnectState.Connected)
  1216. {
  1217. // update NetworkTime
  1218. NetworkTime.UpdateClient();
  1219. // update connection to flush out batched messages
  1220. remoteConnection.Update();
  1221. }
  1222. }
  1223. // process all outgoing messages after updating the world
  1224. if (Transport.activeTransport != null)
  1225. Transport.activeTransport.ClientLateUpdate();
  1226. }
  1227. // shutdown ////////////////////////////////////////////////////////////
  1228. /// <summary>Destroys all networked objects on the client.</summary>
  1229. // Note: NetworkServer.CleanupNetworkIdentities does the same on server.
  1230. public static void DestroyAllClientObjects()
  1231. {
  1232. // user can modify spawned lists which causes InvalidOperationException
  1233. // list can modified either in UnSpawnHandler or in OnDisable/OnDestroy
  1234. // we need the Try/Catch so that the rest of the shutdown does not get stopped
  1235. try
  1236. {
  1237. foreach (NetworkIdentity identity in spawned.Values)
  1238. {
  1239. if (identity != null && identity.gameObject != null)
  1240. {
  1241. if (identity.isLocalPlayer)
  1242. identity.OnStopLocalPlayer();
  1243. identity.OnStopClient();
  1244. // NetworkClient.Shutdown calls DestroyAllClientObjects.
  1245. // which destroys all objects in NetworkClient.spawned.
  1246. // => NC.spawned contains owned & observed objects
  1247. // => in host mode, we CAN NOT destroy observed objects.
  1248. // => that would destroy them other connection's objects
  1249. // on the host server, making them disconnect.
  1250. // https://github.com/vis2k/Mirror/issues/2954
  1251. bool hostOwned = identity.connectionToServer is LocalConnectionToServer;
  1252. bool shouldDestroy = !identity.isServer || hostOwned;
  1253. if (shouldDestroy)
  1254. {
  1255. bool wasUnspawned = InvokeUnSpawnHandler(identity.assetId, identity.gameObject);
  1256. // unspawned objects should be reset for reuse later.
  1257. if (wasUnspawned)
  1258. {
  1259. identity.Reset();
  1260. }
  1261. // without unspawn handler, we need to disable/destroy.
  1262. else
  1263. {
  1264. // scene objects are reset and disabled.
  1265. // they always stay in the scene, we don't destroy them.
  1266. if (identity.sceneId != 0)
  1267. {
  1268. identity.Reset();
  1269. identity.gameObject.SetActive(false);
  1270. }
  1271. // spawned objects are destroyed
  1272. else
  1273. {
  1274. GameObject.Destroy(identity.gameObject);
  1275. }
  1276. }
  1277. }
  1278. }
  1279. }
  1280. spawned.Clear();
  1281. }
  1282. catch (InvalidOperationException e)
  1283. {
  1284. Debug.LogException(e);
  1285. Debug.LogError("Could not DestroyAllClientObjects because spawned list was modified during loop, make sure you are not modifying NetworkIdentity.spawned by calling NetworkServer.Destroy or NetworkServer.Spawn in OnDestroy or OnDisable.");
  1286. }
  1287. }
  1288. /// <summary>Shutdown the client.</summary>
  1289. // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
  1290. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
  1291. public static void Shutdown()
  1292. {
  1293. //Debug.Log("Shutting down client.");
  1294. // calls prefabs.Clear();
  1295. // calls spawnHandlers.Clear();
  1296. // calls unspawnHandlers.Clear();
  1297. ClearSpawners();
  1298. // calls spawned.Clear() if no exception occurs
  1299. DestroyAllClientObjects();
  1300. spawned.Clear();
  1301. handlers.Clear();
  1302. spawnableObjects.Clear();
  1303. // IMPORTANT: do NOT call NetworkIdentity.ResetStatics() here!
  1304. // calling StopClient() in host mode would reset nextNetId to 1,
  1305. // causing next connection to have a duplicate netId accidentally.
  1306. // => see also: https://github.com/vis2k/Mirror/issues/2954
  1307. //NetworkIdentity.ResetStatics();
  1308. // => instead, reset only the client sided statics.
  1309. NetworkIdentity.ResetClientStatics();
  1310. // disconnect the client connection.
  1311. // we do NOT call Transport.Shutdown, because someone only called
  1312. // NetworkClient.Shutdown. we can't assume that the server is
  1313. // supposed to be shut down too!
  1314. if (Transport.activeTransport != null)
  1315. Transport.activeTransport.ClientDisconnect();
  1316. // reset statics
  1317. connectState = ConnectState.None;
  1318. connection = null;
  1319. localPlayer = null;
  1320. ready = false;
  1321. isSpawnFinished = false;
  1322. isLoadingScene = false;
  1323. unbatcher = new Unbatcher();
  1324. // clear events. someone might have hooked into them before, but
  1325. // we don't want to use those hooks after Shutdown anymore.
  1326. OnConnectedEvent = null;
  1327. OnDisconnectedEvent = null;
  1328. OnErrorEvent = null;
  1329. }
  1330. }
  1331. }