Mirror Networking
SyncVarNetworkBehaviour.cs
1// persistent NetworkBehaviour SyncField which stores netId and component index.
2// this is necessary for cases like a player's target.
3// the target might run in and out of visibility range and become 'null'.
4// but the 'netId' remains and will always point to the monster if around.
5// (we also store the component index because GameObject can have multiple
6// NetworkBehaviours of same type)
7//
8// original Weaver code was broken because it didn't store by netId.
9using System;
10using System.Runtime.CompilerServices;
11
12namespace Mirror
13{
14 // SyncField<NetworkBehaviour> needs an uint netId and a byte componentIndex.
15 // we use an ulong SyncField internally to store both.
16 // while providing .spawned lookup for convenience.
17 // NOTE: server always knows all spawned. consider caching the field again.
18 // <T> to support abstract NetworkBehaviour and classes inheriting from it.
19 // => hooks can be OnHook(Monster, Monster) instead of OnHook(NB, NB)
20 // => implicit cast can be to/from Monster instead of only NetworkBehaviour
21 // => Weaver needs explicit types for hooks too, not just OnHook(NB, NB)
22 public class SyncVarNetworkBehaviour<T> : SyncVar<ulong>
23 where T : NetworkBehaviour
24 {
25 // .spawned lookup from netId overwrites base uint .Value
26 public new T Value
27 {
28 [MethodImpl(MethodImplOptions.AggressiveInlining)]
29 get => ULongToNetworkBehaviour(base.Value);
30 [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 set => base.Value = NetworkBehaviourToULong(value);
32 }
33
34 // OnChanged Callback is for <uint, uint>.
35 // Let's also have one for <NetworkBehaviour, NetworkBehaviour>
36 public new event Action<T, T> Callback;
37
38 // overwrite CallCallback to use the NetworkIdentity version instead
39 [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 protected override void InvokeCallback(ulong oldValue, ulong newValue) =>
41 Callback?.Invoke(ULongToNetworkBehaviour(oldValue), ULongToNetworkBehaviour(newValue));
42
43 // ctor
44 // 'value = null' so we can do:
45 // SyncVarNetworkBehaviour = new SyncVarNetworkBehaviour()
46 // instead of
47 // SyncVarNetworkBehaviour = new SyncVarNetworkBehaviour(null);
48 public SyncVarNetworkBehaviour(T value = null)
49 : base(NetworkBehaviourToULong(value)) {}
50
51 // implicit conversion: NetworkBehaviour value = SyncFieldNetworkBehaviour
52 [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 public static implicit operator T(SyncVarNetworkBehaviour<T> field) => field.Value;
54
55 // implicit conversion: SyncFieldNetworkBehaviour = value
56 // even if SyncField is readonly, it's still useful: SyncFieldNetworkBehaviour = target;
57 [MethodImpl(MethodImplOptions.AggressiveInlining)]
58 public static implicit operator SyncVarNetworkBehaviour<T>(T value) => new SyncVarNetworkBehaviour<T>(value);
59
60 // NOTE: overloading all == operators blocks '== null' checks with an
61 // "ambiguous invocation" error. that's good. this way user code like
62 // "player.target == null" won't compile instead of silently failing!
63
64 // == operator for comparisons like Player.target==monster
65 [MethodImpl(MethodImplOptions.AggressiveInlining)]
66 public static bool operator ==(SyncVarNetworkBehaviour<T> a, SyncVarNetworkBehaviour<T> b) =>
67 a.Value == b.Value;
68
69 [MethodImpl(MethodImplOptions.AggressiveInlining)]
70 public static bool operator !=(SyncVarNetworkBehaviour<T> a, SyncVarNetworkBehaviour<T> b) => !(a == b);
71
72 // == operator for comparisons like Player.target==monster
73 [MethodImpl(MethodImplOptions.AggressiveInlining)]
74 public static bool operator ==(SyncVarNetworkBehaviour<T> a, NetworkBehaviour b) =>
75 a.Value == b;
76
77 [MethodImpl(MethodImplOptions.AggressiveInlining)]
78 public static bool operator !=(SyncVarNetworkBehaviour<T> a, NetworkBehaviour b) => !(a == b);
79
80 // == operator for comparisons like Player.target==monster
81 [MethodImpl(MethodImplOptions.AggressiveInlining)]
82 public static bool operator ==(SyncVarNetworkBehaviour<T> a, T b) =>
83 a.Value == b;
84
85 [MethodImpl(MethodImplOptions.AggressiveInlining)]
86 public static bool operator !=(SyncVarNetworkBehaviour<T> a, T b) => !(a == b);
87
88 // == operator for comparisons like Player.target==monster
89 [MethodImpl(MethodImplOptions.AggressiveInlining)]
90 public static bool operator ==(NetworkBehaviour a, SyncVarNetworkBehaviour<T> b) =>
91 a == b.Value;
92
93 [MethodImpl(MethodImplOptions.AggressiveInlining)]
94 public static bool operator !=(NetworkBehaviour a, SyncVarNetworkBehaviour<T> b) => !(a == b);
95
96 // == operator for comparisons like Player.target==monster
97 [MethodImpl(MethodImplOptions.AggressiveInlining)]
98 public static bool operator ==(T a, SyncVarNetworkBehaviour<T> b) =>
99 a == b.Value;
100
101 [MethodImpl(MethodImplOptions.AggressiveInlining)]
102 public static bool operator !=(T a, SyncVarNetworkBehaviour<T> b) => !(a == b);
103
104 // if we overwrite == operators, we also need to overwrite .Equals.
105 [MethodImpl(MethodImplOptions.AggressiveInlining)]
106 public override bool Equals(object obj) => obj is SyncVarNetworkBehaviour<T> value && this == value;
107 [MethodImpl(MethodImplOptions.AggressiveInlining)]
108 public override int GetHashCode() => Value.GetHashCode();
109
110 // helper functions to get/set netId, componentIndex from ulong
111 // netId on the 4 left bytes. compIndex on the right most byte.
112 [MethodImpl(MethodImplOptions.AggressiveInlining)]
113 internal static ulong Pack(uint netId, byte componentIndex) =>
114 (ulong)netId << 32 | componentIndex;
115
116 [MethodImpl(MethodImplOptions.AggressiveInlining)]
117 internal static void Unpack(ulong value, out uint netId, out byte componentIndex)
118 {
119 netId = (uint)(value >> 32);
120 componentIndex = (byte)(value & 0xFF);
121 }
122
123 // helper function to find/get NetworkBehaviour to ulong (netId/compIndex)
124 static T ULongToNetworkBehaviour(ulong value)
125 {
126 // unpack ulong to netId, componentIndex
127 Unpack(value, out uint netId, out byte componentIndex);
128
129 // find spawned NetworkIdentity by netId
130 NetworkIdentity identity = Utils.GetSpawnedInServerOrClient(netId);
131
132 // get the nth component
133 return identity != null ? (T)identity.NetworkBehaviours[componentIndex] : null;
134 }
135
136 static ulong NetworkBehaviourToULong(T value)
137 {
138 // pack netId, componentIndex to ulong
139 return value != null ? Pack(value.netId, (byte)value.ComponentIndex) : 0;
140 }
141
142 // Serialize should only write 4+1 bytes, not 8 bytes ulong
143 [MethodImpl(MethodImplOptions.AggressiveInlining)]
144 public override void OnSerializeAll(NetworkWriter writer)
145 {
146 Unpack(base.Value, out uint netId, out byte componentIndex);
147 writer.WriteUInt(netId);
148 writer.WriteByte(componentIndex);
149 }
150
151 [MethodImpl(MethodImplOptions.AggressiveInlining)]
152 public override void OnSerializeDelta(NetworkWriter writer) =>
153 OnSerializeAll(writer);
154
155 // Deserialize should only write 4+1 bytes, not 8 bytes ulong
156 [MethodImpl(MethodImplOptions.AggressiveInlining)]
157 public override void OnDeserializeAll(NetworkReader reader)
158 {
159 uint netId = reader.ReadUInt();
160 byte componentIndex = reader.ReadByte();
161 base.Value = Pack(netId, componentIndex);
162 }
163
164 [MethodImpl(MethodImplOptions.AggressiveInlining)]
165 public override void OnDeserializeDelta(NetworkReader reader) =>
166 OnDeserializeAll(reader);
167 }
168}
Base class for networked components.
NetworkIdentity identifies objects across the network.
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....