Esempio n. 1
0
        void UpdateClient()
        {
            // client authority, and local player (= allowed to move myself)?
            if (IsClientWithAuthority)
            {
                // send to server each 'sendInterval'
                // NetworkTime.localTime for double precision until Unity has it too
                //
                // IMPORTANT:
                // snapshot interpolation requires constant sending.
                // DO NOT only send if position changed. for example:
                // ---
                // * client sends first position at t=0
                // * ... 10s later ...
                // * client moves again, sends second position at t=10
                // ---
                // * server gets first position at t=0
                // * server gets second position at t=10
                // * server moves from first to second within a time of 10s
                //   => would be a super slow move, instead of a wait & move.
                //
                // IMPORTANT:
                // DO NOT send nulls if not changed 'since last send' either. we
                // send unreliable and don't know which 'last send' the other end
                // received successfully.
                if (NetworkTime.localTime >= lastClientSendTime + sendInterval)
                {
                    // send snapshot without timestamp.
                    // receiver gets it from batch timestamp to save bandwidth.
                    NTSnapshot snapshot = ConstructSnapshot();
                    CmdClientToServerSync(
                        // only sync what the user wants to sync
                        syncPosition ? snapshot.position : new Vector3?(),
                        syncRotation? snapshot.rotation : new Quaternion?(),
                        syncScale ? snapshot.scale : new Vector3?()
                        );

                    lastClientSendTime = NetworkTime.localTime;
                }
            }
            // for all other clients (and for local player if !authority),
            // we need to apply snapshots from the buffer
            else
            {
                // compute snapshot interpolation & apply if any was spit out
                // TODO we don't have Time.deltaTime double yet. float is fine.
                if (SnapshotInterpolation.Compute(
                        NetworkTime.localTime, Time.deltaTime,
                        ref clientInterpolationTime,
                        bufferTime, clientBuffer,
                        catchupThreshold, catchupMultiplier,
                        Interpolate,
                        out NTSnapshot computed))
                {
                    NTSnapshot start = clientBuffer.Values[0];
                    NTSnapshot goal  = clientBuffer.Values[1];
                    ApplySnapshot(start, goal, computed);
                }
            }
        }
Esempio n. 2
0
        protected virtual void DrawGizmos(SortedList <double, NTSnapshot> buffer)
        {
            // only draw if we have at least two entries
            if (buffer.Count < 2)
            {
                return;
            }

            // calcluate threshold for 'old enough' snapshots
            double threshold         = NetworkTime.localTime - bufferTime;
            Color  oldEnoughColor    = new Color(0, 1, 0, 0.5f);
            Color  notOldEnoughColor = new Color(0.5f, 0.5f, 0.5f, 0.3f);

            // draw the whole buffer for easier debugging.
            // it's worth seeing how much we have buffered ahead already
            for (int i = 0; i < buffer.Count; ++i)
            {
                // color depends on if old enough or not
                NTSnapshot entry     = buffer.Values[i];
                bool       oldEnough = entry.localTimestamp <= threshold;
                Gizmos.color = oldEnough ? oldEnoughColor : notOldEnoughColor;
                Gizmos.DrawCube(entry.position, Vector3.one);
            }

            // extra: lines between start<->position<->goal
            Gizmos.color = Color.green;
            Gizmos.DrawLine(buffer.Values[0].position, targetComponent.position);
            Gizmos.color = Color.white;
            Gizmos.DrawLine(targetComponent.position, buffer.Values[1].position);
        }
Esempio n. 3
0
        // Returns true if position, rotation AND scale are unchanged, within given sensitivity range.
        protected virtual bool CompareSnapshots(NTSnapshot currentSnapshot)
        {
            positionChanged = Vector3.SqrMagnitude(lastSnapshot.position - currentSnapshot.position) >= positionSensitivity * positionSensitivity;
            rotationChanged = Quaternion.Angle(lastSnapshot.rotation, currentSnapshot.rotation) >= rotationSensitivity;
            scaleChanged    = Vector3.SqrMagnitude(lastSnapshot.scale - currentSnapshot.scale) >= scaleSensitivity * scaleSensitivity;

            return(!positionChanged && !rotationChanged && !scaleChanged);
        }
Esempio n. 4
0
        // local authority client sends sync message to server for broadcasting
        protected virtual void OnClientToServerSync(Vector3?position, Quaternion?rotation, Vector3?scale)
        {
            // only apply if in client authority mode
            if (!clientAuthority)
            {
                return;
            }

            // protect against ever growing buffer size attacks
            if (serverBuffer.Count >= bufferSizeLimit)
            {
                return;
            }

            // only player owned objects (with a connection) can send to
            // server. we can get the timestamp from the connection.
            double timestamp = connectionToClient.remoteTimeStamp;

            // position, rotation, scale can have no value if same as last time.
            // saves bandwidth.
            // but we still need to feed it to snapshot interpolation. we can't
            // just have gaps in there if nothing has changed. for example, if
            //   client sends snapshot at t=0
            //   client sends nothing for 10s because not moved
            //   client sends snapshot at t=10
            // then the server would assume that it's one super slow move and
            // replay it for 10 seconds.
            if (!position.HasValue)
            {
                position = targetComponent.localPosition;
            }
            if (!rotation.HasValue)
            {
                rotation = targetComponent.localRotation;
            }
            if (!scale.HasValue)
            {
                scale = targetComponent.localScale;
            }

            // construct snapshot with batch timestamp to save bandwidth
            NTSnapshot snapshot = new NTSnapshot(
                timestamp,
                NetworkTime.localTime,
                position.Value, rotation.Value, scale.Value
                );

            // add to buffer (or drop if older than first element)
            SnapshotInterpolation.InsertIfNewEnough(snapshot, serverBuffer);
        }
Esempio n. 5
0
 public static NTSnapshot Interpolate(NTSnapshot from, NTSnapshot to, double t)
 {
     // NOTE:
     // Vector3 & Quaternion components are float anyway, so we can
     // keep using the functions with 't' as float instead of double.
     return(new NTSnapshot(
                // interpolated snapshot is applied directly. don't need timestamps.
                0, 0,
                // lerp position/rotation/scale unclamped in case we ever need
                // to extrapolate. atm SnapshotInterpolation never does.
                Vector3.LerpUnclamped(from.position, to.position, (float)t),
                // IMPORTANT: LerpUnclamped(0, 60, 1.5) extrapolates to ~86.
                //            SlerpUnclamped(0, 60, 1.5) extrapolates to 90!
                //            (0, 90, 1.5) is even worse. for Lerp.
                //            => Slerp works way better for our euler angles.
                Quaternion.SlerpUnclamped(from.rotation, to.rotation, (float)t),
                Vector3.LerpUnclamped(from.scale, to.scale, (float)t)
                ));
 }
Esempio n. 6
0
        // apply a snapshot to the Transform.
        // -> start, end, interpolated are all passed in caes they are needed
        // -> a regular game would apply the 'interpolated' snapshot
        // -> a board game might want to jump to 'goal' directly
        // (it's easier to always interpolate and then apply selectively,
        //  instead of manually interpolating x, y, z, ... depending on flags)
        // => internal for testing
        //
        // NOTE: stuck detection is unnecessary here.
        //       we always set transform.position anyway, we can't get stuck.
        protected virtual void ApplySnapshot(NTSnapshot start, NTSnapshot goal, NTSnapshot interpolated)
        {
            // local position/rotation for VR support
            //
            // if syncPosition/Rotation/Scale is disabled then we received nulls
            // -> current position/rotation/scale would've been added as snapshot
            // -> we still interpolated
            // -> but simply don't apply it. if the user doesn't want to sync
            //    scale, then we should not touch scale etc.
            if (syncPosition)
            {
                targetComponent.localPosition = interpolatePosition ? interpolated.position : goal.position;
            }

            if (syncRotation)
            {
                targetComponent.localRotation = interpolateRotation ? interpolated.rotation : goal.rotation;
            }

            if (syncScale)
            {
                targetComponent.localScale = interpolateScale ? interpolated.scale : goal.scale;
            }
        }
Esempio n. 7
0
        // update //////////////////////////////////////////////////////////////
        void UpdateServer()
        {
            // broadcast to all clients each 'sendInterval'
            // (client with authority will drop the rpc)
            // NetworkTime.localTime for double precision until Unity has it too
            //
            // IMPORTANT:
            // snapshot interpolation requires constant sending.
            // DO NOT only send if position changed. for example:
            // ---
            // * client sends first position at t=0
            // * ... 10s later ...
            // * client moves again, sends second position at t=10
            // ---
            // * server gets first position at t=0
            // * server gets second position at t=10
            // * server moves from first to second within a time of 10s
            //   => would be a super slow move, instead of a wait & move.
            //
            // IMPORTANT:
            // DO NOT send nulls if not changed 'since last send' either. we
            // send unreliable and don't know which 'last send' the other end
            // received successfully.
            //
            // Checks to ensure server only sends snapshots if object is
            // on server authority(!clientAuthority) mode because on client
            // authority mode snapshots are broadcasted right after the authoritative
            // client updates server in the command function(see above), OR,
            // since host does not send anything to update the server, any client
            // authoritative movement done by the host will have to be broadcasted
            // here by checking IsClientWithAuthority.
            if (NetworkTime.localTime >= lastServerSendTime + sendInterval &&
                (!clientAuthority || IsClientWithAuthority))
            {
                // send snapshot without timestamp.
                // receiver gets it from batch timestamp to save bandwidth.
                NTSnapshot snapshot = ConstructSnapshot();
                RpcServerToClientSync(
                    // only sync what the user wants to sync
                    syncPosition ? snapshot.position : new Vector3?(),
                    syncRotation? snapshot.rotation : new Quaternion?(),
                    syncScale ? snapshot.scale : new Vector3?()
                    );

                lastServerSendTime = NetworkTime.localTime;
            }

            // apply buffered snapshots IF client authority
            // -> in server authority, server moves the object
            //    so no need to apply any snapshots there.
            // -> don't apply for host mode player objects either, even if in
            //    client authority mode. if it doesn't go over the network,
            //    then we don't need to do anything.
            if (clientAuthority && !hasAuthority)
            {
                // compute snapshot interpolation & apply if any was spit out
                // TODO we don't have Time.deltaTime double yet. float is fine.
                if (SnapshotInterpolation.Compute(
                        NetworkTime.localTime, Time.deltaTime,
                        ref serverInterpolationTime,
                        bufferTime, serverBuffer,
                        catchupThreshold, catchupMultiplier,
                        Interpolate,
                        out NTSnapshot computed))
                {
                    NTSnapshot start = serverBuffer.Values[0];
                    NTSnapshot goal  = serverBuffer.Values[1];
                    ApplySnapshot(start, goal, computed);
                }
            }
        }
Esempio n. 8
0
        // server broadcasts sync message to all clients
        protected virtual void OnServerToClientSync(Vector3?position, Quaternion?rotation, Vector3?scale)
        {
            // in host mode, the server sends rpcs to all clients.
            // the host client itself will receive them too.
            // -> host server is always the source of truth
            // -> we can ignore any rpc on the host client
            // => otherwise host objects would have ever growing clientBuffers
            // (rpc goes to clients. if isServer is true too then we are host)
            if (isServer)
            {
                return;
            }

            // don't apply for local player with authority
            if (IsClientWithAuthority)
            {
                return;
            }

            // protect against ever growing buffer size attacks
            if (clientBuffer.Count >= bufferSizeLimit)
            {
                return;
            }

            // on the client, we receive rpcs for all entities.
            // not all of them have a connectionToServer.
            // but all of them go through NetworkClient.connection.
            // we can get the timestamp from there.
            double timestamp = NetworkClient.connection.remoteTimeStamp;

            // position, rotation, scale can have no value if same as last time.
            // saves bandwidth.
            // but we still need to feed it to snapshot interpolation. we can't
            // just have gaps in there if nothing has changed. for example, if
            //   client sends snapshot at t=0
            //   client sends nothing for 10s because not moved
            //   client sends snapshot at t=10
            // then the server would assume that it's one super slow move and
            // replay it for 10 seconds.
            if (!position.HasValue)
            {
                position = targetComponent.localPosition;
            }
            if (!rotation.HasValue)
            {
                rotation = targetComponent.localRotation;
            }
            if (!scale.HasValue)
            {
                scale = targetComponent.localScale;
            }

            // construct snapshot with batch timestamp to save bandwidth
            NTSnapshot snapshot = new NTSnapshot(
                timestamp,
                NetworkTime.localTime,
                position.Value, rotation.Value, scale.Value
                );

            // add to buffer (or drop if older than first element)
            SnapshotInterpolation.InsertIfNewEnough(snapshot, clientBuffer);
        }
Esempio n. 9
0
        void UpdateClient()
        {
            // client authority, and local player (= allowed to move myself)?
            if (IsClientWithAuthority)
            {
                // https://github.com/vis2k/Mirror/pull/2992/
                if (!NetworkClient.ready)
                {
                    return;
                }

                // send to server each 'sendInterval'
                // NetworkTime.localTime for double precision until Unity has it too
                //
                // IMPORTANT:
                // snapshot interpolation requires constant sending.
                // DO NOT only send if position changed. for example:
                // ---
                // * client sends first position at t=0
                // * ... 10s later ...
                // * client moves again, sends second position at t=10
                // ---
                // * server gets first position at t=0
                // * server gets second position at t=10
                // * server moves from first to second within a time of 10s
                //   => would be a super slow move, instead of a wait & move.
                //
                // IMPORTANT:
                // DO NOT send nulls if not changed 'since last send' either. we
                // send unreliable and don't know which 'last send' the other end
                // received successfully.
                if (NetworkTime.localTime >= lastClientSendTime + sendInterval)
                {
                    // send snapshot without timestamp.
                    // receiver gets it from batch timestamp to save bandwidth.
                    NTSnapshot snapshot = ConstructSnapshot();
#if onlySyncOnChange_BANDWIDTH_SAVING
                    cachedSnapshotComparison = CompareSnapshots(snapshot);
                    if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange)
                    {
                        return;
                    }
#endif

#if onlySyncOnChange_BANDWIDTH_SAVING
                    CmdClientToServerSync(
                        // only sync what the user wants to sync
                        syncPosition && positionChanged ? snapshot.position : default(Vector3?),
                        syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?),
                        syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
                        );
#else
                    CmdClientToServerSync(
                        // only sync what the user wants to sync
                        syncPosition ? snapshot.position : default(Vector3?),
                        syncRotation ? snapshot.rotation : default(Quaternion?),
                        syncScale ? snapshot.scale : default(Vector3?)
                        );
#endif

                    lastClientSendTime = NetworkTime.localTime;
#if onlySyncOnChange_BANDWIDTH_SAVING
                    if (cachedSnapshotComparison)
                    {
                        hasSentUnchangedPosition = true;
                    }
                    else
                    {
                        hasSentUnchangedPosition = false;
                        lastSnapshot             = snapshot;
                    }
#endif
                }
            }
            // for all other clients (and for local player if !authority),
            // we need to apply snapshots from the buffer
            else
            {
                // compute snapshot interpolation & apply if any was spit out
                // TODO we don't have Time.deltaTime double yet. float is fine.
                if (SnapshotInterpolation.Compute(
                        NetworkTime.localTime, Time.deltaTime,
                        ref clientInterpolationTime,
                        bufferTime, clientBuffer,
                        catchupThreshold, catchupMultiplier,
                        Interpolate,
                        out NTSnapshot computed))
                {
                    NTSnapshot start = clientBuffer.Values[0];
                    NTSnapshot goal  = clientBuffer.Values[1];
                    ApplySnapshot(start, goal, computed);
                }
            }
        }