169 lines
7.8 KiB
C#
169 lines
7.8 KiB
C#
// 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<NetworkBehaviour> 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.
|
|
// <T> 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<T> : SyncVar<ulong>
|
|
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 <uint, uint>.
|
|
// Let's also have one for <NetworkBehaviour, NetworkBehaviour>
|
|
public new event Action<T, T> 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<T> 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>(T value) => new SyncVarNetworkBehaviour<T>(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<T> a, SyncVarNetworkBehaviour<T> b) =>
|
|
a.Value == b.Value;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator !=(SyncVarNetworkBehaviour<T> a, SyncVarNetworkBehaviour<T> b) => !(a == b);
|
|
|
|
// == operator for comparisons like Player.target==monster
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator ==(SyncVarNetworkBehaviour<T> a, NetworkBehaviour b) =>
|
|
a.Value == b;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator !=(SyncVarNetworkBehaviour<T> a, NetworkBehaviour b) => !(a == b);
|
|
|
|
// == operator for comparisons like Player.target==monster
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator ==(SyncVarNetworkBehaviour<T> a, T b) =>
|
|
a.Value == b;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator !=(SyncVarNetworkBehaviour<T> a, T b) => !(a == b);
|
|
|
|
// == operator for comparisons like Player.target==monster
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator ==(NetworkBehaviour a, SyncVarNetworkBehaviour<T> b) =>
|
|
a == b.Value;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator !=(NetworkBehaviour a, SyncVarNetworkBehaviour<T> b) => !(a == b);
|
|
|
|
// == operator for comparisons like Player.target==monster
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator ==(T a, SyncVarNetworkBehaviour<T> b) =>
|
|
a == b.Value;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator !=(T a, SyncVarNetworkBehaviour<T> 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<T> 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);
|
|
}
|
|
}
|