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 } }