-
-
Notifications
You must be signed in to change notification settings - Fork 65
/
NetworkBehaviour.cs
829 lines (734 loc) · 34.7 KB
/
NetworkBehaviour.cs
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
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
using System;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;
namespace Mirror
{
/// <summary>
/// Sync to everyone, or only to owner.
/// </summary>
public enum SyncMode { Observers, Owner }
/// <summary>
/// Base class which should be inherited by scripts which contain networking functionality.
/// </summary>
/// <remarks>
/// <para>This is a MonoBehaviour class so scripts which need to use the networking feature should inherit this class instead of MonoBehaviour. It allows you to invoke networked actions, receive various callbacks, and automatically synchronize state from server-to-client.</para>
/// <para>The NetworkBehaviour component requires a NetworkIdentity on the game object. There can be multiple NetworkBehaviours on a single game object. For an object with sub-components in a hierarchy, the NetworkIdentity must be on the root object, and NetworkBehaviour scripts must also be on the root object.</para>
/// <para>Some of the built-in components of the networking system are derived from NetworkBehaviour, including NetworkTransport, NetworkAnimator and NetworkProximityChecker.</para>
/// </remarks>
[AddComponentMenu("")]
[RequireComponent(typeof(NetworkIdentity))]
[HelpURL("https://mirror-networking.com/docs/Guides/NetworkBehaviour.html")]
public class NetworkBehaviour : MonoBehaviour
{
internal float lastSyncTime;
// hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
/// <summary>
/// sync mode for OnSerialize
/// </summary>
[HideInInspector] public SyncMode syncMode = SyncMode.Observers;
// hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
/// <summary>
/// sync interval for OnSerialize (in seconds)
/// </summary>
[Tooltip("Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)")]
// [0,2] should be enough. anything >2s is too laggy anyway.
[Range(0, 2)]
[HideInInspector] public float syncInterval = 0.1f;
/// <summary>
/// Returns true if this object is active on an active server.
/// <para>This is only true if the object has been spawned. This is different from NetworkServer.active, which is true if the server itself is active rather than this object being active.</para>
/// </summary>
public bool isServer => netIdentity.isServer;
/// <summary>
/// Returns true if running as a client and this object was spawned by a server.
/// </summary>
public bool isClient => netIdentity.isClient;
/// <summary>
/// Returns true if we're on host mode.
/// </summary>
public bool isLocalClient => netIdentity.isLocalClient;
/// <summary>
/// This returns true if this object is the one that represents the player on the local machine.
/// <para>In multiplayer games, there are multiple instances of the Player object. The client needs to know which one is for "themselves" so that only that player processes input and potentially has a camera attached. The IsLocalPlayer function will return true only for the player instance that belongs to the player on the local machine, so it can be used to filter out input for non-local players.</para>
/// </summary>
public bool isLocalPlayer => netIdentity.isLocalPlayer;
/// <summary>
/// True if this object only exists on the server
/// </summary>
public bool isServerOnly => isServer && !isClient;
/// <summary>
/// True if this object exists on a client that is not also acting as a server
/// </summary>
public bool isClientOnly => isClient && !isServer;
/// <summary>
/// This returns true if this object is the authoritative version of the object in the distributed network application.
/// <para>The <see cref="NetworkIdentity.hasAuthority">NetworkIdentity.hasAuthority</see> value on the NetworkIdentity determines how authority is determined. For most objects, authority is held by the server. For objects with <see cref="NetworkIdentity.hasAuthority">NetworkIdentity.hasAuthority</see> set, authority is held by the client of that player.</para>
/// </summary>
public bool hasAuthority => netIdentity.hasAuthority;
/// <summary>
/// The unique network Id of this object.
/// <para>This is assigned at runtime by the network server and will be unique for all objects for that network session.</para>
/// </summary>
public uint netId => netIdentity.netId;
/// <summary>
/// The <see cref="NetworkServer">NetworkClient</see> associated to this object.
/// </summary>
public NetworkServer server => netIdentity.server;
/// <summary>
/// The <see cref="NetworkClient">NetworkClient</see> associated to this object.
/// </summary>
public NetworkClient client => netIdentity.client;
/// <summary>
/// The <see cref="NetworkConnection">NetworkConnection</see> associated with this <see cref="NetworkIdentity">NetworkIdentity.</see> This is only valid for player objects on the server.
/// </summary>
public NetworkConnection connectionToServer => netIdentity.connectionToServer;
/// <summary>
/// The <see cref="NetworkConnection">NetworkConnection</see> associated with this <see cref="NetworkIdentity">NetworkIdentity.</see> This is only valid for player objects on the server.
/// </summary>
public NetworkConnection connectionToClient => netIdentity.connectionToClient;
protected ulong syncVarDirtyBits { get; private set; }
ulong syncVarHookGuard;
protected bool getSyncVarHookGuard(ulong dirtyBit)
{
return (syncVarHookGuard & dirtyBit) != 0UL;
}
protected void setSyncVarHookGuard(ulong dirtyBit, bool value)
{
if (value)
syncVarHookGuard |= dirtyBit;
else
syncVarHookGuard &= ~dirtyBit;
}
/// <summary>
/// objects that can synchronize themselves, such as synclists
/// </summary>
protected readonly List<ISyncObject> syncObjects = new List<ISyncObject>();
/// <summary>
/// NetworkIdentity component caching for easier access
/// </summary>
NetworkIdentity netIdentityCache;
/// <summary>
/// Returns the NetworkIdentity of this object
/// </summary>
public NetworkIdentity netIdentity
{
get
{
// in this specific case, we want to know if we have set it before
// so we can compare if the reference is null
// instead of calling unity's MonoBehaviour == operator
if (((object)netIdentityCache) == null)
{
netIdentityCache = GetComponent<NetworkIdentity>();
}
if (((object)netIdentityCache) == null)
{
Debug.LogError("There is no NetworkIdentity on " + name + ". Please add one.");
}
return netIdentityCache;
}
}
/// <summary>
/// Returns the index of the component on this object
/// </summary>
public int ComponentIndex
{
get
{
// note: FindIndex causes allocations, we search manually instead
for (int i = 0; i < netIdentity.NetworkBehaviours.Length; i++)
{
NetworkBehaviour component = netIdentity.NetworkBehaviours[i];
if (component == this)
return i;
}
// this should never happen
Debug.LogError("Could not find component in GameObject. You should not add/remove components in networked objects dynamically", this);
return -1;
}
}
// this gets called in the constructor by the weaver
// for every SyncObject in the component (e.g. SyncLists).
// We collect all of them and we synchronize them with OnSerialize/OnDeserialize
protected void InitSyncObject(ISyncObject syncObject)
{
syncObjects.Add(syncObject);
}
#region Commands
internal static int GetMethodHash(Type invokeClass, string methodName)
{
// (invokeClass + ":" + cmdName).GetStableHashCode() would cause allocations.
// so hash1 + hash2 is better.
unchecked
{
int hash = invokeClass.FullName.GetStableHashCode();
return hash * 503 + methodName.GetStableHashCode();
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWriter writer, int channelId)
{
// this was in Weaver before
// NOTE: we could remove this later to allow calling Cmds on Server
// to avoid Wrapper functions. a lot of people requested this.
if (!client.active)
{
Debug.LogError("Command Function " + cmdName + " called on server without an active client.");
return;
}
// local players can always send commands, regardless of authority, other objects must have authority.
if (!(isLocalPlayer || hasAuthority))
{
throw new UnauthorizedAccessException($"Trying to send command for object without authority. {invokeClass.ToString()}.{cmdName}");
}
if (client.connection == null)
{
throw new InvalidOperationException("Send command attempted with no client running [client=" + connectionToServer + "].");
}
// construct the message
var message = new CommandMessage
{
netId = netId,
componentIndex = ComponentIndex,
// type+func so Inventory.RpcUse != Equipment.RpcUse
functionHash = GetMethodHash(invokeClass, cmdName),
// segment to avoid reader allocations
payload = writer.ToArraySegment()
};
client.connection.Send(message, channelId);
}
/// <summary>
/// Manually invoke a Command.
/// </summary>
/// <param name="cmdHash">Hash of the Command name.</param>
/// <param name="reader">Parameters to pass to the command.</param>
/// <returns>Returns true if successful.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual bool InvokeCommand(int cmdHash, NetworkReader reader)
{
return InvokeHandlerDelegate(cmdHash, MirrorInvokeType.Command, reader);
}
#endregion
#region Client RPCs
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SendRPCInternal(Type invokeClass, string rpcName, NetworkWriter writer, int channelId)
{
// this was in Weaver before
if (!server.active)
{
Debug.LogError("RPC Function " + rpcName + " called on Client.");
return;
}
// This cannot use NetworkServer.active, as that is not specific to this object.
if (!isServer)
{
Debug.LogWarning("ClientRpc " + rpcName + " called on un-spawned object: " + name);
return;
}
// construct the message
var message = new RpcMessage
{
netId = netId,
componentIndex = ComponentIndex,
// type+func so Inventory.RpcUse != Equipment.RpcUse
functionHash = GetMethodHash(invokeClass, rpcName),
// segment to avoid reader allocations
payload = writer.ToArraySegment()
};
server.SendToReady(netIdentity, message, channelId);
}
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SendTargetRPCInternal(NetworkConnection conn, Type invokeClass, string rpcName, NetworkWriter writer, int channelId)
{
// this was in Weaver before
if (!server.active)
{
Debug.LogError("TargetRPC Function " + rpcName + " called on client.");
return;
}
// connection parameter is optional. assign if null.
if (conn == null)
{
conn = connectionToClient;
}
// this was in Weaver before
if (conn is NetworkConnectionToServer)
{
Debug.LogError("TargetRPC Function " + rpcName + " called on connection to server");
return;
}
// This cannot use NetworkServer.active, as that is not specific to this object.
if (!isServer)
{
Debug.LogWarning("TargetRpc " + rpcName + " called on un-spawned object: " + name);
return;
}
// construct the message
var message = new RpcMessage
{
netId = netId,
componentIndex = ComponentIndex,
// type+func so Inventory.RpcUse != Equipment.RpcUse
functionHash = GetMethodHash(invokeClass, rpcName),
// segment to avoid reader allocations
payload = writer.ToArraySegment()
};
conn.Send(message, channelId);
}
/// <summary>
/// Manually invoke an RPC function.
/// </summary>
/// <param name="rpcHash">Hash of the RPC name.</param>
/// <param name="reader">Parameters to pass to the RPC function.</param>
/// <returns>Returns true if successful.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual bool InvokeRPC(int rpcHash, NetworkReader reader)
{
return InvokeHandlerDelegate(rpcHash, MirrorInvokeType.ClientRpc, reader);
}
#endregion
#region Sync Events
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SendEventInternal(Type invokeClass, string eventName, NetworkWriter writer, int channelId)
{
if (!server.active)
{
Debug.LogWarning("SendEvent no server?");
return;
}
// construct the message
var message = new SyncEventMessage
{
netId = netId,
componentIndex = ComponentIndex,
// type+func so Inventory.RpcUse != Equipment.RpcUse
functionHash = GetMethodHash(invokeClass, eventName),
// segment to avoid reader allocations
payload = writer.ToArraySegment()
};
server.SendToReady(netIdentity, message, channelId);
}
/// <summary>
/// Manually invoke a SyncEvent.
/// </summary>
/// <param name="eventHash">Hash of the SyncEvent name.</param>
/// <param name="reader">Parameters to pass to the SyncEvent.</param>
/// <returns>Returns true if successful.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual bool InvokeSyncEvent(int eventHash, NetworkReader reader)
{
return InvokeHandlerDelegate(eventHash, MirrorInvokeType.SyncEvent, reader);
}
#endregion
#region Code Gen Path Helpers
/// <summary>
/// Delegate for Command functions.
/// </summary>
/// <param name="obj"></param>
/// <param name="reader"></param>
public delegate void CmdDelegate(NetworkBehaviour obj, NetworkReader reader);
protected class Invoker
{
public MirrorInvokeType invokeType;
public Type invokeClass;
public CmdDelegate invokeFunction;
}
static readonly Dictionary<int, Invoker> cmdHandlerDelegates = new Dictionary<int, Invoker>();
// helper function register a Command/Rpc/SyncEvent delegate
[EditorBrowsable(EditorBrowsableState.Never)]
protected static void RegisterDelegate(Type invokeClass, string cmdName, MirrorInvokeType invokerType, CmdDelegate func)
{
// type+func so Inventory.RpcUse != Equipment.RpcUse
int cmdHash = GetMethodHash(invokeClass, cmdName);
if (cmdHandlerDelegates.ContainsKey(cmdHash))
{
// something already registered this hash
Invoker oldInvoker = cmdHandlerDelegates[cmdHash];
if (oldInvoker.invokeClass == invokeClass &&
oldInvoker.invokeType == invokerType &&
oldInvoker.invokeFunction == func)
{
// it's all right, it was the same function
return;
}
Debug.LogError($"Function {oldInvoker.invokeClass}.{oldInvoker.invokeFunction.GetMethodName()} and {invokeClass}.{func.GetMethodName()} have the same hash. Please rename one of them");
}
var invoker = new Invoker
{
invokeType = invokerType,
invokeClass = invokeClass,
invokeFunction = func
};
cmdHandlerDelegates[cmdHash] = invoker;
if (LogFilter.Debug) Debug.Log("RegisterDelegate hash:" + cmdHash + " invokerType: " + invokerType + " method:" + func.GetMethodName());
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void RegisterCommandDelegate(Type invokeClass, string cmdName, CmdDelegate func)
{
RegisterDelegate(invokeClass, cmdName, MirrorInvokeType.Command, func);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void RegisterRpcDelegate(Type invokeClass, string rpcName, CmdDelegate func)
{
RegisterDelegate(invokeClass, rpcName, MirrorInvokeType.ClientRpc, func);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void RegisterEventDelegate(Type invokeClass, string eventName, CmdDelegate func)
{
RegisterDelegate(invokeClass, eventName, MirrorInvokeType.SyncEvent, func);
}
// we need a way to clean up delegates after tests
[EditorBrowsable(EditorBrowsableState.Never)]
internal static void ClearDelegates()
{
cmdHandlerDelegates.Clear();
}
static bool GetInvokerForHash(int cmdHash, MirrorInvokeType invokeType, out Invoker invoker)
{
if (cmdHandlerDelegates.TryGetValue(cmdHash, out invoker) &&
invoker != null &&
invoker.invokeType == invokeType)
{
return true;
}
// debug message if not found, or null, or mismatched type
// (no need to throw an error, an attacker might just be trying to
// call an cmd with an rpc's hash)
if (LogFilter.Debug) Debug.Log("GetInvokerForHash hash:" + cmdHash + " not found");
return false;
}
// InvokeCmd/Rpc/SyncEventDelegate can all use the same function here
internal bool InvokeHandlerDelegate(int cmdHash, MirrorInvokeType invokeType, NetworkReader reader)
{
if (GetInvokerForHash(cmdHash, invokeType, out Invoker invoker) &&
invoker.invokeClass.IsInstanceOfType(this))
{
invoker.invokeFunction(this, reader);
return true;
}
return false;
}
[Obsolete("Use NetworkBehaviour.GetDelegate instead.")]
public static CmdDelegate GetRpcHandler(int cmdHash) => GetDelegate(cmdHash);
/// <summary>
/// Gets the handler function for a given hash
/// Can be used by profilers and debuggers
/// </summary>
/// <param name="cmdHash">rpc function hash</param>
/// <returns>The function delegate that will handle the command</returns>
public static CmdDelegate GetDelegate(int cmdHash)
{
if (cmdHandlerDelegates.TryGetValue(cmdHash, out Invoker invoker))
{
return invoker.invokeFunction;
}
return null;
}
#endregion
#region Helpers
// helper function for [SyncVar] GameObjects.
// IMPORTANT: keep as 'protected', not 'internal', otherwise Weaver
// can't resolve it
[EditorBrowsable(EditorBrowsableState.Never)]
protected bool SyncVarGameObjectEqual(GameObject newGameObject, uint netIdField)
{
uint newNetId = 0;
if (newGameObject != null)
{
NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
if (identity != null)
{
newNetId = identity.netId;
if (newNetId == 0)
{
Debug.LogWarning("SetSyncVarGameObject GameObject " + newGameObject + " has a zero netId. Maybe it is not spawned yet?");
}
}
}
return newNetId == netIdField;
}
// helper function for [SyncVar] GameObjects.
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gameObjectField, ulong dirtyBit, ref uint netIdField)
{
if (getSyncVarHookGuard(dirtyBit))
return;
uint newNetId = 0;
if (newGameObject != null)
{
NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
if (identity != null)
{
newNetId = identity.netId;
if (newNetId == 0)
{
Debug.LogWarning("SetSyncVarGameObject GameObject " + newGameObject + " has a zero netId. Maybe it is not spawned yet?");
}
}
}
if (LogFilter.Debug) Debug.Log("SetSyncVar GameObject " + GetType().Name + " bit [" + dirtyBit + "] netfieldId:" + netIdField + "->" + newNetId);
SetDirtyBit(dirtyBit);
// assign new one on the server, and in case we ever need it on client too
gameObjectField = newGameObject;
netIdField = newNetId;
}
// helper function for [SyncVar] GameObjects.
// -> ref GameObject as second argument makes OnDeserialize processing easier
[EditorBrowsable(EditorBrowsableState.Never)]
protected GameObject GetSyncVarGameObject(uint netId, ref GameObject gameObjectField)
{
if (!isServer && !isClient)
return gameObjectField;
// server always uses the field
if (isServer)
{
return gameObjectField;
}
// client always looks up based on netId because objects might get in and out of range
// over and over again, which shouldn't null them forever
if (client.Spawned.TryGetValue(netId, out NetworkIdentity identity) && identity != null)
return gameObjectField = identity.gameObject;
return null;
}
// helper function for [SyncVar] NetworkIdentities.
// IMPORTANT: keep as 'protected', not 'internal', otherwise Weaver
// can't resolve it
[EditorBrowsable(EditorBrowsableState.Never)]
protected bool SyncVarNetworkIdentityEqual(NetworkIdentity newIdentity, uint netIdField)
{
uint newNetId = 0;
if (newIdentity != null)
{
newNetId = newIdentity.netId;
if (newNetId == 0)
{
Debug.LogWarning("SetSyncVarNetworkIdentity NetworkIdentity " + newIdentity + " has a zero netId. Maybe it is not spawned yet?");
}
}
// netId changed?
return newNetId == netIdField;
}
// helper function for [SyncVar] NetworkIdentities.
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SetSyncVarNetworkIdentity(NetworkIdentity newIdentity, ref NetworkIdentity identityField, ulong dirtyBit, ref uint netIdField)
{
if (getSyncVarHookGuard(dirtyBit))
return;
uint newNetId = 0;
if (newIdentity != null)
{
newNetId = newIdentity.netId;
if (newNetId == 0)
{
Debug.LogWarning("SetSyncVarNetworkIdentity NetworkIdentity " + newIdentity + " has a zero netId. Maybe it is not spawned yet?");
}
}
if (LogFilter.Debug) Debug.Log("SetSyncVarNetworkIdentity NetworkIdentity " + GetType().Name + " bit [" + dirtyBit + "] netIdField:" + netIdField + "->" + newNetId);
SetDirtyBit(dirtyBit);
netIdField = newNetId;
// assign new one on the server, and in case we ever need it on client too
identityField = newIdentity;
}
// helper function for [SyncVar] NetworkIdentities.
// -> ref GameObject as second argument makes OnDeserialize processing easier
[EditorBrowsable(EditorBrowsableState.Never)]
protected NetworkIdentity GetSyncVarNetworkIdentity(uint netId, ref NetworkIdentity identityField)
{
// server always uses the field
if (isServer)
{
return identityField;
}
// client always looks up based on netId because objects might get in and out of range
// over and over again, which shouldn't null them forever
client.Spawned.TryGetValue(netId, out identityField);
return identityField;
}
[EditorBrowsable(EditorBrowsableState.Never)]
protected bool SyncVarEqual<T>(T value, ref T fieldValue)
{
// newly initialized or changed value?
return EqualityComparer<T>.Default.Equals(value, fieldValue);
}
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SetSyncVar<T>(T value, ref T fieldValue, ulong dirtyBit)
{
if (LogFilter.Debug) Debug.Log("SetSyncVar " + GetType().Name + " bit [" + dirtyBit + "] " + fieldValue + "->" + value);
SetDirtyBit(dirtyBit);
fieldValue = value;
}
#endregion
/// <summary>
/// Used to set the behaviour as dirty, so that a network update will be sent for the object.
/// these are masks, not bit numbers, ie. 0x004 not 2
/// </summary>
/// <param name="dirtyBit">Bit mask to set.</param>
public void SetDirtyBit(ulong dirtyBit)
{
syncVarDirtyBits |= dirtyBit;
}
/// <summary>
/// This clears all the dirty bits that were set on this script by SetDirtyBits();
/// <para>This is automatically invoked when an update is sent for this object, but can be called manually as well.</para>
/// </summary>
public void ClearAllDirtyBits()
{
lastSyncTime = Time.time;
syncVarDirtyBits = 0L;
// flush all unsynchronized changes in syncobjects
// note: don't use List.ForEach here, this is a hot path
// List.ForEach: 432b/frame
// for: 231b/frame
for (int i = 0; i < syncObjects.Count; ++i)
{
syncObjects[i].Flush();
}
}
bool AnySyncObjectDirty()
{
// note: don't use Linq here. 1200 networked objects:
// Linq: 187KB GC/frame;, 2.66ms time
// for: 8KB GC/frame; 1.28ms time
for (int i = 0; i < syncObjects.Count; ++i)
{
if (syncObjects[i].IsDirty)
{
return true;
}
}
return false;
}
internal bool IsDirty()
{
if (Time.time - lastSyncTime >= syncInterval)
{
return syncVarDirtyBits != 0L || AnySyncObjectDirty();
}
return false;
}
/// <summary>
/// Virtual function to override to send custom serialization data. The corresponding function to send serialization data is OnDeserialize().
/// </summary>
/// <remarks>
/// <para>The initialState flag is useful to differentiate between the first time an object is serialized and when incremental updates can be sent. The first time an object is sent to a client, it must include a full state snapshot, but subsequent updates can save on bandwidth by including only incremental changes. Note that SyncVar hook functions are not called when initialState is true, only for incremental updates.</para>
/// <para>If a class has SyncVars, then an implementation of this function and OnDeserialize() are added automatically to the class. So a class that has SyncVars cannot also have custom serialization functions.</para>
/// <para>The OnSerialize function should return true to indicate that an update should be sent. If it returns true, then the dirty bits for that script are set to zero, if it returns false then the dirty bits are not changed. This allows multiple changes to a script to be accumulated over time and sent when the system is ready, instead of every frame.</para>
/// </remarks>
/// <param name="writer">Writer to use to write to the stream.</param>
/// <param name="initialState">If this is being called to send initial state.</param>
/// <returns>True if data was written.</returns>
public virtual bool OnSerialize(NetworkWriter writer, bool initialState)
{
if (initialState)
{
return SerializeObjectsAll(writer);
}
return SerializeObjectsDelta(writer);
}
/// <summary>
/// Virtual function to override to receive custom serialization data. The corresponding function to send serialization data is OnSerialize().
/// </summary>
/// <param name="reader">Reader to read from the stream.</param>
/// <param name="initialState">True if being sent initial state.</param>
public virtual void OnDeserialize(NetworkReader reader, bool initialState)
{
if (initialState)
{
DeSerializeObjectsAll(reader);
}
else
{
DeSerializeObjectsDelta(reader);
}
}
internal ulong DirtyObjectBits()
{
ulong dirtyObjects = 0;
for (int i = 0; i < syncObjects.Count; i++)
{
ISyncObject syncObject = syncObjects[i];
if (syncObject.IsDirty)
{
dirtyObjects |= 1UL << i;
}
}
return dirtyObjects;
}
public bool SerializeObjectsAll(NetworkWriter writer)
{
bool dirty = false;
for (int i = 0; i < syncObjects.Count; i++)
{
ISyncObject syncObject = syncObjects[i];
syncObject.OnSerializeAll(writer);
dirty = true;
}
return dirty;
}
public bool SerializeObjectsDelta(NetworkWriter writer)
{
bool dirty = false;
// write the mask
writer.WritePackedUInt64(DirtyObjectBits());
// serializable objects, such as synclists
for (int i = 0; i < syncObjects.Count; i++)
{
ISyncObject syncObject = syncObjects[i];
if (syncObject.IsDirty)
{
syncObject.OnSerializeDelta(writer);
dirty = true;
}
}
return dirty;
}
internal void DeSerializeObjectsAll(NetworkReader reader)
{
for (int i = 0; i < syncObjects.Count; i++)
{
ISyncObject syncObject = syncObjects[i];
syncObject.OnDeserializeAll(reader);
}
}
internal void DeSerializeObjectsDelta(NetworkReader reader)
{
ulong dirty = reader.ReadPackedUInt64();
for (int i = 0; i < syncObjects.Count; i++)
{
ISyncObject syncObject = syncObjects[i];
if ((dirty & (1UL << i)) != 0)
{
syncObject.OnDeserializeDelta(reader);
}
}
}
/// <summary>
/// Callback used by the visibility system to (re)construct the set of observers that can see this object.
/// <para>Implementations of this callback should add network connections of players that can see this object to the observers set.</para>
/// </summary>
/// <param name="observers">The new set of observers for this object.</param>
/// <param name="initialize">True if the set of observers is being built for the first time.</param>
/// <returns>true when overwriting so that Mirror knows that we wanted to rebuild observers ourselves. otherwise it uses built in rebuild.</returns>
public virtual bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initialize)
{
return false;
}
/// <summary>
/// Callback used by the visibility system for objects on a host.
/// <para>Objects on a host (with a local client) cannot be disabled or destroyed when they are not visibile to the local client. So this function is called to allow custom code to hide these objects. A typical implementation will disable renderer components on the object. This is only called on local clients on a host.</para>
/// </summary>
/// <param name="visible">New visibility state.</param>
public virtual void OnSetHostVisibility(bool visible) { }
/// <summary>
/// Callback used by the visibility system to determine if an observer (player) can see this object.
/// <para>If this function returns true, the network connection will be added as an observer.</para>
/// </summary>
/// <param name="conn">Network connection of a player.</param>
/// <returns>True if the player can see this object.</returns>
public virtual bool OnCheckObserver(NetworkConnection conn)
{
return true;
}
}
}