1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
|
// ----------------------------------------------------------------------------
// <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;
}
}
}
}
}
}
|