/// <summary>
        /// Adds a timeline to the manager.
        /// </summary>
        public void Add(TimelineBase timeline)
        {
            if (timeline._manager == this)
            {
                return;
            }

            if (timeline._manager != null)
            {
                timeline._manager.Remove(timeline);
            }

            timeline._manager = this;
            timeline._index   = _nextTimelineIndex;
            timeline._now     = _now;

            lock (_timelineLock)
            {
                _timelinesById[timeline._id]       = timeline;
                _timelinesByIndex[timeline._index] = timeline;
            }

            Connect(timeline);

            if (_nextTimelineIndex == ushort.MaxValue)
            {
                _nextTimelineIndex = 0;
            }
            else
            {
                _nextTimelineIndex++;
            }
        }
        /// <summary>
        /// Increments the local time and process messages.
        /// </summary>
        /// <param name="deltaTime">The time elapsed since the last step.</param>
        ///

        public void Step(double deltaTime)
        {
            // Used to prevent overcorrection.
            var totalError = _targetOffset - _timeOffset;
            // Used to prevent backwards flow of time.
            var minAllowedCorrection = -deltaTime;

            // Speed of correction is based on the amount of error.
            // This results in an inverse-exponential rate of convergence.
            var correctionRate = totalError * ClockSyncCorrectionFactor;

            // Make sure that the rate of correction is never too low,
            // so as to avoid prolonged periods of "close-but-not-quite".
            // We do this by always correcting "as if" the error is at least a certain size.
            correctionRate = Math.Sign(correctionRate) * Math.Max(MinClockSyncCorrectionRate, Math.Abs(correctionRate));

            var correction = correctionRate * deltaTime;

            correction = Math.Max(Math.Min(correction, totalError), minAllowedCorrection);

            _now        += deltaTime + correction;
            _timeOffset += correction;

            lock (_timelineLock)
            {
                var timelines = new TimelineBase[_timelinesById.Count];
                _timelinesById.Values.CopyTo(timelines, 0);

                foreach (TimelineBase timeline in timelines)
                {
                    timeline._now = _now;
                    timeline.Step();
                }
            }
        }
        /// <summary>
        /// Returns the timeline with a given ID. If it does not exist, null is returned.
        /// </summary>
        public TimelineBase Get(byte[] id)
        {
            TimelineBase timeline = null;

            lock (_timelineLock)
            {
                _timelinesById.TryGetValue(id, out timeline);
                return(timeline);
            }
        }
        /// <summary>
        /// Disconnects a timeline from the network. For internal use only.
        /// </summary>
        internal void Disconnect(TimelineBase timeline)
        {
            timeline._isConnected = false;

            byte[]       data   = new byte[sizeof(ushort)];
            BinaryWriter writer = new BinaryWriter(new MemoryStream(data));

            writer.Write(timeline._index);
            writer.Close();

            EnqueueOutgoingMessage(new TimelineMessage(TimelineMessageType.DisconnectTimeline, data));
        }
        /// <summary>
        /// Removes a timeline from the manager.
        /// </summary>
        public void Remove(TimelineBase timeline)
        {
            if (timeline == null || timeline._manager != this)
            {
                return;
            }

            Disconnect(timeline);

            lock (_timelineLock)
            {
                _timelinesById.Remove(timeline._id);
                _timelinesByIndex.Remove(timeline._index);
            }

            timeline._manager = null;
            timeline._index   = 0;
        }
        /// <summary>
        /// Connects a timeline to the network. For internal use only.
        /// </summary>
        internal void Connect(TimelineBase timeline)
        {
            byte[]       data   = new byte[sizeof(ushort) + timeline._id.Length * sizeof(byte)];
            BinaryWriter writer = new BinaryWriter(new MemoryStream(data));

            writer.Write(timeline._index);
            writer.Write(timeline._id);
            writer.Close();

            EnqueueOutgoingMessage(new TimelineMessage(TimelineMessageType.ConnectTimeline, data));

            timeline._isConnected = true;

            // Send a cache size message if the timeline wants a cache size different from the default.
            if (timeline._cacheSize != TimelineBase._defaultCacheSize)
            {
                timeline.CacheSize = timeline._cacheSize;
            }
        }
        /// <summary>
        /// Returns an array of timelines from the given object which belong to this manager.
        /// </summary>
        /// <param name="obj">The object to inspect.</param>
        /// <returns>An array of timelines.</returns>
        public TimelineBase[] GetTimelines(object obj)
        {
            List <TimelineBase> timelines = new List <TimelineBase>();
            Type objType = obj.GetType();

            // Loop through each field...
            foreach (FieldInfo field in objType.GetFields(
                         BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
            {
                Type fieldType = field.FieldType;

                // Only generate timelines for timeline fields.
                if (!fieldType.IsSubclassOf(typeof(TimelineBase)))
                {
                    continue;
                }

                // Fetch the timeline from the field.
                TimelineBase timeline = (TimelineBase)field.GetValue(obj);

                // Don't add null timelines or timelines which belong to other managers.
                if (timeline == null || timeline._manager != this)
                {
                    continue;
                }

                // Don't add timelines specifically marked with Ignore.
                if (field.GetCustomAttributes(typeof(IgnoreAttribute), false).Length != 0)
                {
                    continue;
                }

                // Add the timeline to the list of timelines to return.
                timelines.Add(timeline);
            }

            return(timelines.ToArray());
        }
        /// <summary>
        /// Remove all Timelines from the Timeline Manager
        /// </summary>
        public void Clear()
        {
            lock (_timelineLock)
            {
                TimelineBase[] timelines = new TimelineBase[_timelinesById.Count];
                _timelinesById.Values.CopyTo(timelines, 0);

                foreach (var timeline in timelines)
                {
                    if (timeline._manager != this)
                    {
                        return;
                    }

                    Disconnect(timeline);

                    timeline._manager = null;
                    timeline._index   = 0;
                }

                _timelinesById.Clear();
                _timelinesByIndex.Clear();
            }
        }
        /// <summary>
        /// Add an object's timeline fields.
        /// </summary>
        /// <param name="obj">The object whose timelines to add.</param>
        /// <param name="generateId">The ID generator to use.</param>
        /// <returns>An array containing the generated timelines.</returns>
        public TimelineBase[] AddTimelines(object obj, TimelineIDGenerator generateId = null)
        {
            List <TimelineBase> timelines = new List <TimelineBase>();
            Type objType = obj.GetType();

            // Loop through each field...
            foreach (FieldInfo field in objType.GetFields(
                         BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
            {
                Type fieldType = field.FieldType;

                // Only generate timelines for timeline fields.
                if (!fieldType.IsSubclassOf(typeof(TimelineBase)))
                {
                    continue;
                }

                // Fetch the timeline from the field.
                TimelineBase timeline = (TimelineBase)field.GetValue(obj);

                // Don't add null timelines or timelines which already have managers.
                if (timeline == null || timeline._manager != null)
                {
                    continue;
                }

                // Don't add timelines specifically marked with Ignore.
                if (field.GetCustomAttributes(typeof(IgnoreAttribute), false).Length != 0)
                {
                    continue;
                }

                timeline.Owner = obj;

                var tagsAttrs = field.GetCustomAttributes(typeof(TimelineTagsAttribute), false);

                // Save timeline tags.
                if (tagsAttrs.Length != 0)
                {
                    var tagsAttr = (TimelineTagsAttribute)tagsAttrs[0];

                    foreach (var tag in tagsAttr.Tags)
                    {
                        timeline._tags.Add(tag);
                    }
                }

                // Generate an ID for the timeline if it doesn't already have one.
                if (timeline._id == null)
                {
                    timeline._id = generateId(field.Name);
                }

                // Add the timeline to this manager.
                Add(timeline);

                // Add the timeline to the list of timelines to return.
                timelines.Add(timeline);
            }

            return(timelines.ToArray());
        }