sync TIME ressource to client

- we evaluated multiple ways to sync the time, either store a delta between localtime and ServerTime or just store the ServerTime
This commit is contained in:
Marcel Märtens 2021-09-24 01:02:03 +02:00
parent a579329005
commit 665e9b9378
12 changed files with 101 additions and 40 deletions

View File

@ -39,7 +39,7 @@ use common::{
mounting::Rider, mounting::Rider,
outcome::Outcome, outcome::Outcome,
recipe::{ComponentRecipeBook, RecipeBook}, recipe::{ComponentRecipeBook, RecipeBook},
resources::{GameMode, PlayerEntity, TimeOfDay}, resources::{GameMode, PlayerEntity, ServerTime, Time, TimeOfDay},
spiral::Spiral2d, spiral::Spiral2d,
terrain::{ terrain::{
block::Block, map::MapConfig, neighbors, site::DungeonKindMeta, BiomeKind, block::Block, map::MapConfig, neighbors, site::DungeonKindMeta, BiomeKind,
@ -1683,7 +1683,6 @@ impl Client {
&mut self, &mut self,
inputs: ControllerInputs, inputs: ControllerInputs,
dt: Duration, dt: Duration,
total_tick_time: Duration,
add_foreign_systems: impl Fn(&mut DispatcherBuilder), add_foreign_systems: impl Fn(&mut DispatcherBuilder),
) -> Result<Vec<Event>, Error> { ) -> Result<Vec<Event>, Error> {
span!(_guard, "tick", "Client::tick"); span!(_guard, "tick", "Client::tick");
@ -1712,7 +1711,8 @@ impl Client {
prof_span!("handle and send inputs"); prof_span!("handle and send inputs");
self.next_control.inputs = inputs; self.next_control.inputs = inputs;
let con = std::mem::take(&mut self.next_control); let con = std::mem::take(&mut self.next_control);
let rcon = self.local_command_gen.gen(total_tick_time, con); let time = Duration::from_secs_f64(self.state.ecs().read_resource::<Time>().0);
let rcon = self.local_command_gen.gen(time, con);
let commands = self let commands = self
.state .state
.ecs() .ecs()
@ -2178,15 +2178,27 @@ impl Client {
) -> Result<(), Error> { ) -> Result<(), Error> {
prof_span!("handle_server_in_game_msg"); prof_span!("handle_server_in_game_msg");
match msg { match msg {
ServerGeneral::AckControl(acked_ids) => { ServerGeneral::TimeSync(time) => {
self.state.ecs().write_resource::<ServerTime>().0 = time.0;
let latency = self
.state
.ecs()
.read_storage::<RemoteController>()
.get(self.entity())
.map(|rc| rc.avg_latency())
.unwrap_or_default();
self.state.ecs().write_resource::<Time>().0 = time.0 + latency.as_secs_f64();
},
ServerGeneral::AckControl(acked_ids, time) => {
if let Some(remote_controller) = self if let Some(remote_controller) = self
.state .state
.ecs() .ecs()
.write_storage::<RemoteController>() .write_storage::<RemoteController>()
.get_mut(self.entity()) .get_mut(self.entity())
{ {
remote_controller.acked(acked_ids); let time = Duration::from_secs_f64(time.0);
remote_controller.maintain(); remote_controller.acked(acked_ids, time);
remote_controller.maintain(None);
} }
}, },
ServerGeneral::GroupUpdate(change_notification) => { ServerGeneral::GroupUpdate(change_notification) => {
@ -2870,12 +2882,8 @@ mod tests {
let mut clock = Clock::new(Duration::from_secs_f64(SPT)); let mut clock = Clock::new(Duration::from_secs_f64(SPT));
//tick //tick
let events_result: Result<Vec<Event>, Error> = client.tick( let events_result: Result<Vec<Event>, Error> =
comp::ControllerInputs::default(), client.tick(comp::ControllerInputs::default(), clock.dt(), |_| {});
clock.dt(),
clock.total_tick_time(),
|_| {},
);
//chat functionality //chat functionality
client.send_chat("foobar".to_string()); client.send_chat("foobar".to_string());

View File

@ -142,7 +142,8 @@ pub enum ServerGeneral {
CharacterSuccess, CharacterSuccess,
SpectatorSuccess(Vec3<f32>), SpectatorSuccess(Vec3<f32>),
//Ingame related //Ingame related
AckControl(HashSet<u64>), TimeSync(Time),
AckControl(HashSet<u64>, Time),
GroupUpdate(comp::group::ChangeNotification<Uid>), GroupUpdate(comp::group::ChangeNotification<Uid>),
/// Indicate to the client that they are invited to join a group /// Indicate to the client that they are invited to join a group
Invite { Invite {
@ -316,7 +317,8 @@ impl ServerMsg {
}, },
//Ingame related //Ingame related
ServerGeneral::GroupUpdate(_) ServerGeneral::GroupUpdate(_)
| ServerGeneral::AckControl(_) | ServerGeneral::TimeSync(_)
| ServerGeneral::AckControl(_, _)
| ServerGeneral::Invite { .. } | ServerGeneral::Invite { .. }
| ServerGeneral::InvitePending(_) | ServerGeneral::InvitePending(_)
| ServerGeneral::InviteComplete { .. } | ServerGeneral::InviteComplete { .. }

View File

@ -19,6 +19,7 @@ pub struct RemoteController {
commands: ControlCommands, commands: ControlCommands,
existing_commands: HashSet<u64>, existing_commands: HashSet<u64>,
max_hold: Duration, max_hold: Duration,
avg_latency: Duration,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -29,14 +30,16 @@ pub struct ControlCommand {
} }
impl RemoteController { impl RemoteController {
/// delte old commands /// delte old and outdated( older than server_time) commands
pub fn maintain(&mut self) { pub fn maintain(&mut self, server_time: Option<Duration>) {
self.commands.make_contiguous(); self.commands.make_contiguous();
if let Some(last) = self.commands.back() { if let Some(last) = self.commands.back() {
let min_allowed_age = last.source_time; let min_allowed_age = last.source_time;
while let Some(first) = self.commands.front() { while let Some(first) = self.commands.front() {
if first.source_time + self.max_hold < min_allowed_age { if first.source_time + self.max_hold < min_allowed_age
|| matches!(server_time, Some(x) if first.source_time < x)
{
self.commands self.commands
.pop_front() .pop_front()
.map(|c| self.existing_commands.remove(&c.id)); .map(|c| self.existing_commands.remove(&c.id));
@ -87,7 +90,32 @@ impl RemoteController {
} }
/// arrived at remote and no longer need to be hold locally /// arrived at remote and no longer need to be hold locally
pub fn acked(&mut self, ids: HashSet<u64>) { self.commands.retain(|c| !ids.contains(&c.id)); } pub fn acked(&mut self, ids: HashSet<u64>, server_send_time: Duration) {
//get latency from oldest removed command and server_send_time
let mut highest_source_time = Duration::from_secs(0);
self.commands.retain(|c| {
let retain = !ids.contains(&c.id);
if !retain && c.source_time > highest_source_time {
highest_source_time = c.source_time;
}
retain
});
// we add self.avg_latency here as we must assume that highest_source_time was
// increased by avg_latency in client TimeSync though we assume that
// avg_latency is quite stable and didnt change much between when the component
// was added and now
if let Some(latency) =
(server_send_time + self.avg_latency).checked_sub(highest_source_time)
{
tracing::error!(?latency, "ggg");
self.avg_latency = (99 * self.avg_latency + latency) / 100;
} else {
// add 5% and 10ms to the latency
self.avg_latency =
Duration::from_secs_f64(self.avg_latency.as_secs_f64() * 1.05 + 0.01);
}
tracing::error!(?highest_source_time, ?self.avg_latency, "aaa");
}
pub fn commands(&self) -> &ControlCommands { &self.commands } pub fn commands(&self) -> &ControlCommands { &self.commands }
@ -117,9 +145,6 @@ impl RemoteController {
Ok(i) => i, Ok(i) => i,
Err(i) => i, Err(i) => i,
}; };
println!("start_i {:?}", start_i);
println!("end_exclusive_i {:?}", end_exclusive_i);
if self.commands.is_empty() || end_exclusive_i == start_i { if self.commands.is_empty() || end_exclusive_i == start_i {
return None; return None;
} }
@ -140,8 +165,6 @@ impl RemoteController {
start + dt start + dt
}; };
let local_dur = local_end - local_start; let local_dur = local_end - local_start;
println!("local_start {:?}, local_end {:?}", local_start, local_end);
println!("source_time {:?}", e.source_time);
result.inputs.move_dir += e.msg.inputs.move_dir * local_dur.as_secs_f32(); result.inputs.move_dir += e.msg.inputs.move_dir * local_dur.as_secs_f32();
result.inputs.move_z += e.msg.inputs.move_z * local_dur.as_secs_f32(); result.inputs.move_z += e.msg.inputs.move_z * local_dur.as_secs_f32();
look_dir = result.inputs.look_dir.to_vec() look_dir = result.inputs.look_dir.to_vec()
@ -153,6 +176,16 @@ impl RemoteController {
.break_block_pos .break_block_pos
.or(e.msg.inputs.break_block_pos); .or(e.msg.inputs.break_block_pos);
result.inputs.strafing = result.inputs.strafing || e.msg.inputs.strafing; result.inputs.strafing = result.inputs.strafing || e.msg.inputs.strafing;
// we apply events from all that are started here.
// if we only are part of 1 tick here we would assume that it was already
// covered before
if i != start_i || e.source_time >= start {
result.actions.append(&mut e.msg.actions.clone());
result.events.append(&mut e.msg.events.clone());
result
.queued_inputs
.append(&mut e.msg.queued_inputs.clone());
}
last_start = local_start; last_start = local_start;
} }
result.inputs.move_dir /= dt.as_secs_f32(); result.inputs.move_dir /= dt.as_secs_f32();
@ -161,6 +194,13 @@ impl RemoteController {
Some(result) Some(result)
} }
/// the average latency is 300ms by default and will be adjusted over time
/// with every Ack the server sends its local tick time at the Ack
/// so we can calculate how much time was spent for 1 way from
/// server->client and assume that this is also true for client->server
/// latency
pub fn avg_latency(&self) -> Duration { self.avg_latency }
} }
impl Default for RemoteController { impl Default for RemoteController {
@ -169,6 +209,7 @@ impl Default for RemoteController {
commands: VecDeque::new(), commands: VecDeque::new(),
existing_commands: HashSet::new(), existing_commands: HashSet::new(),
max_hold: Duration::from_secs(1), max_hold: Duration::from_secs(1),
avg_latency: Duration::from_millis(300),
} }
} }
} }

View File

@ -13,6 +13,12 @@ pub struct TimeOfDay(pub f64);
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq)] #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Time(pub f64); pub struct Time(pub f64);
/// A resource that stores the Server-Time, its needed to calculate the input
/// delay and adjust Time on the client keep in mind that it contains a
/// network-latency. Only filled on the client
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct ServerTime(pub f64);
/// A resource that stores the time since the previous tick. /// A resource that stores the time since the previous tick.
#[derive(Default)] #[derive(Default)]
pub struct DeltaTime(pub f32); pub struct DeltaTime(pub f32);

View File

@ -13,8 +13,8 @@ use common::{
outcome::Outcome, outcome::Outcome,
region::RegionMap, region::RegionMap,
resources::{ resources::{
DeltaTime, EntitiesDiedLastTick, GameMode, PlayerEntity, PlayerPhysicsSettings, Time, DeltaTime, EntitiesDiedLastTick, GameMode, PlayerEntity, PlayerPhysicsSettings, ServerTime,
TimeOfDay, Time, TimeOfDay,
}, },
slowjob::SlowJobPool, slowjob::SlowJobPool,
terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid}, terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid},
@ -257,6 +257,7 @@ impl State {
ecs.insert(Time(0.0)); ecs.insert(Time(0.0));
// Register unsynced resources used by the ECS. // Register unsynced resources used by the ECS.
ecs.insert(ServerTime(0.0)); //synced by msg
ecs.insert(DeltaTime(0.0)); ecs.insert(DeltaTime(0.0));
ecs.insert(PlayerEntity(None)); ecs.insert(PlayerEntity(None));
ecs.insert(TerrainGrid::new(map_size_lg, default_chunk).unwrap()); ecs.insert(TerrainGrid::new(map_size_lg, default_chunk).unwrap());

View File

@ -1,6 +1,6 @@
use common::{ use common::{
comp::{Controller, RemoteController}, comp::{Controller, RemoteController},
resources::{Time, DeltaTime,}, resources::{DeltaTime, Time},
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use specs::{ use specs::{

View File

@ -103,7 +103,8 @@ impl Client {
}, },
//In-game related //In-game related
ServerGeneral::GroupUpdate(_) ServerGeneral::GroupUpdate(_)
| ServerGeneral::AckControl(_) | ServerGeneral::TimeSync(_)
| ServerGeneral::AckControl(_, _)
| ServerGeneral::Invite { .. } | ServerGeneral::Invite { .. }
| ServerGeneral::InvitePending(_) | ServerGeneral::InvitePending(_)
| ServerGeneral::InviteComplete { .. } | ServerGeneral::InviteComplete { .. }
@ -176,7 +177,8 @@ impl Client {
}, },
//In-game related //In-game related
ServerGeneral::GroupUpdate(_) ServerGeneral::GroupUpdate(_)
| ServerGeneral::AckControl(_) | ServerGeneral::TimeSync(_)
| ServerGeneral::AckControl(_, _)
| ServerGeneral::Invite { .. } | ServerGeneral::Invite { .. }
| ServerGeneral::InvitePending(_) | ServerGeneral::InvitePending(_)
| ServerGeneral::InviteComplete { .. } | ServerGeneral::InviteComplete { .. }

View File

@ -9,7 +9,7 @@ use common::{
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
link::Is, link::Is,
mounting::Rider, mounting::Rider,
resources::{PlayerPhysicsSetting, PlayerPhysicsSettings}, resources::{PlayerPhysicsSetting, PlayerPhysicsSettings, Time},
slowjob::SlowJobPool, slowjob::SlowJobPool,
terrain::TerrainGrid, terrain::TerrainGrid,
vol::ReadVol, vol::ReadVol,
@ -20,7 +20,7 @@ use common_state::{BlockChange, BuildAreas};
use core::mem; use core::mem;
use rayon::prelude::*; use rayon::prelude::*;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
use std::{borrow::Cow, time::Instant}; use std::{borrow::Cow, time::Instant, time::Duration};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
use vek::*; use vek::*;
@ -47,6 +47,7 @@ impl Sys {
fn handle_client_in_game_msg( fn handle_client_in_game_msg(
server_emitter: &mut common::event::Emitter<'_, ServerEvent>, server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
entity: specs::Entity, entity: specs::Entity,
time: &Time,
client: &Client, client: &Client,
maybe_presence: &mut Option<&mut Presence>, maybe_presence: &mut Option<&mut Presence>,
terrain: &ReadExpect<'_, TerrainGrid>, terrain: &ReadExpect<'_, TerrainGrid>,
@ -96,9 +97,10 @@ impl Sys {
ClientGeneral::Control(rc) => { ClientGeneral::Control(rc) => {
if presence.kind.controlling_char() { if presence.kind.controlling_char() {
let ids = remote_controller.append(rc); let ids = remote_controller.append(rc);
remote_controller.maintain(); remote_controller.maintain(Some(Duration::from_secs_f64(time.0)));
// confirm controls // confirm controls
client.send(ServerGeneral::AckControl(ids))?; client.send(ServerGeneral::AckControl(ids, *time))?;
//Todo: FIXME!!! //Todo: FIXME!!!
/* /*
// Skip respawn if client entity is alive // Skip respawn if client entity is alive
@ -316,6 +318,7 @@ impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, Time>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<ServerEvent>>,
ReadExpect<'a, TerrainGrid>, ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, SlowJobPool>, ReadExpect<'a, SlowJobPool>,
@ -347,6 +350,7 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
( (
entities, entities,
time,
server_event_bus, server_event_bus,
terrain, terrain,
slow_jobs, slow_jobs,
@ -433,6 +437,7 @@ impl<'a> System<'a> for Sys {
Self::handle_client_in_game_msg( Self::handle_client_in_game_msg(
server_emitter, server_emitter,
entity, entity,
&time,
client, client,
&mut clearable_maybe_presence, &mut clearable_maybe_presence,
&terrain, &terrain,

View File

@ -5,6 +5,7 @@ use common::{
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::PingMsg; use common_net::msg::PingMsg;
use common_net::msg::ServerGeneral;
use rayon::prelude::*; use rayon::prelude::*;
use specs::{Entities, ParJoin, Read, WriteStorage}; use specs::{Entities, ParJoin, Read, WriteStorage};
use tracing::{debug, info}; use tracing::{debug, info};
@ -47,8 +48,10 @@ impl<'a> System<'a> for Sys {
client.participant.as_mut().map(|p| p.try_fetch_event()) client.participant.as_mut().map(|p| p.try_fetch_event())
{} {}
client.send_fallible(ServerGeneral::TimeSync(*time));
let res = super::try_recv_all(client, 4, Self::handle_ping_msg); let res = super::try_recv_all(client, 4, Self::handle_ping_msg);
match res { match res {
Err(e) => { Err(e) => {
debug!(?entity, ?e, "network error with client, disconnecting"); debug!(?entity, ?e, "network error with client, disconnecting");

View File

@ -208,7 +208,6 @@ impl PlayState for CharSelectionState {
let res = self.client.borrow_mut().tick( let res = self.client.borrow_mut().tick(
comp::ControllerInputs::default(), comp::ControllerInputs::default(),
global_state.clock.dt(), global_state.clock.dt(),
global_state.clock.total_tick_time(),
|_| {}, |_| {},
); );
match res { match res {

View File

@ -226,7 +226,6 @@ impl PlayState for MainMenuState {
match client.tick( match client.tick(
comp::ControllerInputs::default(), comp::ControllerInputs::default(),
global_state.clock.dt(), global_state.clock.dt(),
global_state.clock.total_tick_time(),
|_| {}, |_| {},
) { ) {
Ok(events) => { Ok(events) => {

View File

@ -270,12 +270,7 @@ impl SessionState {
self.mumble_link.update(player_pos, player_pos); self.mumble_link.update(player_pos, player_pos);
} }
for event in client.tick( for event in client.tick(self.inputs.clone(), dt, crate::ecs::sys::add_local_systems)? {
self.inputs.clone(),
dt,
global_state.clock.total_tick_time(),
crate::ecs::sys::add_local_systems,
)? {
match event { match event {
client::Event::Chat(m) => { client::Event::Chat(m) => {
self.hud.new_message(m); self.hud.new_message(m);