mirror of
https://github.com/Dadechin/Unity-WebSocket.git
synced 2025-07-03 11:54:35 +00:00
552 lines
20 KiB
C#
552 lines
20 KiB
C#
using System;
|
|
using FishNet.CodeGenerating;
|
|
using FishNet.Managing;
|
|
using FishNet.Managing.Timing;
|
|
using FishNet.Serializing;
|
|
using FishNet.Transporting;
|
|
using System.Runtime.CompilerServices;
|
|
using UnityEngine;
|
|
|
|
namespace FishNet.Object.Synchronizing.Internal
|
|
{
|
|
public class SyncBase
|
|
{
|
|
#region Public.
|
|
/// <summary>
|
|
/// True if this SyncBase has been initialized on its NetworkBehaviour.
|
|
/// Being true does not mean that the NetworkBehaviour has been initialized on the network, but rather that this SyncBase has been configured with the basics to be networked.
|
|
/// </summary>
|
|
public bool IsInitialized { get; private set; }
|
|
/// <summary>
|
|
/// True if the object for which this SyncType is for has been initialized for the network.
|
|
/// </summary>
|
|
public bool IsNetworkInitialized => (IsInitialized && (NetworkBehaviour.IsServerStarted || NetworkBehaviour.IsClientStarted));
|
|
/// <summary>
|
|
/// True if a SyncObject, false if a SyncVar.
|
|
/// </summary>
|
|
public bool IsSyncObject { get; private set; }
|
|
/// <summary>
|
|
/// The settings for this SyncVar.
|
|
/// </summary>
|
|
[MakePublic]
|
|
internal SyncTypeSettings Settings;
|
|
/// <summary>
|
|
/// How often updates may send.
|
|
/// </summary>
|
|
[MakePublic]
|
|
internal float SendRate => Settings.SendRate;
|
|
/// <summary>
|
|
/// True if this SyncVar needs to send data.
|
|
/// </summary>
|
|
public bool IsDirty { get; private set; }
|
|
/// <summary>
|
|
/// NetworkManager this uses.
|
|
/// </summary>
|
|
public NetworkManager NetworkManager = null;
|
|
/// <summary>
|
|
/// NetworkBehaviour this SyncVar belongs to.
|
|
/// </summary>
|
|
public NetworkBehaviour NetworkBehaviour = null;
|
|
/// <summary>
|
|
/// True if the server side has initialized this SyncType.
|
|
/// </summary>
|
|
public bool OnStartServerCalled { get; private set; }
|
|
/// <summary>
|
|
/// True if the client side has initialized this SyncType.
|
|
/// </summary>
|
|
public bool OnStartClientCalled { get; private set; }
|
|
/// <summary>
|
|
/// Next time this SyncType may send data.
|
|
/// This is also the next time a client may send to the server when using client-authoritative SyncTypes.
|
|
/// </summary>
|
|
[MakePublic]
|
|
internal uint NextSyncTick = 0;
|
|
/// <summary>
|
|
/// Index within the sync collection.
|
|
/// </summary>
|
|
public uint SyncIndex { get; protected set; } = 0;
|
|
/// <summary>
|
|
/// Channel to send on.
|
|
/// </summary>
|
|
internal Channel Channel => _currentChannel;
|
|
|
|
/// <summary>
|
|
/// Sets a new currentChannel.
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
internal void SetCurrentChannel(Channel channel) => _currentChannel = channel;
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// Sync interval converted to ticks.
|
|
/// </summary>
|
|
private uint _timeToTicks;
|
|
/// <summary>
|
|
/// Channel to use for next write. To ensure eventual consistency this eventually changes to reliable when Settings are unreliable.
|
|
/// </summary>
|
|
private Channel _currentChannel;
|
|
/// <summary>
|
|
/// Last changerId read from sender.
|
|
/// </summary>
|
|
private ushort _lastReadChangeId = UNSET_CHANGE_ID;
|
|
/// <summary>
|
|
/// Last changeId that was sent to receivers.
|
|
/// </summary>
|
|
private ushort _lastWrittenChangeId = UNSET_CHANGE_ID;
|
|
#endregion
|
|
|
|
#region Consts.
|
|
/// <summary>
|
|
/// Value to use when readId is unset.
|
|
/// </summary>
|
|
private const ushort UNSET_CHANGE_ID = 0;
|
|
/// <summary>
|
|
/// Maximum value readId can be before resetting to the beginning.
|
|
/// </summary>
|
|
private const ushort MAXIMUM_CHANGE_ID = ushort.MaxValue;
|
|
#endregion
|
|
|
|
#region Constructors
|
|
public SyncBase() : this(new()) { }
|
|
|
|
public SyncBase(SyncTypeSettings settings)
|
|
{
|
|
Settings = settings;
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Updates settings with new values.
|
|
/// </summary>
|
|
public void UpdateSettings(SyncTypeSettings settings)
|
|
{
|
|
Settings = settings;
|
|
SetTimeToTicks();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates settings with new values.
|
|
/// </summary>
|
|
public void UpdatePermissions(WritePermission writePermissions, ReadPermission readPermissions)
|
|
{
|
|
UpdatePermissions(writePermissions);
|
|
UpdatePermissions(readPermissions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates settings with new values.
|
|
/// </summary>
|
|
public void UpdatePermissions(WritePermission writePermissions) => Settings.WritePermission = writePermissions;
|
|
|
|
/// <summary>
|
|
/// Updates settings with new values.
|
|
/// </summary>
|
|
public void UpdatePermissions(ReadPermission readPermissions) => Settings.ReadPermission = readPermissions;
|
|
|
|
/// <summary>
|
|
/// Updates settings with new values.
|
|
/// </summary>
|
|
public void UpdateSendRate(float sendRate)
|
|
{
|
|
Settings.SendRate = sendRate;
|
|
SetTimeToTicks();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates settings with new values.
|
|
/// </summary>
|
|
public void UpdateSettings(Channel channel)
|
|
{
|
|
CheckChannel(ref channel);
|
|
_currentChannel = channel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates settings with new values.
|
|
/// </summary>
|
|
public void UpdateSettings(WritePermission writePermissions, ReadPermission readPermissions, float sendRate, Channel channel)
|
|
{
|
|
CheckChannel(ref channel);
|
|
_currentChannel = channel;
|
|
Settings = new(writePermissions, readPermissions, sendRate, channel);
|
|
SetTimeToTicks();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks channel and corrects if not valid.
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
private void CheckChannel(ref Channel c)
|
|
{
|
|
if (c == Channel.Unreliable && IsSyncObject)
|
|
{
|
|
c = Channel.Reliable;
|
|
string warning = $"Channel cannot be unreliable for SyncObjects. Channel has been changed to reliable.";
|
|
NetworkManager.LogWarning(warning);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes this SyncBase before user Awake code.
|
|
/// </summary>
|
|
[MakePublic]
|
|
internal void InitializeEarly(NetworkBehaviour nb, uint syncIndex, bool isSyncObject)
|
|
{
|
|
NetworkBehaviour = nb;
|
|
SyncIndex = syncIndex;
|
|
IsSyncObject = isSyncObject;
|
|
|
|
NetworkBehaviour.RegisterSyncType(this, SyncIndex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called during InitializeLate in NetworkBehaviours to indicate user Awake code has executed.
|
|
/// </summary>
|
|
[MakePublic]
|
|
internal void InitializeLate()
|
|
{
|
|
Initialized();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
|
/// </summary>
|
|
protected virtual void Initialized()
|
|
{
|
|
IsInitialized = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// PreInitializes this for use with the network.
|
|
/// </summary>
|
|
[MakePublic]
|
|
protected internal void PreInitialize(NetworkManager networkManager, bool asServer)
|
|
{
|
|
NetworkManager = networkManager;
|
|
|
|
if (Settings.IsDefault())
|
|
{
|
|
float sendRate = Mathf.Max(networkManager.ServerManager.GetSyncTypeRate(), (float)networkManager.TimeManager.TickDelta);
|
|
Settings = new(sendRate);
|
|
}
|
|
|
|
SetTimeToTicks();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets ticks needed to pass for send rate.
|
|
/// </summary>
|
|
private void SetTimeToTicks()
|
|
{
|
|
if (NetworkManager == null)
|
|
return;
|
|
_timeToTicks = NetworkManager.TimeManager.TimeToTicks(Settings.SendRate, TickRounding.RoundUp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called after OnStartXXXX has occurred for the NetworkBehaviour.
|
|
/// </summary>
|
|
/// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
|
[MakePublic]
|
|
protected internal virtual void OnStartCallback(bool asServer)
|
|
{
|
|
if (asServer)
|
|
OnStartServerCalled = true;
|
|
else
|
|
OnStartClientCalled = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called before OnStopXXXX has occurred for the NetworkBehaviour.
|
|
/// </summary>
|
|
/// <param name="asServer">True if OnStopServer was called, false if OnStopClient.</param>
|
|
[MakePublic]
|
|
protected internal virtual void OnStopCallback(bool asServer)
|
|
{
|
|
if (asServer)
|
|
OnStartServerCalled = false;
|
|
else
|
|
OnStartClientCalled = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if can set values and send them over the network.
|
|
/// </summary>
|
|
/// <param name="log"></param>
|
|
/// <returns></returns>
|
|
protected bool CanNetworkSetValues(bool log = true)
|
|
{
|
|
/* If not registered then values can be set
|
|
* since at this point the object is still being initialized
|
|
* in awake so we want those values to be applied. */
|
|
if (!IsInitialized)
|
|
return true;
|
|
/* If the network is not initialized yet then let
|
|
* values be set. Values set here will not synchronize
|
|
* to the network. We are assuming the user is setting
|
|
* these values on client and server appropriately
|
|
* since they are being applied prior to this object
|
|
* being networked. */
|
|
if (!IsNetworkInitialized)
|
|
return true;
|
|
//If server is active then values can be set no matter what.
|
|
if (NetworkBehaviour.IsServerStarted)
|
|
return true;
|
|
/* If here then server is not active and additional
|
|
* checks must be performed. */
|
|
bool result = (Settings.WritePermission == WritePermission.ClientUnsynchronized) || (Settings.ReadPermission == ReadPermission.ExcludeOwner && NetworkBehaviour.IsOwner);
|
|
if (!result && log)
|
|
LogServerNotActiveWarning();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs that the operation could not be completed because the server is not active.
|
|
/// </summary>
|
|
protected void LogServerNotActiveWarning()
|
|
{
|
|
if (NetworkManager != null)
|
|
NetworkManager.LogWarning($"Cannot complete operation as server when server is not active. You can disable this warning by setting WritePermissions to {WritePermission.ClientUnsynchronized.ToString()}.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dirties this Sync and the NetworkBehaviour.
|
|
/// </summary>
|
|
/// <param name="sendRpc">True to send current dirtied values immediately as a RPC. When this occurs values will arrive in the order they are sent and interval is ignored.</param>
|
|
protected bool Dirty() //bool sendRpc = false)
|
|
{
|
|
//if (sendRpc)
|
|
// NextSyncTick = 0;
|
|
/* Reset channel even if already dirty.
|
|
* This is because the value might have changed
|
|
* which will reset the eventual consistency state. */
|
|
_currentChannel = Settings.Channel;
|
|
|
|
/* Once dirty don't undirty until it's
|
|
* processed. This ensures that data
|
|
* is flushed. */
|
|
bool canDirty = NetworkBehaviour.DirtySyncType();
|
|
IsDirty |= canDirty;
|
|
|
|
return canDirty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns if callbacks can be invoked with asServer ture.
|
|
/// This is typically used when the value is changing through user code, causing supplier to be unknown.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected bool CanInvokeCallbackAsServer() => (!IsNetworkInitialized || NetworkBehaviour.IsServerStarted);
|
|
|
|
/// <summary>
|
|
/// Reads a change Id and returns true if the change is new.
|
|
/// </summary>
|
|
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
|
protected virtual bool ReadChangeId(Reader reader)
|
|
{
|
|
if (NetworkManager == null)
|
|
{
|
|
NetworkManager.LogWarning($"NetworkManager is unexpectedly null during a SyncType read.");
|
|
return false;
|
|
}
|
|
|
|
bool rolledOver = reader.ReadBoolean();
|
|
ushort id = reader.ReadUInt16();
|
|
|
|
//Only check lastReadId if its not unset.
|
|
if (_lastReadChangeId != UNSET_CHANGE_ID)
|
|
{
|
|
/* If not rolledOver then Id should always be larger
|
|
* than the last read. If it's not then the data is
|
|
* old.
|
|
*
|
|
* If Id is smaller then rolledOver should be normal,
|
|
* as rolling over means to restart the Id from the lowest
|
|
* value. */
|
|
if (rolledOver)
|
|
{
|
|
if (id >= _lastReadChangeId)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (id <= _lastReadChangeId)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
_lastReadChangeId = id;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the readId for a change.
|
|
/// </summary>
|
|
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
|
protected virtual void WriteChangeId(PooledWriter writer)
|
|
{
|
|
bool rollOver;
|
|
if (_lastWrittenChangeId >= MAXIMUM_CHANGE_ID)
|
|
{
|
|
rollOver = true;
|
|
_lastWrittenChangeId = UNSET_CHANGE_ID;
|
|
}
|
|
else
|
|
{
|
|
rollOver = false;
|
|
}
|
|
|
|
_lastWrittenChangeId++;
|
|
writer.WriteBoolean(rollOver);
|
|
writer.WriteUInt16(_lastWrittenChangeId);
|
|
}
|
|
|
|
#if !FISHNET_STABLE_SYNCTYPES
|
|
/// <summary>
|
|
/// Returns true if values are being read as clientHost.
|
|
/// </summary>
|
|
/// <param name="asServer">True if reading as server.</param>
|
|
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
|
protected bool IsReadAsClientHost(bool asServer) => (!asServer && NetworkManager.IsServerStarted);
|
|
|
|
/// <summary>
|
|
/// Returns true if values are being read as clientHost.
|
|
/// </summary>
|
|
/// <param name="asServer">True if reading as server.</param>
|
|
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
|
protected bool CanReset(bool asServer)
|
|
{
|
|
bool clientStarted = (IsNetworkInitialized && NetworkManager.IsClientStarted);
|
|
return (asServer && !clientStarted) || (!asServer && NetworkBehaviour.IsDeinitializing);
|
|
}
|
|
#else
|
|
/// <summary>
|
|
/// Returns true if values are being read as clientHost.
|
|
/// </summary>
|
|
/// <param name="asServer">True if reading as server.</param>
|
|
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
|
protected bool IsReadAsClientHost(bool asServer) => (!asServer && (NetworkManager != null && NetworkManager.IsServerStarted));
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Outputs values which may be helpful on how to process a read operation.
|
|
/// </summary>
|
|
/// <param name="newChangeId">True if the changeId read is not old data.</param>
|
|
/// <param name="asClientHost">True if being read as clientHost.</param>
|
|
/// <param name="canModifyValues">True if can modify values from the read, typically when asServer or not asServer and not clientHost.</param>
|
|
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
|
protected void SetReadArguments(PooledReader reader, bool asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues)
|
|
{
|
|
newChangeId = ReadChangeId(reader);
|
|
asClientHost = IsReadAsClientHost(asServer);
|
|
canModifyValues = (newChangeId && !asClientHost);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets IsDirty to false.
|
|
/// </summary>
|
|
internal void ResetDirty()
|
|
{
|
|
//If not a sync object and using unreliable channel.
|
|
if (!IsSyncObject && Settings.Channel == Channel.Unreliable)
|
|
{
|
|
//Check if dirty can be unset or if another tick must be run using reliable.
|
|
if (_currentChannel == Channel.Unreliable)
|
|
_currentChannel = Channel.Reliable;
|
|
//Already sent reliable, can undirty. Channel will reset next time this dirties.
|
|
else
|
|
IsDirty = false;
|
|
}
|
|
//If syncObject or using reliable unset dirty.
|
|
else
|
|
{
|
|
IsDirty = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if dirty and enough time has passed to write changes.
|
|
/// </summary>
|
|
internal bool IsNextSyncTimeMet(uint tick) => (IsDirty && tick >= NextSyncTick);
|
|
|
|
[Obsolete("Use IsNextSyncTimeMet.")] //Remove on V5
|
|
internal bool SyncTimeMet(uint tick) => IsNextSyncTimeMet(tick);
|
|
|
|
/// <summary>
|
|
/// Writes current value.
|
|
/// </summary>
|
|
/// <param name="resetSyncTick">True to set the next time data may sync.</param>
|
|
[MakePublic]
|
|
protected internal virtual void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
|
{
|
|
WriteHeader(writer, resetSyncTick);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the header for this SyncType.
|
|
/// </summary>
|
|
protected virtual void WriteHeader(PooledWriter writer, bool resetSyncTick = true)
|
|
{
|
|
if (resetSyncTick)
|
|
NextSyncTick = NetworkManager.TimeManager.LocalTick + _timeToTicks;
|
|
|
|
writer.WriteUInt8Unpacked((byte)SyncIndex);
|
|
WriteChangeId(writer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates that a full write has occurred.
|
|
/// This is called from WriteFull, or can be called manually.
|
|
/// </summary>
|
|
[Obsolete("This method no longer functions. You may remove it from your code.")] //Remove on V5.
|
|
protected void FullWritten() { }
|
|
|
|
/// <summary>
|
|
/// Writes all values for the SyncType.
|
|
/// </summary>
|
|
[MakePublic]
|
|
protected internal virtual void WriteFull(PooledWriter writer) { }
|
|
|
|
/// <summary>
|
|
/// Sets current value as server or client through deserialization.
|
|
/// </summary>
|
|
[MakePublic]
|
|
protected internal virtual void Read(PooledReader reader, bool asServer) { }
|
|
|
|
/// <summary>
|
|
/// Resets initialized values for server and client.
|
|
/// </summary>
|
|
protected internal virtual void ResetState()
|
|
{
|
|
ResetState(true);
|
|
ResetState(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets initialized values for server or client.
|
|
/// </summary>
|
|
[MakePublic]
|
|
protected internal virtual void ResetState(bool asServer)
|
|
{
|
|
if (asServer)
|
|
{
|
|
NextSyncTick = 0;
|
|
SetCurrentChannel(Settings.Channel);
|
|
IsDirty = false;
|
|
}
|
|
|
|
/* This only needs to be reset for clients, since
|
|
* it only applies to clients. But if the server is resetting
|
|
* that means the object is deinitializing, and won't have any
|
|
* client observers anyway. Because of this it's safe to reset
|
|
* with asServer true, or false.
|
|
*
|
|
* This change is made to resolve a bug where asServer:false
|
|
* sometimes does not invoke when stopping clientHost while not
|
|
* also stopping play mode. */
|
|
_lastReadChangeId = UNSET_CHANGE_ID;
|
|
}
|
|
}
|
|
} |