// persistent NetworkBehaviour SyncField which stores netId and component index. // this is necessary for cases like a player's target. // the target might run in and out of visibility range and become 'null'. // but the 'netId' remains and will always point to the monster if around. // (we also store the component index because GameObject can have multiple // NetworkBehaviours of same type) // // original Weaver code was broken because it didn't store by netId. using System; using System.Runtime.CompilerServices; namespace Mirror { // SyncField needs an uint netId and a byte componentIndex. // we use an ulong SyncField internally to store both. // while providing .spawned lookup for convenience. // NOTE: server always knows all spawned. consider caching the field again. // to support abstract NetworkBehaviour and classes inheriting from it. // => hooks can be OnHook(Monster, Monster) instead of OnHook(NB, NB) // => implicit cast can be to/from Monster instead of only NetworkBehaviour // => Weaver needs explicit types for hooks too, not just OnHook(NB, NB) public class SyncVarNetworkBehaviour : SyncVar where T : NetworkBehaviour { // .spawned lookup from netId overwrites base uint .Value public new T Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ULongToNetworkBehaviour(base.Value); [MethodImpl(MethodImplOptions.AggressiveInlining)] set => base.Value = NetworkBehaviourToULong(value); } // OnChanged Callback is for . // Let's also have one for public new event Action Callback; // overwrite CallCallback to use the NetworkIdentity version instead [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void InvokeCallback(ulong oldValue, ulong newValue) => Callback?.Invoke(ULongToNetworkBehaviour(oldValue), ULongToNetworkBehaviour(newValue)); // ctor // 'value = null' so we can do: // SyncVarNetworkBehaviour = new SyncVarNetworkBehaviour() // instead of // SyncVarNetworkBehaviour = new SyncVarNetworkBehaviour(null); public SyncVarNetworkBehaviour(T value = null) : base(NetworkBehaviourToULong(value)) {} // implicit conversion: NetworkBehaviour value = SyncFieldNetworkBehaviour [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator T(SyncVarNetworkBehaviour field) => field.Value; // implicit conversion: SyncFieldNetworkBehaviour = value // even if SyncField is readonly, it's still useful: SyncFieldNetworkBehaviour = target; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator SyncVarNetworkBehaviour(T value) => new SyncVarNetworkBehaviour(value); // NOTE: overloading all == operators blocks '== null' checks with an // "ambiguous invocation" error. that's good. this way user code like // "player.target == null" won't compile instead of silently failing! // == operator for comparisons like Player.target==monster [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(SyncVarNetworkBehaviour a, SyncVarNetworkBehaviour b) => a.Value == b.Value; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(SyncVarNetworkBehaviour a, SyncVarNetworkBehaviour b) => !(a == b); // == operator for comparisons like Player.target==monster [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(SyncVarNetworkBehaviour a, NetworkBehaviour b) => a.Value == b; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(SyncVarNetworkBehaviour a, NetworkBehaviour b) => !(a == b); // == operator for comparisons like Player.target==monster [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(SyncVarNetworkBehaviour a, T b) => a.Value == b; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(SyncVarNetworkBehaviour a, T b) => !(a == b); // == operator for comparisons like Player.target==monster [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NetworkBehaviour a, SyncVarNetworkBehaviour b) => a == b.Value; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NetworkBehaviour a, SyncVarNetworkBehaviour b) => !(a == b); // == operator for comparisons like Player.target==monster [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(T a, SyncVarNetworkBehaviour b) => a == b.Value; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(T a, SyncVarNetworkBehaviour b) => !(a == b); // if we overwrite == operators, we also need to overwrite .Equals. [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) => obj is SyncVarNetworkBehaviour value && this == value; [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() => Value.GetHashCode(); // helper functions to get/set netId, componentIndex from ulong // netId on the 4 left bytes. compIndex on the right most byte. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ulong Pack(uint netId, byte componentIndex) => (ulong)netId << 32 | componentIndex; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Unpack(ulong value, out uint netId, out byte componentIndex) { netId = (uint)(value >> 32); componentIndex = (byte)(value & 0xFF); } // helper function to find/get NetworkBehaviour to ulong (netId/compIndex) static T ULongToNetworkBehaviour(ulong value) { // unpack ulong to netId, componentIndex Unpack(value, out uint netId, out byte componentIndex); // find spawned NetworkIdentity by netId NetworkIdentity identity = Utils.GetSpawnedInServerOrClient(netId); // get the nth component return identity != null ? (T)identity.NetworkBehaviours[componentIndex] : null; } static ulong NetworkBehaviourToULong(T value) { // pack netId, componentIndex to ulong return value != null ? Pack(value.netId, (byte)value.ComponentIndex) : 0; } // Serialize should only write 4+1 bytes, not 8 bytes ulong [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void OnSerializeAll(NetworkWriter writer) { Unpack(base.Value, out uint netId, out byte componentIndex); writer.WriteUInt(netId); writer.WriteByte(componentIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void OnSerializeDelta(NetworkWriter writer) => OnSerializeAll(writer); // Deserialize should only write 4+1 bytes, not 8 bytes ulong [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void OnDeserializeAll(NetworkReader reader) { uint netId = reader.ReadUInt(); byte componentIndex = reader.ReadByte(); base.Value = Pack(netId, componentIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void OnDeserializeDelta(NetworkReader reader) => OnDeserializeAll(reader); } }