private static object m_last_messages_lock       = new object(); // lock used to guard access to buffer contents AND m_last_update_time

        private static void EnqueueToRing(NewPlayerSnapshotToClientMessage msg, bool estimateVelocities = false)
        {
            // For old snapshots, we will fill in the ship velocity if that information is available.
            if (estimateVelocities)
            {
                var last_snapshots = m_last_messages_ring[m_last_messages_ring_pos_last];
                foreach (var snapshot in msg.m_snapshots)
                {
                    var last_snapshot = last_snapshots.m_snapshots.FirstOrDefault(m => m.m_net_id == snapshot.m_net_id);
                    if (last_snapshot != null)
                    {
                        snapshot.m_vel  = (snapshot.m_pos - last_snapshot.m_pos) / Time.fixedDeltaTime;
                        snapshot.m_vrot = (Quaternion.Inverse(snapshot.m_rot) * Quaternion.SlerpUnclamped(last_snapshot.m_rot, snapshot.m_rot, 1f / Time.fixedDeltaTime)).eulerAngles;
                    }
                }
            }

            m_last_messages_ring_pos_last = (m_last_messages_ring_pos_last + 1) & 3;
            m_last_messages_ring[m_last_messages_ring_pos_last] = msg;
            if (m_last_messages_ring_count < 4)
            {
                m_last_messages_ring_count++;
            }
            //Debug.LogFormat("Adding {0} at {1}, have {2}", msg.m_timestamp, Time.time, m_last_messages_ring_count);
        }
예제 #2
0
        // when using the new protocol, this object replaces
        // Client.m_PendingPlayerSnapshotMessages
        //public static Queue<NewPlayerSnapshotToClientMessage> m_PendingPlayerSnapshotMessages = new Queue<NewPlayerSnapshotToClientMessage>();

        public static void OnNewPlayerSnapshotToClient(NetworkMessage msg)
        {
            if (NetworkMatch.GetMatchState() == MatchState.PREGAME || NetworkMatch.InGameplay())
            {
                NewPlayerSnapshotToClientMessage item = msg.ReadMessage <NewPlayerSnapshotToClientMessage>();
                MPClientShipReckoning.AddNewPlayerSnapshot(item);
            }
        }
        // extrapolate a whole NewPlayerSnapshotToClientMessage
        // extrapolate from B into t seconds into the future, t can be negative
        // this is used for generating synthetic snapshot messages in case we detected missing packets
        private static NewPlayerSnapshotToClientMessage ExtrapolatePlayerSnapshotMessage(NewPlayerSnapshotToClientMessage B, float t)
        {
            NewPlayerSnapshotToClientMessage C = new NewPlayerSnapshotToClientMessage();
            int i;

            for (i = 0; i < B.m_num_snapshots; i++)
            {
                ExtrapolatePlayerSnapshot(ref C.m_snapshots[C.m_num_snapshots++], B.m_snapshots[i], t);
            }

            C.m_server_timestamp = B.m_server_timestamp + t;
            return(C);
        }
        public static NewPlayerSnapshot GetPlayerSnapshot(NewPlayerSnapshotToClientMessage msg, Player p)
        {
            for (int i = 0; i < msg.m_num_snapshots; i++)
            {
                NewPlayerSnapshot playerSnapshot = msg.m_snapshots[i];
                Player            candidate      = (Player)_Client_GetPlayerFromNetId_Method.Invoke(null, new object[] { playerSnapshot.m_net_id });

                if (candidate == p)
                {
                    return(playerSnapshot);
                }
            }
            return(null);
        }
예제 #5
0
 public static bool Prefix(NetworkMessage msg)
 {
     if (NetworkMatch.GetMatchState() == MatchState.PREGAME || NetworkMatch.InGameplay())
     {
         PlayerSnapshotToClientMessage    item    = msg.ReadMessage <PlayerSnapshotToClientMessage>();
         NewPlayerSnapshotToClientMessage newItem = new NewPlayerSnapshotToClientMessage {
             m_num_snapshots    = item.m_num_snapshots,
             m_server_timestamp = 0, // Unused.
             m_snapshots        = item.m_snapshots.Select(m => NewPlayerSnapshot.FromOldSnapshot(m)).ToArray()
         };
         MPClientShipReckoning.AddNewPlayerSnapshot(newItem, true);
     }
     return(false);
 }
예제 #6
0
 // add a AddNewPlayerSnapshot(NewPlayerSnapshotToClientMessage
 // this should be called as soon as possible after the message arrives
 // This function adds the message into the ring buffer, and
 // also implements the time sync algorithm between the message sequence and
 // the local render time.
 //
 // It is safe to be called from an arbitrary thread, as accesses are
 // guareded by a lock.
 public static void AddNewPlayerSnapshot(NewPlayerSnapshotToClientMessage msg, bool wasOld = false)
 {
     lock (m_last_messages_lock) {
         if (m_last_messages_ring_count == 0)
         {
             // first packet
             EnqueueToRing(msg);
             m_last_update_time = Time.time;
         }
         else
         {
             // next in sequence, as we expected
             EnqueueToRing(msg, wasOld);
             // this assumes the server sends 60Hz
             // during time dilation (timebombs!) this is not true,
             // it will actually send data packets _worth_ of 16.67ms real time, spread out
             // to longer intervals such as 24 ms.
             // However, this is not a problem, since the reference clock we sync to
             // is Time.time which has the timeScale already applied.
             // That means the 24ms tick will be seen as 16.67 in Time.time,
             // and everything cancles itself out nicely
             m_last_update_time += Time.fixedDeltaTime;
         }
         // check if the time base is still plausible
         float delta = (Time.time - m_last_update_time) / Time.fixedDeltaTime; // in ticks
         // allow a sliding window to catch up for latency jitter
         float frameSoftSyncLimit = 2.0f;                                      ///hard-sync if we're off by more than that many physics ticks
         if (delta < -frameSoftSyncLimit || delta > frameSoftSyncLimit)
         {
             // hard resync
             // Debug.LogFormat("hard resync by {0} frames", delta);
             m_last_update_time = Time.time;
         }
         else
         {
             // soft resync
             float smoothing_factor = 0.1f;
             m_last_update_time += smoothing_factor * delta * Time.fixedDeltaTime;
         }
     } // end lock
 }
        // interpolate a whole NewPlayerSnapshotToClientMessage
        // interpolate between A and B, t is the relative factor between both
        // If a player is not in both messages, it will not be in the resulting messages
        // this is used for generating synthetic snapshot messages in case we detected missing packets
        private static NewPlayerSnapshotToClientMessage InterpolatePlayerSnapshotMessage(NewPlayerSnapshotToClientMessage A, NewPlayerSnapshotToClientMessage B, float t)
        {
            NewPlayerSnapshotToClientMessage C = new NewPlayerSnapshotToClientMessage();
            int i, j;

            C.m_num_snapshots = 0;

            for (i = 0; i < A.m_num_snapshots; i++)
            {
                for (j = 0; j < B.m_num_snapshots; j++)
                {
                    if (A.m_snapshots[i].m_net_id.Value == B.m_snapshots[j].m_net_id.Value)
                    {
                        InterpolatePlayerSnapshot(ref C.m_snapshots[C.m_num_snapshots++], A.m_snapshots[i], B.m_snapshots[j], t);
                        continue;
                    }
                }
            }

            C.m_server_timestamp = (1.0f - t) * A.m_server_timestamp + t * B.m_server_timestamp;
            return(C);
        }
        // Called per frame, moves ships along their interpolation/extrapolation motions
        public static void updatePlayerPositions()
        {
            float now = Time.time;                        // needs to be the same time source we use for m_last_update_time
            NewPlayerSnapshotToClientMessage msgA = null; // interpolation: start
            NewPlayerSnapshotToClientMessage msgB = null; // interpolation: end, extrapolation start
            float interpolate_factor = 0.0f;              // interpolation: factor in [0,1]
            float delta_t            = 0.0f;
            int   interpolate_ticks  = 0;
            bool  do_interpolation   = false;

            // find out which case we have, and get the relevant snapshot message(s)
            lock (m_last_messages_lock) {
                /*
                 * for (int xxx=0; xxx<m_last_messages_ring_count; xxx++) {
                 *  Debug.LogFormat("having snapshot from {0} represents {1}", m_last_messages_ring[(m_last_messages_ring_pos_last + 4 - xxx)&3].m_timestamp, m_last_update_time - xxx* Time.fixedDeltaTime);
                 * }
                 */
                if (m_last_messages_ring_count < 1)
                {
                    // we do not have any snapshot messages...
                    return;
                }

                // make sure m_last_update_time is up-to-date
                ResyncTime();

                // NOTE: now and m_last_update_time indirectly have timeScale already applied, as they are based on Time.time
                //       we need to adjust just the ping and the mms_ship_max_interpolate_frames offset...
                //       Also note that the server still sends the unscaled velocities.
                delta_t = now + Time.timeScale * MPClientExtrapolation.GetShipExtrapolationTime() - m_last_update_time;
                // if we want interpolation, add this as a _negative_ offset
                // we use delta_t=0  as the base for from which we extrapolate into the future
                delta_t -= (Menus.mms_lag_compensation_ship_added_lag / 1000f) * Time.timeScale;
                // it might sound absurd, but after this point, the Time.fixedDeltaTime is correct
                // and MUST NOT be scaled by timeScale. The data packets do contain 16.67ms of
                // movement each, we already have taken the time dilation into account above...
                // time difference in physics ticks
                float delta_ticks = delta_t / Time.fixedDeltaTime;
                // the number of frames we need to interpolate into
                // <= 0 means no interpolation at all,
                // 1 would mean we use the second most recent and the most recent snapshot, and so on...
                interpolate_ticks = -(int)Mathf.Floor(delta_ticks);
                // do we need to do interpolation?
                do_interpolation = (interpolate_ticks > 0);

                if (do_interpolation)
                {
                    // we need interpolate_ticks + 1 elements in the ring buffer
                    // NOTE: in the code below, the index [(m_last_messages_ring_pos_last + 4 - i) &3]
                    //       effectively acceses the i-ith most recent element (i starting by 0)
                    //       since 4-(i-1) == 4-i+ 1 = 5-i, 5-i references the next older one
                    if (interpolate_ticks < m_last_messages_ring_count)
                    {
                        msgA = m_last_messages_ring[(m_last_messages_ring_pos_last + 4 - interpolate_ticks) & 3];
                        msgB = m_last_messages_ring[(m_last_messages_ring_pos_last + 5 - interpolate_ticks) & 3];
                        interpolate_factor = delta_ticks - Mathf.Floor(delta_ticks);
                    }
                    else
                    {
                        // not enough packets received so far
                        // "extrapolate" into the past
                        do_interpolation = false;
                        // get the oldest snapshot we have
                        msgB = m_last_messages_ring[(m_last_messages_ring_pos_last + 5 - m_last_messages_ring_count) & 3];
                        // offset the time for the extrapolation
                        // delta_t is currently relative to the most recent element we have,
                        // but we need it relative to msgA
                        delta_t += Time.fixedDeltaTime * (m_last_messages_ring_count - 1);
                    }
                }
                else
                {
                    // extrapolation case
                    // use the most recently received snapshot
                    msgB = m_last_messages_ring[m_last_messages_ring_pos_last];
                }
            } // lock
            m_last_frame_time = now;

            /*
             * Debug.LogFormat("At: {0} Setting: {1} IntFrames: {2}, dt: {3}, IntFact {4}",now,Menus.mms_ship_max_interpolate_frames, interpolate_ticks, delta_t, interpolate_factor);
             * if (interpolate_ticks > 0) {
             *  Debug.LogFormat("Using A from {0}", msgA.m_timestamp);
             *  Debug.LogFormat("Using B from {0}", msgB.m_timestamp);
             * } else {
             *  Debug.LogFormat("Using B from {0}", msgB.m_timestamp);
             * }
             */

            // keep statistics
            m_compensation_sum += delta_t;
            m_compensation_count++;
            // NOTE: one can't replace(interpolate_ticks > 0) by do_interpolation here,
            //       because even in the (interpolate_ticks > 0) case the code above could
            //       have reset do_interpolation to false because we technically want
            //       the "extrapolation" into the past thing, but we don't want to count that
            //       as extrapolation...
            m_compensation_interpol_count += (interpolate_ticks > 0)?1:0;
            if (Time.time >= m_compensation_last + 5.0 && m_compensation_count > 0)
            {
                // Debug.LogFormat("ship lag compensation over last {0} frames: {1}ms / {2} physics ticks, {3} interpolation ({4}%) packets: {5} received / {6} missing / {7} old ignored",
                //                 m_compensation_count, 1000.0f* (m_compensation_sum/ m_compensation_count),
                //                 (m_compensation_sum/m_compensation_count)/Time.fixedDeltaTime,
                //                 m_compensation_interpol_count,
                //                 100.0f*((float)m_compensation_interpol_count/(float)m_compensation_count),
                //                 m_received_packets_count, m_missing_packets_count, m_ignored_packets_count);
                m_compensation_sum            = 0.0f;
                m_compensation_count          = 0;
                m_compensation_interpol_count = 0;
                m_received_packets_count      = 0;
                m_missing_packets_count       = 0;
                m_ignored_packets_count       = 0;
                m_compensation_last           = Time.time;
            }

            // actually apply the operation to each player
            foreach (Player player in Overload.NetworkManager.m_Players)
            {
                if (player != null && !player.isLocalPlayer && !player.m_spectator)
                {
                    // do the actual interpolation or extrapolation, as calculated above
                    if (do_interpolation)
                    {
                        NewPlayerSnapshot A = GetPlayerSnapshot(msgA, player);
                        NewPlayerSnapshot B = GetPlayerSnapshot(msgB, player);
                        if (A != null && B != null)
                        {
                            interpolatePlayer(player, A, B, interpolate_factor);
                        }
                    }
                    else
                    {
                        NewPlayerSnapshot snapshot = GetPlayerSnapshot(msgB, player);
                        if (snapshot != null)
                        {
                            extrapolatePlayer(player, snapshot, delta_t);
                        }
                    }
                }
            }
        }
        // add a AddNewPlayerSnapshot(NewPlayerSnapshotToClientMessage
        // this should be called as soon as possible after the message arrives
        // This function adds the message into the ring buffer, and
        // also implements the time sync algorithm between the message sequence and
        // the local render time.
        //
        // It is safe to be called from an arbitrary thread, as accesses are
        // guareded by a lock.
        public static void AddNewPlayerSnapshot(NewPlayerSnapshotToClientMessage msg, MPNoPositionCompression.SnapshotVersion version)
        {
            lock (m_last_messages_lock) {
                if (m_last_messages_ring_count == 0)
                {
                    // first packet
                    EnqueueToRing(msg);
                    m_last_update_time        = Time.time;
                    m_unsynced_messages_count = 0;
                }
                else
                {
                    bool estimateVelocities = (version == MPNoPositionCompression.SnapshotVersion.VANILLA);
                    int  deltaFrames;
                    if (version != MPNoPositionCompression.SnapshotVersion.VELOCITY_TIMESTAMP)
                    {
                        // we do not have server timestamps,
                        // just assume the packet is the next in sequence
                        deltaFrames = 1;
                    }
                    else
                    {
                        // determine how many frames in the future the new packet is,
                        // relative to the last one we received
                        deltaFrames = (int)((msg.m_server_timestamp - m_last_message_server_time) / Time.fixedDeltaTime + 0.5f);
                    }

                    if (deltaFrames == 1)
                    {
                        // FAST PATH:
                        // next in sequence, as we expected
                        EnqueueToRing(msg, estimateVelocities);
                        m_unsynced_messages_count++;
                    }
                    else if (deltaFrames > 1)
                    {
                        // SLOW PATH: at least one packet is missing
                        // we actually do the creation of missing packets here
                        // once per received new snapshot, so that the per-frame
                        // update code path can stay simple
                        // Debug.LogFormat("detected {0} missing packets",  deltaFrames - 1);
                        NewPlayerSnapshotToClientMessage lastMsg = m_last_messages_ring[m_last_messages_ring_pos_last];
                        if (deltaFrames == 2)
                        {
                            // there is one missing snapshot
                            EnqueueToRing(InterpolatePlayerSnapshotMessage(lastMsg, msg, 0.5f));
                            EnqueueToRing(msg);
                        }
                        else if (deltaFrames == 3)
                        {
                            // there are two missing snapshots
                            EnqueueToRing(InterpolatePlayerSnapshotMessage(lastMsg, msg, 0.3333f));
                            EnqueueToRing(InterpolatePlayerSnapshotMessage(lastMsg, msg, 0.6667f));
                            EnqueueToRing(msg);
                        }
                        else if (deltaFrames == 4)
                        {
                            // there are three missing snapshots
                            EnqueueToRing(InterpolatePlayerSnapshotMessage(lastMsg, msg, 0.25f));
                            EnqueueToRing(InterpolatePlayerSnapshotMessage(lastMsg, msg, 0.5f));
                            EnqueueToRing(InterpolatePlayerSnapshotMessage(lastMsg, msg, 0.75f));
                            EnqueueToRing(msg);
                        }
                        else
                        {
                            // there are more than 3 missing snapshots,
                            // just take the completely new data in
                            EnqueueToRing(ExtrapolatePlayerSnapshotMessage(msg, -3.0f * Time.fixedDeltaTime));
                            EnqueueToRing(ExtrapolatePlayerSnapshotMessage(msg, -2.0f * Time.fixedDeltaTime));
                            EnqueueToRing(ExtrapolatePlayerSnapshotMessage(msg, -1.0f * Time.fixedDeltaTime));
                            EnqueueToRing(msg);
                        }
                        m_unsynced_messages_count += deltaFrames;
                        m_missing_packets_count   += (deltaFrames - 1);
                    }
                    else if (deltaFrames < -180)
                    {
                        // WEIRD PATH: message from the past older than 3 seconds
                        // this means something completely weird is going on on the network
                        // or on the server, and we better completely re-sync with the server
                        // Debug.LogFormat("detected message {0} frames from the past, FULL RESYNC!",  -deltaFrames);
                        ClearRing();
                        EnqueueToRing(msg, false);
                        m_last_update_time        = Time.time;
                        m_unsynced_messages_count = 0;
                    }
                    else
                    {
                        // Debug.LogFormat("detected old / duplicated message {0} frames from the past",  -deltaFrames);
                        // OLD / DUPLICATED messages: these are simply ignored
                        // If it is a true duplicate, it is worthless, and if we got messages
                        // out of order, we have synthesized the missing packet already with
                        // the data we had, so this is not an exact duplicate, but it is
                        // useless now anyway.
                        //
                        // With the "reliable" UDP connection unity offers, this path should
                        // never be taken (and I never saw it happen), but this code
                        // is written to deal with any input whatsoever as good as possible.
                        // Note that we might also experiment with using a completely
                        // unreliable connection in the future, and then, this code path
                        //  becomes essential.
                        m_ignored_packets_count++;
                    }
                }
                m_received_packets_count++;
                m_last_message_time        = Time.time;
                m_last_message_server_time = msg.m_server_timestamp;
            } // end lock
        }