#if FISHNET_STABLE_SYNCTYPES using FishNet.CodeGenerating; using FishNet.Documenting; using FishNet.Managing; 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 { internal interface ISyncVar { } [APIExclude] [System.Serializable] [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)] public class SyncVar : SyncBase, ISyncVar { #region Types. public struct InterpolationContainer { /// /// Value prior to setting new. /// public T LastValue; /// /// Tick when LastValue was set. /// public float UpdateTime; public void Update(T prevValue) { LastValue = prevValue; UpdateTime = Time.unscaledTime; } } /// /// 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. /// /// Value interpolated between last received and current. /// /// True if to ignore interpolated calculations and use the current value. /// This can be useful if you are able to write this SyncVars values in update. /// public T InterpolatedValue(bool useCurrentValue = false) { if (useCurrentValue) return _value; float diff = (Time.unscaledTime - _interpolator.UpdateTime); float percent = Mathf.InverseLerp(0f, base.Settings.SendRate, diff); return Interpolate(_interpolator.LastValue, _value, percent); } /// /// 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; /// /// Holds information about interpolating between values. /// private InterpolationContainer _interpolator = new(); /// /// True if T IsValueType. /// private bool _isValueType; /// /// True if value was ever set after the SyncType initialized. /// This is true even if SetInitialValues was called at runtime. /// private bool _valueSetAfterInitialized; #endregion #region Constructors. public SyncVar(SyncTypeSettings settings = new()) : this(default(T), settings) { } public SyncVar(T initialValue, SyncTypeSettings settings = new()) : 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, true); if (base.IsInitialized) _valueSetAfterInitialized = true; } /// /// Sets current and previous values. /// /// private void UpdateValues(T next, bool updateClient) { if (updateClient) _previousClientValue = next; //If network initialized then update interpolator. if (base.IsNetworkInitialized) _interpolator.Update(_value); _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. internal void SetValue(T nextValue, bool calledByUser, bool sendRpc = false) { /* IsInitialized is only set after the script containing this SyncVar * has executed our codegen in the beginning of awake, and after awake * user logic. When not set update the initial values */ if (!base.IsInitialized) { SetInitialValues(nextValue); return; } else { _valueSetAfterInitialized = true; } /* 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 = CanInvokeCallbackAsServer(); /* 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, false); //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; UpdateValues(nextValue, false); 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) _previousClientValue = nextValue; /* If server is not started then update both. */ else UpdateValues(nextValue, true); 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); } } /// /// Returns interpolated values between previous and current using a percentage. /// protected virtual T Interpolate(T previous, T current, float percent) { base.NetworkManager.LogError($"Type {typeof(T).FullName} does not support interpolation. Implement a supported type class or create your own. See class FloatSyncVar for an example."); return default; } /// /// True if callback can be invoked with asServer true. /// /// private bool AsServerInvoke() => (!base.IsNetworkInitialized || base.NetworkBehaviour.IsServerStarted); /// /// Dirties the the syncVar for a full send. /// public void DirtyAll() { if (!base.IsInitialized) return; if (!base.CanNetworkSetValues(true)) return; base.Dirty(); /* Invoke even if was unable to dirty. Dirtying only * becomes true if server is running, but also if there are * observers. Even if there are not observers we still want * to invoke for the server side. */ //todo: this behaviour needs to be done for all synctypes with dirt/dirtyall. bool asServerInvoke = CanInvokeCallbackAsServer(); InvokeOnChange(_value, _value, asServerInvoke); } /// /// 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. [MakePublic] protected internal 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] protected internal override void WriteDelta(PooledWriter writer, bool resetSyncTick = true) { base.WriteDelta(writer, resetSyncTick); writer.Write(_value); } /// /// Writes current value if not initialized value. /// m> [MakePublic] protected internal override void WriteFull(PooledWriter obj0) { /* If a class then skip comparer check. * InitialValue and Value will be the same reference. * * If a value then compare field changes, since the references * will not be the same. */ //Compare if a value type. if (_isValueType) { if (Comparers.EqualityCompare(_initialValue, _value)) return; } else { if (!_valueSetAfterInitialized) 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(); if (!ReadChangeId(reader)) return; SetValue(value, false); //TODO this needs to separate invokes from setting values so that syncvar can be written like remainder of synctypes. } //SyncVars do not use changeId. [APIExclude] protected override bool ReadChangeId(Reader reader) => true; //SyncVars do not use changeId. [APIExclude] protected override void WriteChangeId(PooledWriter writer) { } /// /// Resets to initialized values. /// [MakePublic] protected internal override void ResetState(bool asServer) { base.ResetState(asServer); /* Only full reset under the following conditions: * asServer is true. * Is not network initialized. * asServer is false, and server is not started. */ bool clientStarted = (base.IsNetworkInitialized && base.NetworkManager.IsClientStarted); if ((asServer && !clientStarted) || (!asServer && base.NetworkBehaviour.IsDeinitializing)) { _value = _initialValue; _previousClientValue = _initialValue; _valueSetAfterInitialized = false; } } } } #endif