diff --git a/strafe-client/src/main.rs b/strafe-client/src/main.rs index faae714..968c630 100644 --- a/strafe-client/src/main.rs +++ b/strafe-client/src/main.rs @@ -4,6 +4,7 @@ mod setup; mod window; mod worker; mod physics; +mod session; mod graphics; mod settings; mod push_solve; @@ -13,6 +14,7 @@ mod model_physics; mod model_graphics; mod physics_worker; mod graphics_worker; +mod mouse_interpolator; const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION")); diff --git a/strafe-client/src/mouse_interpolator.rs b/strafe-client/src/mouse_interpolator.rs new file mode 100644 index 0000000..25cfd21 --- /dev/null +++ b/strafe-client/src/mouse_interpolator.rs @@ -0,0 +1,217 @@ +use strafesnet_common::instruction; +use strafesnet_common::mouse::MouseState; +use strafesnet_common::physics::Instruction as PhysicsInputInstruction; +use strafesnet_common::integer::Time; +use strafesnet_common::instruction::TimedInstruction; +use strafesnet_common::timer::{Scaled,Timer,TimerState}; +#[derive(Debug)] +pub enum InputInstruction{ + MoveMouse(glam::IVec2), + MoveRight(bool), + MoveUp(bool), + MoveBack(bool), + MoveLeft(bool), + MoveDown(bool), + MoveForward(bool), + Jump(bool), + Zoom(bool), + ResetAndRestart, + ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId), + PracticeFly, +} +pub enum Instruction{ + Input(InputInstruction), + Render, + Resize(winit::dpi::PhysicalSize), + ChangeMap(strafesnet_common::map::CompleteMap), + //SetPaused is not an InputInstruction: the physics doesn't know that it's paused. + SetPaused(bool), + //Graphics(crate::graphics_worker::Instruction), +} +pub struct MouseInterpolator{ + //"PlayerController" + user_settings:crate::settings::UserSettings, + //"MouseInterpolator" + timeline:std::collections::VecDeque>, + last_mouse_time:Time,//this value is pre-transformed to simulation time + mouse_blocking:bool, + //"Simulation" + timer:Timer, +} +impl instruction::InstructionConsumer for MouseInterpolator{ + fn process_instruction(&mut self,ins:TimedInstruction){ + atomic_state_update(&mut self,ins) + } +} +impl instruction::InstructionEmitter for MouseInterpolator{ + fn next_instruction(&self,time_limit:Time)->Option>{ + next_instruction_internal(&self,time_limit) + } +} + +impl MouseInterpolator{ + pub fn new( + physics:crate::physics::PhysicsContext, + user_settings:crate::settings::UserSettings, + )->MouseInterpolator{ + MouseInterpolator{ + mouse_blocking:true, + last_mouse_time:physics.get_next_mouse().time, + timeline:std::collections::VecDeque::new(), + timer:Timer::from_state(Scaled::identity(),false), + physics, + user_settings, + } + } + fn push_mouse_instruction(&mut self,ins:&TimedInstruction,m:glam::IVec2){ + if self.mouse_blocking{ + //tell the game state which is living in the past about its future + self.timeline.push_front(TimedInstruction{ + time:self.last_mouse_time, + instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(ins.time),pos:m}), + }); + }else{ + //mouse has just started moving again after being still for longer than 10ms. + //replace the entire mouse interpolation state to avoid an intermediate state with identical m0.t m1.t timestamps which will divide by zero + self.timeline.push_front(TimedInstruction{ + time:self.last_mouse_time, + instruction:PhysicsInputInstruction::ReplaceMouse( + MouseState{time:self.last_mouse_time,pos:self.physics.get_next_mouse().pos}, + MouseState{time:self.timer.time(ins.time),pos:m} + ), + }); + //delay physics execution until we have an interpolation target + self.mouse_blocking=true; + } + self.last_mouse_time=self.timer.time(ins.time); + } + fn push(&mut self,time:Time,phys_input:PhysicsInputInstruction){ + //This is always a non-mouse event + self.timeline.push_back(TimedInstruction{ + time:self.timer.time(time), + instruction:phys_input, + }); + } + /// returns should_empty_queue + /// may or may not mutate internal state XD! + fn map_instruction(&mut self,ins:&TimedInstruction)->bool{ + let mut update_mouse_blocking=true; + match &ins.instruction{ + Instruction::Input(input_instruction)=>match input_instruction{ + &InputInstruction::MoveMouse(m)=>{ + if !self.timer.is_paused(){ + self.push_mouse_instruction(ins,m); + } + update_mouse_blocking=false; + }, + &InputInstruction::MoveForward(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveForward(s)), + &InputInstruction::MoveLeft(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveLeft(s)), + &InputInstruction::MoveBack(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveBack(s)), + &InputInstruction::MoveRight(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveRight(s)), + &InputInstruction::MoveUp(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveUp(s)), + &InputInstruction::MoveDown(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveDown(s)), + &InputInstruction::Jump(s)=>self.push(ins.time,PhysicsInputInstruction::SetJump(s)), + &InputInstruction::Zoom(s)=>self.push(ins.time,PhysicsInputInstruction::SetZoom(s)), + &InputInstruction::ResetAndSpawn(mode_id,stage_id)=>{ + self.push(ins.time,PhysicsInputInstruction::Reset); + self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity())); + self.push(ins.time,PhysicsInputInstruction::Spawn(mode_id,stage_id)); + }, + InputInstruction::ResetAndRestart=>{ + self.push(ins.time,PhysicsInputInstruction::Reset); + self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity())); + self.push(ins.time,PhysicsInputInstruction::Restart); + }, + InputInstruction::PracticeFly=>self.push(ins.time,PhysicsInputInstruction::PracticeFly), + }, + //do these really need to idle the physics? + //sending None dumps the instruction queue + Instruction::ChangeMap(_)=>self.push(ins.time,PhysicsInputInstruction::Idle), + Instruction::Resize(_)=>self.push(ins.time,PhysicsInputInstruction::Idle), + Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle), + &Instruction::SetPaused(paused)=>{ + if let Err(e)=self.timer.set_paused(ins.time,paused){ + println!("Cannot SetPaused: {e}"); + } + self.push(ins.time,PhysicsInputInstruction::Idle); + }, + } + if update_mouse_blocking{ + //this returns the bool for us + self.update_mouse_blocking(ins.time) + }else{ + //do flush that queue + true + } + } + /// must check if self.mouse_blocking==true before calling! + fn unblock_mouse(&mut self,time:Time){ + //push an event to extrapolate no movement from + self.timeline.push_front(TimedInstruction{ + time:self.last_mouse_time, + instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(time),pos:self.physics.get_next_mouse().pos}), + }); + self.last_mouse_time=self.timer.time(time); + //stop blocking. the mouse is not moving so the physics does not need to live in the past and wait for interpolation targets. + self.mouse_blocking=false; + } + fn update_mouse_blocking(&mut self,time:Time)->bool{ + if self.mouse_blocking{ + //assume the mouse has stopped moving after 10ms. + //shitty mice are 125Hz which is 8ms so this should cover that. + //setting this to 100us still doesn't print even though it's 10x lower than the polling rate, + //so mouse events are probably not handled separately from drawing and fire right before it :( + if Time::from_millis(10)){ + let should_empty_queue=self.map_instruction(ins); + if should_empty_queue{ + self.empty_queue(); + } + } + pub fn get_frame_state(&self,time:Time)->crate::graphics::FrameState{ + crate::graphics::FrameState{ + body:self.physics.camera_body(), + camera:self.physics.camera(), + time:self.timer.time(time), + } + } + pub fn change_map(&mut self,time:Time,map:&strafesnet_common::map::CompleteMap){ + //dump any pending interpolation state + if self.mouse_blocking{ + self.unblock_mouse(time); + } + self.empty_queue(); + + //doing it like this to avoid doing PhysicsInstruction::ChangeMap(Rc) + self.physics.generate_models(&map); + + //use the standard input interface so the instructions are written out to bots + self.handle_instruction(&TimedInstruction{ + time:self.timer.time(time), + instruction:Instruction::Input(InputInstruction::ResetAndSpawn( + strafesnet_common::gameplay_modes::ModeId::MAIN, + strafesnet_common::gameplay_modes::StageId::FIRST, + )), + }); + } + pub const fn user_settings(&self)->&crate::settings::UserSettings{ + &self.user_settings + } +} diff --git a/strafe-client/src/physics_worker.rs b/strafe-client/src/physics_worker.rs index 1e7ffa3..7c48b9d 100644 --- a/strafe-client/src/physics_worker.rs +++ b/strafe-client/src/physics_worker.rs @@ -1,223 +1,4 @@ -use strafesnet_common::mouse::MouseState; -use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; -use strafesnet_common::physics::{Time as PhysicsTime,TimeInner as PhysicsTimeInner,Instruction as PhysicsInputInstruction}; use strafesnet_common::instruction::TimedInstruction; -use strafesnet_common::timer::{Scaled,Timer,TimerState}; -use mouse_interpolator::MouseInterpolator; - - -pub struct FrameState{ - pub body:crate::physics::Body, - pub camera:crate::physics::PhysicsCamera, - pub time:PhysicsTime, -} - -#[derive(Debug)] -pub enum InputInstruction{ - MoveMouse(glam::IVec2), - MoveRight(bool), - MoveUp(bool), - MoveBack(bool), - MoveLeft(bool), - MoveDown(bool), - MoveForward(bool), - Jump(bool), - Zoom(bool), - ResetAndRestart, - ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId), - PracticeFly, -} -pub enum Instruction{ - Input(InputInstruction), - Render, - Resize(winit::dpi::PhysicalSize), - ChangeMap(strafesnet_common::map::CompleteMap), - //SetPaused is not an InputInstruction: the physics doesn't know that it's paused. - SetPaused(bool), - //Graphics(crate::graphics_worker::Instruction), -} -mod mouse_interpolator{ - use super::*; - //TODO: move this or tab -pub struct MouseInterpolator{ - //"PlayerController" - user_settings:crate::settings::UserSettings, - //"MouseInterpolator" - timeline:std::collections::VecDeque>, - last_mouse_time:PhysicsTime, - mouse_blocking:bool, - //"Simulation" - timer:Timer>, - physics:crate::physics::PhysicsContext, - -} -impl MouseInterpolator{ - pub fn new( - physics:crate::physics::PhysicsContext, - user_settings:crate::settings::UserSettings, - )->MouseInterpolator{ - MouseInterpolator{ - mouse_blocking:true, - last_mouse_time:physics.get_next_mouse().time, - timeline:std::collections::VecDeque::new(), - timer:Timer::from_state(Scaled::identity(),false), - physics, - user_settings, - } - } - fn push_mouse_instruction(&mut self,ins:&TimedInstruction,m:glam::IVec2){ - if self.mouse_blocking{ - //tell the game state which is living in the past about its future - self.timeline.push_front(TimedInstruction{ - time:self.last_mouse_time, - instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(ins.time),pos:m}), - }); - }else{ - //mouse has just started moving again after being still for longer than 10ms. - //replace the entire mouse interpolation state to avoid an intermediate state with identical m0.t m1.t timestamps which will divide by zero - self.timeline.push_front(TimedInstruction{ - time:self.last_mouse_time, - instruction:PhysicsInputInstruction::ReplaceMouse( - MouseState{time:self.last_mouse_time,pos:self.physics.get_next_mouse().pos}, - MouseState{time:self.timer.time(ins.time),pos:m} - ), - }); - //delay physics execution until we have an interpolation target - self.mouse_blocking=true; - } - self.last_mouse_time=self.timer.time(ins.time); - } - fn push(&mut self,time:SessionTime,phys_input:PhysicsInputInstruction){ - //This is always a non-mouse event - self.timeline.push_back(TimedInstruction{ - time:self.timer.time(time), - instruction:phys_input, - }); - } - /// returns should_empty_queue - /// may or may not mutate internal state XD! - fn map_instruction(&mut self,ins:&TimedInstruction)->bool{ - let mut update_mouse_blocking=true; - match &ins.instruction{ - Instruction::Input(input_instruction)=>match input_instruction{ - &InputInstruction::MoveMouse(m)=>{ - if !self.timer.is_paused(){ - self.push_mouse_instruction(ins,m); - } - update_mouse_blocking=false; - }, - &InputInstruction::MoveForward(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveForward(s)), - &InputInstruction::MoveLeft(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveLeft(s)), - &InputInstruction::MoveBack(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveBack(s)), - &InputInstruction::MoveRight(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveRight(s)), - &InputInstruction::MoveUp(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveUp(s)), - &InputInstruction::MoveDown(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveDown(s)), - &InputInstruction::Jump(s)=>self.push(ins.time,PhysicsInputInstruction::SetJump(s)), - &InputInstruction::Zoom(s)=>self.push(ins.time,PhysicsInputInstruction::SetZoom(s)), - &InputInstruction::ResetAndSpawn(mode_id,stage_id)=>{ - self.push(ins.time,PhysicsInputInstruction::Reset); - self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity())); - self.push(ins.time,PhysicsInputInstruction::Spawn(mode_id,stage_id)); - }, - InputInstruction::ResetAndRestart=>{ - self.push(ins.time,PhysicsInputInstruction::Reset); - self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity())); - self.push(ins.time,PhysicsInputInstruction::Restart); - }, - InputInstruction::PracticeFly=>self.push(ins.time,PhysicsInputInstruction::PracticeFly), - }, - //do these really need to idle the physics? - //sending None dumps the instruction queue - Instruction::ChangeMap(_)=>self.push(ins.time,PhysicsInputInstruction::Idle), - Instruction::Resize(_)=>self.push(ins.time,PhysicsInputInstruction::Idle), - Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle), - &Instruction::SetPaused(paused)=>{ - if let Err(e)=self.timer.set_paused(ins.time,paused){ - println!("Cannot SetPaused: {e}"); - } - self.push(ins.time,PhysicsInputInstruction::Idle); - }, - } - if update_mouse_blocking{ - //this returns the bool for us - self.update_mouse_blocking(ins.time) - }else{ - //do flush that queue - true - } - } - /// must check if self.mouse_blocking==true before calling! - fn unblock_mouse(&mut self,time:SessionTime){ - //push an event to extrapolate no movement from - self.timeline.push_front(TimedInstruction{ - time:self.last_mouse_time, - instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(time),pos:self.physics.get_next_mouse().pos}), - }); - self.last_mouse_time=self.timer.time(time); - //stop blocking. the mouse is not moving so the physics does not need to live in the past and wait for interpolation targets. - self.mouse_blocking=false; - } - fn update_mouse_blocking(&mut self,time:SessionTime)->bool{ - if self.mouse_blocking{ - //assume the mouse has stopped moving after 10ms. - //shitty mice are 125Hz which is 8ms so this should cover that. - //setting this to 100us still doesn't print even though it's 10x lower than the polling rate, - //so mouse events are probably not handled separately from drawing and fire right before it :( - if PhysicsTime::from_millis(10)){ - let should_empty_queue=self.map_instruction(ins); - if should_empty_queue{ - self.empty_queue(); - } - } - pub fn get_frame_state(&self,time:SessionTime)->FrameState{ - FrameState{ - body:self.physics.camera_body(), - camera:self.physics.camera(), - time:self.timer.time(time), - } - } - pub fn change_map(&mut self,time:SessionTime,map:&strafesnet_common::map::CompleteMap){ - //dump any pending interpolation state - if self.mouse_blocking{ - self.unblock_mouse(time); - } - self.empty_queue(); - - //doing it like this to avoid doing PhysicsInstruction::ChangeMap(Rc) - self.physics.generate_models(&map); - - //use the standard input interface so the instructions are written out to bots - self.handle_instruction(&TimedInstruction{ - time, - instruction:Instruction::Input(InputInstruction::ResetAndSpawn( - strafesnet_common::gameplay_modes::ModeId::MAIN, - strafesnet_common::gameplay_modes::StageId::FIRST, - )), - }); - } - pub const fn user_settings(&self)->&crate::settings::UserSettings{ - &self.user_settings - } -} -} pub fn new<'a>( mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>, diff --git a/strafe-client/src/session.rs b/strafe-client/src/session.rs new file mode 100644 index 0000000..3fca7af --- /dev/null +++ b/strafe-client/src/session.rs @@ -0,0 +1,8 @@ +// session represents the non-hardware state of the client. +// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state. + +pub struct SessionState{ + mouse_interpolator:MouseInterpolator, + //gui:GuiState + physics:PhysicsContext, +}