From a579329005acf710d21fb26dd8201a4dfa73441c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Fri, 17 Sep 2021 15:30:19 +0200 Subject: [PATCH] input avg by time --- common/src/comp/remote_controller.rs | 302 ++++++++++++++++++++--- common/systems/src/predict_controller.rs | 21 +- 2 files changed, 276 insertions(+), 47 deletions(-) diff --git a/common/src/comp/remote_controller.rs b/common/src/comp/remote_controller.rs index 49a537fe6d..983414bd3a 100644 --- a/common/src/comp/remote_controller.rs +++ b/common/src/comp/remote_controller.rs @@ -1,8 +1,9 @@ -use crate::comp::Controller; +use crate::{comp::Controller, util::Dir}; use hashbrown::HashSet; use serde::{Deserialize, Serialize}; use specs::{Component, DenseVecStorage}; use std::{collections::VecDeque, time::Duration}; +use vek::Vec3; pub type ControlCommands = VecDeque; @@ -30,15 +31,18 @@ pub struct ControlCommand { impl RemoteController { /// delte old commands pub fn maintain(&mut self) { - let min_allowed_age = self.commands.back().unwrap().source_time; + self.commands.make_contiguous(); + if let Some(last) = self.commands.back() { + let min_allowed_age = last.source_time; - while let Some(first) = self.commands.front() { - if first.source_time + self.max_hold < min_allowed_age { - self.commands - .pop_front() - .map(|c| self.existing_commands.remove(&c.id)); - } else { - break; + while let Some(first) = self.commands.front() { + if first.source_time + self.max_hold < min_allowed_age { + self.commands + .pop_front() + .map(|c| self.existing_commands.remove(&c.id)); + } else { + break; + } } } } @@ -49,10 +53,18 @@ impl RemoteController { if self.existing_commands.contains(&id) { return None; // element already existed } - self.existing_commands.insert(id); - self.commands.push_back(command); + match self + .commands + .binary_search_by_key(&command.source_time, |e| e.source_time) + { + Ok(_) => None, + Err(i) => { + self.existing_commands.insert(id); + self.commands.insert(i, command); - Some(id) + Some(id) + }, + } } pub fn append(&mut self, commands: ControlCommands) -> HashSet { @@ -60,37 +72,95 @@ impl RemoteController { for command in commands { let id = command.id; if !self.existing_commands.contains(&id) { - result.insert(id); - self.existing_commands.insert(id); - self.commands.push_back(command); - } - } - result - } - - pub fn current(&self, time: Duration) -> Option<&ControlCommand> { - //TODO: actually sort it! - let mut lowest = None; - let mut lowest_cmd = None; - for c in &self.commands { - if c.source_time >= time { - let diff = c.source_time - time; - if match lowest { - None => true, - Some(lowest) => diff < lowest, - } { - lowest = Some(diff); - lowest_cmd = Some(c); + // TODO: improve algorithm to move binary search out of loop + if let Err(i) = self + .commands + .binary_search_by_key(&command.source_time, |e| e.source_time) + { + result.insert(id); + self.existing_commands.insert(id); + self.commands.insert(i, command); } } } - lowest_cmd + result } /// arrived at remote and no longer need to be hold locally pub fn acked(&mut self, ids: HashSet) { self.commands.retain(|c| !ids.contains(&c.id)); } pub fn commands(&self) -> &ControlCommands { &self.commands } + + /// Creates a single Controller event from all existing Controllers in the + /// time range. E.g. when client with 60Hz sends: STAY JUMP STAY + /// and server runs at 30Hz it could oversee the JUMP Control otherwise + /// At the cost of a command to may be executed for longer + /// on the server the client (1/30 instead 1/60) + pub fn compress(&self, start: Duration, dt: Duration) -> Option { + // Assume we have 1 event every sec: 0,1,2,3,4 + // compressing 1s - 3s should lead to index 1-2 + // compressing 1s - 2s should lead to index 1 only + // compressing 1s - 1.2s should lead to index 1 only + // compressing 0.8s - 1.2s should lead to index 0-1 + let start_i = match self + .commands + .binary_search_by_key(&start, |e| e.source_time) + { + Ok(i) => i, + Err(0) => 0, + Err(i) => i - 1, + }; + let end_exclusive_i = match self + .commands + .binary_search_by_key(&(start + dt), |e| e.source_time) + { + Ok(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 { + return None; + } + let mut result = Controller::default(); + let mut look_dir = Vec3::zero(); + //if self.commands[start_i].source_time + // Inputs are averaged over all elements by time + // Queued Inputs are just added + // Events and Actions are not included when the frame isn't fully inserted, must + // not be duplicated + let mut last_start = start; + for i in start_i..end_exclusive_i { + let e = &self.commands[i]; + let local_start = e.source_time.max(last_start); + let local_end = if let Some(e) = self.commands.get(i + 1) { + e.source_time.min(start + dt) + } else { + start + dt + }; + 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_z += e.msg.inputs.move_z * local_dur.as_secs_f32(); + look_dir = result.inputs.look_dir.to_vec() + + e.msg.inputs.look_dir.to_vec() * local_dur.as_secs_f32(); + //TODO: manually combine 70% up and 30% down to UP + result.inputs.climb = result.inputs.climb.or(e.msg.inputs.climb); + result.inputs.break_block_pos = result + .inputs + .break_block_pos + .or(e.msg.inputs.break_block_pos); + result.inputs.strafing = result.inputs.strafing || e.msg.inputs.strafing; + last_start = local_start; + } + result.inputs.move_dir /= dt.as_secs_f32(); + result.inputs.move_z /= dt.as_secs_f32(); + result.inputs.look_dir = Dir::new(look_dir.normalized()); + + Some(result) + } } impl Default for RemoteController { @@ -130,14 +200,58 @@ impl ControlCommand { #[cfg(test)] mod tests { use super::*; + use crate::{ + comp, + comp::{Climb, ControllerInputs}, + }; + use std::collections::BTreeMap; + use vek::{Vec2, Vec3}; + + const INCREASE: Duration = Duration::from_millis(33); fn generate_control_cmds(count: usize) -> ControlCommands { let mut result = VecDeque::new(); let mut generator = CommandGenerator::default(); let mut time = Duration::new(0, 0); - const INCREASE: Duration = Duration::from_millis(33); - for _ in 0..count { - let msg = Controller::default(); + for i in 0..count { + let mut msg = Controller::default(); + msg.inputs.move_dir = Vec2::new(i as f32 / 5.0, i as f32 / 10.0); + msg.inputs.move_z = i as f32; + if i == 7 { + msg.inputs.break_block_pos = Some(Vec3::new(2.0, 3.0, 4.0)); + } + if i > 6 { + msg.inputs.climb = Some(comp::Climb::Up); + } + if i == 4 { + msg.inputs.strafing = true; + } + if i < 5 { + msg.events.push(comp::ControlEvent::EnableLantern); + }; + if i >= 5 && i < 10 { + msg.events.push(comp::ControlEvent::DisableLantern); + } + if i == 1 { + msg.actions.push(comp::ControlAction::Dance); + } + if i == 9 { + msg.actions.push(comp::ControlAction::Wield); + } + if i < 5 { + msg.queued_inputs + .insert(comp::InputKind::Jump, comp::InputAttr { + select_pos: None, + target_entity: None, + }); + } + if i >= 10 && i < 15 { + msg.queued_inputs + .insert(comp::InputKind::Primary, comp::InputAttr { + select_pos: None, + target_entity: None, + }); + } let cc = generator.gen(time, msg); result.push_back(cc); time += INCREASE; @@ -146,7 +260,7 @@ mod tests { } #[test] - fn test_resend_data() { + fn resend_data() { let data = generate_control_cmds(5); let mut list = RemoteController::default(); assert_eq!(list.push(data[0].clone()), Some(1)); @@ -173,7 +287,7 @@ mod tests { } #[test] - fn test_auto_evict() { + fn auto_evict() { let data = generate_control_cmds(6); let mut list = RemoteController::default(); list.max_hold = Duration::from_millis(100); @@ -200,7 +314,7 @@ mod tests { } #[test] - fn test_acked() { + fn acked() { let data = generate_control_cmds(7); let mut list = RemoteController::default(); assert_eq!(list.push(data[0].clone()), Some(1)); @@ -224,7 +338,7 @@ mod tests { } #[test] - fn test_append() { + fn append() { let data = generate_control_cmds(5); let mut list = RemoteController::default(); let set = list.append(data); @@ -237,4 +351,110 @@ mod tests { assert!(set.contains(&5)); assert!(!set.contains(&6)); } + + #[test] + fn compress_basic() { + let data = generate_control_cmds(5); + let mut list = RemoteController::default(); + list.append(data); + let compressed = list.compress(0 * INCREASE, 1 * INCREASE).unwrap(); + assert_eq!(compressed, Controller { + inputs: comp::ControllerInputs { + break_block_pos: None, + climb: None, + move_dir: Vec2::new(0.0, 0.0), + move_z: 0., + look_dir: Dir::new(Vec3::new(0.0, 1.0, 0.0)), + strafing: false, + }, + queued_inputs: BTreeMap::new(), + events: Vec::new(), + actions: Vec::new(), + }); + } + + #[test] + fn compress_avg_input_full() { + let data = generate_control_cmds(4); + let mut list = RemoteController::default(); + list.append(data); + let compressed = list.compress(0 * INCREASE, 2 * INCREASE).unwrap(); + assert_eq!(compressed.inputs, ControllerInputs { + move_dir: Vec2::new(0.1, 0.05), + move_z: 0.5, + ..ControllerInputs::default() + }); + let compressed = list.compress(0 * INCREASE, 3 * INCREASE).unwrap(); + assert_eq!(compressed.inputs, ControllerInputs { + move_dir: Vec2::new(0.2, 0.1), + move_z: 1.0, + ..ControllerInputs::default() + }); + let compressed = list.compress(1 * INCREASE, 3 * INCREASE).unwrap(); + assert_eq!(compressed.inputs, ControllerInputs { + move_dir: Vec2::new(0.4, 0.2), + move_z: 2.0, + ..ControllerInputs::default() + }); + } + + #[test] + fn compress_avg_input_partial() { + let data = generate_control_cmds(4); + let mut list = RemoteController::default(); + list.append(data); + const HALF: Duration = Duration::from_nanos(INCREASE.as_nanos() as u64 / 2); + let compressed = list.compress(HALF, INCREASE).unwrap(); + assert_eq!(compressed.inputs, ControllerInputs { + move_dir: Vec2::new(0.1, 0.05), + move_z: 0.5, + ..ControllerInputs::default() + }); + let compressed = list.compress(HALF, 3 * INCREASE).unwrap(); + assert_eq!(compressed.inputs, ControllerInputs { + move_dir: Vec2::new(0.3, 0.15), + move_z: 1.5, + ..ControllerInputs::default() + }); + let compressed = list.compress(INCREASE + HALF, 2 * INCREASE).unwrap(); + assert_eq!(compressed.inputs, ControllerInputs { + move_dir: Vec2::new(0.4, 0.2), + move_z: 2.0, + ..ControllerInputs::default() + }); + } + + #[test] + fn compress_avg_input_missing() { + let mut data = generate_control_cmds(7); + let mut list = RemoteController::default(); + data.pop_front(); + data.pop_front(); + data.pop_front(); + list.append(data); + let compressed = list.compress(0 * INCREASE, 2 * INCREASE); + assert_eq!(compressed, None); + //let compressed = list.compress(10 * INCREASE, 2 * INCREASE); + //assert_eq!(compressed, None); + } + + #[test] + fn compress_other_input() { + let data = generate_control_cmds(10); + let mut list = RemoteController::default(); + list.append(data); + let compressed = list.compress(5 * INCREASE, 5 * INCREASE).unwrap(); + assert_eq!( + compressed.inputs.break_block_pos, + Some(Vec3::new(2.0, 3.0, 4.0)) + ); + assert_eq!(compressed.inputs.climb, Some(Climb::Up)); + assert_eq!(compressed.inputs.move_dir, Vec2::new(1.4, 0.7)); + let move_z = (compressed.inputs.move_z - 7.0).abs(); + println!("move_z: {}", move_z); + assert!(move_z < 0.0001); + assert_eq!(compressed.inputs.strafing, false); + } + + // } diff --git a/common/systems/src/predict_controller.rs b/common/systems/src/predict_controller.rs index b304dfa8b8..a7d2014266 100644 --- a/common/systems/src/predict_controller.rs +++ b/common/systems/src/predict_controller.rs @@ -1,10 +1,17 @@ -use common::comp::{Controller, RemoteController}; +use common::{ + comp::{Controller, RemoteController}, + resources::{Time, DeltaTime,}, +}; use common_ecs::{Job, Origin, Phase, System}; -use specs::{shred::ResourceId, Entities, Join, ReadStorage, SystemData, World, WriteStorage}; +use specs::{ + shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage, +}; use std::time::Duration; #[derive(SystemData)] pub struct ReadData<'a> { + time: Read<'a, Time>, + dt: Read<'a, DeltaTime>, entities: Entities<'a>, remote_controllers: ReadStorage<'a, RemoteController>, } @@ -22,7 +29,8 @@ impl<'a> System<'a> for Sys { fn run(_job: &mut Job, (read_data, mut controllers): Self::SystemData) { for (entity, _a) in (&read_data.entities, &read_data.remote_controllers).join() { let i = _a.commands().len(); - tracing::warn!(?i, "foo"); + let time = Duration::from_secs_f64(read_data.time.0); + tracing::warn!(?i, ?time, "foo"); let _ = controllers .entry(entity) .map(|e| e.or_insert_with(Default::default)); @@ -35,9 +43,10 @@ impl<'a> System<'a> for Sys { ) .join() { - // grab the most fitting entry - let time = Duration::from_millis(1000); - let r = remote_controller.current(time).map(|c| c.msg()); + let dt = Duration::from_secs_f32(read_data.dt.0); + let time = Duration::from_secs_f64(read_data.time.0); + // grab the most fitting entry; + let r = remote_controller.compress(time, dt); // Do nothing when already populated and we dont have a new value if let Some(r) = r { *controller = r.clone()