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