NetworkClient.cs 65 KB

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