123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- // ----------------------------------------------------------------------------
- // <copyright file="PhotonHandler.cs" company="Exit Games GmbH">
- // PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
- // </copyright>
- // <summary>
- // PhotonHandler is a runtime MonoBehaviour to include PUN into the main loop.
- // </summary>
- // <author>developer@exitgames.com</author>
- // ----------------------------------------------------------------------------
- namespace Photon.Pun
- {
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using ExitGames.Client.Photon;
- using Photon.Realtime;
- using UnityEngine;
- using UnityEngine.Profiling;
- using Debug = UnityEngine.Debug;
- /// <summary>
- /// Internal MonoBehaviour that allows Photon to run an Update loop.
- /// </summary>
- public class PhotonHandler : ConnectionHandler, IInRoomCallbacks, IMatchmakingCallbacks
- {
- private static PhotonHandler instance;
- internal static PhotonHandler Instance
- {
- get
- {
- if (instance == null)
- {
- instance = FindObjectOfType<PhotonHandler>();
- if (instance == null)
- {
- GameObject obj = new GameObject();
- obj.name = "PhotonMono";
- instance = obj.AddComponent<PhotonHandler>();
- }
- }
- return instance;
- }
- }
- /// <summary>Limits the number of datagrams that are created in each LateUpdate.</summary>
- /// <remarks>Helps spreading out sending of messages minimally.</remarks>
- public static int MaxDatagrams = 10;
- /// <summary>Signals that outgoing messages should be sent in the next LateUpdate call.</summary>
- /// <remarks>Up to MaxDatagrams are created to send queued messages.</remarks>
- public static bool SendAsap;
- /// <summary>This corrects the "next time to serialize the state" value by some ms.</summary>
- /// <remarks>As LateUpdate typically gets called every 15ms it's better to be early(er) than late to achieve a SerializeRate.</remarks>
- private const int SerializeRateFrameCorrection = 8;
- protected internal int UpdateInterval; // time [ms] between consecutive SendOutgoingCommands calls
- protected internal int UpdateIntervalOnSerialize; // time [ms] between consecutive RunViewUpdate calls (sending syncs, etc)
- private readonly Stopwatch swSendOutgoing = new Stopwatch();
- private readonly Stopwatch swViewUpdate = new Stopwatch();
- private SupportLogger supportLoggerComponent;
- protected override void Awake()
- {
- this.swSendOutgoing.Start();
- this.swViewUpdate.Start();
- if (instance == null || ReferenceEquals(this, instance))
- {
- instance = this;
- base.Awake();
- }
- else
- {
- Destroy(this);
- }
- }
- protected virtual void OnEnable()
- {
- if (Instance != this)
- {
- Debug.LogError("PhotonHandler is a singleton but there are multiple instances. this != Instance.");
- return;
- }
- this.Client = PhotonNetwork.NetworkingClient;
- if (PhotonNetwork.PhotonServerSettings.EnableSupportLogger)
- {
- SupportLogger supportLogger = this.gameObject.GetComponent<SupportLogger>();
- if (supportLogger == null)
- {
- supportLogger = this.gameObject.AddComponent<SupportLogger>();
- }
- if (this.supportLoggerComponent != null)
- {
- if (supportLogger.GetInstanceID() != this.supportLoggerComponent.GetInstanceID())
- {
- Debug.LogWarningFormat("Cached SupportLogger component is different from the one attached to PhotonMono GameObject");
- }
- }
- this.supportLoggerComponent = supportLogger;
- this.supportLoggerComponent.Client = PhotonNetwork.NetworkingClient;
- }
- this.UpdateInterval = 1000 / PhotonNetwork.SendRate;
- this.UpdateIntervalOnSerialize = 1000 / PhotonNetwork.SerializationRate;
- PhotonNetwork.AddCallbackTarget(this);
- this.StartFallbackSendAckThread(); // this is not done in the base class
- }
- protected void Start()
- {
- UnityEngine.SceneManagement.SceneManager.sceneLoaded += (scene, loadingMode) =>
- {
- PhotonNetwork.NewSceneLoaded();
- };
- }
- protected override void OnDisable()
- {
- PhotonNetwork.RemoveCallbackTarget(this);
- base.OnDisable();
- }
- /// <summary>Called in intervals by UnityEngine. Affected by Time.timeScale.</summary>
- protected void FixedUpdate()
- {
- #if PUN_DISPATCH_IN_FIXEDUPDATE
- this.Dispatch();
- #elif PUN_DISPATCH_IN_LATEUPDATE
- // do not dispatch here
- #else
- if (Time.timeScale > PhotonNetwork.MinimalTimeScaleToDispatchInFixedUpdate)
- {
- this.Dispatch();
- }
- #endif
- }
- /// <summary>Called in intervals by UnityEngine, after running the normal game code and physics.</summary>
- protected void LateUpdate()
- {
- #if PUN_DISPATCH_IN_LATEUPDATE
- this.Dispatch();
- #elif PUN_DISPATCH_IN_FIXEDUPDATE
- // do not dispatch here
- #else
- // see MinimalTimeScaleToDispatchInFixedUpdate and FixedUpdate for explanation:
- if (Time.timeScale <= PhotonNetwork.MinimalTimeScaleToDispatchInFixedUpdate)
- {
- this.Dispatch();
- }
- #endif
- if (PhotonNetwork.IsMessageQueueRunning && this.swViewUpdate.ElapsedMilliseconds >= this.UpdateIntervalOnSerialize - SerializeRateFrameCorrection)
- {
- PhotonNetwork.RunViewUpdate();
- this.swViewUpdate.Restart();
- SendAsap = true; // immediately send when synchronization code was running
- }
-
- if (SendAsap || this.swSendOutgoing.ElapsedMilliseconds >= this.UpdateInterval)
- {
- SendAsap = false;
- bool doSend = true;
- int sendCounter = 0;
- while (PhotonNetwork.IsMessageQueueRunning && doSend && sendCounter < MaxDatagrams)
- {
- // Send all outgoing commands
- Profiler.BeginSample("SendOutgoingCommands");
- doSend = PhotonNetwork.NetworkingClient.LoadBalancingPeer.SendOutgoingCommands();
- sendCounter++;
- Profiler.EndSample();
- }
- if (sendCounter >= MaxDatagrams)
- {
- SendAsap = true;
- }
- this.swSendOutgoing.Restart();
- }
- }
- /// <summary>Dispatches incoming network messages for PUN. Called in FixedUpdate or LateUpdate.</summary>
- /// <remarks>
- /// It may make sense to dispatch incoming messages, even if the timeScale is near 0.
- /// That can be configured with PhotonNetwork.MinimalTimeScaleToDispatchInFixedUpdate.
- ///
- /// Without dispatching messages, PUN won't change state and does not handle updates.
- /// </remarks>
- protected void Dispatch()
- {
- if (PhotonNetwork.NetworkingClient == null)
- {
- Debug.LogError("NetworkPeer broke!");
- return;
- }
- //if (PhotonNetwork.NetworkClientState == ClientState.PeerCreated || PhotonNetwork.NetworkClientState == ClientState.Disconnected || PhotonNetwork.OfflineMode)
- //{
- // return;
- //}
- bool doDispatch = true;
- Exception ex = null;
- int exceptionCount = 0;
- while (PhotonNetwork.IsMessageQueueRunning && doDispatch)
- {
- // DispatchIncomingCommands() returns true of it dispatched any command (event, response or state change)
- Profiler.BeginSample("DispatchIncomingCommands");
- try
- {
- doDispatch = PhotonNetwork.NetworkingClient.LoadBalancingPeer.DispatchIncomingCommands();
- }
- catch (Exception e)
- {
- exceptionCount++;
- if (ex == null)
- {
- ex = e;
- }
- }
- Profiler.EndSample();
- }
- if (ex != null)
- {
- throw new AggregateException("Caught " + exceptionCount + " exception(s) in methods called by DispatchIncomingCommands(). Rethrowing first only (see above).", ex);
- }
- }
- public void OnCreatedRoom()
- {
- PhotonNetwork.SetLevelInPropsIfSynced(SceneManagerHelper.ActiveSceneName);
- }
- public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
- {
- PhotonNetwork.LoadLevelIfSynced();
- }
- public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { }
- public void OnMasterClientSwitched(Player newMasterClient)
- {
- var views = PhotonNetwork.PhotonViewCollection;
- foreach (var view in views)
- {
- if (view.IsRoomView)
- {
- view.OwnerActorNr= newMasterClient.ActorNumber;
- view.ControllerActorNr = newMasterClient.ActorNumber;
- }
- }
- }
- public void OnFriendListUpdate(System.Collections.Generic.List<FriendInfo> friendList) { }
- public void OnCreateRoomFailed(short returnCode, string message) { }
- public void OnJoinRoomFailed(short returnCode, string message) { }
- public void OnJoinRandomFailed(short returnCode, string message) { }
- protected List<int> reusableIntList = new List<int>();
- public void OnJoinedRoom()
- {
- if (PhotonNetwork.ViewCount == 0)
- return;
- var views = PhotonNetwork.PhotonViewCollection;
- bool amMasterClient = PhotonNetwork.IsMasterClient;
- bool amRejoiningMaster = amMasterClient && PhotonNetwork.CurrentRoom.PlayerCount > 1;
- if (amRejoiningMaster)
- reusableIntList.Clear();
- // If this is the master rejoining, reassert ownership of non-creator owners
- foreach (var view in views)
- {
- int viewOwnerId = view.OwnerActorNr;
- int viewCreatorId = view.CreatorActorNr;
- // on join / rejoin, assign control to either the Master Client (for room objects) or the owner (for anything else)
- view.RebuildControllerCache();
- // Rejoining master should enforce its world view, and override any changes that happened while it was soft disconnected
- if (amRejoiningMaster)
- if (viewOwnerId != viewCreatorId)
- {
- reusableIntList.Add(view.ViewID);
- reusableIntList.Add(viewOwnerId);
- }
- }
- if (amRejoiningMaster && reusableIntList.Count > 0)
- {
- PhotonNetwork.OwnershipUpdate(reusableIntList.ToArray());
- }
- }
- public void OnLeftRoom()
- {
- // Destroy spawned objects and reset scene objects
- PhotonNetwork.LocalCleanupAnythingInstantiated(true);
- }
- public void OnPlayerEnteredRoom(Player newPlayer)
- {
- // note: if the master client becomes inactive, someone else becomes master. so there is no case where the active master client reconnects
- // what may happen is that the Master Client disconnects locally and uses ReconnectAndRejoin before anyone (including the server) notices.
- bool amMasterClient = PhotonNetwork.IsMasterClient;
- var views = PhotonNetwork.PhotonViewCollection;
- if (amMasterClient)
- {
- reusableIntList.Clear();
- }
- foreach (var view in views)
- {
- view.RebuildControllerCache(); // all clients will potentially have to clean up owner and controller, if someone re-joins
- // the master client notifies joining players of any non-creator ownership
- if (amMasterClient)
- {
- int viewOwnerId = view.OwnerActorNr;
- if (viewOwnerId != view.CreatorActorNr)
- {
- reusableIntList.Add(view.ViewID);
- reusableIntList.Add(viewOwnerId);
- }
- }
- }
- // update the joining player of non-creator ownership in the room
- if (amMasterClient && reusableIntList.Count > 0)
- {
- PhotonNetwork.OwnershipUpdate(reusableIntList.ToArray(), newPlayer.ActorNumber);
- }
- }
- public void OnPlayerLeftRoom(Player otherPlayer)
- {
- var views = PhotonNetwork.PhotonViewCollection;
- int leavingPlayerId = otherPlayer.ActorNumber;
- bool isInactive = otherPlayer.IsInactive;
- // SOFT DISCONNECT: A player has timed out to the relay but has not yet exceeded PlayerTTL and may reconnect.
- // Master will take control of this objects until the player hard disconnects, or returns.
- if (isInactive)
- {
- foreach (var view in views)
- {
- // v2.27: changed from owner-check to controller-check
- if (view.ControllerActorNr == leavingPlayerId)
- view.ControllerActorNr = PhotonNetwork.MasterClient.ActorNumber;
- }
- }
- // HARD DISCONNECT: Player permanently removed. Remove that actor as owner for all items they created (Unless AutoCleanUp is false)
- else
- {
- bool autocleanup = PhotonNetwork.CurrentRoom.AutoCleanUp;
- foreach (var view in views)
- {
- // Skip changing Owner/Controller for items that will be cleaned up.
- if (autocleanup && view.CreatorActorNr == leavingPlayerId)
- continue;
- // Any views owned by the leaving player, default to null owner (which will become master controlled).
- if (view.OwnerActorNr == leavingPlayerId || view.ControllerActorNr == leavingPlayerId)
- {
- view.OwnerActorNr = 0;
- view.ControllerActorNr = PhotonNetwork.MasterClient.ActorNumber;
- }
- }
- }
- }
- }
- }
|