#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.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; /// /// Current value on the server, or client. /// [SerializeField] private T _value; /// /// Holds information about interpolating between values. /// private InterpolationContainer _interpolator = new(); /// /// 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(); _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; /* Only update current if a value has not been set already. * A value normally would not be set unless a SyncVar came through * as the object was enabling, such as if it started in a disabled state * and was later enabled. */ if (!_valueSetAfterInitialized) UpdateValues(value); if (base.IsInitialized) _valueSetAfterInitialized = true; } /// /// Sets current and previous values. /// /// private void UpdateValues(T 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); //Still call invoke because change will be cached for when the network initializes. InvokeOnChange(prev, _value, asServer: true); } else { if (Comparers.EqualityCompare(_value, nextValue)) return; T prev = _value; UpdateValues(nextValue); InvokeOnChange(prev, _value, asServerInvoke); } TryDirty(asServerInvoke); } //Not called by user. else { /* Only perform the equality checks when not host. * * In the previous SyncVar version it was okay to call * this on host because a separate clientHost value was kept for * the client side, and that was compared against. * * In newer SyncVar(this one) a client side copy is * not kept so when compariing against the current vlaue * as clientHost, it will always return as matched. * * But it's impossible for clientHost to send a value * and it not have changed, so this check is not needed. */ // /* 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 = _value; // if (Comparers.EqualityCompare(prev, nextValue)) // return; T prev = _value; /* If also server do not update value. * Server side has say of the current value. */ /* Only update value if not server. We do not want * clientHost overwriting servers current with what * they just received.*/ if (!base.NetworkManager.IsServerStarted) UpdateValues(nextValue); InvokeOnChange(prev, nextValue, asServer: false); } /* 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(log: true)) return; //Also set that values have changed since the user is forcing a sync. _valueSetAfterInitialized = true; 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; // } 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. */ if (base.CanReset(asServer)) { _value = _initialValue; _valueSetAfterInitialized = false; } } } } #endif