NetworkClient.cs 63 KB

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