You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
15 KiB
401 lines
15 KiB
// ----------------------------------------------------------------------------
|
|
// <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 ExitGames.Client.Photon;
|
|
using Photon.Realtime;
|
|
using UnityEngine;
|
|
using UnityEngine.Profiling;
|
|
|
|
|
|
/// <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 = 3;
|
|
|
|
/// <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 int nextSendTickCount;
|
|
|
|
private int nextSendTickCountOnSerialize;
|
|
|
|
private SupportLogger supportLoggerComponent;
|
|
|
|
|
|
protected override void Awake()
|
|
{
|
|
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
|
|
|
|
int currentMsSinceStart = (int)(Time.realtimeSinceStartup * 1000); // avoiding Environment.TickCount, which could be negative on long-running platforms
|
|
if (PhotonNetwork.IsMessageQueueRunning && currentMsSinceStart > this.nextSendTickCountOnSerialize)
|
|
{
|
|
PhotonNetwork.RunViewUpdate();
|
|
this.nextSendTickCountOnSerialize = currentMsSinceStart + this.UpdateIntervalOnSerialize - SerializeRateFrameCorrection;
|
|
this.nextSendTickCount = 0; // immediately send when synchronization code was running
|
|
}
|
|
|
|
currentMsSinceStart = (int)(Time.realtimeSinceStartup * 1000);
|
|
if (SendAsap || currentMsSinceStart > this.nextSendTickCount)
|
|
{
|
|
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();
|
|
}
|
|
|
|
this.nextSendTickCount = currentMsSinceStart + this.UpdateInterval;
|
|
}
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |