diff --git a/client/src/lib.rs b/client/src/lib.rs index 9d24b84102..c0c90dd2de 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -28,9 +28,9 @@ use common::{ invite::{InviteKind, InviteResponse}, skills::Skill, slot::{EquipSlot, InvSlotId, Slot}, - CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, - GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent, - MapMarkerChange, UtteranceKind, + CharacterState, ChatMode, CommandGenerator, ControlAction, ControlEvent, Controller, + ControllerInputs, GroupManip, InputKind, InventoryAction, InventoryEvent, + InventoryUpdateEvent, MapMarkerChange, RemoteController, UtteranceKind, }, event::{EventBus, LocalEvent, UpdateCharacterMetadata}, grid::Grid, @@ -73,7 +73,7 @@ use num::traits::FloatConst; use rayon::prelude::*; use specs::Component; use std::{ - collections::{BTreeMap, VecDeque}, + collections::VecDeque, mem, sync::Arc, time::{Duration, Instant}, @@ -235,6 +235,9 @@ pub struct Client { // The pending trade the client is involved in, and it's id pending_trade: Option<(TradeId, PendingTrade, Option)>, + local_command_gen: CommandGenerator, + next_control: Controller, + network: Option, participant: Option, general_stream: Stream, @@ -716,6 +719,9 @@ impl Client { pending_invites: HashSet::new(), pending_trade: None, + local_command_gen: CommandGenerator::default(), + next_control: Controller::default(), + network: Some(network), participant: Some(participant), general_stream: stream, @@ -841,9 +847,7 @@ impl Client { | ClientGeneral::Character(_, _) | ClientGeneral::Spectate(_) => &mut self.character_screen_stream, //Only in game - ClientGeneral::ControllerInputs(_) - | ClientGeneral::ControlEvent(_) - | ClientGeneral::ControlAction(_) + ClientGeneral::Control(_) | ClientGeneral::SetViewDistance(_) | ClientGeneral::BreakBlock(_) | ClientGeneral::PlaceBlock(_, _) @@ -1032,9 +1036,11 @@ impl Client { ControlAction::InventoryAction(InventoryAction::Swap(equip, slot)), ), (Slot::Inventory(inv1), Slot::Inventory(inv2)) => { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( - InventoryEvent::Swap(inv1, inv2), - ))) + self.next_control + .events + .push(ControlEvent::InventoryEvent(InventoryEvent::Swap( + inv1, inv2, + ))); }, } } @@ -1044,9 +1050,10 @@ impl Client { Slot::Equip(equip) => { self.control_action(ControlAction::InventoryAction(InventoryAction::Drop(equip))) }, - Slot::Inventory(inv) => self.send_msg(ClientGeneral::ControlEvent( - ControlEvent::InventoryEvent(InventoryEvent::Drop(inv)), - )), + Slot::Inventory(inv) => self + .next_control + .events + .push(ControlEvent::InventoryEvent(InventoryEvent::Drop(inv))), } } @@ -1059,9 +1066,9 @@ impl Client { if let TradeAction::Decline = action { self.pending_trade.take(); } - self.send_msg(ClientGeneral::ControlEvent( - ControlEvent::PerformTradeAction(id, action), - )); + self.next_control + .events + .push(ControlEvent::PerformTradeAction(id, action)); } } @@ -1077,11 +1084,9 @@ impl Client { (Slot::Equip(equip), slot) | (slot, Slot::Equip(equip)) => self.control_action( ControlAction::InventoryAction(InventoryAction::Swap(equip, slot)), ), - (Slot::Inventory(inv1), Slot::Inventory(inv2)) => { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( - InventoryEvent::SplitSwap(inv1, inv2), - ))) - }, + (Slot::Inventory(inv1), Slot::Inventory(inv2)) => self.next_control.events.push( + ControlEvent::InventoryEvent(InventoryEvent::SplitSwap(inv1, inv2)), + ), } } @@ -1090,9 +1095,10 @@ impl Client { Slot::Equip(equip) => { self.control_action(ControlAction::InventoryAction(InventoryAction::Drop(equip))) }, - Slot::Inventory(inv) => self.send_msg(ClientGeneral::ControlEvent( - ControlEvent::InventoryEvent(InventoryEvent::SplitDrop(inv)), - )), + Slot::Inventory(inv) => self + .next_control + .events + .push(ControlEvent::InventoryEvent(InventoryEvent::SplitDrop(inv))), } } @@ -1104,10 +1110,9 @@ impl Client { if self.is_dead() { return; } - - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( - InventoryEvent::Pickup(uid), - ))); + self.next_control + .events + .push(ControlEvent::InventoryEvent(InventoryEvent::Pickup(uid))); } } @@ -1118,7 +1123,7 @@ impl Client { } if let Some(uid) = self.state.read_component_copied(npc_entity) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Interact(uid))); + self.next_control.events.push(ControlEvent::Interact(uid)); } } @@ -1165,7 +1170,7 @@ impl Client { let (can_craft, required_sprite) = self.can_craft_recipe(recipe, amount); let has_sprite = required_sprite.map_or(true, |s| Some(s) == craft_sprite.map(|(_, s)| s)); if can_craft && has_sprite { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( + self.next_control.events.push(ControlEvent::InventoryEvent( InventoryEvent::CraftRecipe { craft_event: CraftEvent::Simple { recipe: recipe.to_string(), @@ -1174,7 +1179,7 @@ impl Client { }, craft_sprite: craft_sprite.map(|(pos, _)| pos), }, - ))); + )); true } else { false @@ -1194,12 +1199,12 @@ impl Client { pub fn salvage_item(&mut self, slot: InvSlotId, salvage_pos: Vec3) -> bool { let is_salvageable = self.can_salvage_item(slot); if is_salvageable { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( + self.next_control.events.push(ControlEvent::InventoryEvent( InventoryEvent::CraftRecipe { craft_event: CraftEvent::Salvage(slot), craft_sprite: Some(salvage_pos), }, - ))); + )); } is_salvageable } @@ -1241,7 +1246,7 @@ impl Client { (mod_kind(primary_component), mod_kind(secondary_component)) { drop(inventories); - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( + self.next_control.events.push(ControlEvent::InventoryEvent( InventoryEvent::CraftRecipe { craft_event: CraftEvent::ModularWeapon { primary_component, @@ -1249,7 +1254,7 @@ impl Client { }, craft_sprite: sprite_pos, }, - ))); + )); true } else { false @@ -1264,8 +1269,9 @@ impl Client { slots: Vec<(u32, InvSlotId)>, sprite_pos: Option>, ) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( - InventoryEvent::CraftRecipe { + self.next_control + .events + .push(ControlEvent::InventoryEvent(InventoryEvent::CraftRecipe { craft_event: CraftEvent::ModularWeaponPrimaryComponent { toolkind, material, @@ -1273,8 +1279,7 @@ impl Client { slots, }, craft_sprite: sprite_pos, - }, - ))); + })); } fn update_available_recipes(&mut self) { @@ -1301,18 +1306,16 @@ impl Client { pub fn sites_mut(&mut self) -> &mut HashMap { &mut self.sites } - pub fn enable_lantern(&mut self) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::EnableLantern)); - } + pub fn enable_lantern(&mut self) { self.next_control.events.push(ControlEvent::EnableLantern); } pub fn disable_lantern(&mut self) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::DisableLantern)); + self.next_control.events.push(ControlEvent::DisableLantern); } pub fn remove_buff(&mut self, buff_id: BuffKind) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::RemoveBuff( - buff_id, - ))); + self.next_control + .events + .push(ControlEvent::RemoveBuff(buff_id)); } pub fn unlock_skill(&mut self, skill: Skill) { @@ -1338,43 +1341,43 @@ impl Client { pub fn is_trading(&self) -> bool { self.pending_trade.is_some() } pub fn send_invite(&mut self, invitee: Uid, kind: InviteKind) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InitiateInvite( - invitee, kind, - ))) + self.next_control + .events + .push(ControlEvent::InitiateInvite(invitee, kind)); } pub fn accept_invite(&mut self) { // Clear invite self.invite.take(); - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InviteResponse( - InviteResponse::Accept, - ))); + self.next_control + .events + .push(ControlEvent::InviteResponse(InviteResponse::Accept)); } pub fn decline_invite(&mut self) { // Clear invite self.invite.take(); - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InviteResponse( - InviteResponse::Decline, - ))); + self.next_control + .events + .push(ControlEvent::InviteResponse(InviteResponse::Decline)); } pub fn leave_group(&mut self) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( - GroupManip::Leave, - ))); + self.next_control + .events + .push(ControlEvent::GroupManip(GroupManip::Leave)); } pub fn kick_from_group(&mut self, uid: Uid) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( - GroupManip::Kick(uid), - ))); + self.next_control + .events + .push(ControlEvent::GroupManip(GroupManip::Kick(uid))); } pub fn assign_group_leader(&mut self, uid: Uid) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( - GroupManip::AssignLeader(uid), - ))); + self.next_control + .events + .push(ControlEvent::GroupManip(GroupManip::AssignLeader(uid))); } pub fn is_riding(&self) -> bool { @@ -1395,11 +1398,11 @@ impl Client { pub fn mount(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Mount(uid))); + self.next_control.events.push(ControlEvent::Mount(uid)); } } - pub fn unmount(&mut self) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Unmount)); } + pub fn unmount(&mut self) { self.next_control.events.push(ControlEvent::Unmount); } pub fn respawn(&mut self) { if self @@ -1409,7 +1412,7 @@ impl Client { .get(self.entity()) .map_or(false, |h| h.is_dead) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Respawn)); + self.next_control.events.push(ControlEvent::Respawn); } } @@ -1490,7 +1493,7 @@ impl Client { } pub fn utter(&mut self, kind: UtteranceKind) { - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Utterance(kind))); + self.next_control.events.push(ControlEvent::Utterance(kind)); } pub fn toggle_sneak(&mut self) { @@ -1542,15 +1545,7 @@ impl Client { } fn control_action(&mut self, control_action: ControlAction) { - if let Some(controller) = self - .state - .ecs() - .write_storage::() - .get_mut(self.entity()) - { - controller.push_action(control_action); - } - self.send_msg(ClientGeneral::ControlAction(control_action)); + self.next_control.actions.push(control_action); } pub fn view_distance(&self) -> Option { self.view_distance } @@ -1675,11 +1670,11 @@ impl Client { ) }); - self.send_msg(ClientGeneral::ControlEvent(ControlEvent::ChangeAbility { + self.next_control.events.push(ControlEvent::ChangeAbility { slot, auxiliary_key, new_ability, - })) + }); } /// Execute a single client tick, handle input and update the game state by @@ -1688,6 +1683,7 @@ impl Client { &mut self, inputs: ControllerInputs, dt: Duration, + total_tick_time: Duration, add_foreign_systems: impl Fn(&mut DispatcherBuilder), ) -> Result, Error> { span!(_guard, "tick", "Client::tick"); @@ -1714,30 +1710,25 @@ impl Client { // Pass character actions from frontend input to the player's entity. if self.presence.is_some() { prof_span!("handle and send inputs"); - if let Err(e) = self + self.next_control.inputs = inputs; + let con = std::mem::take(&mut self.next_control); + let rcon = self.local_command_gen.gen(total_tick_time, con); + let commands = self .state .ecs() - .write_storage::() + .write_storage::() .entry(self.entity()) - .map(|entry| { - entry - .or_insert_with(|| Controller { - inputs: inputs.clone(), - queued_inputs: BTreeMap::new(), - events: Vec::new(), - actions: Vec::new(), - }) - .inputs = inputs.clone(); - }) - { - let entry = self.entity(); - error!( - ?e, - ?entry, - "Couldn't access controller component on client entity" - ); - } - self.send_msg_err(ClientGeneral::ControllerInputs(Box::new(inputs)))?; + .map(|rc| { + let rc = rc.or_insert_with(RemoteController::default); + rc.push(rcon); + rc.commands().clone() + }); + match commands { + Ok(commands) => self.send_msg_err(ClientGeneral::Control(commands))?, + Err(e) => { + error!(?e, "couldn't create RemoteController for own entity"); + }, + }; } // 2) Build up a list of events for this frame, to be passed to the frontend. @@ -2187,6 +2178,17 @@ impl Client { ) -> Result<(), Error> { prof_span!("handle_server_in_game_msg"); match msg { + ServerGeneral::AckControl(acked_ids) => { + if let Some(remote_controller) = self + .state + .ecs() + .write_storage::() + .get_mut(self.entity()) + { + remote_controller.acked(acked_ids); + remote_controller.maintain(); + } + }, ServerGeneral::GroupUpdate(change_notification) => { use comp::group::ChangeNotification::*; // Note: we use a hashmap since this would not work with entities outside @@ -2868,8 +2870,12 @@ mod tests { let mut clock = Clock::new(Duration::from_secs_f64(SPT)); //tick - let events_result: Result, Error> = - client.tick(ControllerInputs::default(), clock.dt(), |_| {}); + let events_result: Result, Error> = client.tick( + comp::ControllerInputs::default(), + clock.dt(), + clock.total_tick_time(), + |_| {}, + ); //chat functionality client.send_chat("foobar".to_string()); diff --git a/common/net/src/msg/client.rs b/common/net/src/msg/client.rs index e91f728d02..d732434c64 100644 --- a/common/net/src/msg/client.rs +++ b/common/net/src/msg/client.rs @@ -58,9 +58,7 @@ pub enum ClientGeneral { Character(CharacterId, ViewDistances), Spectate(ViewDistances), //Only in game - ControllerInputs(Box), - ControlEvent(comp::ControlEvent), - ControlAction(comp::ControlAction), + Control(comp::ControlCommands), SetViewDistance(ViewDistances), BreakBlock(Vec3), PlaceBlock(Vec3, Block), @@ -118,9 +116,7 @@ impl ClientMsg { c_type == ClientType::Game && presence.is_none() }, //Only in game - ClientGeneral::ControllerInputs(_) - | ClientGeneral::ControlEvent(_) - | ClientGeneral::ControlAction(_) + ClientGeneral::Control(_) | ClientGeneral::SetViewDistance(_) | ClientGeneral::BreakBlock(_) | ClientGeneral::PlaceBlock(_, _) diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index f3434e2352..8e4b405858 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -18,7 +18,7 @@ use common::{ uuid::Uuid, weather::WeatherGrid, }; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; use std::time::Duration; use tracing::warn; @@ -142,6 +142,7 @@ pub enum ServerGeneral { CharacterSuccess, SpectatorSuccess(Vec3), //Ingame related + AckControl(HashSet), GroupUpdate(comp::group::ChangeNotification), /// Indicate to the client that they are invited to join a group Invite { @@ -315,6 +316,7 @@ impl ServerMsg { }, //Ingame related ServerGeneral::GroupUpdate(_) + | ServerGeneral::AckControl(_) | ServerGeneral::Invite { .. } | ServerGeneral::InvitePending(_) | ServerGeneral::InviteComplete { .. } diff --git a/common/src/clock.rs b/common/src/clock.rs index cbc86839ca..c3624ea31f 100644 --- a/common/src/clock.rs +++ b/common/src/clock.rs @@ -103,6 +103,8 @@ impl Clock { } } + pub fn total_tick_time(&self) -> Duration { self.total_tick_time } + /// Do not modify without asking @xMAC94x first! pub fn tick(&mut self) { span!(_guard, "tick", "Clock::tick"); diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 2a2758f15b..d6ad2d8c1d 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -127,6 +127,7 @@ pub enum UtteranceKind { * sounds */ } +/// instantaneous Events that can happen anytime and are #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ControlEvent { //ToggleLantern, @@ -150,6 +151,8 @@ pub enum ControlEvent { }, } +/// in contrast to events, action move a entity between 2 states, we want to +/// limit when they can happen #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum ControlAction { SwapEquippedWeapons, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 9c0db9731d..4e280ac1c7 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -40,6 +40,8 @@ pub mod loot_owner; #[cfg(not(target_arch = "wasm32"))] pub mod projectile; #[cfg(not(target_arch = "wasm32"))] +mod remote_controller; +#[cfg(not(target_arch = "wasm32"))] pub mod shockwave; #[cfg(not(target_arch = "wasm32"))] pub mod skillset; @@ -108,6 +110,7 @@ pub use self::{ player::{AliasError, Player, MAX_ALIAS_LEN}, poise::{Poise, PoiseChange, PoiseState}, projectile::{Projectile, ProjectileConstructor}, + remote_controller::{CommandGenerator, ControlCommands, RemoteController}, shockwave::{Shockwave, ShockwaveHitEntities}, skillset::{ skills::{self, Skill}, diff --git a/common/src/comp/remote_controller.rs b/common/src/comp/remote_controller.rs new file mode 100644 index 0000000000..49a537fe6d --- /dev/null +++ b/common/src/comp/remote_controller.rs @@ -0,0 +1,240 @@ +use crate::comp::Controller; +use hashbrown::HashSet; +use serde::{Deserialize, Serialize}; +use specs::{Component, DenseVecStorage}; +use std::{collections::VecDeque, time::Duration}; + +pub type ControlCommands = VecDeque; + +/// Controller messages are real-time-combat relevant. +/// Controller contains all kind of inputs a entity can change +/// Remote controlled entities (e.g. clients) have a latency +/// They can predict the future locally and send us their TIMED controls +/// Other things, like chunk requests or build a weapon are done one other +/// channel via other methods. +#[derive(Debug)] +pub struct RemoteController { + ///sorted, deduplicated + commands: ControlCommands, + existing_commands: HashSet, + max_hold: Duration, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ControlCommand { + id: u64, + source_time: Duration, + msg: Controller, +} + +impl RemoteController { + /// delte old commands + pub fn maintain(&mut self) { + let min_allowed_age = self.commands.back().unwrap().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; + } + } + } + + pub fn push(&mut self, command: ControlCommand) -> Option { + let id = command.id; + //check if command fits on the end. + if self.existing_commands.contains(&id) { + return None; // element already existed + } + self.existing_commands.insert(id); + self.commands.push_back(command); + + Some(id) + } + + pub fn append(&mut self, commands: ControlCommands) -> HashSet { + let mut result = HashSet::new(); + 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); + } + } + } + lowest_cmd + } + + /// 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 } +} + +impl Default for RemoteController { + fn default() -> Self { + Self { + commands: VecDeque::new(), + existing_commands: HashSet::new(), + max_hold: Duration::from_secs(1), + } + } +} + +impl Component for RemoteController { + type Storage = DenseVecStorage; +} + +#[derive(Default)] +pub struct CommandGenerator { + id: u64, +} + +impl CommandGenerator { + pub fn gen(&mut self, time: Duration, msg: Controller) -> ControlCommand { + self.id += 1; + ControlCommand { + source_time: time, + id: self.id, + msg, + } + } +} + +impl ControlCommand { + pub fn msg(&self) -> &Controller { &self.msg } +} + +#[cfg(test)] +mod tests { + use super::*; + + 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(); + let cc = generator.gen(time, msg); + result.push_back(cc); + time += INCREASE; + } + result + } + + #[test] + fn test_resend_data() { + let data = generate_control_cmds(5); + let mut list = RemoteController::default(); + assert_eq!(list.push(data[0].clone()), Some(1)); + assert_eq!(list.commands.len(), 1); + + assert_eq!(list.push(data[0].clone()), None); + assert_eq!(list.push(data[1].clone()), Some(2)); + assert_eq!(list.commands.len(), 2); + + assert_eq!(list.push(data[0].clone()), None); + assert_eq!(list.push(data[1].clone()), None); + assert_eq!(list.push(data[2].clone()), Some(3)); + assert_eq!(list.commands.len(), 3); + + assert_eq!(list.push(data[1].clone()), None); + assert_eq!(list.push(data[2].clone()), None); + assert_eq!(list.push(data[3].clone()), Some(4)); + assert_eq!(list.commands.len(), 4); + + assert_eq!(list.push(data[2].clone()), None); + assert_eq!(list.push(data[3].clone()), None); + assert_eq!(list.push(data[4].clone()), Some(5)); + assert_eq!(list.commands.len(), 5); + } + + #[test] + fn test_auto_evict() { + let data = generate_control_cmds(6); + let mut list = RemoteController::default(); + list.max_hold = Duration::from_millis(100); + assert_eq!(list.push(data[0].clone()), Some(1)); + assert_eq!(list.push(data[1].clone()), Some(2)); + assert_eq!(list.push(data[2].clone()), Some(3)); + assert_eq!(list.commands.len(), 3); + assert_eq!(list.push(data[3].clone()), Some(4)); + assert_eq!(list.commands.len(), 4); + assert_eq!(list.commands[0].id, 1); + assert_eq!(list.push(data[4].clone()), Some(5)); + assert_eq!(list.commands.len(), 5); + list.maintain(); + assert_eq!(list.commands.len(), 4); + assert_eq!(list.commands[0].id, 2); + assert_eq!(list.push(data[5].clone()), Some(6)); + assert_eq!(list.commands.len(), 5); + list.maintain(); + assert_eq!(list.commands.len(), 4); + assert_eq!(list.commands[0].id, 3); + assert_eq!(list.commands[1].id, 4); + assert_eq!(list.commands[2].id, 5); + assert_eq!(list.commands[3].id, 6); + } + + #[test] + fn test_acked() { + let data = generate_control_cmds(7); + let mut list = RemoteController::default(); + assert_eq!(list.push(data[0].clone()), Some(1)); + assert_eq!(list.push(data[1].clone()), Some(2)); + assert_eq!(list.push(data[2].clone()), Some(3)); + assert_eq!(list.push(data[3].clone()), Some(4)); + assert_eq!(list.push(data[4].clone()), Some(5)); + let mut to_export = list.commands().iter().map(|e| e.id).collect::>(); + // damange one entry + to_export.remove(&3); + list.acked(to_export); + assert_eq!(list.push(data[5].clone()), Some(6)); + assert_eq!(list.push(data[6].clone()), Some(7)); + println!("asd{:?}", &list); + + let to_export = list.commands().clone(); + assert_eq!(to_export.len(), 3); + assert_eq!(to_export[0].id, 3); + assert_eq!(to_export[1].id, 6); + assert_eq!(to_export[2].id, 7); + } + + #[test] + fn test_append() { + let data = generate_control_cmds(5); + let mut list = RemoteController::default(); + let set = list.append(data); + assert_eq!(set.len(), 5); + assert!(!set.contains(&0)); + assert!(set.contains(&1)); + assert!(set.contains(&2)); + assert!(set.contains(&3)); + assert!(set.contains(&4)); + assert!(set.contains(&5)); + assert!(!set.contains(&6)); + } +} diff --git a/common/state/src/state.rs b/common/state/src/state.rs index 00d8aa66e9..aa2ee521cb 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -207,7 +207,7 @@ impl State { ecs.register::(); // Register components send from clients -> server - ecs.register::(); + ecs.register::(); // Register components send directly from server -> all but one client ecs.register::(); @@ -224,6 +224,7 @@ impl State { // Register client-local components // TODO: only register on the client + ecs.register::(); ecs.register::(); ecs.register::>(); ecs.register::>(); diff --git a/common/systems/src/lib.rs b/common/systems/src/lib.rs index a0e18fcf5f..e15a3083fe 100644 --- a/common/systems/src/lib.rs +++ b/common/systems/src/lib.rs @@ -10,6 +10,7 @@ mod interpolation; pub mod melee; mod mount; pub mod phys; +pub mod predict_controller; pub mod projectile; mod shockwave; mod stats; @@ -19,6 +20,7 @@ use common_ecs::{dispatch, System}; use specs::DispatcherBuilder; pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { + dispatch::(dispatch_builder, &[]); //TODO: don't run interpolation on server dispatch::(dispatch_builder, &[]); dispatch::(dispatch_builder, &[]); diff --git a/common/systems/src/predict_controller.rs b/common/systems/src/predict_controller.rs new file mode 100644 index 0000000000..b304dfa8b8 --- /dev/null +++ b/common/systems/src/predict_controller.rs @@ -0,0 +1,47 @@ +use common::comp::{Controller, RemoteController}; +use common_ecs::{Job, Origin, Phase, System}; +use specs::{shred::ResourceId, Entities, Join, ReadStorage, SystemData, World, WriteStorage}; +use std::time::Duration; + +#[derive(SystemData)] +pub struct ReadData<'a> { + entities: Entities<'a>, + remote_controllers: ReadStorage<'a, RemoteController>, +} + +#[derive(Default)] +pub struct Sys; + +impl<'a> System<'a> for Sys { + type SystemData = (ReadData<'a>, WriteStorage<'a, Controller>); + + const NAME: &'static str = "predict_controller"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + + 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 _ = controllers + .entry(entity) + .map(|e| e.or_insert_with(Default::default)); + } + + for (_, remote_controller, controller) in ( + &read_data.entities, + &read_data.remote_controllers, + &mut controllers, + ) + .join() + { + // grab the most fitting entry + let time = Duration::from_millis(1000); + let r = remote_controller.current(time).map(|c| c.msg()); + // Do nothing when already populated and we dont have a new value + if let Some(r) = r { + *controller = r.clone() + } + } + } +} diff --git a/server/src/client.rs b/server/src/client.rs index bd6ec33458..286d00ea5a 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -103,6 +103,7 @@ impl Client { }, //In-game related ServerGeneral::GroupUpdate(_) + | ServerGeneral::AckControl(_) | ServerGeneral::Invite { .. } | ServerGeneral::InvitePending(_) | ServerGeneral::InviteComplete { .. } @@ -175,6 +176,7 @@ impl Client { }, //In-game related ServerGeneral::GroupUpdate(_) + | ServerGeneral::AckControl(_) | ServerGeneral::Invite { .. } | ServerGeneral::InvitePending(_) | ServerGeneral::InviteComplete { .. } diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index 6c50879800..ee8bc0c3bd 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -3,8 +3,8 @@ use crate::TerrainPersistence; use crate::{client::Client, presence::Presence, Settings}; use common::{ comp::{ - Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player, - Pos, SkillSet, Vel, + Admin, AdminRole, CanBuild, ForceUpdate, Health, Ori, Player, Pos, RemoteController, + SkillSet, Vel, }, event::{EventBus, ServerEvent}, link::Is, @@ -59,7 +59,7 @@ impl Sys { position: Option<&mut Pos>, velocity: Option<&mut Vel>, orientation: Option<&mut Ori>, - controller: Option<&mut Controller>, + remote_controller: &mut RemoteController, settings: &Read<'_, Settings>, build_areas: &Read<'_, BuildAreas>, player_physics_setting: Option<&mut PlayerPhysicsSetting>, @@ -93,32 +93,32 @@ impl Sys { client.send(ServerGeneral::SetViewDistance(clamped_vds.terrain))?; } }, - ClientGeneral::ControllerInputs(inputs) => { + ClientGeneral::Control(rc) => { if presence.kind.controlling_char() { - if let Some(controller) = controller { - controller.inputs.update_with_new(*inputs); - } - } - }, - ClientGeneral::ControlEvent(event) => { - if presence.kind.controlling_char() { - // Skip respawn if client entity is alive - if let ControlEvent::Respawn = event { - if healths.get(entity).map_or(true, |h| !h.is_dead) { - //Todo: comment why return! - return Ok(()); + let ids = remote_controller.append(rc); + remote_controller.maintain(); + // confirm controls + client.send(ServerGeneral::AckControl(ids))?; + //Todo: FIXME!!! + /* + // Skip respawn if client entity is alive + if let ControlEvent::Respawn = event { + if healths.get(entity).map_or(true, |h| !h.is_dead) { + //Todo: comment why return! + return Ok(()); + } + } + } + if let Some(controller) = controllers.get_mut(entity) { + controller.push_event(event); + } } - } - if let Some(controller) = controller { - controller.push_event(event); - } - } - }, - ClientGeneral::ControlAction(event) => { - if presence.kind.controlling_char() { - if let Some(controller) = controller { - controller.push_action(event); - } + }, + ClientGeneral::ControlAction(event) => { + if presence.kind.controlling_char() { + if let Some(controller) = controllers.get_mut(entity) { + controller.push_action(event); + */ } }, ClientGeneral::PlayerPhysics { pos, vel, ori, force_counter } => { @@ -330,7 +330,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Ori>, WriteStorage<'a, Presence>, WriteStorage<'a, Client>, - WriteStorage<'a, Controller>, + WriteStorage<'a, RemoteController>, Read<'a, Settings>, Read<'a, BuildAreas>, Write<'a, PlayerPhysicsSettings>, @@ -361,7 +361,7 @@ impl<'a> System<'a> for Sys { mut orientations, mut presences, mut clients, - mut controllers, + mut remote_controllers, settings, build_areas, mut player_physics_settings_, @@ -390,7 +390,7 @@ impl<'a> System<'a> for Sys { (&mut positions).maybe(), (&mut velocities).maybe(), (&mut orientations).maybe(), - (&mut controllers).maybe(), + (&mut remote_controllers).maybe(), ) .join() // NOTE: Required because Specs has very poor work splitting for sparse joins. @@ -407,7 +407,7 @@ impl<'a> System<'a> for Sys { ref mut pos, ref mut vel, ref mut ori, - ref mut controller, + remote_controller, )| { let old_player_physics_setting = maybe_player.map(|p| { player_physics_settings @@ -421,6 +421,14 @@ impl<'a> System<'a> for Sys { // ingame messages to be ignored. let mut clearable_maybe_presence = maybe_presence.as_deref_mut(); let mut skill_set = skill_set.map(Cow::Borrowed); + let mut new_remote_controller = None; + let remote_controller = match remote_controller { + Some(rc) => rc, + None => { + new_remote_controller = Some(RemoteController::default()); + new_remote_controller.as_mut().unwrap() + } + }; let _ = super::try_recv_all(client, 2, |client, msg| { Self::handle_client_in_game_msg( server_emitter, @@ -437,7 +445,7 @@ impl<'a> System<'a> for Sys { pos.as_deref_mut(), vel.as_deref_mut(), ori.as_deref_mut(), - controller.as_deref_mut(), + remote_controller, &settings, &build_areas, new_player_physics_setting.as_mut(), @@ -467,27 +475,26 @@ impl<'a> System<'a> for Sys { let physics_update = maybe_player.map(|p| p.uuid()) .zip(new_player_physics_setting .filter(|_| old_player_physics_setting != new_player_physics_setting)); - (skill_set_update, physics_update) + (skill_set_update, physics_update, new_remote_controller.map(|c| (entity, c))) }, ) // NOTE: Would be nice to combine this with the map_init somehow, but I'm not sure if // that's possible. - .filter(|(x, y)| x.is_some() || y.is_some()) + .filter(|(x, y, z)| x.is_some() || y.is_some() || z.is_some()) // NOTE: I feel like we shouldn't actually need to allocate here, but hopefully this // doesn't turn out to be important as there shouldn't be that many connected clients. // The reason we can't just use unzip is that the two sides might be different lengths. .collect::>(); let player_physics_settings = &mut *player_physics_settings_; - // Deferred updates to skillsets and player physics. + // Deferred updates to skillsets and player physics and new_remote_controller. // // NOTE: It is an invariant that there is at most one client entry per player // uuid; since we joined on clients, it follows that there's just one update // per uuid, so the physics update is sound and doesn't depend on evaluation // order, even though we're not updating directly by entity or uid (note that // for a given entity, we process messages serially). - deferred_updates - .iter_mut() - .for_each(|(skill_set_update, physics_update)| { + deferred_updates.iter_mut().for_each( + |(skill_set_update, physics_update, new_remote_controller)| { if let Some((entity, new_skill_set)) = skill_set_update { // We know this exists, because we already iterated over it with the skillset // lock taken, so we can ignore the error. @@ -506,7 +513,11 @@ impl<'a> System<'a> for Sys { .settings .insert(uuid, player_physics_setting); } - }); + if let Some((uuid, new_remote_controller)) = new_remote_controller.take() { + let _ = remote_controllers.insert(uuid, new_remote_controller); + } + }, + ); // Finally, drop the deferred updates in another thread. slow_jobs.spawn("CHUNK_DROP", move || { drop(deferred_updates); diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index a024fa8f5c..f54cdf67ee 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -208,6 +208,7 @@ impl PlayState for CharSelectionState { let res = self.client.borrow_mut().tick( comp::ControllerInputs::default(), global_state.clock.dt(), + global_state.clock.total_tick_time(), |_| {}, ); match res { diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 5cd3201780..722ec2a1d9 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -226,6 +226,7 @@ impl PlayState for MainMenuState { match client.tick( comp::ControllerInputs::default(), global_state.clock.dt(), + global_state.clock.total_tick_time(), |_| {}, ) { Ok(events) => { diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 073728b03b..0ba5898baf 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -270,7 +270,12 @@ impl SessionState { self.mumble_link.update(player_pos, player_pos); } - for event in client.tick(self.inputs.clone(), dt, crate::ecs::sys::add_local_systems)? { + for event in client.tick( + self.inputs.clone(), + dt, + global_state.clock.total_tick_time(), + crate::ecs::sys::add_local_systems, + )? { match event { client::Event::Chat(m) => { self.hud.new_message(m);