using FishNet.CodeGenerating;
using FishNet.Managing;
using FishNet.Managing.Timing;
using FishNet.Serializing;
using FishNet.Transporting;
using System.Runtime.CompilerServices;
namespace FishNet.Object.Synchronizing.Internal
{
public class SyncBase
{
#region Public.
///
/// 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.
///
public bool IsInitialized { get; private set; }
///
/// True if the object for which this SyncType is for has been initialized for the network.
///
public bool IsNetworkInitialized => (IsInitialized && (NetworkBehaviour.IsServerStarted || NetworkBehaviour.IsClientStarted));
///
/// True if a SyncObject, false if a SyncVar.
///
public bool IsSyncObject { get; private set; }
///
/// The settings for this SyncVar.
///
[MakePublic]
internal SyncTypeSettings Settings;
///
/// How often updates may send.
///
[MakePublic]
internal float SendRate => Settings.SendRate;
///
/// True if this SyncVar needs to send data.
///
public bool IsDirty { get; private set; }
///
/// NetworkManager this uses.
///
[MakePublic]
internal NetworkManager NetworkManager = null;
///
/// NetworkBehaviour this SyncVar belongs to.
///
[MakePublic]
internal NetworkBehaviour NetworkBehaviour = null;
///
/// True if the server side has initialized this SyncType.
///
public bool OnStartServerCalled { get; private set; }
///
/// True if the client side has initialized this SyncType.
///
public bool OnStartClientCalled { get; private set; }
///
/// 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.
///
[MakePublic]
internal uint NextSyncTick = 0;
///
/// Index within the sync collection.
///
public uint SyncIndex { get; protected set; } = 0;
///
/// Channel to send on.
///
internal Channel Channel => _currentChannel;
///
/// Sets a new currentChannel.
///
///
internal void SetCurrentChannel(Channel channel) => _currentChannel = channel;
#endregion
#region Private.
///
/// Sync interval converted to ticks.
///
private uint _timeToTicks;
///
/// Channel to use for next write. To ensure eventual consistency this eventually changes to reliable when Settings are unreliable.
///
private Channel _currentChannel;
///
/// Last localTick when full data was written.
///
protected uint _lastWriteFullLocalTick;
///
/// Id of the current change since the last full write.
/// This is used to prevent duplicates caused by deltas writing after full writes when clients already received the delta in the full write, such as a spawn message.
///
protected uint _changeId;
///
/// Last changeId read.
///
private long _lastReadDirtyId = DEFAULT_LAST_READ_DIRTYID;
#endregion
#region Const.
///
/// Default value for LastReadDirtyId.
///
private const long DEFAULT_LAST_READ_DIRTYID = -1;
#endregion
#region Constructors
public SyncBase() : this(new SyncTypeSettings()) { }
public SyncBase(SyncTypeSettings settings)
{
Settings = settings;
}
#endregion
///
/// Updates settings with new values.
///
public void UpdateSettings(SyncTypeSettings settings)
{
Settings = settings;
SetTimeToTicks();
}
///
/// Updates settings with new values.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdatePermissions(WritePermission writePermissions, ReadPermission readPermissions)
{
UpdatePermissions(writePermissions);
UpdatePermissions(readPermissions);
}
///
/// Updates settings with new values.
///
public void UpdatePermissions(WritePermission writePermissions) => Settings.WritePermission = writePermissions;
///
/// Updates settings with new values.
///
public void UpdatePermissions(ReadPermission readPermissions) => Settings.ReadPermission = readPermissions;
///
/// Updates settings with new values.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateSendRate(float sendRate)
{
Settings.SendRate = sendRate;
SetTimeToTicks();
}
///
/// Updates settings with new values.
///
public void UpdateSettings(Channel channel)
{
CheckChannel(ref channel);
_currentChannel = channel;
}
///
/// Updates settings with new values.
///
public void UpdateSettings(WritePermission writePermissions, ReadPermission readPermissions, float sendRate, Channel channel)
{
CheckChannel(ref channel);
_currentChannel = channel;
Settings = new SyncTypeSettings(writePermissions, readPermissions, sendRate, channel);
SetTimeToTicks();
}
///
/// Checks channel and corrects if not valid.
///
///
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);
}
}
///
/// Initializes this SyncBase before user Awake code.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MakePublic]
internal void InitializeEarly(NetworkBehaviour nb, uint syncIndex, bool isSyncObject)
{
NetworkBehaviour = nb;
SyncIndex = syncIndex;
IsSyncObject = isSyncObject;
NetworkBehaviour.RegisterSyncType(this, SyncIndex);
}
///
/// Called during InitializeLate in NetworkBehaviours to indicate user Awake code has executed.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MakePublic]
internal void InitializeLate()
{
Initialized();
}
///
/// Called when the SyncType has been registered, but not yet initialized over the network.
///
protected virtual void Initialized()
{
IsInitialized = true;
}
///
/// PreInitializes this for use with the network.
///
[MakePublic]
internal protected void PreInitialize(NetworkManager networkManager)
{
NetworkManager = networkManager;
SetTimeToTicks();
}
///
/// Sets ticks needed to pass for send rate.
///
private void SetTimeToTicks()
{
if (NetworkManager == null)
return;
_timeToTicks = NetworkManager.TimeManager.TimeToTicks(Settings.SendRate, TickRounding.RoundUp);
}
///
/// Called after OnStartXXXX has occurred for the NetworkBehaviour.
///
/// True if OnStartServer was called, false if OnStartClient.
[MakePublic]
internal protected virtual void OnStartCallback(bool asServer)
{
if (asServer)
OnStartServerCalled = true;
else
OnStartClientCalled = true;
}
///
/// Called before OnStopXXXX has occurred for the NetworkBehaviour.
///
/// True if OnStopServer was called, false if OnStopClient.
[MakePublic]
internal protected virtual void OnStopCallback(bool asServer)
{
if (asServer)
OnStartServerCalled = false;
else
OnStartClientCalled = false;
}
///
/// True if can set values and send them over the network.
///
///
///
protected bool CanNetworkSetValues(bool warn = 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 && warn)
LogServerNotActiveWarning();
return result;
}
///
/// Logs that the operation could not be completed because the server is not active.
///
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()}.");
}
///
/// Dirties this Sync and the NetworkBehaviour.
///
/// 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.
public 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();
//If first time dirtying increase dirtyId.
if (IsDirty != canDirty)
_changeId++;
IsDirty |= canDirty;
return canDirty;
}
///
/// Reads the change Id and returns if changes should be ignored.
///
///
protected bool ReadChangeId(PooledReader reader)
{
bool reset = reader.ReadBoolean();
uint id = reader.ReadUInt32();
bool ignoreResults = !reset && (id <= _lastReadDirtyId);
_lastReadDirtyId = id;
return ignoreResults;
}
///
/// Writers the current ChangeId, and if it has been reset.
///
protected void WriteChangeId(PooledWriter writer, bool fullWrite)
{
/* Fullwrites do not reset the Id, only
* delta changes do. */
bool resetId = (!fullWrite && NetworkManager.TimeManager.LocalTick > _lastWriteFullLocalTick);
writer.WriteBoolean(resetId);
//If to reset Id then do so.
if (resetId)
_changeId = 0;
//Write Id.
writer.WriteUInt32(_changeId);
}
///
/// Sets IsDirty to false.
///
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;
}
}
///
/// True if dirty and enough time has passed to write changes.
///
///
///
internal bool SyncTimeMet(uint tick)
{
return (IsDirty && tick >= NextSyncTick);
}
///
/// Writes current value.
///
/// True to set the next time data may sync.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MakePublic]
internal protected virtual void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
{
WriteHeader(writer, resetSyncTick);
}
///
/// Writers the header for this SyncType.
///
protected virtual void WriteHeader(PooledWriter writer, bool resetSyncTick = true)
{
if (resetSyncTick)
NextSyncTick = NetworkManager.TimeManager.LocalTick + _timeToTicks;
writer.WriteByte((byte)SyncIndex);
}
///
/// Indicates that a full write has occurred.
/// This is called from WriteFull, or can be called manually.
///
protected void FullWritten()
{
_lastWriteFullLocalTick = NetworkManager.TimeManager.LocalTick;
}
///
/// Writes all values for the SyncType.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MakePublic]
internal protected virtual void WriteFull(PooledWriter writer)
{
FullWritten();
}
///
/// Sets current value as server or client through deserialization.
///
[MakePublic]
internal protected virtual void Read(PooledReader reader, bool asServer) { }
///
/// Resets initialized values for server and client.
///
internal protected virtual void ResetState()
{
ResetState(true);
ResetState(false);
}
///
/// Resets initialized values for server or client.
///
[MakePublic]
internal protected virtual void ResetState(bool asServer)
{
if (asServer)
{
_lastWriteFullLocalTick = 0;
_changeId = 0;
NextSyncTick = 0;
SetCurrentChannel(Settings.Channel);
IsDirty = false;
}
else
{
_lastReadDirtyId = DEFAULT_LAST_READ_DIRTYID;
}
}
}
}