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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|