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}; use mouse_interpolator::MouseInterpolator; #[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:Time,//this value is pre-transformed to simulation time 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: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 pause: {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{ physics_output:self.physics.output(), mouse_pos:self.physics.get_next_mouse().pos, predicted_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 } } } pub fn new<'a>( mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>, user_settings:crate::settings::UserSettings, )->crate::compat_worker::QNWorker<'a,TimedInstruction>{ let physics=crate::physics::PhysicsContext::default(); let mut interpolator=MouseInterpolator::new( physics, user_settings ); crate::compat_worker::QNWorker::new(move |ins:TimedInstruction|{ interpolator.handle_instruction(&ins); match ins.instruction{ Instruction::Render=>{ let frame_state=interpolator.get_frame_state(ins.time); graphics_worker.send(crate::graphics_worker::Instruction::Render(frame_state)).unwrap(); }, Instruction::Resize(size)=>{ graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,interpolator.user_settings().clone())).unwrap(); }, Instruction::ChangeMap(map)=>{ interpolator.change_map(ins.time,&map); graphics_worker.send(crate::graphics_worker::Instruction::ChangeMap(map)).unwrap(); }, Instruction::Input(_)=>(), Instruction::SetPaused(_)=>(), } }) }