PhotonHandler.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. // ----------------------------------------------------------------------------
  2. // <copyright file="PhotonHandler.cs" company="Exit Games GmbH">
  3. // PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
  4. // </copyright>
  5. // <summary>
  6. // PhotonHandler is a runtime MonoBehaviour to include PUN into the main loop.
  7. // </summary>
  8. // <author>developer@exitgames.com</author>
  9. // ----------------------------------------------------------------------------
  10. namespace Photon.Pun
  11. {
  12. using System;
  13. using System.Collections.Generic;
  14. using ExitGames.Client.Photon;
  15. using Photon.Realtime;
  16. using UnityEngine;
  17. using UnityEngine.Profiling;
  18. /// <summary>
  19. /// Internal MonoBehaviour that allows Photon to run an Update loop.
  20. /// </summary>
  21. public class PhotonHandler : ConnectionHandler, IInRoomCallbacks, IMatchmakingCallbacks
  22. {
  23. private static PhotonHandler instance;
  24. internal static PhotonHandler Instance
  25. {
  26. get
  27. {
  28. if (instance == null)
  29. {
  30. instance = FindObjectOfType<PhotonHandler>();
  31. if (instance == null)
  32. {
  33. GameObject obj = new GameObject();
  34. obj.name = "PhotonMono";
  35. instance = obj.AddComponent<PhotonHandler>();
  36. }
  37. }
  38. return instance;
  39. }
  40. }
  41. /// <summary>Limits the number of datagrams that are created in each LateUpdate.</summary>
  42. /// <remarks>Helps spreading out sending of messages minimally.</remarks>
  43. public static int MaxDatagrams = 3;
  44. /// <summary>Signals that outgoing messages should be sent in the next LateUpdate call.</summary>
  45. /// <remarks>Up to MaxDatagrams are created to send queued messages.</remarks>
  46. public static bool SendAsap;
  47. /// <summary>This corrects the "next time to serialize the state" value by some ms.</summary>
  48. /// <remarks>As LateUpdate typically gets called every 15ms it's better to be early(er) than late to achieve a SerializeRate.</remarks>
  49. private const int SerializeRateFrameCorrection = 8;
  50. protected internal int UpdateInterval; // time [ms] between consecutive SendOutgoingCommands calls
  51. protected internal int UpdateIntervalOnSerialize; // time [ms] between consecutive RunViewUpdate calls (sending syncs, etc)
  52. private int nextSendTickCount;
  53. private int nextSendTickCountOnSerialize;
  54. private SupportLogger supportLoggerComponent;
  55. protected override void Awake()
  56. {
  57. if (instance == null || ReferenceEquals(this, instance))
  58. {
  59. instance = this;
  60. base.Awake();
  61. }
  62. else
  63. {
  64. Destroy(this);
  65. }
  66. }
  67. protected virtual void OnEnable()
  68. {
  69. if (Instance != this)
  70. {
  71. Debug.LogError("PhotonHandler is a singleton but there are multiple instances. this != Instance.");
  72. return;
  73. }
  74. this.Client = PhotonNetwork.NetworkingClient;
  75. if (PhotonNetwork.PhotonServerSettings.EnableSupportLogger)
  76. {
  77. SupportLogger supportLogger = this.gameObject.GetComponent<SupportLogger>();
  78. if (supportLogger == null)
  79. {
  80. supportLogger = this.gameObject.AddComponent<SupportLogger>();
  81. }
  82. if (this.supportLoggerComponent != null)
  83. {
  84. if (supportLogger.GetInstanceID() != this.supportLoggerComponent.GetInstanceID())
  85. {
  86. Debug.LogWarningFormat("Cached SupportLogger component is different from the one attached to PhotonMono GameObject");
  87. }
  88. }
  89. this.supportLoggerComponent = supportLogger;
  90. this.supportLoggerComponent.Client = PhotonNetwork.NetworkingClient;
  91. }
  92. this.UpdateInterval = 1000 / PhotonNetwork.SendRate;
  93. this.UpdateIntervalOnSerialize = 1000 / PhotonNetwork.SerializationRate;
  94. PhotonNetwork.AddCallbackTarget(this);
  95. this.StartFallbackSendAckThread(); // this is not done in the base class
  96. }
  97. protected void Start()
  98. {
  99. UnityEngine.SceneManagement.SceneManager.sceneLoaded += (scene, loadingMode) =>
  100. {
  101. PhotonNetwork.NewSceneLoaded();
  102. };
  103. }
  104. protected override void OnDisable()
  105. {
  106. PhotonNetwork.RemoveCallbackTarget(this);
  107. base.OnDisable();
  108. }
  109. /// <summary>Called in intervals by UnityEngine. Affected by Time.timeScale.</summary>
  110. protected void FixedUpdate()
  111. {
  112. #if PUN_DISPATCH_IN_FIXEDUPDATE
  113. this.Dispatch();
  114. #elif PUN_DISPATCH_IN_LATEUPDATE
  115. // do not dispatch here
  116. #else
  117. if (Time.timeScale > PhotonNetwork.MinimalTimeScaleToDispatchInFixedUpdate)
  118. {
  119. this.Dispatch();
  120. }
  121. #endif
  122. }
  123. /// <summary>Called in intervals by UnityEngine, after running the normal game code and physics.</summary>
  124. protected void LateUpdate()
  125. {
  126. #if PUN_DISPATCH_IN_LATEUPDATE
  127. this.Dispatch();
  128. #elif PUN_DISPATCH_IN_FIXEDUPDATE
  129. // do not dispatch here
  130. #else
  131. // see MinimalTimeScaleToDispatchInFixedUpdate and FixedUpdate for explanation:
  132. if (Time.timeScale <= PhotonNetwork.MinimalTimeScaleToDispatchInFixedUpdate)
  133. {
  134. this.Dispatch();
  135. }
  136. #endif
  137. int currentMsSinceStart = (int)(Time.realtimeSinceStartup * 1000); // avoiding Environment.TickCount, which could be negative on long-running platforms
  138. if (PhotonNetwork.IsMessageQueueRunning && currentMsSinceStart > this.nextSendTickCountOnSerialize)
  139. {
  140. PhotonNetwork.RunViewUpdate();
  141. this.nextSendTickCountOnSerialize = currentMsSinceStart + this.UpdateIntervalOnSerialize - SerializeRateFrameCorrection;
  142. this.nextSendTickCount = 0; // immediately send when synchronization code was running
  143. }
  144. currentMsSinceStart = (int)(Time.realtimeSinceStartup * 1000);
  145. if (SendAsap || currentMsSinceStart > this.nextSendTickCount)
  146. {
  147. SendAsap = false;
  148. bool doSend = true;
  149. int sendCounter = 0;
  150. while (PhotonNetwork.IsMessageQueueRunning && doSend && sendCounter < MaxDatagrams)
  151. {
  152. // Send all outgoing commands
  153. Profiler.BeginSample("SendOutgoingCommands");
  154. doSend = PhotonNetwork.NetworkingClient.LoadBalancingPeer.SendOutgoingCommands();
  155. sendCounter++;
  156. Profiler.EndSample();
  157. }
  158. this.nextSendTickCount = currentMsSinceStart + this.UpdateInterval;
  159. }
  160. }
  161. /// <summary>Dispatches incoming network messages for PUN. Called in FixedUpdate or LateUpdate.</summary>
  162. /// <remarks>
  163. /// It may make sense to dispatch incoming messages, even if the timeScale is near 0.
  164. /// That can be configured with PhotonNetwork.MinimalTimeScaleToDispatchInFixedUpdate.
  165. ///
  166. /// Without dispatching messages, PUN won't change state and does not handle updates.
  167. /// </remarks>
  168. protected void Dispatch()
  169. {
  170. if (PhotonNetwork.NetworkingClient == null)
  171. {
  172. Debug.LogError("NetworkPeer broke!");
  173. return;
  174. }
  175. //if (PhotonNetwork.NetworkClientState == ClientState.PeerCreated || PhotonNetwork.NetworkClientState == ClientState.Disconnected || PhotonNetwork.OfflineMode)
  176. //{
  177. // return;
  178. //}
  179. bool doDispatch = true;
  180. Exception ex = null;
  181. int exceptionCount = 0;
  182. while (PhotonNetwork.IsMessageQueueRunning && doDispatch)
  183. {
  184. // DispatchIncomingCommands() returns true of it dispatched any command (event, response or state change)
  185. Profiler.BeginSample("DispatchIncomingCommands");
  186. try
  187. {
  188. doDispatch = PhotonNetwork.NetworkingClient.LoadBalancingPeer.DispatchIncomingCommands();
  189. }
  190. catch (Exception e)
  191. {
  192. exceptionCount++;
  193. if (ex == null)
  194. {
  195. ex = e;
  196. }
  197. }
  198. Profiler.EndSample();
  199. }
  200. if (ex != null)
  201. {
  202. throw new AggregateException("Caught " + exceptionCount + " exception(s) in methods called by DispatchIncomingCommands(). Rethrowing first only (see above).", ex);
  203. }
  204. }
  205. public void OnCreatedRoom()
  206. {
  207. PhotonNetwork.SetLevelInPropsIfSynced(SceneManagerHelper.ActiveSceneName);
  208. }
  209. public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
  210. {
  211. PhotonNetwork.LoadLevelIfSynced();
  212. }
  213. public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { }
  214. public void OnMasterClientSwitched(Player newMasterClient)
  215. {
  216. var views = PhotonNetwork.PhotonViewCollection;
  217. foreach (var view in views)
  218. {
  219. if (view.IsRoomView)
  220. {
  221. view.OwnerActorNr= newMasterClient.ActorNumber;
  222. view.ControllerActorNr = newMasterClient.ActorNumber;
  223. }
  224. }
  225. }
  226. public void OnFriendListUpdate(System.Collections.Generic.List<FriendInfo> friendList) { }
  227. public void OnCreateRoomFailed(short returnCode, string message) { }
  228. public void OnJoinRoomFailed(short returnCode, string message) { }
  229. public void OnJoinRandomFailed(short returnCode, string message) { }
  230. protected List<int> reusableIntList = new List<int>();
  231. public void OnJoinedRoom()
  232. {
  233. if (PhotonNetwork.ViewCount == 0)
  234. return;
  235. var views = PhotonNetwork.PhotonViewCollection;
  236. bool amMasterClient = PhotonNetwork.IsMasterClient;
  237. bool amRejoiningMaster = amMasterClient && PhotonNetwork.CurrentRoom.PlayerCount > 1;
  238. if (amRejoiningMaster)
  239. reusableIntList.Clear();
  240. // If this is the master rejoining, reassert ownership of non-creator owners
  241. foreach (var view in views)
  242. {
  243. int viewOwnerId = view.OwnerActorNr;
  244. int viewCreatorId = view.CreatorActorNr;
  245. // on join / rejoin, assign control to either the Master Client (for room objects) or the owner (for anything else)
  246. view.RebuildControllerCache();
  247. // Rejoining master should enforce its world view, and override any changes that happened while it was soft disconnected
  248. if (amRejoiningMaster)
  249. if (viewOwnerId != viewCreatorId)
  250. {
  251. reusableIntList.Add(view.ViewID);
  252. reusableIntList.Add(viewOwnerId);
  253. }
  254. }
  255. if (amRejoiningMaster && reusableIntList.Count > 0)
  256. {
  257. PhotonNetwork.OwnershipUpdate(reusableIntList.ToArray());
  258. }
  259. }
  260. public void OnLeftRoom()
  261. {
  262. // Destroy spawned objects and reset scene objects
  263. PhotonNetwork.LocalCleanupAnythingInstantiated(true);
  264. }
  265. public void OnPlayerEnteredRoom(Player newPlayer)
  266. {
  267. // note: if the master client becomes inactive, someone else becomes master. so there is no case where the active master client reconnects
  268. // what may happen is that the Master Client disconnects locally and uses ReconnectAndRejoin before anyone (including the server) notices.
  269. bool amMasterClient = PhotonNetwork.IsMasterClient;
  270. var views = PhotonNetwork.PhotonViewCollection;
  271. if (amMasterClient)
  272. {
  273. reusableIntList.Clear();
  274. }
  275. foreach (var view in views)
  276. {
  277. view.RebuildControllerCache(); // all clients will potentially have to clean up owner and controller, if someone re-joins
  278. // the master client notifies joining players of any non-creator ownership
  279. if (amMasterClient)
  280. {
  281. int viewOwnerId = view.OwnerActorNr;
  282. if (viewOwnerId != view.CreatorActorNr)
  283. {
  284. reusableIntList.Add(view.ViewID);
  285. reusableIntList.Add(viewOwnerId);
  286. }
  287. }
  288. }
  289. // update the joining player of non-creator ownership in the room
  290. if (amMasterClient && reusableIntList.Count > 0)
  291. {
  292. PhotonNetwork.OwnershipUpdate(reusableIntList.ToArray(), newPlayer.ActorNumber);
  293. }
  294. }
  295. public void OnPlayerLeftRoom(Player otherPlayer)
  296. {
  297. var views = PhotonNetwork.PhotonViewCollection;
  298. int leavingPlayerId = otherPlayer.ActorNumber;
  299. bool isInactive = otherPlayer.IsInactive;
  300. // SOFT DISCONNECT: A player has timed out to the relay but has not yet exceeded PlayerTTL and may reconnect.
  301. // Master will take control of this objects until the player hard disconnects, or returns.
  302. if (isInactive)
  303. {
  304. foreach (var view in views)
  305. {
  306. // v2.27: changed from owner-check to controller-check
  307. if (view.ControllerActorNr == leavingPlayerId)
  308. view.ControllerActorNr = PhotonNetwork.MasterClient.ActorNumber;
  309. }
  310. }
  311. // HARD DISCONNECT: Player permanently removed. Remove that actor as owner for all items they created (Unless AutoCleanUp is false)
  312. else
  313. {
  314. bool autocleanup = PhotonNetwork.CurrentRoom.AutoCleanUp;
  315. foreach (var view in views)
  316. {
  317. // Skip changing Owner/Controller for items that will be cleaned up.
  318. if (autocleanup && view.CreatorActorNr == leavingPlayerId)
  319. continue;
  320. // Any views owned by the leaving player, default to null owner (which will become master controlled).
  321. if (view.OwnerActorNr == leavingPlayerId || view.ControllerActorNr == leavingPlayerId)
  322. {
  323. view.OwnerActorNr = 0;
  324. view.ControllerActorNr = PhotonNetwork.MasterClient.ActorNumber;
  325. }
  326. }
  327. }
  328. }
  329. }
  330. }