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