mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
add unit tests for replication system
- make tracy experience better by adding a 0.05 to client local TIME. - fix an error that the look_dir was wrongly predicted - add a jump graph for testing - update in_game code that was commented out in system - track the simulation ahead on the debug menu - add simulated lag with `sudo tc qdisc replace dev lo root netem delay 700ms 10ms 25%` add basic tests for phys
This commit is contained in:
parent
714c346ded
commit
4343dd3aea
@ -1734,7 +1734,7 @@ 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);
|
let 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(time, con);
|
||||||
@ -1748,6 +1748,7 @@ impl Client {
|
|||||||
rc.push(rcon);
|
rc.push(rcon);
|
||||||
rc.prepare_commands(monotonic_time)
|
rc.prepare_commands(monotonic_time)
|
||||||
});
|
});
|
||||||
|
|
||||||
match commands {
|
match commands {
|
||||||
Ok(commands) => self.send_msg_err(ClientGeneral::Control(commands))?,
|
Ok(commands) => self.send_msg_err(ClientGeneral::Control(commands))?,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -2158,15 +2159,16 @@ impl Client {
|
|||||||
match msg {
|
match msg {
|
||||||
ServerGeneral::TimeSync(time) => {
|
ServerGeneral::TimeSync(time) => {
|
||||||
let dt = self.state.ecs().read_resource::<DeltaTime>().0 as f64;
|
let dt = self.state.ecs().read_resource::<DeltaTime>().0 as f64;
|
||||||
let latency = self
|
let simulate_ahead = self
|
||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.read_storage::<RemoteController>()
|
.read_storage::<RemoteController>()
|
||||||
.get(self.entity())
|
.get(self.entity())
|
||||||
.map(|rc| rc.avg_latency())
|
.map(|rc| rc.simulate_ahead())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
//remove dt as it is applied in state.tick again
|
//remove dt as it is applied in state.tick again
|
||||||
self.state.ecs().write_resource::<Time>().0 = time.0 + latency.as_secs_f64() /* - dt */;
|
self.state.ecs().write_resource::<Time>().0 =
|
||||||
|
time.0 + simulate_ahead.as_secs_f64() - dt;
|
||||||
},
|
},
|
||||||
ServerGeneral::AckControl(acked_ids, _time) => {
|
ServerGeneral::AckControl(acked_ids, _time) => {
|
||||||
if let Some(remote_controller) = self
|
if let Some(remote_controller) = self
|
||||||
|
@ -110,7 +110,7 @@ pub use self::{
|
|||||||
player::{AliasError, Player, MAX_ALIAS_LEN},
|
player::{AliasError, Player, MAX_ALIAS_LEN},
|
||||||
poise::{Poise, PoiseChange, PoiseState},
|
poise::{Poise, PoiseChange, PoiseState},
|
||||||
projectile::{Projectile, ProjectileConstructor},
|
projectile::{Projectile, ProjectileConstructor},
|
||||||
remote_controller::{CommandGenerator, ControlCommands, RemoteController},
|
remote_controller::{CommandGenerator, ControlCommand, ControlCommands, RemoteController},
|
||||||
shockwave::{Shockwave, ShockwaveHitEntities},
|
shockwave::{Shockwave, ShockwaveHitEntities},
|
||||||
skillset::{
|
skillset::{
|
||||||
skills::{self, Skill},
|
skills::{self, Skill},
|
||||||
|
@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use specs::{Component, DenseVecStorage};
|
use specs::{Component, DenseVecStorage};
|
||||||
use std::{collections::VecDeque, time::Duration};
|
use std::{collections::VecDeque, time::Duration};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use vek::Vec3;
|
|
||||||
|
|
||||||
pub type ControlCommands = VecDeque<ControlCommand>;
|
pub type ControlCommands = VecDeque<ControlCommand>;
|
||||||
|
|
||||||
@ -164,7 +163,7 @@ impl RemoteController {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut result = Controller::default();
|
let mut result = Controller::default();
|
||||||
let mut look_dir = Vec3::zero();
|
let mut look_dir = result.inputs.look_dir.to_vec();
|
||||||
//if self.commands[start_i].source_time
|
//if self.commands[start_i].source_time
|
||||||
// Inputs are averaged over all elements by time
|
// Inputs are averaged over all elements by time
|
||||||
// Queued Inputs are just added
|
// Queued Inputs are just added
|
||||||
@ -182,8 +181,7 @@ impl RemoteController {
|
|||||||
let local_dur = local_end - local_start;
|
let local_dur = local_end - local_start;
|
||||||
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 = look_dir + e.msg.inputs.look_dir.to_vec() * local_dur.as_secs_f32();
|
||||||
+ e.msg.inputs.look_dir.to_vec() * local_dur.as_secs_f32();
|
|
||||||
//TODO: manually combine 70% up and 30% down to UP
|
//TODO: manually combine 70% up and 30% down to UP
|
||||||
result.inputs.climb = result.inputs.climb.or(e.msg.inputs.climb);
|
result.inputs.climb = result.inputs.climb.or(e.msg.inputs.climb);
|
||||||
result.inputs.break_block_pos = result
|
result.inputs.break_block_pos = result
|
||||||
@ -230,6 +228,11 @@ impl RemoteController {
|
|||||||
/// 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 }
|
||||||
|
|
||||||
|
pub fn simulate_ahead(&self) -> Duration {
|
||||||
|
const FIXED_OFFSET: Duration = Duration::from_millis(50);
|
||||||
|
self.avg_latency() + FIXED_OFFSET
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RemoteController {
|
impl Default for RemoteController {
|
||||||
|
@ -1025,6 +1025,7 @@ pub fn handle_jump(
|
|||||||
_update: &mut StateUpdate,
|
_update: &mut StateUpdate,
|
||||||
strength: f32,
|
strength: f32,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
common_base::plot!("jumps", 0.0);
|
||||||
(input_is_pressed(data, InputKind::Jump) && data.physics.on_ground.is_some())
|
(input_is_pressed(data, InputKind::Jump) && data.physics.on_ground.is_some())
|
||||||
.then(|| data.body.jump_impulse())
|
.then(|| data.body.jump_impulse())
|
||||||
.flatten()
|
.flatten()
|
||||||
@ -1033,6 +1034,7 @@ pub fn handle_jump(
|
|||||||
data.entity,
|
data.entity,
|
||||||
strength * impulse / data.mass.0 * data.stats.move_speed_modifier,
|
strength * impulse / data.mass.0 * data.stats.move_speed_modifier,
|
||||||
));
|
));
|
||||||
|
common_base::plot!("jumps", 1.0);
|
||||||
})
|
})
|
||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
|
204
common/systems/tests/predict_controller/basic.rs
Normal file
204
common/systems/tests/predict_controller/basic.rs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
use crate::utils;
|
||||||
|
use approx::assert_relative_eq;
|
||||||
|
use common::{
|
||||||
|
comp::{self, CommandGenerator, Controller, InputKind},
|
||||||
|
resources::Time,
|
||||||
|
};
|
||||||
|
use specs::WorldExt;
|
||||||
|
use std::{error::Error, time::Duration};
|
||||||
|
use utils::{DT, DTT};
|
||||||
|
use vek::{approx, Vec2};
|
||||||
|
use veloren_common_systems::add_local_systems;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_run() {
|
||||||
|
let mut state = utils::setup();
|
||||||
|
utils::create_player(&mut state);
|
||||||
|
state.tick(
|
||||||
|
DT,
|
||||||
|
|dispatcher_builder| {
|
||||||
|
add_local_systems(dispatcher_builder);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn emulate_walk() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut state = utils::setup();
|
||||||
|
let p1 = utils::create_player(&mut state);
|
||||||
|
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_eq!(state.ecs_mut().read_resource::<Time>().0, DTT);
|
||||||
|
|
||||||
|
let mut generator = CommandGenerator::default();
|
||||||
|
let mut actions = Controller::default();
|
||||||
|
actions.inputs.move_dir = Vec2::new(10.0, 0.0);
|
||||||
|
utils::push_remote(&mut state, p1, generator.gen(DT * 3, actions))?;
|
||||||
|
|
||||||
|
let mut actions = Controller::default();
|
||||||
|
actions.inputs.move_dir = Vec2::new(20.0, 0.0);
|
||||||
|
utils::push_remote(&mut state, p1, generator.gen(DT * 5, actions))?;
|
||||||
|
|
||||||
|
let mut actions = Controller::default();
|
||||||
|
actions.inputs.move_dir = Vec2::new(30.0, 0.0);
|
||||||
|
utils::push_remote(&mut state, p1, generator.gen(DT * 6, actions))?;
|
||||||
|
|
||||||
|
//DDT 2 - no data yet
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 0.0);
|
||||||
|
|
||||||
|
//DDT 3
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 10.0);
|
||||||
|
|
||||||
|
//DDT 4
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 10.0);
|
||||||
|
|
||||||
|
//DDT 5
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 20.0);
|
||||||
|
|
||||||
|
//DDT 6
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 30.0);
|
||||||
|
|
||||||
|
//DDT 7
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 30.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn emulate_partial_client_walk() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut state = utils::setup();
|
||||||
|
let p1 = utils::create_player(&mut state);
|
||||||
|
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_eq!(state.ecs_mut().read_resource::<Time>().0, DTT);
|
||||||
|
|
||||||
|
let mut generator = CommandGenerator::default();
|
||||||
|
let mut actions = Controller::default();
|
||||||
|
actions.inputs.move_dir = Vec2::new(10.0, 0.0);
|
||||||
|
utils::push_remote(
|
||||||
|
&mut state,
|
||||||
|
p1,
|
||||||
|
generator.gen(Duration::from_secs_f64(2.5 * DTT), actions),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
//DDT 2 - no data yet officially, but we interpret
|
||||||
|
// Explaination why we interpolate here:
|
||||||
|
// - Client says move at 2.5, server is at 2.0 currently.
|
||||||
|
// - Assume there was no interpolation:
|
||||||
|
// - Client would assume to be at pos 5 at 3.0 and 10 at 3.5
|
||||||
|
// - Server would notice that client started moving at 3.0 and is at 5 at 3.5
|
||||||
|
// => Client would lag 5 behind always
|
||||||
|
// - Now with interpolation:
|
||||||
|
// - Client would assume to be at pos 5 at 3.0 and 10 at 3.5
|
||||||
|
// - Server lets client run with speed of 5 at 2.0, so
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 5.0);
|
||||||
|
|
||||||
|
//DDT 3
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 10.0);
|
||||||
|
|
||||||
|
//DDT 4
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 10.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn emulate_partial_server_walk() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut state = utils::setup();
|
||||||
|
let p1 = utils::create_player(&mut state);
|
||||||
|
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_eq!(state.ecs_mut().read_resource::<Time>().0, DTT);
|
||||||
|
|
||||||
|
let mut generator = CommandGenerator::default();
|
||||||
|
let mut actions = Controller::default();
|
||||||
|
actions.inputs.move_dir = Vec2::new(10.0, 0.0);
|
||||||
|
utils::push_remote(
|
||||||
|
&mut state,
|
||||||
|
p1,
|
||||||
|
generator.gen(Duration::from_secs_f64(3.0 * DTT), actions),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
//DDT 1.5 - no data yet
|
||||||
|
utils::tick(&mut state, Duration::from_secs_f64(0.5 * DTT));
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 0.0);
|
||||||
|
|
||||||
|
//DDT 2.5/3.5 - No Data for first 0.5, then 10
|
||||||
|
// We interpolate here to 5 for the whole tick
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 5.0);
|
||||||
|
|
||||||
|
//DDT 3
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 10.0);
|
||||||
|
|
||||||
|
//DDT 4
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_relative_eq!(utils::get_controller(&state, p1)?.inputs.move_dir.x, 10.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn emulate_jump() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut state = utils::setup();
|
||||||
|
let p1 = utils::create_player(&mut state);
|
||||||
|
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert_eq!(state.ecs_mut().read_resource::<Time>().0, DTT);
|
||||||
|
|
||||||
|
let mut generator = CommandGenerator::default();
|
||||||
|
let mut actions = Controller::default();
|
||||||
|
actions
|
||||||
|
.queued_inputs
|
||||||
|
.insert(comp::InputKind::Jump, comp::InputAttr {
|
||||||
|
select_pos: None,
|
||||||
|
target_entity: None,
|
||||||
|
});
|
||||||
|
utils::push_remote(
|
||||||
|
&mut state,
|
||||||
|
p1,
|
||||||
|
generator.gen(Duration::from_secs_f64(5.0 * DTT), actions),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
//DDT 2
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert!(utils::get_controller(&state, p1)?.queued_inputs.is_empty());
|
||||||
|
|
||||||
|
//DDT 3
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert!(utils::get_controller(&state, p1)?.queued_inputs.is_empty());
|
||||||
|
|
||||||
|
//DDT 4
|
||||||
|
// TODO: i am NOT sure if this is correct behavior, just adjusted it in a rebase
|
||||||
|
// to fix the test
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
let inputs = utils::get_controller(&state, p1)?.queued_inputs;
|
||||||
|
assert!(!inputs.is_empty());
|
||||||
|
assert!(inputs.contains_key(&InputKind::Jump));
|
||||||
|
|
||||||
|
//DDT 5
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
let inputs = utils::get_controller(&state, p1)?.queued_inputs;
|
||||||
|
assert!(!inputs.is_empty());
|
||||||
|
assert!(inputs.contains_key(&InputKind::Jump));
|
||||||
|
|
||||||
|
//DDT 6
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert!(utils::get_controller(&state, p1)?.queued_inputs.is_empty());
|
||||||
|
|
||||||
|
//DDT 7
|
||||||
|
utils::tick(&mut state, DT);
|
||||||
|
assert!(utils::get_controller(&state, p1)?.queued_inputs.is_empty());
|
||||||
|
Ok(())
|
||||||
|
}
|
2
common/systems/tests/predict_controller/main.rs
Normal file
2
common/systems/tests/predict_controller/main.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mod basic;
|
||||||
|
mod utils;
|
99
common/systems/tests/predict_controller/utils.rs
Normal file
99
common/systems/tests/predict_controller/utils.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
use common::{
|
||||||
|
comp::{
|
||||||
|
inventory::item::MaterialStatManifest, tool::AbilityMap, ControlCommand, ControlCommands,
|
||||||
|
Controller, RemoteController,
|
||||||
|
},
|
||||||
|
resources::{DeltaTime, GameMode, Time},
|
||||||
|
terrain::{MapSizeLg, TerrainChunk},
|
||||||
|
};
|
||||||
|
use common_ecs::dispatch;
|
||||||
|
use common_net::sync::WorldSyncExt;
|
||||||
|
use common_state::State;
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
use specs::{Builder, Entity, WorldExt};
|
||||||
|
use std::{error::Error, sync::Arc, time::Duration};
|
||||||
|
use vek::Vec2;
|
||||||
|
use veloren_common_systems::predict_controller;
|
||||||
|
|
||||||
|
pub const DTT: f64 = 1.0 / 10.0;
|
||||||
|
pub const DT: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
const DEFAULT_WORLD_CHUNKS_LG: MapSizeLg =
|
||||||
|
if let Ok(map_size_lg) = MapSizeLg::new(Vec2 { x: 10, y: 10 }) {
|
||||||
|
map_size_lg
|
||||||
|
} else {
|
||||||
|
panic!("Default world chunk size does not satisfy required invariants.");
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn setup() -> State {
|
||||||
|
let pools = State::pools(GameMode::Server);
|
||||||
|
let mut state = State::new(
|
||||||
|
GameMode::Server,
|
||||||
|
pools,
|
||||||
|
DEFAULT_WORLD_CHUNKS_LG,
|
||||||
|
Arc::new(TerrainChunk::water(0)),
|
||||||
|
);
|
||||||
|
|
||||||
|
state.ecs_mut().insert(MaterialStatManifest::with_empty());
|
||||||
|
state.ecs_mut().insert(AbilityMap::load().cloned());
|
||||||
|
state.ecs_mut().read_resource::<Time>();
|
||||||
|
state.ecs_mut().read_resource::<DeltaTime>();
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(state: &mut State, dt: Duration) {
|
||||||
|
state.tick(
|
||||||
|
dt,
|
||||||
|
|dispatch_builder| {
|
||||||
|
dispatch::<predict_controller::Sys>(dispatch_builder, &[]);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_remote(
|
||||||
|
state: &mut State,
|
||||||
|
entity: Entity,
|
||||||
|
command: ControlCommand,
|
||||||
|
) -> Result<u64, Box<dyn Error>> {
|
||||||
|
let mut storage = state.ecs_mut().write_storage::<RemoteController>();
|
||||||
|
let remote_controller = storage.get_mut(entity).ok_or(Box::new(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"Storage does not contain Entity RemoteController",
|
||||||
|
)))?;
|
||||||
|
remote_controller
|
||||||
|
.push(command)
|
||||||
|
.ok_or(Box::new(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"Command couldn't be pushed",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_controller(state: &State, entity: Entity) -> Result<Controller, Box<dyn Error>> {
|
||||||
|
let storage = state.ecs().read_storage::<Controller>();
|
||||||
|
let controller = storage.get(entity).ok_or(Box::new(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"Storage does not contain Entity Controller",
|
||||||
|
)))?;
|
||||||
|
Ok(controller.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn push_remotes(state: &mut State, entity: Entity, commands: ControlCommands) -> HashSet<u64> {
|
||||||
|
let mut storage = state.ecs_mut().write_storage::<RemoteController>();
|
||||||
|
let remote_controller = storage.get_mut(entity).unwrap();
|
||||||
|
remote_controller.append(commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_player(state: &mut State) -> Entity {
|
||||||
|
let remote_controller = RemoteController::default();
|
||||||
|
let controller = Controller::default();
|
||||||
|
|
||||||
|
state
|
||||||
|
.ecs_mut()
|
||||||
|
.create_entity_synced()
|
||||||
|
.with(remote_controller)
|
||||||
|
.with(controller)
|
||||||
|
.build()
|
||||||
|
}
|
@ -261,6 +261,7 @@ widget_ids! {
|
|||||||
debug_bg,
|
debug_bg,
|
||||||
fps_counter,
|
fps_counter,
|
||||||
ping,
|
ping,
|
||||||
|
simulate_ahead,
|
||||||
coordinates,
|
coordinates,
|
||||||
velocity,
|
velocity,
|
||||||
glide_ratio,
|
glide_ratio,
|
||||||
@ -654,6 +655,7 @@ pub struct DebugInfo {
|
|||||||
pub tps: f64,
|
pub tps: f64,
|
||||||
pub frame_time: Duration,
|
pub frame_time: Duration,
|
||||||
pub ping_ms: f64,
|
pub ping_ms: f64,
|
||||||
|
pub simulate_ahead: f64,
|
||||||
pub coordinates: Option<comp::Pos>,
|
pub coordinates: Option<comp::Pos>,
|
||||||
pub velocity: Option<comp::Vel>,
|
pub velocity: Option<comp::Vel>,
|
||||||
pub ori: Option<comp::Ori>,
|
pub ori: Option<comp::Ori>,
|
||||||
@ -2501,6 +2503,16 @@ impl Hud {
|
|||||||
.font_id(self.fonts.cyri.conrod_id)
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
.font_size(self.fonts.cyri.scale(14))
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
.set(self.ids.ping, ui_widgets);
|
.set(self.ids.ping, ui_widgets);
|
||||||
|
// Simulate Ahead timing
|
||||||
|
Text::new(&format!(
|
||||||
|
"Simulate Ahead: {:.0}ms",
|
||||||
|
debug_info.simulate_ahead * 1000.0
|
||||||
|
))
|
||||||
|
.color(TEXT_COLOR)
|
||||||
|
.down_from(self.ids.ping, V_PAD)
|
||||||
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
|
.set(self.ids.simulate_ahead, ui_widgets);
|
||||||
// Player's position
|
// Player's position
|
||||||
let coordinates_text = match debug_info.coordinates {
|
let coordinates_text = match debug_info.coordinates {
|
||||||
Some(coordinates) => format!(
|
Some(coordinates) => format!(
|
||||||
@ -2511,7 +2523,7 @@ impl Hud {
|
|||||||
};
|
};
|
||||||
Text::new(&coordinates_text)
|
Text::new(&coordinates_text)
|
||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
.down_from(self.ids.ping, V_PAD)
|
.down_from(self.ids.simulate_ahead, V_PAD)
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
.font_size(self.fonts.cyri.scale(14))
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
.set(self.ids.coordinates, ui_widgets);
|
.set(self.ids.coordinates, ui_widgets);
|
||||||
|
@ -1334,11 +1334,17 @@ impl PlayState for SessionState {
|
|||||||
.read_storage::<comp::CharacterState>()
|
.read_storage::<comp::CharacterState>()
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.cloned();
|
.cloned();
|
||||||
|
let simulate_ahead = ecs
|
||||||
|
.read_storage::<comp::RemoteController>()
|
||||||
|
.get(entity)
|
||||||
|
.map(|rc| rc.simulate_ahead().as_secs_f64())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
DebugInfo {
|
DebugInfo {
|
||||||
tps: global_state.clock.stats().average_tps,
|
tps: global_state.clock.stats().average_tps,
|
||||||
frame_time: global_state.clock.stats().average_busy_dt,
|
frame_time: global_state.clock.stats().average_busy_dt,
|
||||||
ping_ms: self.client.borrow().get_ping_ms_rolling_avg(),
|
ping_ms: self.client.borrow().get_ping_ms_rolling_avg(),
|
||||||
|
simulate_ahead,
|
||||||
coordinates,
|
coordinates,
|
||||||
velocity,
|
velocity,
|
||||||
ori,
|
ori,
|
||||||
|
Loading…
Reference in New Issue
Block a user