[Add] FishNet
This commit is contained in:
@@ -0,0 +1,543 @@
|
||||
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.IsServerInitialized || NetworkBehaviour.IsClientInitialized);
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user