using FishNet.CodeGenerating; using FishNet.Documenting; using FishNet.Object.Helping; using FishNet.Object.Synchronizing.Internal; using FishNet.Serializing; using FishNet.Serializing.Helping; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using UnityEngine; namespace FishNet.Object.Synchronizing { [APIExclude] [System.Serializable] [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)] public class SyncVar : SyncBase { #region Types. /// /// Information needed to invoke a callback. /// private struct CachedOnChange { internal readonly T Previous; internal readonly T Next; public CachedOnChange(T previous, T next) { Previous = previous; Next = next; } } #endregion #region Public. /// /// Gets and sets the current value for this SyncVar. /// public T Value { get => _value; set => SetValue(value, true); } ///// ///// Sets the current value for this SyncVar while sending it immediately. ///// //public T ValueRpc //{ // set => SetValue(value, true, true); //} ///// ///// Gets the current value for this SyncVar while marking it dirty. This could be useful to change properties or fields on a reference type SyncVar and have the SyncVar be dirtied after. ///// //public T ValueDirty //{ // get // { // base.Dirty(); // return _value; // } //} ///// ///// Gets the current value for this SyncVar while sending it imediately. This could be useful to change properties or fields on a reference type SyncVar and have the SyncVar send after. ///// //public T ValueDirtyRpc //{ // get // { // base.Dirty(true); // return _value; // } //} /// /// Called when the SyncDictionary changes. /// public event OnChanged OnChange; public delegate void OnChanged(T prev, T next, bool asServer); #endregion #region Private. /// /// Server OnChange event waiting for start callbacks. /// private CachedOnChange? _serverOnChange; /// /// Client OnChange event waiting for start callbacks. /// private CachedOnChange? _clientOnChange; /// /// Value before the network is initialized on the containing object. /// private T _initialValue; /// /// Previous value on the client. /// private T _previousClientValue; /// /// Current value on the server, or client. /// [SerializeField] private T _value; /// /// True if T IsValueType. /// private bool _isValueType; #endregion #region Constructors. public SyncVar(SyncTypeSettings settings = new SyncTypeSettings()) : this(default(T), settings) { } public SyncVar(T initialValue, SyncTypeSettings settings = new SyncTypeSettings()) : base(settings) => SetInitialValues(initialValue); #endregion /// /// Called when the SyncType has been registered, but not yet initialized over the network. /// protected override void Initialized() { base.Initialized(); _isValueType = typeof(T).IsValueType; _initialValue = _value; } /// /// Sets initial values. /// Initial values are not automatically synchronized, as it is assumed clients and server already have them set to the specified value. /// When a SyncVar is reset, such as when the object despawns, current values are set to initial values. /// public void SetInitialValues(T value) { _initialValue = value; UpdateValues(value); } /// /// Sets current and previous values. /// /// private void UpdateValues(T next) { _previousClientValue = next; _value = next; } /// /// Sets current value and marks the SyncVar dirty when able to. Returns if able to set value. /// /// True if SetValue was called in response to user code. False if from automated code. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetValue(T nextValue, bool calledByUser, bool sendRpc = false) { /* If not registered then that means Awake * has not completed on the owning class. This would be true * when setting values within awake on the owning class. Registered * is called at the end of awake, so it would be unset until awake completed. * * Registered however will be true when setting from another script, * even if the owning class of this was just spawned. This is because * the unity cycle will fire awake on the object soon as it's spawned, * completing awake, and the user would set the value after. */ if (!base.IsInitialized) return; /* If not client or server then set skipChecks * as true. When neither is true it's likely user is changing * value before object is initialized. This is allowed * but checks cannot be processed because they would otherwise * stop setting the value. */ bool isNetworkInitialized = base.IsNetworkInitialized; //Object is deinitializing. if (isNetworkInitialized && CodegenHelper.NetworkObject_Deinitializing(this.NetworkBehaviour)) return; //If being set by user code. if (calledByUser) { if (!base.CanNetworkSetValues(true)) return; /* We will only be this far if the network is not active yet, * server is active, or client has setting permissions. * We only need to set asServerInvoke to false if the network * is initialized and the server is not active. */ bool asServerInvoke = (!isNetworkInitialized || base.NetworkBehaviour.IsServerStarted); /* If the network has not been network initialized then * Value is expected to be set on server and client since * it's being set before the object is initialized. */ if (!isNetworkInitialized) { T prev = _value; UpdateValues(nextValue); //Still call invoke because change will be cached for when the network initializes. InvokeOnChange(prev, _value, calledByUser); } else { if (Comparers.EqualityCompare(_value, nextValue)) return; T prev = _value; _value = nextValue; InvokeOnChange(prev, _value, asServerInvoke); } TryDirty(asServerInvoke); } //Not called by user. else { /* Previously clients were not allowed to set values * but this has been changed because clients may want * to update values locally while occasionally * letting the syncvar adjust their side. */ T prev = _previousClientValue; if (Comparers.EqualityCompare(prev, nextValue)) return; /* If also server do not update value. * Server side has say of the current value. */ if (!base.NetworkManager.IsServerStarted) UpdateValues(nextValue); else _previousClientValue = nextValue; InvokeOnChange(prev, nextValue, calledByUser); } /* Tries to dirty so update * is sent over network. This needs to be called * anytime the data changes because there is no way * to know if the user set the value on both server * and client or just one side. */ void TryDirty(bool asServer) { //Cannot dirty when network is not initialized. if (!isNetworkInitialized) return; if (asServer) base.Dirty(); //base.Dirty(sendRpc); } } /// /// Invokes OnChanged callback. /// private void InvokeOnChange(T prev, T next, bool asServer) { if (asServer) { if (base.NetworkBehaviour.OnStartServerCalled) OnChange?.Invoke(prev, next, asServer); else _serverOnChange = new CachedOnChange(prev, next); } else { if (base.NetworkBehaviour.OnStartClientCalled) OnChange?.Invoke(prev, next, asServer); else _clientOnChange = new CachedOnChange(prev, next); } } /// /// Called after OnStartXXXX has occurred. /// /// True if OnStartServer was called, false if OnStartClient. [MethodImpl(MethodImplOptions.AggressiveInlining)] [MakePublic] internal protected override void OnStartCallback(bool asServer) { base.OnStartCallback(asServer); if (OnChange != null) { CachedOnChange? change = (asServer) ? _serverOnChange : _clientOnChange; if (change != null) InvokeOnChange(change.Value.Previous, change.Value.Next, asServer); } if (asServer) _serverOnChange = null; else _clientOnChange = null; } /// /// Writes current value. /// /// True to set the next time data may sync. [MakePublic] internal protected override void WriteDelta(PooledWriter writer, bool resetSyncTick = true) { base.WriteDelta(writer, resetSyncTick); writer.Write(_value); } /// /// Writes current value if not initialized value. /// m> [MakePublic] internal protected override void WriteFull(PooledWriter obj0) { /* If a class then skip comparer check. * InitialValue and Value will be the same reference. * * If a struct then compare field changes, since the references * will not be the same. Otherwise comparer normally. */ //Compare if a value type. if (_isValueType) { if (Comparers.EqualityCompare(_initialValue, _value)) return; } /* SyncVars only hold latest value, so just * write current delta. */ WriteDelta(obj0, false); } /// /// Reads a SyncVar value. /// protected internal override void Read(PooledReader reader, bool asServer) { T value = reader.Read(); SetValue(value, false); } /// /// Resets to initialized values. /// [MakePublic] internal protected override void ResetState(bool asServer) { base.ResetState(asServer); _value = _initialValue; _previousClientValue = _initialValue; } } }