public DeltaUpdate ReadDeltaUpdate()
        {
            if (!_playing)
            {
                return(null);
            }

            if (_playbackDeltaUpdates.Count == 0)
            {
                return(null);
            }

            DeltaUpdate deltaUpdate = _playbackDeltaUpdates.Peek();

            // If this update is newer than the current playback time, bail
            if (deltaUpdate.timestamp > _playbackTime)
            {
                return(null);
            }

            // Dequeue
            _playbackDeltaUpdates.Dequeue();

            return(deltaUpdate);
        }
 public bool ReadDeltaUpdate(double playbackTime, DeltaUpdate deltaUpdate)
 {
     return(_fileStream.ReadDeltaUpdate(playbackTime, ref deltaUpdate.timestamp, ref deltaUpdate.sender, ref deltaUpdate.data, ref deltaUpdate.reliable, ref deltaUpdate.updateID, ref deltaUpdate.incoming));
 }
        private static bool AdjustOutgoingUnreliableDeltaUpdateTimestamp(double playbackTime, PlaybackStream playbackStream, DeltaUpdate deltaUpdate)
        {
            // Adjust the timestamp
            double timestamp = deltaUpdate.timestamp + playbackStream.sendTimestampOffset;

            // If the update is now newer than the current playback time, then bail.
            if (timestamp > playbackTime)
            {
                return(false);
            }

            // Adjust update timestamp
            deltaUpdate.timestamp += playbackStream.sendTimestampOffset;

            return(true);
        }
        private static bool AdjustPlaybackStreamSendTimestampOffsetWithOutgoingReliableDeltaUpdate(PlaybackStream playbackStream, Dictionary <uint, DeltaUpdate> incomingReliableUpdates, DeltaUpdate outgoingReliableDeltaUpdate)
        {
            // Check for a matching reliable delta update. If we haven't received it yet, then bail, it's not time to decode this update yet.
            DeltaUpdate incomingReliableDeltaUpdate;

            if (incomingReliableUpdates == null || !incomingReliableUpdates.TryGetValue(outgoingReliableDeltaUpdate.updateID, out incomingReliableDeltaUpdate))
            {
                return(false);
            }

            // Found a matching reliable update. Use the round trip time to adjust outgoing unreliable updates.
            playbackStream.sendTimestampOffset = incomingReliableDeltaUpdate.timestamp - outgoingReliableDeltaUpdate.timestamp;

            // Remove from incoming reliable updates
            incomingReliableUpdates.Remove(outgoingReliableDeltaUpdate.updateID);

            return(true);
        }
        public void PlaybackTick(double deltaTime)
        {
            if (!_playing)
            {
                return;
            }

            // Increment playback time
            _playbackTime += deltaTime;

            // Keep track of whether this frame produced any updates for playback
            bool didAddDeltaUpdateToPlaybackQueue = false;

            // Read delta updates from primary playback stream
            DeltaUpdate deltaUpdate = new DeltaUpdate();

            while (_primaryPlaybackStream.ReadDeltaUpdate(_playbackTime, deltaUpdate))
            {
                // Add all outgoing updates to the queue to be processed
                if (deltaUpdate.outgoing)
                {
                    _primaryPlaybackDeltaUpdatesToBeProcessed.Enqueue(deltaUpdate);
                }

                // If it's an incoming reliable update, keep track so we can use the timestamp to calculate timestamp offsets.
                if (deltaUpdate.incoming && deltaUpdate.reliable)
                {
                    // If this is an incoming reliable update, add it directly to the queue of updates to play back. It doesn't need any timestamp adjustments or anything.
                    _playbackDeltaUpdates.Enqueue(deltaUpdate);
                    didAddDeltaUpdateToPlaybackQueue = true;

                    // Retrieve dictionary for this sender. Create a new one if needed.
                    Dictionary <uint, DeltaUpdate> incomingReliableUpdates;
                    if (!_clientToIncomingReliableUpdatesMap.TryGetValue(deltaUpdate.sender, out incomingReliableUpdates))
                    {
                        incomingReliableUpdates = new Dictionary <uint, DeltaUpdate>();
                        _clientToIncomingReliableUpdatesMap.Add(deltaUpdate.sender, incomingReliableUpdates);
                    }

                    // Add the update
                    incomingReliableUpdates.Add(deltaUpdate.updateID, deltaUpdate);
                }

                // Create a new update for the next loop
                deltaUpdate = new DeltaUpdate();
            }

            // Adjust the timestamps for outgoing unreliable messages by measuring the roundtrip time on reliable messages.
            // We're using incoming reliable updates for playback (because the server assigns uniqueIDs and things), but unreliable updates are recorded when they're sent.
            // That means that the reliable updates will have timestamps that are later than when they were actually sent. To fix this, we adjust the timestamps of the
            // unreliable updates to account for the roundtrip delay that we're seeing.

            // Grab the incoming reliable updates for the primary stream's client ID
            Dictionary <uint, DeltaUpdate> primaryPlaybackClientIncomingReliableUpdates;

            _clientToIncomingReliableUpdatesMap.TryGetValue(_primaryPlaybackStream.clientID, out primaryPlaybackClientIncomingReliableUpdates);

            // Loop through updates and process them until we run out or we hit one that requires us to wait for more time to pass / more frames to be decoded
            while (_primaryPlaybackDeltaUpdatesToBeProcessed.Count > 0)
            {
                // Peek at update
                deltaUpdate = _primaryPlaybackDeltaUpdatesToBeProcessed.Peek();

                // Outgoing unreliable update.
                if (deltaUpdate.outgoing && deltaUpdate.unreliable)
                {
                    bool success = AdjustOutgoingUnreliableDeltaUpdateTimestamp(_playbackTime, _primaryPlaybackStream, deltaUpdate);

                    // If we were unable to successfully adjust the update, break out of the loop. We'll try again next frame after more time has passed.
                    if (!success)
                    {
                        break;
                    }

                    // Add to outgoing updates
                    _playbackDeltaUpdates.Enqueue(deltaUpdate);
                    didAddDeltaUpdateToPlaybackQueue = true;
                }

                // Outgoing reliable update. Find the matching incoming reliable update and use it to adjust the unreliableTimestampOffset
                else if (deltaUpdate.outgoing && deltaUpdate.reliable)
                {
                    bool success = AdjustPlaybackStreamSendTimestampOffsetWithOutgoingReliableDeltaUpdate(_primaryPlaybackStream, primaryPlaybackClientIncomingReliableUpdates, deltaUpdate);

                    // If we were unable to successfully adjust the send timestamp offset, break out of the loop. We'll try again next frame after more time has passed.
                    if (!success)
                    {
                        break;
                    }
                }

                // If we hit this point, the update has been processed, dequeue it and move onto the next one.
                _primaryPlaybackDeltaUpdatesToBeProcessed.Dequeue();
            }

            // Loop through each secondary stream. Perform the timestamp adjustments and add them to the playback queue.
            foreach (KeyValuePair <int, PlaybackStream> secondaryPlaybackStream in _secondaryPlaybackStreams)
            {
                int            clientID       = secondaryPlaybackStream.Key;
                PlaybackStream playbackStream = secondaryPlaybackStream.Value;

                // Grab the incoming reliable updates for this stream's clientID
                Dictionary <uint, DeltaUpdate> incomingReliableUpdates;
                _clientToIncomingReliableUpdatesMap.TryGetValue(clientID, out incomingReliableUpdates);

                // Read updates up until playbackTime processing them as we go. Stop if we run out of updates or we hit an update that we can't process yet.
                deltaUpdate = new DeltaUpdate();
                while (playbackStream.ReadDeltaUpdate(_playbackTime, deltaUpdate))
                {
                    // Outgoing unreliable update.
                    if (deltaUpdate.outgoing && deltaUpdate.unreliable)
                    {
                        bool success = AdjustOutgoingUnreliableDeltaUpdateTimestamp(_playbackTime, playbackStream, deltaUpdate);

                        // If we were unable to successfully adjust the update, break out of the loop. We'll try again next frame after more time has passed.
                        if (!success)
                        {
                            break;
                        }

                        // Add to outgoing updates
                        _playbackDeltaUpdates.Enqueue(deltaUpdate);
                        didAddDeltaUpdateToPlaybackQueue = true;
                    }

                    // Outgoing reliable update. Find the matching incoming reliable update and use it to adjust the unreliableTimestampOffset
                    else if (deltaUpdate.outgoing && deltaUpdate.reliable)
                    {
                        bool success = AdjustPlaybackStreamSendTimestampOffsetWithOutgoingReliableDeltaUpdate(playbackStream, incomingReliableUpdates, deltaUpdate);

                        // If we were unable to successfully adjust the send timestamp offset, break out of the loop. We'll try again next frame after more time has passed.
                        if (!success)
                        {
                            break;
                        }
                    }
                }
            }

            // If we added any updates to the playback queue, sort it to make sure that they're in order after timestamp adjustments were made.
            if (didAddDeltaUpdateToPlaybackQueue)
            {
                _playbackDeltaUpdates = new Queue <DeltaUpdate>(_playbackDeltaUpdates.OrderBy(playbackDeltaUpdate => playbackDeltaUpdate.timestamp));
            }

            // If we've finished reading updates, we're done playing.
            _playing = _primaryPlaybackStream.reading;
        }