PhotonHandler.cs 15 KB

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