291 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FishNet.Documenting;
 | |
| using FishNet.Object.Helping;
 | |
| using FishNet.Object.Synchronizing;
 | |
| using FishNet.Object.Synchronizing.Internal;
 | |
| using FishNet.Serializing;
 | |
| using FishNet.Serializing.Helping;
 | |
| using FishNet.Transporting;
 | |
| using System;
 | |
| using System.Runtime.CompilerServices;
 | |
| using System.Runtime.InteropServices;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace FishNet.Object.Synchronizing
 | |
| {
 | |
|     [APIExclude]
 | |
|     [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
 | |
|     public class SyncVar<T> : SyncBase
 | |
|     {
 | |
|         #region Types.
 | |
|         /// <summary>
 | |
|         /// Information needed to invoke a callback.
 | |
|         /// </summary>
 | |
|         private struct CachedOnChange
 | |
|         {
 | |
|             internal readonly T Previous;
 | |
|             internal readonly T Next;
 | |
| 
 | |
|             public CachedOnChange(T previous, T next)
 | |
|             {
 | |
|                 Previous = previous;
 | |
|                 Next = next;
 | |
|             }
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Public.
 | |
|         /// <summary>
 | |
|         /// Called when the SyncDictionary changes.
 | |
|         /// </summary>
 | |
|         public event Action<T, T, bool> OnChange;
 | |
|         #endregion
 | |
| 
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// Server OnChange event waiting for start callbacks.
 | |
|         /// </summary>
 | |
|         private CachedOnChange? _serverOnChange;
 | |
|         /// <summary>
 | |
|         /// Client OnChange event waiting for start callbacks.
 | |
|         /// </summary>
 | |
|         private CachedOnChange? _clientOnChange;
 | |
|         /// <summary>
 | |
|         /// Value before the network is initialized on the containing object.
 | |
|         /// </summary>
 | |
|         private T _initialValue;
 | |
|         /// <summary>
 | |
|         /// Previous value on the client.
 | |
|         /// </summary>
 | |
|         private T _previousClientValue;
 | |
|         /// <summary>
 | |
|         /// Current value on the server, or client.
 | |
|         /// </summary>
 | |
|         private T _value;
 | |
|         #endregion
 | |
| 
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         public SyncVar(NetworkBehaviour nb, uint syncIndex, WritePermission writePermission, ReadPermission readPermission, float sendRate, Channel channel, T value)
 | |
|         {
 | |
|             SetInitialValues(value);
 | |
|             base.InitializeInstance(nb, syncIndex, writePermission, readPermission, sendRate, channel, false);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when the SyncType has been registered, but not yet initialized over the network.
 | |
|         /// </summary>
 | |
|         protected override void Registered()
 | |
|         {
 | |
|             base.Registered();
 | |
|             _initialValue = _value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets initial values to next.
 | |
|         /// </summary>
 | |
|         /// <param name="next"></param>
 | |
|         private void SetInitialValues(T next)
 | |
|         {
 | |
|             _initialValue = next;
 | |
|             UpdateValues(next);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Sets current and previous values.
 | |
|         /// </summary>
 | |
|         /// <param name="next"></param>
 | |
|         private void UpdateValues(T next)
 | |
|         {
 | |
|             _previousClientValue = next;
 | |
|             _value = next;
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Sets current value and marks the SyncVar dirty when able to. Returns if able to set value.
 | |
|         /// </summary>
 | |
|         /// <param name="calledByUser">True if SetValue was called in response to user code. False if from automated code.</param>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         public void SetValue(T nextValue, bool calledByUser)
 | |
|         {
 | |
|             /* 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.IsRegistered)
 | |
|                 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.IsServer);
 | |
| 
 | |
|                 /* 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<T>(_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<T>(prev, nextValue))
 | |
|                     return;
 | |
| 
 | |
|                 /* If also server do not update value.
 | |
|                  * Server side has say of the current value. */
 | |
|                 if (!base.NetworkManager.IsServer)
 | |
|                     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();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Invokes OnChanged callback.
 | |
|         /// </summary>
 | |
|         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);
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called after OnStartXXXX has occurred.
 | |
|         /// </summary>
 | |
|         /// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         public 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;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes current value.
 | |
|         /// </summary>
 | |
|         /// <param name="resetSyncTick">True to set the next time data may sync.</param>
 | |
|         public override void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
 | |
|         {
 | |
|             base.WriteDelta(writer, resetSyncTick);
 | |
|             writer.Write<T>(_value);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes current value if not initialized value.
 | |
|         /// </summary>m>
 | |
|         public override void WriteFull(PooledWriter obj0)
 | |
|         {
 | |
|             if (Comparers.EqualityCompare<T>(_initialValue, _value))
 | |
|                 return;
 | |
|             /* SyncVars only hold latest value, so just
 | |
|              * write current delta. */
 | |
|             WriteDelta(obj0, false);
 | |
|         }
 | |
| 
 | |
|         //Read isn't used by SyncVar<T>, it's done within the NB.
 | |
|         //public override void Read(PooledReader reader) { }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Gets current value.
 | |
|         /// </summary>
 | |
|         /// <param name="calledByUser"></param>
 | |
|         /// <returns></returns>
 | |
|         public T GetValue(bool calledByUser) => (calledByUser) ? _value : _previousClientValue;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Resets to initialized values.
 | |
|         /// </summary>
 | |
|         public override void Reset()
 | |
|         {
 | |
|             base.Reset();
 | |
|             _value = _initialValue;
 | |
|             _previousClientValue = _initialValue;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 |