mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
sync
make systems more stable by: - fix wobbly, by doing CompSync and TimeSync in same ecs system - dont interpolate on voxygen - sync PhysState to the client in order to get rid of the jump snap to ground bug - make the simulate_ahead more constant over time, as "jumps" in this number will be noticed by the client as "lag jumps" There are still some problems - [ ] Snap to the ground on jump isn't fixed completly, as we depend on tick n-1 and we only sync tick n to the client - [ ] Arrows dont work properly yet with this commit, e.g. right click arrow attack - [ ] Verify that clientcommands are actually broadcasted to all clients - [ ] Followup: Agent needs to send clientcommands rather than commands
This commit is contained in:
parent
65a0c15054
commit
8ab5d53f2c
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
pub mod addr;
|
pub mod addr;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
mod revertclock;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use crate::error::Error;
|
pub use crate::error::Error;
|
||||||
@ -15,7 +16,7 @@ pub use specs::{
|
|||||||
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, World, WorldExt,
|
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, World, WorldExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::addr::ConnectionArgs;
|
use crate::{addr::ConnectionArgs, revertclock::RevertClock};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use common::{
|
use common::{
|
||||||
character::{CharacterId, CharacterItem},
|
character::{CharacterId, CharacterItem},
|
||||||
@ -64,7 +65,7 @@ use common_net::{
|
|||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
use common_state::State;
|
use common_state::State;
|
||||||
use common_systems::{add_local_systems, add_rewind_systems};
|
use common_systems::add_local_systems;
|
||||||
use comp::BuffKind;
|
use comp::BuffKind;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
@ -237,9 +238,7 @@ pub struct Client {
|
|||||||
|
|
||||||
local_command_gen: CommandGenerator,
|
local_command_gen: CommandGenerator,
|
||||||
next_control: Controller,
|
next_control: Controller,
|
||||||
inter_tick_reverted_time: Option<Duration>,
|
revert_clock: RevertClock,
|
||||||
inter_tick_time_syncs: usize,
|
|
||||||
rewind_fluctuation_budget: f64,
|
|
||||||
|
|
||||||
network: Option<Network>,
|
network: Option<Network>,
|
||||||
participant: Option<Participant>,
|
participant: Option<Participant>,
|
||||||
@ -724,9 +723,7 @@ impl Client {
|
|||||||
|
|
||||||
local_command_gen: CommandGenerator::default(),
|
local_command_gen: CommandGenerator::default(),
|
||||||
next_control: Controller::default(),
|
next_control: Controller::default(),
|
||||||
inter_tick_reverted_time: None,
|
revert_clock: RevertClock::default(),
|
||||||
inter_tick_time_syncs: 0,
|
|
||||||
rewind_fluctuation_budget: 0.0,
|
|
||||||
|
|
||||||
network: Some(network),
|
network: Some(network),
|
||||||
participant: Some(participant),
|
participant: Some(participant),
|
||||||
@ -1705,8 +1702,7 @@ impl Client {
|
|||||||
|
|
||||||
// 1) Build up a list of events for this frame, to be passed to the frontend.
|
// 1) Build up a list of events for this frame, to be passed to the frontend.
|
||||||
let mut frontend_events = Vec::new();
|
let mut frontend_events = Vec::new();
|
||||||
self.inter_tick_reverted_time = None;
|
self.revert_clock.reset();
|
||||||
self.inter_tick_time_syncs = 0;
|
|
||||||
|
|
||||||
// Prepare for new events
|
// Prepare for new events
|
||||||
{
|
{
|
||||||
@ -1736,256 +1732,9 @@ impl Client {
|
|||||||
// Handle new messages from the server.
|
// Handle new messages from the server.
|
||||||
frontend_events.append(&mut self.handle_new_messages()?);
|
frontend_events.append(&mut self.handle_new_messages()?);
|
||||||
|
|
||||||
// Simulate Ahead
|
// When the server sends a componenet update, rewind back
|
||||||
|
self.revert_clock.tick(&mut self.state, dt);
|
||||||
//TODO: also waro budget in non rewind workflow
|
//prepare this tick after we rewindet
|
||||||
//Dies hier. außerdem ist immernoch 200 als fixer wert im controler drinn
|
|
||||||
|
|
||||||
let time = self.state.ecs().read_resource::<Time>().0 as f64;
|
|
||||||
|
|
||||||
common_base::plot!("recived_time_sync", self.inter_tick_time_syncs as f64);
|
|
||||||
|
|
||||||
if let Some(rewind_time) = self.inter_tick_reverted_time {
|
|
||||||
let time = self.state.ecs().read_resource::<Time>().0 as f64;
|
|
||||||
let simulate_ahead = self
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<RemoteController>()
|
|
||||||
.get(self.entity())
|
|
||||||
.map(|rc| rc.simulate_ahead())
|
|
||||||
.unwrap_or_default();
|
|
||||||
// We substract `dt` here, as otherwise we
|
|
||||||
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms,
|
|
||||||
// rewind_tick=130ms end_tick=100+130+60=290
|
|
||||||
// Tick2: server_time=130ms dt=30ms ping=30 simulate_ahead=130ms,
|
|
||||||
// rewind_tick=130ms end_tick=130+130+30=290
|
|
||||||
// Tick3: server_time=160ms dt=60ms ping=30 simulate_ahead=130ms,
|
|
||||||
// rewind_tick=130ms end_tick=160+130+60=350 with dt substraction
|
|
||||||
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms,
|
|
||||||
// rewind_tick=70ms end_tick=100+70+60=230 Tick2: server_time=130ms
|
|
||||||
// dt=30ms ping=30 simulate_ahead=130ms, rewind_tick=100ms
|
|
||||||
// end_tick=130+100+30=260 Tick3: server_time=160ms dt=60ms ping=30
|
|
||||||
// simulate_ahead=130ms, rewind_tick=70ms end_tick=160+70+60=290
|
|
||||||
let simulate_ahead = simulate_ahead.max(dt) - dt;
|
|
||||||
let simulate_ahead = simulate_ahead + Duration::from_secs_f64(1.0 / 30.0);
|
|
||||||
/*
|
|
||||||
let x = if rewind_time > simulate_ahead + Duration::from_millis(25) {
|
|
||||||
common_base::plot!("xxx", 1.0);
|
|
||||||
common_base::plot!("gggggg", simulate_ahead.as_secs_f64() - rewind_time.as_secs_f64() );
|
|
||||||
simulate_ahead + Duration::from_millis(15)
|
|
||||||
} else if rewind_time < simulate_ahead - Duration::from_millis(25) {
|
|
||||||
common_base::plot!("xxx", -1.0);
|
|
||||||
simulate_ahead - Duration::from_millis(15)
|
|
||||||
} else {
|
|
||||||
common_base::plot!("xxx", 0.0);
|
|
||||||
simulate_ahead
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
let time = self.state.ecs().read_resource::<Time>().0;
|
|
||||||
common_base::plot!("tick_before", time);
|
|
||||||
|
|
||||||
let vel = self
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<Vel>()
|
|
||||||
.get(self.entity())
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(Vel(Vec3::zero()));
|
|
||||||
|
|
||||||
common_base::plot!("vel_x_before1", vel.0.x as f64);
|
|
||||||
common_base::plot!("vel_y_before1", vel.0.y as f64);
|
|
||||||
common_base::plot!("vel_z_before1", vel.0.z as f64);
|
|
||||||
let pos = self
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<common::comp::Pos>()
|
|
||||||
.get(self.entity())
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(common::comp::Pos(Vec3::zero()));
|
|
||||||
|
|
||||||
common_base::plot!("pos_x_before1", pos.0.x as f64);
|
|
||||||
common_base::plot!("pos_y_before1", pos.0.y as f64);
|
|
||||||
common_base::plot!("pos_z_before1", pos.0.z as f64);
|
|
||||||
}
|
|
||||||
|
|
||||||
common_base::plot!("rewind_time", rewind_time.as_secs_f64());
|
|
||||||
self.state.rewind_tick(
|
|
||||||
simulate_ahead,
|
|
||||||
|dispatch_builder| {
|
|
||||||
add_rewind_systems(dispatch_builder);
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
{
|
|
||||||
let time = self.state.ecs().read_resource::<Time>().0;
|
|
||||||
common_base::plot!("tick_afterwards1", time);
|
|
||||||
let vel = self
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<Vel>()
|
|
||||||
.get(self.entity())
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(Vel(Vec3::zero()));
|
|
||||||
|
|
||||||
common_base::plot!("vel_x_after1", vel.0.x as f64);
|
|
||||||
common_base::plot!("vel_y_after1", vel.0.y as f64);
|
|
||||||
common_base::plot!("vel_z_after1", vel.0.z as f64);
|
|
||||||
let pos = self
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<common::comp::Pos>()
|
|
||||||
.get(self.entity())
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(common::comp::Pos(Vec3::zero()));
|
|
||||||
|
|
||||||
common_base::plot!("pos_x_after1", pos.0.x as f64);
|
|
||||||
common_base::plot!("pos_y_after1", pos.0.y as f64);
|
|
||||||
common_base::plot!("pos_z_after1", pos.0.z as f64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
let mut rewind_time = Duration::from_secs_f64(0.0);
|
|
||||||
const WARP_PERCENT: f64 = 0.05; // make sure we end up not further than 5% from the estimated tick
|
|
||||||
let mut target_smooth_dt = dt.as_secs_f64();
|
|
||||||
common_base::plot!("recived_time_sync", 0.0);
|
|
||||||
if let Some(reverted_time) = self.inter_tick_reverted_time {
|
|
||||||
// At smooth_time we wouldn't notice any rewind
|
|
||||||
let smooth_time = reverted_time + dt;
|
|
||||||
common_base::plot!("recived_time_sync", 1.0);
|
|
||||||
common_base::plot!("reverted_time", reverted_time.as_secs_f64());
|
|
||||||
let time = self.state.ecs().read_resource::<Time>().0 as f64;
|
|
||||||
let simulate_ahead = self
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<RemoteController>()
|
|
||||||
.get(self.entity())
|
|
||||||
.map(|rc| rc.simulate_ahead())
|
|
||||||
.unwrap_or_default();
|
|
||||||
// We substract `dt` here, as otherwise we
|
|
||||||
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms, rewind_tick=130ms end_tick=100+130+60=290
|
|
||||||
// Tick2: server_time=130ms dt=30ms ping=30 simulate_ahead=130ms, rewind_tick=130ms end_tick=130+130+30=290
|
|
||||||
// Tick3: server_time=160ms dt=60ms ping=30 simulate_ahead=130ms, rewind_tick=130ms end_tick=160+130+60=350
|
|
||||||
// with dt substraction
|
|
||||||
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms, rewind_tick=70ms end_tick=100+70+60=230
|
|
||||||
// Tick2: server_time=130ms dt=30ms ping=30 simulate_ahead=130ms, rewind_tick=100ms end_tick=130+100+30=260
|
|
||||||
// Tick3: server_time=160ms dt=60ms ping=30 simulate_ahead=130ms, rewind_tick=70ms end_tick=160+70+60=290
|
|
||||||
let simulate_ahead = simulate_ahead.max(dt) - dt;
|
|
||||||
// measurements lead to the effect that smooth_diff is == 0.0 when we add 2 server ticks here.
|
|
||||||
let simulate_ahead = simulate_ahead + Duration::from_secs_f64(1.0 / 30.0);
|
|
||||||
rewind_time = simulate_ahead;
|
|
||||||
|
|
||||||
// Simulate_ahead still fluctionates because Server Tick != Client Tick, and we cant
|
|
||||||
// control the phase in which the sync happens.
|
|
||||||
// In order to dampen it, we calculate the target_smooth_dt and make sure to not derive
|
|
||||||
// to much from it
|
|
||||||
|
|
||||||
ERROR, muss man das hier swappen ?
|
|
||||||
target_smooth_dt = reverted_time.as_secs_f64() - simulate_ahead.as_secs_f64();
|
|
||||||
// we store it, and will apply it over the course of the next ticks
|
|
||||||
self.rewind_fluctuation_budget = target_smooth_dt;
|
|
||||||
common_base::plot!("new_rewind_fluctuation_budget", self.rewind_fluctuation_budget);
|
|
||||||
}
|
|
||||||
common_base::plot!("rewind_time", rewind_time.as_secs_f64());
|
|
||||||
|
|
||||||
|
|
||||||
// we need to subtract the normal dt as we have a separate tick call for it.
|
|
||||||
// make it positive as we wont allow direct go in past
|
|
||||||
let corrected_target_smooth_dt = (target_smooth_dt - dt.as_secs_f64()).max(0.0);
|
|
||||||
common_base::plot!("corrected_target_smooth_dt", corrected_target_smooth_dt);
|
|
||||||
|
|
||||||
// apply our budget to rewind_time
|
|
||||||
let mut smooth_rewind_time = rewind_time.as_secs_f64();
|
|
||||||
smooth_rewind_time += self.rewind_fluctuation_budget;
|
|
||||||
let xxx = smooth_rewind_time.clamp( corrected_target_smooth_dt * (1.0-WARP_PERCENT), corrected_target_smooth_dt * (1.0+WARP_PERCENT));
|
|
||||||
common_base::plot!("xxx", xxx);
|
|
||||||
smooth_rewind_time = xxx;
|
|
||||||
self.rewind_fluctuation_budget -= smooth_rewind_time - rewind_time.as_secs_f64();
|
|
||||||
|
|
||||||
common_base::plot!("rewind_fluctuation_budget", self.rewind_fluctuation_budget);
|
|
||||||
common_base::plot!("target_smooth_dt", target_smooth_dt);
|
|
||||||
|
|
||||||
|
|
||||||
tracing::warn!(?smooth_rewind_time, ?dt, "simulating ahead again");
|
|
||||||
self.state.rewind_tick(
|
|
||||||
Duration::from_secs_f64(smooth_rewind_time),
|
|
||||||
|dispatch_builder| {
|
|
||||||
add_rewind_systems(dispatch_builder);
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
if let Some(rewind_time) = self.inter_tick_reverted_time {
|
|
||||||
common_base::plot!("recived_time_sync", 1.0);
|
|
||||||
let _time = self.state.ecs().read_resource::<Time>().0 as f64;
|
|
||||||
let simulate_ahead = self
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<RemoteController>()
|
|
||||||
.get(self.entity())
|
|
||||||
.map(|rc| rc.simulate_ahead())
|
|
||||||
.unwrap_or_default();
|
|
||||||
// We substract `dt` here, as otherwise we
|
|
||||||
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms,
|
|
||||||
// rewind_tick=130ms end_tick=100+130+60=290
|
|
||||||
// Tick2: server_time=130ms dt=30ms ping=30 simulate_ahead=130ms,
|
|
||||||
// rewind_tick=130ms end_tick=130+130+30=290
|
|
||||||
// Tick3: server_time=160ms dt=60ms ping=30 simulate_ahead=130ms,
|
|
||||||
// rewind_tick=130ms end_tick=160+130+60=350 with dt substraction
|
|
||||||
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms,
|
|
||||||
// rewind_tick=70ms end_tick=100+70+60=230 Tick2: server_time=130ms
|
|
||||||
// dt=30ms ping=30 simulate_ahead=130ms, rewind_tick=100ms
|
|
||||||
// end_tick=130+100+30=260 Tick3: server_time=160ms dt=60ms ping=30
|
|
||||||
// simulate_ahead=130ms, rewind_tick=70ms end_tick=160+70+60=290
|
|
||||||
let simulate_ahead = simulate_ahead.max(dt) - dt;
|
|
||||||
// measurements lead to the effect that smooth_diff is == 0.0 when we add 2
|
|
||||||
// server ticks here.
|
|
||||||
let simulate_ahead = simulate_ahead + Duration::from_secs_f64(1.0 / 30.0);
|
|
||||||
|
|
||||||
// Simulate_ahead still fluctionates because Server Tick != Client Tick, and we cant
|
|
||||||
// control the phase in which the sync happens.
|
|
||||||
// In order to dampen it, we calculate the smooth_time and make sure to not derive
|
|
||||||
// to much from it
|
|
||||||
let smooth_diff = simulate_ahead.as_secs_f64() - rewind_time.as_secs_f64();
|
|
||||||
|
|
||||||
const WARP_PERCENT: f64 = 0.05; // make sure we end up not further than 5% from the estimated tick
|
|
||||||
let mut warp_budget = dt.as_secs_f64() * WARP_PERCENT;
|
|
||||||
if smooth_diff.abs() > warp_budget {
|
|
||||||
self.rewind_fluctuation_budget += (dt.as_secs_f64() * (1.0-WARP_PERCENT)) * smooth_diff.signum();
|
|
||||||
warp_budget = warp_budget.clamp(-warp_budget, warp_budget);
|
|
||||||
}
|
|
||||||
|
|
||||||
// extend from
|
|
||||||
let add = if smooth_diff >= 0.0 {
|
|
||||||
smooth_diff.min(self.rewind_fluctuation_budget).max(0.0)
|
|
||||||
} else {
|
|
||||||
smooth_diff.max(self.rewind_fluctuation_budget).min(0.0)
|
|
||||||
};
|
|
||||||
self.rewind_fluctuation_budget -= add;
|
|
||||||
let simulate_ahead = Duration::from_secs_f64(time + rewind_time.as_secs_f64() + add);
|
|
||||||
|
|
||||||
common_base::plot!("rewind_fluctuation_budget", self.rewind_fluctuation_budget);
|
|
||||||
common_base::plot!("smooth_diff", smooth_diff);
|
|
||||||
|
|
||||||
//let simulate_ahead = simulate_ahead.max(dt)/* - dt*/;
|
|
||||||
//let simulate_ahead = rewind_time.min(simulate_ahead);
|
|
||||||
tracing::warn!(?simulate_ahead, ?dt, "simulating ahead again");
|
|
||||||
common_base::plot!("rewind_time", rewind_time.as_secs_f64());
|
|
||||||
self.state.rewind_tick(
|
|
||||||
simulate_ahead,
|
|
||||||
|dispatch_builder| {
|
|
||||||
add_rewind_systems(dispatch_builder);
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 2) Handle input from frontend.
|
// 2) Handle input from frontend.
|
||||||
// Pass character actions from frontend input to the player's entity.
|
// Pass character actions from frontend input to the player's entity.
|
||||||
@ -1993,10 +1742,11 @@ 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 time = Duration::from_secs_f64(self.state.ecs().read_resource::<Time>().0) + dt;
|
let this_tick_time =
|
||||||
|
Duration::from_secs_f64(self.state.ecs().read_resource::<Time>().0) + dt;
|
||||||
let monotonic_time =
|
let monotonic_time =
|
||||||
Duration::from_secs_f64(self.state.ecs().read_resource::<MonotonicTime>().0);
|
Duration::from_secs_f64(self.state.ecs().read_resource::<MonotonicTime>().0);
|
||||||
let rcon = self.local_command_gen.gen(time, con);
|
let rcon = self.local_command_gen.gen(this_tick_time, con);
|
||||||
let commands = self
|
let commands = self
|
||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
@ -2446,23 +2196,15 @@ 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::TimeSync(time) => {
|
ServerGeneral::TimeSync {
|
||||||
self.inter_tick_time_syncs += 1;
|
server_time,
|
||||||
// Even with a stable network, expect time to oscillate around the actual time
|
inter_tick_offset,
|
||||||
// by SERVER_TICK (33.3ms)
|
} => {
|
||||||
let old_time = self.state.ecs().read_resource::<Time>().0;
|
let old_client_time = self.state.ecs().read_resource::<Time>().0;
|
||||||
let diff = old_time - time.0;
|
self.state.ecs().write_resource::<Time>().0 = server_time.0;
|
||||||
self.state.ecs().write_resource::<Time>().0 = time.0;
|
self.revert_clock
|
||||||
if diff > 0.0 {
|
.sync(old_client_time, server_time, inter_tick_offset);
|
||||||
tracing::warn!(?old_time, ?diff, "Time was reverted by server");
|
|
||||||
let rewind_time = self.inter_tick_reverted_time.unwrap_or_default()
|
|
||||||
+ Duration::from_secs_f64(diff);
|
|
||||||
self.inter_tick_reverted_time = Some(rewind_time);
|
|
||||||
} else {
|
|
||||||
tracing::warn!(?old_time, ?diff, "Time was advanced by server");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
ServerGeneral::AckControl {
|
ServerGeneral::AckControl {
|
||||||
acked_ids,
|
acked_ids,
|
||||||
highest_ahead_command,
|
highest_ahead_command,
|
||||||
|
249
client/src/revertclock.rs
Normal file
249
client/src/revertclock.rs
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
use common::{
|
||||||
|
comp::{RemoteController, Vel},
|
||||||
|
resources::{DeltaTime, MonotonicTime, PlayerEntity, Time, TimeOfDay},
|
||||||
|
};
|
||||||
|
use common_state::State;
|
||||||
|
use common_systems::add_rewind_systems;
|
||||||
|
use specs::WorldExt;
|
||||||
|
use std::time::Duration;
|
||||||
|
use vek::{ops::Clamp, Vec3};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct RevertClock {
|
||||||
|
reverted_time: Option<Duration>,
|
||||||
|
time_syncs: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RevertClock {
|
||||||
|
pub(crate) fn reset(&mut self) {
|
||||||
|
self.reverted_time = None;
|
||||||
|
self.time_syncs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server might need up to 33ms to calcualte its tick. It will send this
|
||||||
|
// time with it, so that we can substract it from
|
||||||
|
pub(crate) fn sync(
|
||||||
|
&mut self,
|
||||||
|
old_client_time: f64,
|
||||||
|
new_server_time: Time,
|
||||||
|
_inter_tick_offset: Duration,
|
||||||
|
) {
|
||||||
|
self.time_syncs += 1;
|
||||||
|
// Even with a stable network, expect time to oscillate around the actual time
|
||||||
|
// by SERVER_TICK (33.3ms)
|
||||||
|
let diff = old_client_time - new_server_time.0;
|
||||||
|
if diff > 0.0 {
|
||||||
|
tracing::warn!(?old_client_time, ?diff, "Time was reverted by server");
|
||||||
|
let rewind_time =
|
||||||
|
self.reverted_time.unwrap_or_default() + Duration::from_secs_f64(diff);
|
||||||
|
self.reverted_time = Some(rewind_time);
|
||||||
|
} else {
|
||||||
|
tracing::warn!(?old_client_time, ?diff, "Time was advanced by server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rewind local changes after the server send some old state
|
||||||
|
fn rewind_tick(simulate_ahead: Duration, state: &mut State) {
|
||||||
|
common_base::plot!("simulate_ahead", simulate_ahead.as_secs_f64());
|
||||||
|
|
||||||
|
// store changes
|
||||||
|
let time_of_day = state.ecs().read_resource::<TimeOfDay>().0;
|
||||||
|
let monotonic_time = state.ecs().read_resource::<MonotonicTime>().0;
|
||||||
|
let delta_time = state.ecs().read_resource::<DeltaTime>().0;
|
||||||
|
|
||||||
|
const MAX_INCREMENTS: usize = 100; // The maximum number of collision tests per tick
|
||||||
|
const STEP_SEC: f64 = 0.04;
|
||||||
|
let increments =
|
||||||
|
((simulate_ahead.as_secs_f64() / STEP_SEC).ceil() as usize).clamped(1, MAX_INCREMENTS);
|
||||||
|
for _ in 0..increments {
|
||||||
|
let partial = simulate_ahead / (increments as u32);
|
||||||
|
state.tick(
|
||||||
|
partial,
|
||||||
|
|dispatch_builder| {
|
||||||
|
add_rewind_systems(dispatch_builder);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
None, // TODO: fix metrics
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewind changes
|
||||||
|
state.ecs().write_resource::<TimeOfDay>().0 = time_of_day;
|
||||||
|
state.ecs().write_resource::<MonotonicTime>().0 = monotonic_time;
|
||||||
|
state.ecs().write_resource::<DeltaTime>().0 = delta_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn tick(&mut self, state: &mut State, dt: Duration) {
|
||||||
|
let entity = state
|
||||||
|
.ecs()
|
||||||
|
.read_resource::<PlayerEntity>()
|
||||||
|
.0
|
||||||
|
.expect("Client::entity should always have PlayerEntity be Some");
|
||||||
|
|
||||||
|
common_base::plot!("recived_time_sync", self.time_syncs as f64);
|
||||||
|
|
||||||
|
let rewind_time = match self.reverted_time {
|
||||||
|
Some(rewind_time) => rewind_time,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let simulate_ahead = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<RemoteController>()
|
||||||
|
.get(entity)
|
||||||
|
.map(|rc| rc.simulate_ahead())
|
||||||
|
.unwrap_or_default();
|
||||||
|
// We substract `dt` here, as otherwise we
|
||||||
|
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms,
|
||||||
|
// rewind_tick=130ms end_tick=100+130+60=290
|
||||||
|
// Tick2: server_time=130ms dt=30ms ping=30 simulate_ahead=130ms,
|
||||||
|
// rewind_tick=130ms end_tick=130+130+30=290
|
||||||
|
// Tick3: server_time=160ms dt=60ms ping=30 simulate_ahead=130ms,
|
||||||
|
// rewind_tick=130ms end_tick=160+130+60=350 with dt substraction
|
||||||
|
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms,
|
||||||
|
// rewind_tick=70ms end_tick=100+70+60=230 Tick2: server_time=130ms
|
||||||
|
// dt=30ms ping=30 simulate_ahead=130ms, rewind_tick=100ms
|
||||||
|
// end_tick=130+100+30=260 Tick3: server_time=160ms dt=60ms ping=30
|
||||||
|
// simulate_ahead=130ms, rewind_tick=70ms end_tick=160+70+60=290
|
||||||
|
let simulate_ahead = simulate_ahead.max(dt) - dt;
|
||||||
|
let simulate_ahead = simulate_ahead + Duration::from_secs_f64(1.0 / 30.0);
|
||||||
|
/*
|
||||||
|
let x = if rewind_time > simulate_ahead + Duration::from_millis(25) {
|
||||||
|
common_base::plot!("xxx", 1.0);
|
||||||
|
common_base::plot!("gggggg", simulate_ahead.as_secs_f64() - rewind_time.as_secs_f64() );
|
||||||
|
simulate_ahead + Duration::from_millis(15)
|
||||||
|
} else if rewind_time < simulate_ahead - Duration::from_millis(25) {
|
||||||
|
common_base::plot!("xxx", -1.0);
|
||||||
|
simulate_ahead - Duration::from_millis(15)
|
||||||
|
} else {
|
||||||
|
common_base::plot!("xxx", 0.0);
|
||||||
|
simulate_ahead
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
let last_tick_time = state.ecs().read_resource::<Time>().0 as f64;
|
||||||
|
common_base::plot!("tick_before", last_tick_time);
|
||||||
|
|
||||||
|
let vel = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<Vel>()
|
||||||
|
.get(entity)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Vel(Vec3::zero()));
|
||||||
|
|
||||||
|
common_base::plot!("vel_x_before1", vel.0.x as f64);
|
||||||
|
common_base::plot!("vel_y_before1", vel.0.y as f64);
|
||||||
|
common_base::plot!("vel_z_before1", vel.0.z as f64);
|
||||||
|
let pos = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<common::comp::Pos>()
|
||||||
|
.get(entity)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(common::comp::Pos(Vec3::zero()));
|
||||||
|
|
||||||
|
common_base::plot!("pos_x_before1", pos.0.x as f64);
|
||||||
|
common_base::plot!("pos_y_before1", pos.0.y as f64);
|
||||||
|
common_base::plot!("pos_z_before1", pos.0.z as f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
common_base::plot!("rewind_time", rewind_time.as_secs_f64());
|
||||||
|
RevertClock::rewind_tick(simulate_ahead, state);
|
||||||
|
|
||||||
|
{
|
||||||
|
let time = state.ecs().read_resource::<Time>().0 as f64;
|
||||||
|
common_base::plot!("tick_afterwards1", time);
|
||||||
|
let vel = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<Vel>()
|
||||||
|
.get(entity)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Vel(Vec3::zero()));
|
||||||
|
|
||||||
|
common_base::plot!("vel_x_after1", vel.0.x as f64);
|
||||||
|
common_base::plot!("vel_y_after1", vel.0.y as f64);
|
||||||
|
common_base::plot!("vel_z_after1", vel.0.z as f64);
|
||||||
|
let pos = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<common::comp::Pos>()
|
||||||
|
.get(entity)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(common::comp::Pos(Vec3::zero()));
|
||||||
|
|
||||||
|
common_base::plot!("pos_x_after1", pos.0.x as f64);
|
||||||
|
common_base::plot!("pos_y_after1", pos.0.y as f64);
|
||||||
|
common_base::plot!("pos_z_after1", pos.0.z as f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
let mut rewind_time = Duration::from_secs_f64(0.0);
|
||||||
|
const WARP_PERCENT: f64 = 0.05; // make sure we end up not further than 5% from the estimated tick
|
||||||
|
let mut target_smooth_dt = dt.as_secs_f64();
|
||||||
|
common_base::plot!("recived_time_sync", 0.0);
|
||||||
|
if let Some(reverted_time) = self.inter_tick_reverted_time {
|
||||||
|
// At smooth_time we wouldn't notice any rewind
|
||||||
|
let smooth_time = reverted_time + dt;
|
||||||
|
common_base::plot!("recived_time_sync", 1.0);
|
||||||
|
common_base::plot!("reverted_time", reverted_time.as_secs_f64());
|
||||||
|
let time = self.state.ecs().read_resource::<Time>().0 as f64;
|
||||||
|
let simulate_ahead = self
|
||||||
|
.state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<RemoteController>()
|
||||||
|
.get(entity)
|
||||||
|
.map(|rc| rc.simulate_ahead())
|
||||||
|
.unwrap_or_default();
|
||||||
|
// We substract `dt` here, as otherwise we
|
||||||
|
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms, rewind_tick=130ms end_tick=100+130+60=290
|
||||||
|
// Tick2: server_time=130ms dt=30ms ping=30 simulate_ahead=130ms, rewind_tick=130ms end_tick=130+130+30=290
|
||||||
|
// Tick3: server_time=160ms dt=60ms ping=30 simulate_ahead=130ms, rewind_tick=130ms end_tick=160+130+60=350
|
||||||
|
// with dt substraction
|
||||||
|
// Tick1: server_time=100ms dt=60ms ping=30 simulate_ahead=130ms, rewind_tick=70ms end_tick=100+70+60=230
|
||||||
|
// Tick2: server_time=130ms dt=30ms ping=30 simulate_ahead=130ms, rewind_tick=100ms end_tick=130+100+30=260
|
||||||
|
// Tick3: server_time=160ms dt=60ms ping=30 simulate_ahead=130ms, rewind_tick=70ms end_tick=160+70+60=290
|
||||||
|
let simulate_ahead = simulate_ahead.max(dt) - dt;
|
||||||
|
// measurements lead to the effect that smooth_diff is == 0.0 when we add 2 server ticks here.
|
||||||
|
let simulate_ahead = simulate_ahead + Duration::from_secs_f64(1.0 / 30.0);
|
||||||
|
rewind_time = simulate_ahead;
|
||||||
|
|
||||||
|
// Simulate_ahead still fluctionates because Server Tick != Client Tick, and we cant
|
||||||
|
// control the phase in which the sync happens.
|
||||||
|
// In order to dampen it, we calculate the target_smooth_dt and make sure to not derive
|
||||||
|
// to much from it
|
||||||
|
|
||||||
|
ERROR, muss man das hier swappen ?
|
||||||
|
target_smooth_dt = reverted_time.as_secs_f64() - simulate_ahead.as_secs_f64();
|
||||||
|
// we store it, and will apply it over the course of the next ticks
|
||||||
|
self.rewind_fluctuation_budget = target_smooth_dt;
|
||||||
|
common_base::plot!("new_rewind_fluctuation_budget", self.rewind_fluctuation_budget);
|
||||||
|
}
|
||||||
|
common_base::plot!("rewind_time", rewind_time.as_secs_f64());
|
||||||
|
|
||||||
|
|
||||||
|
// we need to subtract the normal dt as we have a separate tick call for it.
|
||||||
|
// make it positive as we wont allow direct go in past
|
||||||
|
let corrected_target_smooth_dt = (target_smooth_dt - dt.as_secs_f64()).max(0.0);
|
||||||
|
common_base::plot!("corrected_target_smooth_dt", corrected_target_smooth_dt);
|
||||||
|
|
||||||
|
// apply our budget to rewind_time
|
||||||
|
let mut smooth_rewind_time = rewind_time.as_secs_f64();
|
||||||
|
smooth_rewind_time += self.rewind_fluctuation_budget;
|
||||||
|
let xxx = smooth_rewind_time.clamp( corrected_target_smooth_dt * (1.0-WARP_PERCENT), corrected_target_smooth_dt * (1.0+WARP_PERCENT));
|
||||||
|
common_base::plot!("xxx", xxx);
|
||||||
|
smooth_rewind_time = xxx;
|
||||||
|
self.rewind_fluctuation_budget -= smooth_rewind_time - rewind_time.as_secs_f64();
|
||||||
|
|
||||||
|
common_base::plot!("rewind_fluctuation_budget", self.rewind_fluctuation_budget);
|
||||||
|
common_base::plot!("target_smooth_dt", target_smooth_dt);
|
||||||
|
|
||||||
|
|
||||||
|
tracing::warn!(?smooth_rewind_time, ?dt, "simulating ahead again");
|
||||||
|
self.state.rewind_tick(
|
||||||
|
Duration::from_secs_f64(smooth_rewind_time),
|
||||||
|
|dispatch_builder| {
|
||||||
|
add_rewind_systems(dispatch_builder);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ macro_rules! comp_packet {
|
|||||||
Pos(comp::Pos),
|
Pos(comp::Pos),
|
||||||
Vel(comp::Vel),
|
Vel(comp::Vel),
|
||||||
Ori(comp::Ori),
|
Ori(comp::Ori),
|
||||||
|
PhysicsState(comp::PhysicsState),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ macro_rules! comp_packet {
|
|||||||
Pos(PhantomData<comp::Pos>),
|
Pos(PhantomData<comp::Pos>),
|
||||||
Vel(PhantomData<comp::Vel>),
|
Vel(PhantomData<comp::Vel>),
|
||||||
Ori(PhantomData<comp::Ori>),
|
Ori(PhantomData<comp::Ori>),
|
||||||
|
PhysicsState(PhantomData<comp::PhysicsState>),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +59,9 @@ macro_rules! comp_packet {
|
|||||||
Self::Ori(comp) => {
|
Self::Ori(comp) => {
|
||||||
sync::handle_interp_insert(comp, entity, world, true)
|
sync::handle_interp_insert(comp, entity, world, true)
|
||||||
},
|
},
|
||||||
|
Self::PhysicsState(comp) => {
|
||||||
|
crate::sync::packet::handle_insert(comp, entity, world);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +80,9 @@ macro_rules! comp_packet {
|
|||||||
Self::Ori(comp) => {
|
Self::Ori(comp) => {
|
||||||
sync::handle_interp_modify(comp, entity, world, true)
|
sync::handle_interp_modify(comp, entity, world, true)
|
||||||
},
|
},
|
||||||
|
Self::PhysicsState(comp) => {
|
||||||
|
crate::sync::packet::handle_modify(comp, entity, world);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +100,9 @@ macro_rules! comp_packet {
|
|||||||
EcsCompPhantom::Ori(_) => {
|
EcsCompPhantom::Ori(_) => {
|
||||||
sync::handle_interp_remove::<comp::Ori>(entity, world)
|
sync::handle_interp_remove::<comp::Ori>(entity, world)
|
||||||
},
|
},
|
||||||
|
EcsCompPhantom::PhysicsState(_) => {
|
||||||
|
crate::sync::packet::handle_remove::<comp::PhysicsState>(entity, world);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,10 @@ pub enum ServerGeneral {
|
|||||||
CharacterSuccess,
|
CharacterSuccess,
|
||||||
SpectatorSuccess(Vec3<f32>),
|
SpectatorSuccess(Vec3<f32>),
|
||||||
//Ingame related
|
//Ingame related
|
||||||
TimeSync(Time),
|
TimeSync {
|
||||||
|
server_time: Time,
|
||||||
|
inter_tick_offset: Duration,
|
||||||
|
},
|
||||||
AckControl {
|
AckControl {
|
||||||
acked_ids: HashSet<u64>,
|
acked_ids: HashSet<u64>,
|
||||||
/// measured by the time the furthest command to become active <>
|
/// measured by the time the furthest command to become active <>
|
||||||
@ -324,7 +327,7 @@ impl ServerMsg {
|
|||||||
},
|
},
|
||||||
//Ingame related
|
//Ingame related
|
||||||
ServerGeneral::GroupUpdate(_)
|
ServerGeneral::GroupUpdate(_)
|
||||||
| ServerGeneral::TimeSync(_)
|
| ServerGeneral::TimeSync { .. }
|
||||||
| ServerGeneral::AckControl { .. }
|
| ServerGeneral::AckControl { .. }
|
||||||
| ServerGeneral::Invite { .. }
|
| ServerGeneral::Invite { .. }
|
||||||
| ServerGeneral::InvitePending(_)
|
| ServerGeneral::InvitePending(_)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// `common_net` is downstream of `common`, and an `InterpolationSystem` that
|
// `common_net` is downstream of `common`, and an `InterpolationSystem` that
|
||||||
// applies them
|
// applies them
|
||||||
use super::InterpolatableComponent;
|
use super::InterpolatableComponent;
|
||||||
use common::comp::{Ori, Pos, Vel};
|
use common::comp::{Ori, Pos, Vel, PhysicsState};
|
||||||
use specs::Component;
|
use specs::Component;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use vek::ops::{Lerp, Slerp};
|
use vek::ops::{Lerp, Slerp};
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// sync arises
|
// sync arises
|
||||||
pub mod interpolation;
|
pub mod interpolation;
|
||||||
mod net_sync;
|
mod net_sync;
|
||||||
mod packet;
|
pub (crate) mod packet;
|
||||||
mod sync_ext;
|
mod sync_ext;
|
||||||
mod track;
|
mod track;
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ pub struct RemoteController {
|
|||||||
existing_commands: HashSet<u64>,
|
existing_commands: HashSet<u64>,
|
||||||
max_hold: Duration,
|
max_hold: Duration,
|
||||||
avg_latency: Duration,
|
avg_latency: Duration,
|
||||||
|
avg_ahead_time: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@ -105,7 +106,7 @@ impl RemoteController {
|
|||||||
monotonic_time: Duration,
|
monotonic_time: Duration,
|
||||||
highest_ahead_command: f64,
|
highest_ahead_command: f64,
|
||||||
) {
|
) {
|
||||||
// calculating avg_latency.
|
// calculating avg_ahead_time.
|
||||||
// - server returns the time the furthest away command has left till it becomes
|
// - server returns the time the furthest away command has left till it becomes
|
||||||
// active. As the server time is constant and we stored this value locally,
|
// active. As the server time is constant and we stored this value locally,
|
||||||
// we use it to calculate the first ahead_command for the first command in
|
// we use it to calculate the first ahead_command for the first command in
|
||||||
@ -113,83 +114,54 @@ impl RemoteController {
|
|||||||
// - we try to keep the remote_ahead in a specific window to allow for
|
// - we try to keep the remote_ahead in a specific window to allow for
|
||||||
// retransmittion and remote tick time
|
// retransmittion and remote tick time
|
||||||
|
|
||||||
|
// Buffer we try to keep on the server, make this a config
|
||||||
|
const AHEAD_TARGET: f64 = 0.055;
|
||||||
|
const AHEAD_TARGET_DIFF: f64 = 0.005;
|
||||||
|
const AHEAD_TARGET_STEP: Duration = Duration::from_millis(2);
|
||||||
|
const AHEAD_TARGET_NO_ACK_STEP: Duration = Duration::from_nanos(500000);
|
||||||
|
|
||||||
// find time it took from client -> server
|
// find time it took from client -> server
|
||||||
let high_filter = self
|
let high_filter = self
|
||||||
.commands
|
.commands
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|c| ids.contains(&c.id) && c.first_acked_monotonic_time.is_none());
|
.filter(|c| ids.contains(&c.id) && c.first_acked_monotonic_time.is_none());
|
||||||
if let Some(highest) = high_filter.max_by_key(|c| c.action_time) {
|
if let Some(highest) = high_filter.max_by_key(|c| c.action_time) {
|
||||||
let remote_time = highest.action_time.as_secs_f64() - highest_ahead_command;
|
if let Some(first_send_monotonic_time) = highest.first_send_monotonic_time {
|
||||||
//let latency = highest.first_send_simulate_ahead_time.unwrap_or_default() -
|
let latency = monotonic_time - first_send_monotonic_time;
|
||||||
// remote_time;
|
self.avg_latency = Duration::from_secs_f64(
|
||||||
common_base::plot!("prob_remote_time", remote_time);
|
self.avg_latency.as_secs_f64() * 0.99 + latency.as_secs_f64() * 0.01,
|
||||||
//common_base::plot!("prob_avg_latency", latency);
|
);
|
||||||
//let low_filter = self.commands.iter().filter(|c| ids.contains(&c.id) &&
|
}
|
||||||
// c.first_send_monotonic_time == highest.first_send_monotonic_time);
|
|
||||||
let low_filter = self
|
let low_filter = self
|
||||||
.commands
|
.commands
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|c| ids.contains(&c.id) && c.first_acked_monotonic_time.is_none());
|
.filter(|c| ids.contains(&c.id) && c.first_acked_monotonic_time.is_none());
|
||||||
if let Some(lowest) = low_filter.min_by_key(|c| c.action_time) {
|
if let Some(lowest) = low_filter.min_by_key(|c| c.action_time) {
|
||||||
let low_high_diff = highest.action_time - lowest.action_time;
|
let low_high_diff = highest.action_time - lowest.action_time;
|
||||||
// if this is 50ms, it means the lowest command arrived at server 50 before it
|
// if this is 50ms, it means the lowest command arrived at server 50ms before it
|
||||||
// was used
|
// was used
|
||||||
common_base::plot!("highest_ahead_command", highest_ahead_command);
|
|
||||||
let ahead_time = highest_ahead_command - low_high_diff.as_secs_f64();
|
let ahead_time = highest_ahead_command - low_high_diff.as_secs_f64();
|
||||||
common_base::plot!("low_high_diff", low_high_diff.as_secs_f64());
|
common_base::plot!("low_high_diff", low_high_diff.as_secs_f64());
|
||||||
common_base::plot!("ahead_time", ahead_time);
|
common_base::plot!("ahead_time", ahead_time);
|
||||||
const LOWER_END: f64 = 0.18;
|
let avg_ahead_time = (lowest.first_send_simulate_ahead_time.unwrap_or_default()
|
||||||
const UPPER_END: f64 = 0.22;
|
- ahead_time)
|
||||||
//if ahead_time > 2.0 {
|
.max(0.0)
|
||||||
let len = self.commands.len();
|
+ AHEAD_TARGET;
|
||||||
tracing::error!(
|
// avg_ahead_time fluctuates by 1 dt, so we average it.
|
||||||
?ahead_time,
|
self.avg_ahead_time = Duration::from_secs_f64(
|
||||||
?ids,
|
self.avg_ahead_time.as_secs_f64() * 0.995 + avg_ahead_time * 0.005,
|
||||||
?highest_ahead_command,
|
|
||||||
?highest,
|
|
||||||
?lowest,
|
|
||||||
?len,
|
|
||||||
"bigger2"
|
|
||||||
);
|
);
|
||||||
//}
|
|
||||||
if ahead_time < LOWER_END {
|
|
||||||
self.avg_latency += Duration::from_millis(10);
|
|
||||||
}
|
|
||||||
if ahead_time > UPPER_END && self.avg_latency > Duration::from_millis(3) {
|
|
||||||
self.avg_latency -= Duration::from_millis(3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.avg_ahead_time += AHEAD_TARGET_NO_ACK_STEP;
|
||||||
|
tracing::warn!(?self.avg_ahead_time, "No Highest");
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in &mut self.commands {
|
for c in &mut self.commands.iter_mut().filter(|c| ids.contains(&c.id)) {
|
||||||
c.first_acked_monotonic_time = Some(monotonic_time);
|
c.first_acked_monotonic_time = Some(monotonic_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
common_base::plot!("avg_ahead_time", self.avg_ahead_time.as_secs_f64());
|
||||||
let mut lowest_monotonic_time = None;
|
|
||||||
for c in &mut self.commands {
|
|
||||||
let new_acked = c.first_acked_monotonic_time.is_none() && ids.contains(&c.id);
|
|
||||||
if new_acked {
|
|
||||||
c.first_acked_monotonic_time = Some(monotonic_time);
|
|
||||||
if c.first_send_monotonic_time.map_or(false, |mt| {
|
|
||||||
mt < lowest_monotonic_time.unwrap_or(monotonic_time)
|
|
||||||
}) {
|
|
||||||
lowest_monotonic_time = c.first_send_monotonic_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(lowest_monotonic_time) = lowest_monotonic_time {
|
|
||||||
if let Some(latency) = monotonic_time.checked_sub(lowest_monotonic_time) {
|
|
||||||
common_base::plot!("latency", latency.as_secs_f64());
|
|
||||||
self.avg_latency = ((99 * self.avg_latency + latency) / 100).max(latency);
|
|
||||||
} else {
|
|
||||||
warn!(
|
|
||||||
"latency is negative, this should never be the case as this value is not \
|
|
||||||
synced and monotonic!"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
common_base::plot!("avg_latency", self.avg_latency.as_secs_f64());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// prepare commands for sending
|
/// prepare commands for sending
|
||||||
@ -305,19 +277,22 @@ impl RemoteController {
|
|||||||
Some(result)
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the average latency is 300ms by default and will be adjusted over time
|
/// the average latency is 500ms by default and will be adjusted over time
|
||||||
/// with every Ack the server sends its local tick time at the Ack
|
/// 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
|
/// 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
|
/// server->client and assume that this is also true for client->server
|
||||||
/// latency
|
/// latency
|
||||||
pub fn avg_latency(&self) -> Duration { self.avg_latency }
|
//pub fn avg_latency(&self) -> Duration { self.avg_latency }
|
||||||
|
|
||||||
|
/// In contrast to avg_latency this calculates how much ahead we should
|
||||||
|
/// simulate in order to be just received on the server in time
|
||||||
pub fn simulate_ahead(&self) -> Duration {
|
pub fn simulate_ahead(&self) -> Duration {
|
||||||
//const FIXED_OFFSET: Duration = Duration::from_millis(0);
|
const OVERWRITE_SIMULATE_AHEAD: Option<Duration> = None; // Some(Durations::from_millis(1100));
|
||||||
//self.avg_latency() + FIXED_OFFSET
|
if let Some(d) = OVERWRITE_SIMULATE_AHEAD {
|
||||||
// TODO:: mocked, as we use it internally for the new input functions and assume
|
d
|
||||||
// that time - simulate ahead = server time iirc
|
} else {
|
||||||
Duration::from_millis(200)
|
self.avg_ahead_time
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +302,8 @@ impl Default for RemoteController {
|
|||||||
commands: VecDeque::new(),
|
commands: VecDeque::new(),
|
||||||
existing_commands: HashSet::new(),
|
existing_commands: HashSet::new(),
|
||||||
max_hold: Duration::from_secs(5),
|
max_hold: Duration::from_secs(5),
|
||||||
avg_latency: Duration::from_millis(50),
|
avg_latency: Duration::from_millis(500),
|
||||||
|
avg_ahead_time: Duration::from_millis(500),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -590,37 +590,6 @@ impl State {
|
|||||||
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
|
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewind local changes after the server send some old state
|
|
||||||
pub fn rewind_tick(
|
|
||||||
&mut self,
|
|
||||||
simulate_ahead: Duration,
|
|
||||||
add_systems: impl Fn(&mut DispatcherBuilder) + Clone,
|
|
||||||
update_terrain_and_regions: bool,
|
|
||||||
) {
|
|
||||||
common_base::plot!("simulate_ahead", simulate_ahead.as_secs_f64());
|
|
||||||
let time_of_day = self.ecs.read_resource::<TimeOfDay>().0;
|
|
||||||
let _time = self.ecs.read_resource::<Time>().0;
|
|
||||||
let monotonic_time = self.ecs.read_resource::<MonotonicTime>().0;
|
|
||||||
let delta_time = self.ecs.read_resource::<DeltaTime>().0;
|
|
||||||
|
|
||||||
const MAX_INCREMENTS: usize = 100; // The maximum number of collision tests per tick
|
|
||||||
const STEP_SEC: f64 = 0.04;
|
|
||||||
let increments =
|
|
||||||
((simulate_ahead.as_secs_f64() / STEP_SEC).ceil() as usize).clamped(1, MAX_INCREMENTS);
|
|
||||||
for _i in 0..increments {
|
|
||||||
//tracing::trace!(?i, ?increments, "subtick");
|
|
||||||
let partial = simulate_ahead / (increments as u32);
|
|
||||||
self.tick(partial, add_systems.clone(), update_terrain_and_regions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// rewind changes
|
|
||||||
|
|
||||||
self.ecs.write_resource::<TimeOfDay>().0 = time_of_day;
|
|
||||||
//self.ecs.write_resource::<Time>().0 = time;
|
|
||||||
self.ecs.write_resource::<MonotonicTime>().0 = monotonic_time;
|
|
||||||
self.ecs.write_resource::<DeltaTime>().0 = delta_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a single tick, simulating the game state by the given duration.
|
/// Execute a single tick, simulating the game state by the given duration.
|
||||||
pub fn tick(
|
pub fn tick(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -65,8 +65,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
for a in r.actions {
|
for a in r.actions {
|
||||||
if let common::comp::ControlAction::StartInput {
|
if let common::comp::ControlAction::StartInput {
|
||||||
input,
|
input,
|
||||||
target_entity,
|
target_entity: _,
|
||||||
select_pos,
|
select_pos: _,
|
||||||
} = a
|
} = a
|
||||||
{
|
{
|
||||||
if input == common::comp::InputKind::Jump {
|
if input == common::comp::InputKind::Jump {
|
||||||
|
@ -103,7 +103,7 @@ impl Client {
|
|||||||
},
|
},
|
||||||
//In-game related
|
//In-game related
|
||||||
ServerGeneral::GroupUpdate(_)
|
ServerGeneral::GroupUpdate(_)
|
||||||
| ServerGeneral::TimeSync(_)
|
| ServerGeneral::TimeSync{ .. }
|
||||||
| ServerGeneral::AckControl(_, _)
|
| ServerGeneral::AckControl(_, _)
|
||||||
| ServerGeneral::Invite { .. }
|
| ServerGeneral::Invite { .. }
|
||||||
| ServerGeneral::InvitePending(_)
|
| ServerGeneral::InvitePending(_)
|
||||||
@ -177,7 +177,7 @@ impl Client {
|
|||||||
},
|
},
|
||||||
//In-game related
|
//In-game related
|
||||||
ServerGeneral::GroupUpdate(_)
|
ServerGeneral::GroupUpdate(_)
|
||||||
| ServerGeneral::TimeSync(_)
|
| ServerGeneral::TimeSync { .. }
|
||||||
| ServerGeneral::AckControl { .. }
|
| ServerGeneral::AckControl { .. }
|
||||||
| ServerGeneral::Invite { .. }
|
| ServerGeneral::Invite { .. }
|
||||||
| ServerGeneral::InvitePending(_)
|
| ServerGeneral::InvitePending(_)
|
||||||
|
@ -2,7 +2,7 @@ use super::sentinel::{DeletedEntities, TrackedStorages, UpdateTrackers};
|
|||||||
use crate::{
|
use crate::{
|
||||||
client::Client,
|
client::Client,
|
||||||
presence::{Presence, RegionSubscription},
|
presence::{Presence, RegionSubscription},
|
||||||
Tick,
|
Tick, TickStart,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
@ -20,6 +20,7 @@ use common_net::{msg::ServerGeneral, sync::CompSyncPackage};
|
|||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
|
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
use common::comp::PhysicsState;
|
||||||
|
|
||||||
/// This system will send physics updates to the client
|
/// This system will send physics updates to the client
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -33,12 +34,14 @@ impl<'a> System<'a> for Sys {
|
|||||||
TrackedStorages<'a>,
|
TrackedStorages<'a>,
|
||||||
ReadExpect<'a, TimeOfDay>,
|
ReadExpect<'a, TimeOfDay>,
|
||||||
ReadExpect<'a, Time>,
|
ReadExpect<'a, Time>,
|
||||||
|
ReadExpect<'a, TickStart>,
|
||||||
ReadExpect<'a, Calendar>,
|
ReadExpect<'a, Calendar>,
|
||||||
ReadExpect<'a, RegionMap>,
|
ReadExpect<'a, RegionMap>,
|
||||||
ReadExpect<'a, UpdateTrackers>,
|
ReadExpect<'a, UpdateTrackers>,
|
||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
ReadStorage<'a, Vel>,
|
ReadStorage<'a, Vel>,
|
||||||
ReadStorage<'a, Ori>,
|
ReadStorage<'a, Ori>,
|
||||||
|
ReadStorage<'a, PhysicsState>,
|
||||||
ReadStorage<'a, RegionSubscription>,
|
ReadStorage<'a, RegionSubscription>,
|
||||||
ReadStorage<'a, Presence>,
|
ReadStorage<'a, Presence>,
|
||||||
ReadStorage<'a, Client>,
|
ReadStorage<'a, Client>,
|
||||||
@ -63,12 +66,14 @@ impl<'a> System<'a> for Sys {
|
|||||||
tracked_storages,
|
tracked_storages,
|
||||||
time_of_day,
|
time_of_day,
|
||||||
time,
|
time,
|
||||||
|
tick_start,
|
||||||
calendar,
|
calendar,
|
||||||
region_map,
|
region_map,
|
||||||
trackers,
|
trackers,
|
||||||
positions,
|
positions,
|
||||||
velocities,
|
velocities,
|
||||||
orientations,
|
orientations,
|
||||||
|
physics_states,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
presences,
|
presences,
|
||||||
clients,
|
clients,
|
||||||
@ -83,6 +88,11 @@ impl<'a> System<'a> for Sys {
|
|||||||
) {
|
) {
|
||||||
let tick = tick.0;
|
let tick = tick.0;
|
||||||
|
|
||||||
|
//use rand::Rng;
|
||||||
|
//let _rng = rand::thread_rng();
|
||||||
|
//std::thread::sleep(std::time::Duration::from_millis(rng.gen_range(0..2) *
|
||||||
|
// 28));
|
||||||
|
|
||||||
// Storages already provided in `TrackedStorages` that we need to use
|
// Storages already provided in `TrackedStorages` that we need to use
|
||||||
// for other things besides change detection.
|
// for other things besides change detection.
|
||||||
let uids = &tracked_storages.uid;
|
let uids = &tracked_storages.uid;
|
||||||
@ -229,14 +239,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
for (client, _, client_entity, client_pos) in &mut subscribers {
|
for (client, _, client_entity, client_pos) in &mut subscribers {
|
||||||
let mut comp_sync_package = CompSyncPackage::new();
|
let mut comp_sync_package = CompSyncPackage::new();
|
||||||
|
|
||||||
for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider) in (
|
for (_, entity, &uid, (&pos, last_pos), vel, ori, physic_state, force_update, collider) in (
|
||||||
region.entities(),
|
region.entities(),
|
||||||
&entities,
|
&entities,
|
||||||
uids,
|
uids,
|
||||||
(&positions, last_pos.mask().maybe()),
|
(&positions, last_pos.mask().maybe()),
|
||||||
(&velocities, last_vel.mask().maybe()).maybe(),
|
(&velocities, last_vel.mask().maybe()).maybe(),
|
||||||
(&orientations, last_vel.mask().maybe()).maybe(),
|
(&orientations, last_vel.mask().maybe()).maybe(),
|
||||||
force_updates.maybe(),
|
(&physics_states, physics_states.mask().maybe()).maybe(),
|
||||||
|
force_updates.mask().maybe(),
|
||||||
colliders.maybe(),
|
colliders.maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
@ -246,8 +257,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Don't send client physics updates about itself unless force update is
|
// Don't send client physics updates about itself unless force update is
|
||||||
// set or the client is subject to
|
// set or the client is subject to
|
||||||
// server-authoritative physics
|
// server-authoritative physics
|
||||||
true || force_update.map_or(false, |f| f.is_forced())
|
true || is_rider.get(entity).is_some()
|
||||||
|| is_rider.get(entity).is_some()
|
|
||||||
} else if matches!(collider, Some(Collider::Voxel { .. })) {
|
} else if matches!(collider, Some(Collider::Voxel { .. })) {
|
||||||
// Things with a voxel collider (airships, etc.) need to have very
|
// Things with a voxel collider (airships, etc.) need to have very
|
||||||
// stable physics so we always send updated
|
// stable physics so we always send updated
|
||||||
@ -298,12 +308,32 @@ impl<'a> System<'a> for Sys {
|
|||||||
comp_sync_package.comp_modified(uid, *o);
|
comp_sync_package.comp_modified(uid, *o);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some((physic_state, last_physic_state)) = physic_state {
|
||||||
|
if last_physic_state.is_none() {
|
||||||
|
comp_sync_package.comp_inserted(uid, physic_state.clone());
|
||||||
|
} else if send_now {
|
||||||
|
comp_sync_package.comp_modified(uid, physic_state.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.send_fallible(ServerGeneral::CompSync(
|
client.send_fallible(ServerGeneral::CompSync(
|
||||||
comp_sync_package,
|
comp_sync_package,
|
||||||
force_updates.get(*client_entity).map_or(0, |f| f.counter()),
|
force_updates.get(*client_entity).map_or(0, |f| f.counter()),
|
||||||
));
|
));
|
||||||
|
/*
|
||||||
|
multiple problems
|
||||||
|
- CompSync and TimeSync could (theoretically be received in 2 different ticks, client would be confused for 1 tick then
|
||||||
|
- we dont send the client in which part of this tick we are sending this. Imaging a fluctuating server, that either needs 1ms or 100ms per tick.
|
||||||
|
The client would get confused as the dt might be 1 ms, but the information arrives 99ms late because of server is running behind.
|
||||||
|
As we send this info as fast as possible, this also is a problem when the server is running "ahead" (faster than 33ms)
|
||||||
|
To mitigate this, we might use the Duration between now and TickStart
|
||||||
|
*/
|
||||||
|
client.send_fallible(ServerGeneral::TimeSync {
|
||||||
|
server_time: *time,
|
||||||
|
inter_tick_offset: tick_start.0.elapsed(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ use common::{
|
|||||||
resources::Time,
|
resources::Time,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::{PingMsg, ServerGeneral};
|
use common_net::msg::{PingMsg};
|
||||||
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,7 +47,6 @@ 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 {
|
||||||
|
@ -45,9 +45,11 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Note, these values are specifically tuned for smoother motion with high
|
// Note, these values are specifically tuned for smoother motion with high
|
||||||
// network latency or low network sampling rate and for smooth
|
// network latency or low network sampling rate and for smooth
|
||||||
// block hopping (which is instantaneous)
|
// block hopping (which is instantaneous)
|
||||||
const POS_LERP_RATE_FACTOR: f32 = 10.0;
|
//const POS_LERP_RATE_FACTOR: f32 = 10.0;
|
||||||
i.pos = Lerp::lerp(i.pos, pos.0 + vel.0 * 0.03, POS_LERP_RATE_FACTOR * dt.0);
|
//i.pos = Lerp::lerp(i.pos, pos.0 + vel.0 * 0.03, POS_LERP_RATE_FACTOR * dt.0);
|
||||||
i.ori = Ori::slerp(i.ori, *ori, base_ori_interp(body) * dt.0);
|
//i.ori = Ori::slerp(i.ori, *ori, base_ori_interp(body) * dt.0);
|
||||||
|
i.pos = pos.0;
|
||||||
|
i.ori = *ori;
|
||||||
} else {
|
} else {
|
||||||
i.pos = pos.0;
|
i.pos = pos.0;
|
||||||
i.ori = *ori;
|
i.ori = *ori;
|
||||||
|
Loading…
Reference in New Issue
Block a user