Mirror Networking
NetworkBehaviour.cs
1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.Runtime.CompilerServices;
5using UnityEngine;
6
7namespace Mirror
8{
9 public enum SyncMode { Observers, Owner }
10
12 [AddComponentMenu("")]
13 [RequireComponent(typeof(NetworkIdentity))]
14 [HelpURL("https://mirror-networking.gitbook.io/docs/guides/networkbehaviour")]
15 public abstract class NetworkBehaviour : MonoBehaviour
16 {
18 // hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
19 [Tooltip("By default synced data is sent from the server to all Observers of the object.\nChange this to Owner to only have the server update the client that has ownership authority for this object")]
20 [HideInInspector] public SyncMode syncMode = SyncMode.Observers;
21
23 // hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
24 // [0,2] should be enough. anything >2s is too laggy anyway.
25 [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.)")]
26 [Range(0, 2)]
27 [HideInInspector] public float syncInterval = 0.1f;
28 internal double lastSyncTime;
29
31 // This is different from NetworkServer.active, which is true if the
32 // server itself is active rather than this object being active.
34
37
40
43
46
49
51 public uint netId => netIdentity.netId;
52
54 // TODO change to NetworkConnectionToServer, but might cause some breaking
56
59
60 // SyncLists, SyncSets, etc.
61 protected readonly List<SyncObject> syncObjects = new List<SyncObject>();
62
63 // NetworkBehaviourInspector needs to know if we have SyncObjects
64 internal bool HasSyncObjects() => syncObjects.Count > 0;
65
66 // NetworkIdentity based values set from NetworkIdentity.Awake(),
67 // which is way more simple and way faster than trying to figure out
68 // component index from in here by searching all NetworkComponents.
69
71 public NetworkIdentity netIdentity { get; internal set; }
72
74 public int ComponentIndex { get; internal set; }
75
76 // to avoid fully serializing entities every time, we have two options:
77 // * run a delta compression algorithm
78 // -> for fixed size types this is as easy as varint(b-a) for all
79 // -> for dynamically sized types like strings this is not easy.
80 // algorithms need to detect inserts/deletions, i.e. Myers Diff.
81 // those are very cpu intensive and barely fast enough for large
82 // scale multiplayer games (in Unity)
83 // * or we use dirty bits as meta data about which fields have changed
84 // -> spares us from running delta algorithms
85 // -> still supports dynamically sized types
86 //
87 // 64 bit mask, tracking up to 64 SyncVars.
88 protected ulong syncVarDirtyBits { get; private set; }
89 // 64 bit mask, tracking up to 64 sync collections (internal for tests).
90 // internal for tests, field for faster access (instead of property)
91 // TODO 64 SyncLists are too much. consider smaller mask later.
92 internal ulong syncObjectDirtyBits;
93
94 // Weaver replaces '[SyncVar] int health' with 'Networkhealth' property.
95 // setter calls the hook if value changed.
96 // if we then modify the [SyncVar] from inside the setter,
97 // the setter would call the hook and we deadlock.
98 // hook guard prevents that.
99 ulong syncVarHookGuard;
100
101 // USED BY WEAVER to set syncvars in host mode without deadlocking
102 protected bool GetSyncVarHookGuard(ulong dirtyBit) =>
103 (syncVarHookGuard & dirtyBit) != 0UL;
104
105 // USED BY WEAVER to set syncvars in host mode without deadlocking
106 protected void SetSyncVarHookGuard(ulong dirtyBit, bool value)
107 {
108 // set the bit
109 if (value)
110 syncVarHookGuard |= dirtyBit;
111 // clear the bit
112 else
113 syncVarHookGuard &= ~dirtyBit;
114 }
115
117 // these are masks, not bit numbers, ie. 110011b not '2' for 2nd bit.
118 public void SetSyncVarDirtyBit(ulong dirtyBit)
119 {
120 syncVarDirtyBits |= dirtyBit;
121 }
122
123 // true if syncInterval elapsed and any SyncVar or SyncObject is dirty
124 public bool IsDirty()
125 {
126 if (NetworkTime.localTime - lastSyncTime >= syncInterval)
127 {
128 // OR both bitmasks. != 0 if either was dirty.
129 return (syncVarDirtyBits | syncObjectDirtyBits) != 0UL;
130 }
131 return false;
132 }
133
135 // automatically invoked when an update is sent for this object, but can
136 // be called manually as well.
137 public void ClearAllDirtyBits()
138 {
139 lastSyncTime = NetworkTime.localTime;
140 syncVarDirtyBits = 0L;
141 syncObjectDirtyBits = 0L;
142
143 // clear all unsynchronized changes in syncobjects
144 // (Linq allocates, use for instead)
145 for (int i = 0; i < syncObjects.Count; ++i)
146 {
147 syncObjects[i].ClearChanges();
148 }
149 }
150
151 // this gets called in the constructor by the weaver
152 // for every SyncObject in the component (e.g. SyncLists).
153 // We collect all of them and we synchronize them with OnSerialize/OnDeserialize
154 protected void InitSyncObject(SyncObject syncObject)
155 {
156 if (syncObject == null)
157 {
158 Debug.LogError("Uninitialized SyncObject. Manually call the constructor on your SyncList, SyncSet, SyncDictionary or SyncField<T>");
159 return;
160 }
161
162 // add it, remember the index in list (if Count=0, index=0 etc.)
163 int index = syncObjects.Count;
164 syncObjects.Add(syncObject);
165
166 // OnDirty needs to set nth bit in our dirty mask
167 ulong nthBit = 1UL << index;
168 syncObject.OnDirty = () => syncObjectDirtyBits |= nthBit;
169
170 // only record changes while we have observers.
171 // prevents ever growing .changes lists:
172 // if a monster has no observers but we keep modifing a SyncObject,
173 // then the changes would never be flushed and keep growing,
174 // because OnSerialize isn't called without observers.
175 syncObject.IsRecording = () => netIdentity.observers?.Count > 0;
176 }
177
178 // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
179 protected void SendCommandInternal(string functionFullName, NetworkWriter writer, int channelId, bool requiresAuthority = true)
180 {
181 // this was in Weaver before
182 // NOTE: we could remove this later to allow calling Cmds on Server
183 // to avoid Wrapper functions. a lot of people requested this.
184 if (!NetworkClient.active)
185 {
186 Debug.LogError($"Command Function {functionFullName} called on {name} without an active client.", gameObject);
187 return;
188 }
189
190 // previously we used NetworkClient.readyConnection.
191 // now we check .ready separately.
192 if (!NetworkClient.ready)
193 {
194 // Unreliable Cmds from NetworkTransform may be generated,
195 // or client may have been set NotReady intentionally, so
196 // only warn if on the reliable channel.
197 if (channelId == Channels.Reliable)
198 Debug.LogWarning($"Command Function {functionFullName} called on {name} while NetworkClient is not ready.\nThis may be ignored if client intentionally set NotReady.", gameObject);
199 return;
200 }
201
202 // local players can always send commands, regardless of authority, other objects must have authority.
203 if (!(!requiresAuthority || isLocalPlayer || hasAuthority))
204 {
205 Debug.LogWarning($"Command Function {functionFullName} called on {name} without authority.", gameObject);
206 return;
207 }
208
209 // IMPORTANT: can't use .connectionToServer here because calling
210 // a command on other objects is allowed if requireAuthority is
211 // false. other objects don't have a .connectionToServer.
212 // => so we always need to use NetworkClient.connection instead.
213 // => see also: https://github.com/vis2k/Mirror/issues/2629
214 if (NetworkClient.connection == null)
215 {
216 Debug.LogError($"Command Function {functionFullName} called on {name} with no client running.", gameObject);
217 return;
218 }
219
220 // construct the message
221 CommandMessage message = new CommandMessage
222 {
223 netId = netId,
224 componentIndex = (byte)ComponentIndex,
225 // type+func so Inventory.RpcUse != Equipment.RpcUse
226 functionHash = (ushort)functionFullName.GetStableHashCode(),
227 // segment to avoid reader allocations
228 payload = writer.ToArraySegment()
229 };
230
231 // IMPORTANT: can't use .connectionToServer here because calling
232 // a command on other objects is allowed if requireAuthority is
233 // false. other objects don't have a .connectionToServer.
234 // => so we always need to use NetworkClient.connection instead.
235 // => see also: https://github.com/vis2k/Mirror/issues/2629
236 NetworkClient.connection.Send(message, channelId);
237 }
238
239 // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
240 protected void SendRPCInternal(string functionFullName, NetworkWriter writer, int channelId, bool includeOwner)
241 {
242 // this was in Weaver before
243 if (!NetworkServer.active)
244 {
245 Debug.LogError($"RPC Function {functionFullName} called on Client.", gameObject);
246 return;
247 }
248
249 // This cannot use NetworkServer.active, as that is not specific to this object.
250 if (!isServer)
251 {
252 Debug.LogWarning($"ClientRpc {functionFullName} called on un-spawned object: {name}", gameObject);
253 return;
254 }
255
256 // construct the message
257 RpcMessage message = new RpcMessage
258 {
259 netId = netId,
260 componentIndex = (byte)ComponentIndex,
261 // type+func so Inventory.RpcUse != Equipment.RpcUse
262 functionHash = (ushort)functionFullName.GetStableHashCode(),
263 // segment to avoid reader allocations
264 payload = writer.ToArraySegment()
265 };
266
267 NetworkServer.SendToReadyObservers(netIdentity, message, includeOwner, channelId);
268 }
269
270 // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
271 protected void SendTargetRPCInternal(NetworkConnection conn, string functionFullName, NetworkWriter writer, int channelId)
272 {
273 if (!NetworkServer.active)
274 {
275 Debug.LogError($"TargetRPC {functionFullName} called on {name} when server not active", gameObject);
276 return;
277 }
278
279 if (!isServer)
280 {
281 Debug.LogWarning($"TargetRpc {functionFullName} called on {name} but that object has not been spawned or has been unspawned", gameObject);
282 return;
283 }
284
285 // connection parameter is optional. assign if null.
286 if (conn is null)
287 {
288 conn = connectionToClient;
289 }
290
291 // if still null
292 if (conn is null)
293 {
294 Debug.LogError($"TargetRPC {functionFullName} was given a null connection, make sure the object {name} has an owner or you pass in the target connection", gameObject);
295 return;
296 }
297
298 if (!(conn is NetworkConnectionToClient))
299 {
300 Debug.LogError($"TargetRPC {functionFullName} called on {name} requires a NetworkConnectionToClient but was given {conn.GetType().Name}", gameObject);
301 return;
302 }
303
304 // construct the message
305 RpcMessage message = new RpcMessage
306 {
307 netId = netId,
308 componentIndex = (byte)ComponentIndex,
309 // type+func so Inventory.RpcUse != Equipment.RpcUse
310 functionHash = (ushort)functionFullName.GetStableHashCode(),
311 // segment to avoid reader allocations
312 payload = writer.ToArraySegment()
313 };
314
315 conn.Send(message, channelId);
316 }
317
318 // move the [SyncVar] generated property's .set into C# to avoid much IL
319 //
320 // public int health = 42;
321 //
322 // public int Networkhealth
323 // {
324 // get
325 // {
326 // return health;
327 // }
328 // [param: In]
329 // set
330 // {
331 // if (!NetworkBehaviour.SyncVarEqual(value, ref health))
332 // {
333 // int oldValue = health;
334 // SetSyncVar(value, ref health, 1uL);
335 // if (NetworkServer.localClientActive && !GetSyncVarHookGuard(1uL))
336 // {
337 // SetSyncVarHookGuard(1uL, value: true);
338 // OnChanged(oldValue, value);
339 // SetSyncVarHookGuard(1uL, value: false);
340 // }
341 // }
342 // }
343 // }
344 [MethodImpl(MethodImplOptions.AggressiveInlining)]
345 public void GeneratedSyncVarSetter<T>(T value, ref T field, ulong dirtyBit, Action<T, T> OnChanged)
346 {
347 if (!SyncVarEqual(value, ref field))
348 {
349 T oldValue = field;
350 SetSyncVar(value, ref field, dirtyBit);
351
352 // call hook (if any)
353 if (OnChanged != null)
354 {
355 // in host mode, setting a SyncVar calls the hook directly.
356 // in client-only mode, OnDeserialize would call it.
357 // we use hook guard to protect against deadlock where hook
358 // changes syncvar, calling hook again.
359 if (NetworkServer.localClientActive && !GetSyncVarHookGuard(dirtyBit))
360 {
361 SetSyncVarHookGuard(dirtyBit, true);
362 OnChanged(oldValue, value);
363 SetSyncVarHookGuard(dirtyBit, false);
364 }
365 }
366 }
367 }
368
369 // GameObject needs custom handling for persistence via netId.
370 // has one extra parameter.
371 [MethodImpl(MethodImplOptions.AggressiveInlining)]
372 public void GeneratedSyncVarSetter_GameObject(GameObject value, ref GameObject field, ulong dirtyBit, Action<GameObject, GameObject> OnChanged, ref uint netIdField)
373 {
374 if (!SyncVarGameObjectEqual(value, netIdField))
375 {
376 GameObject oldValue = field;
377 SetSyncVarGameObject(value, ref field, dirtyBit, ref netIdField);
378
379 // call hook (if any)
380 if (OnChanged != null)
381 {
382 // in host mode, setting a SyncVar calls the hook directly.
383 // in client-only mode, OnDeserialize would call it.
384 // we use hook guard to protect against deadlock where hook
385 // changes syncvar, calling hook again.
386 if (NetworkServer.localClientActive && !GetSyncVarHookGuard(dirtyBit))
387 {
388 SetSyncVarHookGuard(dirtyBit, true);
389 OnChanged(oldValue, value);
390 SetSyncVarHookGuard(dirtyBit, false);
391 }
392 }
393 }
394 }
395
396 // NetworkIdentity needs custom handling for persistence via netId.
397 // has one extra parameter.
398 [MethodImpl(MethodImplOptions.AggressiveInlining)]
399 public void GeneratedSyncVarSetter_NetworkIdentity(NetworkIdentity value, ref NetworkIdentity field, ulong dirtyBit, Action<NetworkIdentity, NetworkIdentity> OnChanged, ref uint netIdField)
400 {
401 if (!SyncVarNetworkIdentityEqual(value, netIdField))
402 {
403 NetworkIdentity oldValue = field;
404 SetSyncVarNetworkIdentity(value, ref field, dirtyBit, ref netIdField);
405
406 // call hook (if any)
407 if (OnChanged != null)
408 {
409 // in host mode, setting a SyncVar calls the hook directly.
410 // in client-only mode, OnDeserialize would call it.
411 // we use hook guard to protect against deadlock where hook
412 // changes syncvar, calling hook again.
413 if (NetworkServer.localClientActive && !GetSyncVarHookGuard(dirtyBit))
414 {
415 SetSyncVarHookGuard(dirtyBit, true);
416 OnChanged(oldValue, value);
417 SetSyncVarHookGuard(dirtyBit, false);
418 }
419 }
420 }
421 }
422
423 // NetworkBehaviour needs custom handling for persistence via netId.
424 // has one extra parameter.
425 [MethodImpl(MethodImplOptions.AggressiveInlining)]
426 public void GeneratedSyncVarSetter_NetworkBehaviour<T>(T value, ref T field, ulong dirtyBit, Action<T, T> OnChanged, ref NetworkBehaviourSyncVar netIdField)
427 where T : NetworkBehaviour
428 {
429 if (!SyncVarNetworkBehaviourEqual(value, netIdField))
430 {
431 T oldValue = field;
432 SetSyncVarNetworkBehaviour(value, ref field, dirtyBit, ref netIdField);
433
434 // call hook (if any)
435 if (OnChanged != null)
436 {
437 // in host mode, setting a SyncVar calls the hook directly.
438 // in client-only mode, OnDeserialize would call it.
439 // we use hook guard to protect against deadlock where hook
440 // changes syncvar, calling hook again.
441 if (NetworkServer.localClientActive && !GetSyncVarHookGuard(dirtyBit))
442 {
443 SetSyncVarHookGuard(dirtyBit, true);
444 OnChanged(oldValue, value);
445 SetSyncVarHookGuard(dirtyBit, false);
446 }
447 }
448 }
449 }
450
451 // helper function for [SyncVar] GameObjects.
452 // needs to be public so that tests & NetworkBehaviours from other
453 // assemblies both find it
454 [EditorBrowsable(EditorBrowsableState.Never)]
455 public static bool SyncVarGameObjectEqual(GameObject newGameObject, uint netIdField)
456 {
457 uint newNetId = 0;
458 if (newGameObject != null)
459 {
460 NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
461 if (identity != null)
462 {
463 newNetId = identity.netId;
464 if (newNetId == 0)
465 {
466 Debug.LogWarning($"SetSyncVarGameObject GameObject {newGameObject} has a zero netId. Maybe it is not spawned yet?");
467 }
468 }
469 }
470
471 return newNetId == netIdField;
472 }
473
474 // helper function for [SyncVar] GameObjects.
475 // dirtyBit is a mask like 00010
476 protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gameObjectField, ulong dirtyBit, ref uint netIdField)
477 {
478 if (GetSyncVarHookGuard(dirtyBit))
479 return;
480
481 uint newNetId = 0;
482 if (newGameObject != null)
483 {
484 NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
485 if (identity != null)
486 {
487 newNetId = identity.netId;
488 if (newNetId == 0)
489 {
490 Debug.LogWarning($"SetSyncVarGameObject GameObject {newGameObject} has a zero netId. Maybe it is not spawned yet?");
491 }
492 }
493 }
494
495 //Debug.Log($"SetSyncVar GameObject {GetType().Name} bit:{dirtyBit} netfieldId:{netIdField} -> {newNetId}");
496 SetSyncVarDirtyBit(dirtyBit);
497 // assign new one on the server, and in case we ever need it on client too
498 gameObjectField = newGameObject;
499 netIdField = newNetId;
500 }
501
502 // helper function for [SyncVar] GameObjects.
503 // -> ref GameObject as second argument makes OnDeserialize processing easier
504 protected GameObject GetSyncVarGameObject(uint netId, ref GameObject gameObjectField)
505 {
506 // server always uses the field
507 if (isServer)
508 {
509 return gameObjectField;
510 }
511
512 // client always looks up based on netId because objects might get in and out of range
513 // over and over again, which shouldn't null them forever
514 if (NetworkClient.spawned.TryGetValue(netId, out NetworkIdentity identity) && identity != null)
515 return gameObjectField = identity.gameObject;
516 return null;
517 }
518
519 // helper function for [SyncVar] NetworkIdentities.
520 // needs to be public so that tests & NetworkBehaviours from other
521 // assemblies both find it
522 [EditorBrowsable(EditorBrowsableState.Never)]
523 public static bool SyncVarNetworkIdentityEqual(NetworkIdentity newIdentity, uint netIdField)
524 {
525 uint newNetId = 0;
526 if (newIdentity != null)
527 {
528 newNetId = newIdentity.netId;
529 if (newNetId == 0)
530 {
531 Debug.LogWarning($"SetSyncVarNetworkIdentity NetworkIdentity {newIdentity} has a zero netId. Maybe it is not spawned yet?");
532 }
533 }
534
535 // netId changed?
536 return newNetId == netIdField;
537 }
538
539 // move the [SyncVar] generated OnDeserialize C# to avoid much IL.
540 //
541 // before:
542 // public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
543 // {
544 // base.DeserializeSyncVars(reader, initialState);
545 // if (initialState)
546 // {
547 // int num = health;
548 // Networkhealth = reader.ReadInt();
549 // if (!NetworkBehaviour.SyncVarEqual(num, ref health))
550 // {
551 // OnChanged(num, health);
552 // }
553 // return;
554 // }
555 // long num2 = (long)reader.ReadULong();
556 // if ((num2 & 1L) != 0L)
557 // {
558 // int num3 = health;
559 // Networkhealth = reader.ReadInt();
560 // if (!NetworkBehaviour.SyncVarEqual(num3, ref health))
561 // {
562 // OnChanged(num3, health);
563 // }
564 // }
565 // }
566 //
567 // after:
568 //
569 // public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
570 // {
571 // base.DeserializeSyncVars(reader, initialState);
572 // if (initialState)
573 // {
574 // GeneratedSyncVarDeserialize(reader, ref health, null, reader.ReadInt());
575 // return;
576 // }
577 // long num = (long)reader.ReadULong();
578 // if ((num & 1L) != 0L)
579 // {
580 // GeneratedSyncVarDeserialize(reader, ref health, null, reader.ReadInt());
581 // }
582 // }
583 [MethodImpl(MethodImplOptions.AggressiveInlining)]
584 public void GeneratedSyncVarDeserialize<T>(ref T field, Action<T, T> OnChanged, T value)
585 {
586 T previous = field;
587 field = value;
588
589 // any hook? then call if changed.
590 if (OnChanged != null && !SyncVarEqual(previous, ref field))
591 {
592 OnChanged(previous, field);
593 }
594 }
595
596 // move the [SyncVar] generated OnDeserialize C# to avoid much IL.
597 //
598 // before:
599 // public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
600 // {
601 // base.DeserializeSyncVars(reader, initialState);
602 // if (initialState)
603 // {
604 // uint __targetNetId = ___targetNetId;
605 // GameObject networktarget = Networktarget;
606 // ___targetNetId = reader.ReadUInt();
607 // if (!NetworkBehaviour.SyncVarEqual(__targetNetId, ref ___targetNetId))
608 // {
609 // OnChangedNB(networktarget, Networktarget);
610 // }
611 // return;
612 // }
613 // long num = (long)reader.ReadULong();
614 // if ((num & 1L) != 0L)
615 // {
616 // uint __targetNetId2 = ___targetNetId;
617 // GameObject networktarget2 = Networktarget;
618 // ___targetNetId = reader.ReadUInt();
619 // if (!NetworkBehaviour.SyncVarEqual(__targetNetId2, ref ___targetNetId))
620 // {
621 // OnChangedNB(networktarget2, Networktarget);
622 // }
623 // }
624 // }
625 //
626 // after:
627 // public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
628 // {
629 // base.DeserializeSyncVars(reader, initialState);
630 // if (initialState)
631 // {
632 // GeneratedSyncVarDeserialize_GameObject(reader, ref target, OnChangedNB, ref ___targetNetId);
633 // return;
634 // }
635 // long num = (long)reader.ReadULong();
636 // if ((num & 1L) != 0L)
637 // {
638 // GeneratedSyncVarDeserialize_GameObject(reader, ref target, OnChangedNB, ref ___targetNetId);
639 // }
640 // }
641 [MethodImpl(MethodImplOptions.AggressiveInlining)]
642 public void GeneratedSyncVarDeserialize_GameObject(ref GameObject field, Action<GameObject, GameObject> OnChanged, NetworkReader reader, ref uint netIdField)
643 {
644 uint previousNetId = netIdField;
645 GameObject previousGameObject = field;
646 netIdField = reader.ReadUInt();
647
648 // get the new GameObject now that netId field is set
649 field = GetSyncVarGameObject(netIdField, ref field);
650
651 // any hook? then call if changed.
652 if (OnChanged != null && !SyncVarEqual(previousNetId, ref netIdField))
653 {
654 OnChanged(previousGameObject, field);
655 }
656 }
657
658 // move the [SyncVar] generated OnDeserialize C# to avoid much IL.
659 //
660 // before:
661 // public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
662 // {
663 // base.DeserializeSyncVars(reader, initialState);
664 // if (initialState)
665 // {
666 // uint __targetNetId = ___targetNetId;
667 // NetworkIdentity networktarget = Networktarget;
668 // ___targetNetId = reader.ReadUInt();
669 // if (!NetworkBehaviour.SyncVarEqual(__targetNetId, ref ___targetNetId))
670 // {
671 // OnChangedNI(networktarget, Networktarget);
672 // }
673 // return;
674 // }
675 // long num = (long)reader.ReadULong();
676 // if ((num & 1L) != 0L)
677 // {
678 // uint __targetNetId2 = ___targetNetId;
679 // NetworkIdentity networktarget2 = Networktarget;
680 // ___targetNetId = reader.ReadUInt();
681 // if (!NetworkBehaviour.SyncVarEqual(__targetNetId2, ref ___targetNetId))
682 // {
683 // OnChangedNI(networktarget2, Networktarget);
684 // }
685 // }
686 // }
687 //
688 // after:
689 //
690 // public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
691 // {
692 // base.DeserializeSyncVars(reader, initialState);
693 // if (initialState)
694 // {
695 // GeneratedSyncVarDeserialize_NetworkIdentity(reader, ref target, OnChangedNI, ref ___targetNetId);
696 // return;
697 // }
698 // long num = (long)reader.ReadULong();
699 // if ((num & 1L) != 0L)
700 // {
701 // GeneratedSyncVarDeserialize_NetworkIdentity(reader, ref target, OnChangedNI, ref ___targetNetId);
702 // }
703 // }
704 [MethodImpl(MethodImplOptions.AggressiveInlining)]
705 public void GeneratedSyncVarDeserialize_NetworkIdentity(ref NetworkIdentity field, Action<NetworkIdentity, NetworkIdentity> OnChanged, NetworkReader reader, ref uint netIdField)
706 {
707 uint previousNetId = netIdField;
708 NetworkIdentity previousIdentity = field;
709 netIdField = reader.ReadUInt();
710
711 // get the new NetworkIdentity now that netId field is set
712 field = GetSyncVarNetworkIdentity(netIdField, ref field);
713
714 // any hook? then call if changed.
715 if (OnChanged != null && !SyncVarEqual(previousNetId, ref netIdField))
716 {
717 OnChanged(previousIdentity, field);
718 }
719 }
720
721 // move the [SyncVar] generated OnDeserialize C# to avoid much IL.
722 //
723 // before:
724 //
725 // public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
726 // {
727 // base.DeserializeSyncVars(reader, initialState);
728 // if (initialState)
729 // {
730 // NetworkBehaviourSyncVar __targetNetId = ___targetNetId;
731 // Tank networktarget = Networktarget;
732 // ___targetNetId = reader.ReadNetworkBehaviourSyncVar();
733 // if (!NetworkBehaviour.SyncVarEqual(__targetNetId, ref ___targetNetId))
734 // {
735 // OnChangedNB(networktarget, Networktarget);
736 // }
737 // return;
738 // }
739 // long num = (long)reader.ReadULong();
740 // if ((num & 1L) != 0L)
741 // {
742 // NetworkBehaviourSyncVar __targetNetId2 = ___targetNetId;
743 // Tank networktarget2 = Networktarget;
744 // ___targetNetId = reader.ReadNetworkBehaviourSyncVar();
745 // if (!NetworkBehaviour.SyncVarEqual(__targetNetId2, ref ___targetNetId))
746 // {
747 // OnChangedNB(networktarget2, Networktarget);
748 // }
749 // }
750 // }
751 //
752 // after:
753 //
754 // public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
755 // {
756 // base.DeserializeSyncVars(reader, initialState);
757 // if (initialState)
758 // {
759 // GeneratedSyncVarDeserialize_NetworkBehaviour(reader, ref target, OnChangedNB, ref ___targetNetId);
760 // return;
761 // }
762 // long num = (long)reader.ReadULong();
763 // if ((num & 1L) != 0L)
764 // {
765 // GeneratedSyncVarDeserialize_NetworkBehaviour(reader, ref target, OnChangedNB, ref ___targetNetId);
766 // }
767 // }
768 [MethodImpl(MethodImplOptions.AggressiveInlining)]
769 public void GeneratedSyncVarDeserialize_NetworkBehaviour<T>(ref T field, Action<T, T> OnChanged, NetworkReader reader, ref NetworkBehaviourSyncVar netIdField)
770 where T : NetworkBehaviour
771 {
772 NetworkBehaviourSyncVar previousNetId = netIdField;
773 T previousBehaviour = field;
774 netIdField = reader.ReadNetworkBehaviourSyncVar();
775
776 // get the new NetworkBehaviour now that netId field is set
777 field = GetSyncVarNetworkBehaviour(netIdField, ref field);
778
779 // any hook? then call if changed.
780 if (OnChanged != null && !SyncVarEqual(previousNetId, ref netIdField))
781 {
782 OnChanged(previousBehaviour, field);
783 }
784 }
785
786 // helper function for [SyncVar] NetworkIdentities.
787 // dirtyBit is a mask like 00010
788 protected void SetSyncVarNetworkIdentity(NetworkIdentity newIdentity, ref NetworkIdentity identityField, ulong dirtyBit, ref uint netIdField)
789 {
790 if (GetSyncVarHookGuard(dirtyBit))
791 return;
792
793 uint newNetId = 0;
794 if (newIdentity != null)
795 {
796 newNetId = newIdentity.netId;
797 if (newNetId == 0)
798 {
799 Debug.LogWarning($"SetSyncVarNetworkIdentity NetworkIdentity {newIdentity} has a zero netId. Maybe it is not spawned yet?");
800 }
801 }
802
803 //Debug.Log($"SetSyncVarNetworkIdentity NetworkIdentity {GetType().Name} bit:{dirtyBit} netIdField:{netIdField} -> {newNetId}");
804 SetSyncVarDirtyBit(dirtyBit);
805 netIdField = newNetId;
806 // assign new one on the server, and in case we ever need it on client too
807 identityField = newIdentity;
808 }
809
810 // helper function for [SyncVar] NetworkIdentities.
811 // -> ref GameObject as second argument makes OnDeserialize processing easier
812 protected NetworkIdentity GetSyncVarNetworkIdentity(uint netId, ref NetworkIdentity identityField)
813 {
814 // server always uses the field
815 if (isServer)
816 {
817 return identityField;
818 }
819
820 // client always looks up based on netId because objects might get in and out of range
821 // over and over again, which shouldn't null them forever
822 NetworkClient.spawned.TryGetValue(netId, out identityField);
823 return identityField;
824 }
825
826 protected static bool SyncVarNetworkBehaviourEqual<T>(T newBehaviour, NetworkBehaviourSyncVar syncField) where T : NetworkBehaviour
827 {
828 uint newNetId = 0;
829 int newComponentIndex = 0;
830 if (newBehaviour != null)
831 {
832 newNetId = newBehaviour.netId;
833 newComponentIndex = newBehaviour.ComponentIndex;
834 if (newNetId == 0)
835 {
836 Debug.LogWarning($"SetSyncVarNetworkIdentity NetworkIdentity {newBehaviour} has a zero netId. Maybe it is not spawned yet?");
837 }
838 }
839
840 // netId changed?
841 return syncField.Equals(newNetId, newComponentIndex);
842 }
843
844 // helper function for [SyncVar] NetworkIdentities.
845 // dirtyBit is a mask like 00010
846 protected void SetSyncVarNetworkBehaviour<T>(T newBehaviour, ref T behaviourField, ulong dirtyBit, ref NetworkBehaviourSyncVar syncField) where T : NetworkBehaviour
847 {
848 if (GetSyncVarHookGuard(dirtyBit))
849 return;
850
851 uint newNetId = 0;
852 int componentIndex = 0;
853 if (newBehaviour != null)
854 {
855 newNetId = newBehaviour.netId;
856 componentIndex = newBehaviour.ComponentIndex;
857 if (newNetId == 0)
858 {
859 Debug.LogWarning($"{nameof(SetSyncVarNetworkBehaviour)} NetworkIdentity {newBehaviour} has a zero netId. Maybe it is not spawned yet?");
860 }
861 }
862
863 syncField = new NetworkBehaviourSyncVar(newNetId, componentIndex);
864
865 SetSyncVarDirtyBit(dirtyBit);
866
867 // assign new one on the server, and in case we ever need it on client too
868 behaviourField = newBehaviour;
869
870 // Debug.Log($"SetSyncVarNetworkBehaviour NetworkIdentity {GetType().Name} bit [{dirtyBit}] netIdField:{oldField}->{syncField}");
871 }
872
873 // helper function for [SyncVar] NetworkIdentities.
874 // -> ref GameObject as second argument makes OnDeserialize processing easier
875 protected T GetSyncVarNetworkBehaviour<T>(NetworkBehaviourSyncVar syncNetBehaviour, ref T behaviourField) where T : NetworkBehaviour
876 {
877 // server always uses the field
878 if (isServer)
879 {
880 return behaviourField;
881 }
882
883 // client always looks up based on netId because objects might get in and out of range
884 // over and over again, which shouldn't null them forever
885 if (!NetworkClient.spawned.TryGetValue(syncNetBehaviour.netId, out NetworkIdentity identity))
886 {
887 return null;
888 }
889
890 behaviourField = identity.NetworkBehaviours[syncNetBehaviour.componentIndex] as T;
891 return behaviourField;
892 }
893
894 // backing field for sync NetworkBehaviour
895 public struct NetworkBehaviourSyncVar : IEquatable<NetworkBehaviourSyncVar>
896 {
897 public uint netId;
898 // limited to 255 behaviours per identity
899 public byte componentIndex;
900
901 public NetworkBehaviourSyncVar(uint netId, int componentIndex) : this()
902 {
903 this.netId = netId;
904 this.componentIndex = (byte)componentIndex;
905 }
906
907 public bool Equals(NetworkBehaviourSyncVar other)
908 {
909 return other.netId == netId && other.componentIndex == componentIndex;
910 }
911
912 public bool Equals(uint netId, int componentIndex)
913 {
914 return this.netId == netId && this.componentIndex == componentIndex;
915 }
916
917 public override string ToString()
918 {
919 return $"[netId:{netId} compIndex:{componentIndex}]";
920 }
921 }
922
923 protected static bool SyncVarEqual<T>(T value, ref T fieldValue)
924 {
925 // newly initialized or changed value?
926 // value.Equals(fieldValue) allocates without 'where T : IEquatable'
927 // seems like we use EqualityComparer to avoid allocations,
928 // because not all SyncVars<T> are IEquatable
929 return EqualityComparer<T>.Default.Equals(value, fieldValue);
930 }
931
932 // dirtyBit is a mask like 00010
933 protected void SetSyncVar<T>(T value, ref T fieldValue, ulong dirtyBit)
934 {
935 //Debug.Log($"SetSyncVar {GetType().Name} bit:{dirtyBit} fieldValue:{value}");
936 SetSyncVarDirtyBit(dirtyBit);
937 fieldValue = value;
938 }
939
941 // if a class has syncvars, then OnSerialize/OnDeserialize are added
942 // automatically.
943 //
944 // initialState is true for full spawns, false for delta syncs.
945 // note: SyncVar hooks are only called when inital=false
946 public virtual bool OnSerialize(NetworkWriter writer, bool initialState)
947 {
948 // if initialState: write all SyncVars.
949 // otherwise write dirtyBits+dirty SyncVars
950 bool objectWritten = initialState ? SerializeObjectsAll(writer) : SerializeObjectsDelta(writer);
951 bool syncVarWritten = SerializeSyncVars(writer, initialState);
952 return objectWritten || syncVarWritten;
953 }
954
956 public virtual void OnDeserialize(NetworkReader reader, bool initialState)
957 {
958 if (initialState)
959 {
960 DeSerializeObjectsAll(reader);
961 }
962 else
963 {
964 DeSerializeObjectsDelta(reader);
965 }
966
967 DeserializeSyncVars(reader, initialState);
968 }
969
970 // USED BY WEAVER
971 protected virtual bool SerializeSyncVars(NetworkWriter writer, bool initialState)
972 {
973 return false;
974
975 // SyncVar are written here in subclass
976
977 // if initialState
978 // write all SyncVars
979 // else
980 // write syncVarDirtyBits
981 // write dirty SyncVars
982 }
983
984 // USED BY WEAVER
985 protected virtual void DeserializeSyncVars(NetworkReader reader, bool initialState)
986 {
987 // SyncVars are read here in subclass
988
989 // if initialState
990 // read all SyncVars
991 // else
992 // read syncVarDirtyBits
993 // read dirty SyncVars
994 }
995
996 public bool SerializeObjectsAll(NetworkWriter writer)
997 {
998 bool dirty = false;
999 for (int i = 0; i < syncObjects.Count; i++)
1000 {
1001 SyncObject syncObject = syncObjects[i];
1002 syncObject.OnSerializeAll(writer);
1003 dirty = true;
1004 }
1005 return dirty;
1006 }
1007
1008 public bool SerializeObjectsDelta(NetworkWriter writer)
1009 {
1010 bool dirty = false;
1011 // write the mask
1012 writer.WriteULong(syncObjectDirtyBits);
1013 // serializable objects, such as synclists
1014 for (int i = 0; i < syncObjects.Count; i++)
1015 {
1016 // check dirty mask at nth bit
1017 SyncObject syncObject = syncObjects[i];
1018 if ((syncObjectDirtyBits & (1UL << i)) != 0)
1019 {
1020 syncObject.OnSerializeDelta(writer);
1021 dirty = true;
1022 }
1023 }
1024 return dirty;
1025 }
1026
1027 internal void DeSerializeObjectsAll(NetworkReader reader)
1028 {
1029 for (int i = 0; i < syncObjects.Count; i++)
1030 {
1031 SyncObject syncObject = syncObjects[i];
1032 syncObject.OnDeserializeAll(reader);
1033 }
1034 }
1035
1036 internal void DeSerializeObjectsDelta(NetworkReader reader)
1037 {
1038 ulong dirty = reader.ReadULong();
1039 for (int i = 0; i < syncObjects.Count; i++)
1040 {
1041 // check dirty mask at nth bit
1042 SyncObject syncObject = syncObjects[i];
1043 if ((dirty & (1UL << i)) != 0)
1044 {
1045 syncObject.OnDeserializeDelta(reader);
1046 }
1047 }
1048 }
1049
1050 internal void ResetSyncObjects()
1051 {
1052 foreach (SyncObject syncObject in syncObjects)
1053 {
1054 syncObject.Reset();
1055 }
1056 }
1057
1059 public virtual void OnStartServer() {}
1060
1062 public virtual void OnStopServer() {}
1063
1065 public virtual void OnStartClient() {}
1066
1068 public virtual void OnStopClient() {}
1069
1071 public virtual void OnStartLocalPlayer() {}
1072
1074 public virtual void OnStopLocalPlayer() {}
1075
1077 public virtual void OnStartAuthority() {}
1078
1080 public virtual void OnStopAuthority() {}
1081 }
1082}
Base class for networked components.
void ClearAllDirtyBits()
Clears all the dirty bits that were set by SetDirtyBits()
bool isLocalPlayer
True if this object is the the client's own local player.
void SetSyncVarDirtyBit(ulong dirtyBit)
Set as dirty so that it's synced to clients again.
virtual void OnStartAuthority()
Like Start(), but only called for objects the client has authority over.
NetworkIdentity netIdentity
Returns the NetworkIdentity of this object
virtual void OnDeserialize(NetworkReader reader, bool initialState)
Override to do custom deserialization (instead of SyncVars/SyncLists). Use OnSerialize too.
virtual void OnStopServer()
Stop event, only called on server and host.
int ComponentIndex
Returns the index of the component on this object
virtual void OnStartClient()
Like Start(), but only called on client and host.
bool isServer
True if this object is on the server and has been spawned.
virtual void OnStartServer()
Like Start(), but only called on server and host.
virtual void OnStartLocalPlayer()
Like Start(), but only called on client and host for the local player object.
bool isServerOnly
True if this object is on the server-only, not host.
float syncInterval
sync interval for OnSerialize (in seconds)
NetworkConnection connectionToServer
Client's network connection to the server. This is only valid for player objects on the client.
bool isClient
True if this object is on the client and has been spawned by the server.
NetworkConnectionToClient connectionToClient
Server's network connection to the client. This is only valid for player objects on the server.
bool hasAuthority
True on client if that component has been assigned to the client. E.g. player, pets,...
bool isClientOnly
True if this object is on the client-only, not host.
SyncMode syncMode
sync mode for OnSerialize
virtual void OnStopClient()
Stop event, only called on client and host.
virtual void OnStopLocalPlayer()
Stop event, but only called on client and host for the local player object.
uint netId
The unique network Id of this object (unique at runtime).
virtual bool OnSerialize(NetworkWriter writer, bool initialState)
Override to do custom serialization (instead of SyncVars/SyncLists). Use OnDeserialize too.
virtual void OnStopAuthority()
Stop event, only called for objects the client has authority over.
Base NetworkConnection class for server-to-client and client-to-server connection.
NetworkIdentity identifies objects across the network.
bool isClient
Returns true if running as a client and this object was spawned by a server.
bool hasAuthority
True on client if that component has been assigned to the client. E.g. player, pets,...
uint netId
The unique network Id of this object (unique at runtime).
bool isServer
Returns true if NetworkServer.active and server is not stopped.
bool isLocalPlayer
Return true if this object represents the player on the local machine.
Dictionary< int, NetworkConnectionToClient > observers
The set of network connections (players) that can see this object.
bool isServerOnly
True if this object only exists on the server
NetworkConnectionToClient connectionToClient
Server's network connection to the client. This is only valid for client-owned objects (including the...
bool isClientOnly
True if this object exists on a client that is not also acting as a server.
NetworkConnection connectionToServer
Client's network connection to the server. This is only valid for player objects on the client.
Network Reader for most simple types like floats, ints, buffers, structs, etc. Use NetworkReaderPool....
Synchronizes server time to clients.
Definition: NetworkTime.cs:12
Network Writer for most simple types like floats, ints, buffers, structs, etc. Use NetworkWriterPool....
SyncObjects sync state between server and client. E.g. SyncLists.
Definition: SyncObject.cs:14
abstract void OnSerializeAll(NetworkWriter writer)
Write a full copy of the object
abstract void OnSerializeDelta(NetworkWriter writer)
Write the changes made to the object since last sync