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