Mirror Networking
SyncVar.cs
1// SyncVar<T> to make [SyncVar] weaving easier.
2//
3// we can possibly move a lot of complex logic out of weaver:
4// * set dirty bit
5// * calling the hook
6// * hook guard in host mode
7// * GameObject/NetworkIdentity internal netId storage
8//
9// here is the plan:
10// 1. develop SyncVar<T> along side [SyncVar]
11// 2. internally replace [SyncVar]s with SyncVar<T>
12// 3. eventually obsolete [SyncVar]
13//
14// downsides:
15// - generic <T> types don't show in Unity Inspector
16//
17using System;
18using System.Collections.Generic;
19using System.Runtime.CompilerServices;
20using UnityEngine;
21
22namespace Mirror
23{
24 // 'class' so that we can track it in SyncObjects list, and iterate it for
25 // de/serialization.
26 [Serializable]
27 public class SyncVar<T> : SyncObject, IEquatable<T>
28 {
29 // Unity 2020+ can show [SerializeField]<T> in inspector.
30 // (only if SyncVar<T> isn't readonly though)
31 [SerializeField] T _Value;
32
33 // Value property with hooks
34 // virtual for SyncFieldNetworkIdentity netId trick etc.
35 public virtual T Value
36 {
37 [MethodImpl(MethodImplOptions.AggressiveInlining)]
38 get => _Value;
39 set
40 {
41 // only if value changed. otherwise don't dirty/hook.
42 // we have .Equals(T), simply reuse it here.
43 if (!Equals(value))
44 {
45 // set value, set dirty bit
46 T old = _Value;
47 _Value = value;
48 OnDirty();
49
50 // Value.set calls the hook if changed.
51 // calling Value.set from within the hook would call the
52 // hook again and deadlock. prevent it with hookGuard.
53 // (see test: Hook_Set_DoesntDeadlock)
54 if (!hookGuard &&
55 // original [SyncVar] only calls hook on clients.
56 // let's keep it for consistency for now
57 // TODO remove check & dependency in the future.
58 // use isClient/isServer in the hook instead.
60 {
61 hookGuard = true;
62 InvokeCallback(old, value);
63 hookGuard = false;
64 }
65 }
66 }
67 }
68
69 // OnChanged Callback.
70 // named 'Callback' for consistency with SyncList etc.
71 // needs to be public so we can assign it in OnStartClient.
72 // (ctor passing doesn't work, it can only take static functions)
73 // assign via: field.Callback += ...!
74 public event Action<T, T> Callback;
75
76 // OnCallback is responsible for calling the callback.
77 // this is necessary for inheriting classes like SyncVarGameObject,
78 // where the netIds should be converted to GOs and call the GO hook.
79 [MethodImpl(MethodImplOptions.AggressiveInlining)]
80 protected virtual void InvokeCallback(T oldValue, T newValue) =>
81 Callback?.Invoke(oldValue, newValue);
82
83 // Value.set calls the hook if changed.
84 // calling Value.set from within the hook would call the hook again and
85 // deadlock. prevent it with a simple 'are we inside the hook' bool.
86 bool hookGuard;
87
88 public override void ClearChanges() {}
89 public override void Reset() {}
90
91 // ctor from value <T> and OnChanged hook.
92 // it was always called 'hook'. let's keep naming for convenience.
93 public SyncVar(T value)
94 {
95 // recommend explicit GameObject, NetworkIdentity, NetworkBehaviour
96 // with persistent netId method
97 if (this is SyncVar<GameObject>)
98 Debug.LogWarning($"Use explicit {nameof(SyncVarGameObject)} class instead of {nameof(SyncVar<T>)}<GameObject>. It stores netId internally for persistence.");
99
100 if (this is SyncVar<NetworkIdentity>)
101 Debug.LogWarning($"Use explicit {nameof(SyncVarNetworkIdentity)} class instead of {nameof(SyncVar<T>)}<NetworkIdentity>. It stores netId internally for persistence.");
102
103 if (this is SyncVar<NetworkBehaviour>)
104 Debug.LogWarning($"Use explicit SyncVarNetworkBehaviour class instead of {nameof(SyncVar<T>)}<NetworkBehaviour>. It stores netId internally for persistence.");
105
106 _Value = value;
107 }
108
109 // NOTE: copy ctor is unnecessary.
110 // SyncVar<T>s are readonly and only initialized by 'Value' once.
111
112 // implicit conversion: int value = SyncVar<T>
113 [MethodImpl(MethodImplOptions.AggressiveInlining)]
114 public static implicit operator T(SyncVar<T> field) => field.Value;
115
116 // implicit conversion: SyncVar<T> = value
117 // even if SyncVar<T> is readonly, it's still useful: SyncVar<int> = 1;
118 [MethodImpl(MethodImplOptions.AggressiveInlining)]
119 public static implicit operator SyncVar<T>(T value) => new SyncVar<T>(value);
120
121 // serialization (use .Value instead of _Value so hook is called!)
122 [MethodImpl(MethodImplOptions.AggressiveInlining)]
123 public override void OnSerializeAll(NetworkWriter writer) => writer.Write(Value);
124
125 [MethodImpl(MethodImplOptions.AggressiveInlining)]
126 public override void OnSerializeDelta(NetworkWriter writer) => writer.Write(Value);
127
128 [MethodImpl(MethodImplOptions.AggressiveInlining)]
129 public override void OnDeserializeAll(NetworkReader reader) => Value = reader.Read<T>();
130
131 [MethodImpl(MethodImplOptions.AggressiveInlining)]
132 public override void OnDeserializeDelta(NetworkReader reader) => Value = reader.Read<T>();
133
134 // IEquatable should compare Value.
135 // SyncVar<T> should act invisibly like [SyncVar] before.
136 // this way we can do SyncVar<int> health == 0 etc.
137 [MethodImpl(MethodImplOptions.AggressiveInlining)]
138 public bool Equals(T other) =>
139 // from NetworkBehaviour.SyncVarEquals:
140 // EqualityComparer method avoids allocations.
141 // otherwise <T> would have to be :IEquatable (not all structs are)
142 EqualityComparer<T>.Default.Equals(Value, other);
143
144 // ToString should show Value.
145 // SyncVar<T> should act invisibly like [SyncVar] before.
146 public override string ToString() => Value.ToString();
147 }
148}
NetworkClient with connection to server.
static bool active
active is true while a client is connecting/connected
Network Reader for most simple types like floats, ints, buffers, structs, etc. Use NetworkReaderPool....
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
Action OnDirty
Used internally to set owner NetworkBehaviour's dirty mask bit when changed.
Definition: SyncObject.cs:16
override void OnDeserializeDelta(NetworkReader reader)
Reads the changes made to the object since last sync
override void OnSerializeDelta(NetworkWriter writer)
Write the changes made to the object since last sync
override void OnDeserializeAll(NetworkReader reader)
Reads a full copy of the object
override void ClearChanges()
Discard all the queued changes
Definition: SyncVar.cs:88
override void Reset()
Resets the SyncObject so that it can be re-used
Definition: SyncVar.cs:89
override void OnSerializeAll(NetworkWriter writer)
Write a full copy of the object