SyncVar.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. //
  17. using System;
  18. using System.Collections.Generic;
  19. using System.Runtime.CompilerServices;
  20. using UnityEngine;
  21. namespace Mirror
  22. {
  23. // 'class' so that we can track it in SyncObjects list, and iterate it for
  24. // de/serialization.
  25. [Serializable]
  26. public class SyncVar<T> : SyncObject, IEquatable<T>
  27. {
  28. // Unity 2020+ can show [SerializeField]<T> in inspector.
  29. // (only if SyncVar<T> isn't readonly though)
  30. [SerializeField] T _Value;
  31. // Value property with hooks
  32. // virtual for SyncFieldNetworkIdentity netId trick etc.
  33. public virtual T Value
  34. {
  35. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  36. get => _Value;
  37. set
  38. {
  39. // only if value changed. otherwise don't dirty/hook.
  40. // we have .Equals(T), simply reuse it here.
  41. if (!Equals(value))
  42. {
  43. // set value, set dirty bit
  44. T old = _Value;
  45. _Value = value;
  46. OnDirty();
  47. // Value.set calls the hook if changed.
  48. // calling Value.set from within the hook would call the
  49. // hook again and deadlock. prevent it with hookGuard.
  50. // (see test: Hook_Set_DoesntDeadlock)
  51. if (!hookGuard &&
  52. // original [SyncVar] only calls hook on clients.
  53. // let's keep it for consistency for now
  54. // TODO remove check & dependency in the future.
  55. // use isClient/isServer in the hook instead.
  56. NetworkClient.active)
  57. {
  58. hookGuard = true;
  59. InvokeCallback(old, value);
  60. hookGuard = false;
  61. }
  62. }
  63. }
  64. }
  65. // OnChanged Callback.
  66. // named 'Callback' for consistency with SyncList etc.
  67. // needs to be public so we can assign it in OnStartClient.
  68. // (ctor passing doesn't work, it can only take static functions)
  69. // assign via: field.Callback += ...!
  70. public event Action<T, T> Callback;
  71. // OnCallback is responsible for calling the callback.
  72. // this is necessary for inheriting classes like SyncVarGameObject,
  73. // where the netIds should be converted to GOs and call the GO hook.
  74. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  75. protected virtual void InvokeCallback(T oldValue, T newValue) =>
  76. Callback?.Invoke(oldValue, newValue);
  77. // Value.set calls the hook if changed.
  78. // calling Value.set from within the hook would call the hook again and
  79. // deadlock. prevent it with a simple 'are we inside the hook' bool.
  80. bool hookGuard;
  81. public override void ClearChanges() {}
  82. public override void Reset() {}
  83. // ctor from value <T> and OnChanged hook.
  84. // it was always called 'hook'. let's keep naming for convenience.
  85. public SyncVar(T value)
  86. {
  87. // recommend explicit GameObject, NetworkIdentity, NetworkBehaviour
  88. // with persistent netId method
  89. if (this is SyncVar<GameObject>)
  90. Debug.LogWarning($"Use explicit {nameof(SyncVarGameObject)} class instead of {nameof(SyncVar<T>)}<GameObject>. It stores netId internally for persistence.");
  91. if (this is SyncVar<NetworkIdentity>)
  92. Debug.LogWarning($"Use explicit {nameof(SyncVarNetworkIdentity)} class instead of {nameof(SyncVar<T>)}<NetworkIdentity>. It stores netId internally for persistence.");
  93. if (this is SyncVar<NetworkBehaviour>)
  94. Debug.LogWarning($"Use explicit SyncVarNetworkBehaviour class instead of {nameof(SyncVar<T>)}<NetworkBehaviour>. It stores netId internally for persistence.");
  95. _Value = value;
  96. }
  97. // NOTE: copy ctor is unnecessary.
  98. // SyncVar<T>s are readonly and only initialized by 'Value' once.
  99. // implicit conversion: int value = SyncVar<T>
  100. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  101. public static implicit operator T(SyncVar<T> field) => field.Value;
  102. // implicit conversion: SyncVar<T> = value
  103. // even if SyncVar<T> is readonly, it's still useful: SyncVar<int> = 1;
  104. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  105. public static implicit operator SyncVar<T>(T value) => new SyncVar<T>(value);
  106. // serialization (use .Value instead of _Value so hook is called!)
  107. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  108. public override void OnSerializeAll(NetworkWriter writer) => writer.Write(Value);
  109. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  110. public override void OnSerializeDelta(NetworkWriter writer) => writer.Write(Value);
  111. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  112. public override void OnDeserializeAll(NetworkReader reader) => Value = reader.Read<T>();
  113. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  114. public override void OnDeserializeDelta(NetworkReader reader) => Value = reader.Read<T>();
  115. // IEquatable should compare Value.
  116. // SyncVar<T> should act invisibly like [SyncVar] before.
  117. // this way we can do SyncVar<int> health == 0 etc.
  118. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  119. public bool Equals(T other) =>
  120. // from NetworkBehaviour.SyncVarEquals:
  121. // EqualityComparer method avoids allocations.
  122. // otherwise <T> would have to be :IEquatable (not all structs are)
  123. EqualityComparer<T>.Default.Equals(Value, other);
  124. // ToString should show Value.
  125. // SyncVar<T> should act invisibly like [SyncVar] before.
  126. public override string ToString() => Value.ToString();
  127. }
  128. }