diff --git a/src/physics.rs b/src/physics.rs index 6658c1b..07de30d 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -1006,9 +1006,6 @@ impl instruction::InstructionEmitter for PhysicsCont } } impl PhysicsContext{ - pub fn clear(&mut self){ - self.state.clear(); - } pub const fn output(&self)->PhysicsOutputState{ PhysicsOutputState{ body:self.state.body, @@ -1020,7 +1017,9 @@ impl PhysicsContext{ pub const fn get_next_mouse(&self)->&MouseState{ self.state.input_state.get_next_mouse() } - fn generate_models(&mut self,map:&map::CompleteMap){ + /// use with caution, this is the only non-instruction way to mess with physics + pub fn generate_models(&mut self,map:&map::CompleteMap){ + self.state.clear(); self.data.modes=map.modes.clone(); for mode in &mut self.data.modes.modes{ mode.denormalize_data(); @@ -1474,6 +1473,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI //the body may as well be a quantum wave function //as far as these instruction are concerned (they don't care where it is) PhysicsInputInstruction::SetSensitivity(..) + |PhysicsInputInstruction::Reset |PhysicsInputInstruction::Restart |PhysicsInputInstruction::Spawn(..) |PhysicsInputInstruction::SetZoom(..) @@ -1535,10 +1535,13 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI state.input_state.set_control(Controls::Zoom,s); b_refresh_walk_target=false; }, - PhysicsInputInstruction::Restart=>{ + PhysicsInputInstruction::Reset=>{ //totally reset physics state state.reset_to_default(); - //spawn at start zone + b_refresh_walk_target=false; + }, + PhysicsInputInstruction::Restart=>{ + //teleport to start zone let spawn_point=data.modes.get_mode(state.mode_state.get_mode_id()).map(|mode| //TODO: spawn at the bottom of the start zone plus the hitbox size //TODO: set camera andles to face the same way as the start zone diff --git a/src/physics_worker.rs b/src/physics_worker.rs index de12a03..5e41e07 100644 --- a/src/physics_worker.rs +++ b/src/physics_worker.rs @@ -16,6 +16,10 @@ pub enum InputInstruction{ MoveForward(bool), Jump(bool), Zoom(bool), + /// Reset: fully replace the physics state. + /// This forgets all inputs and settings which need to be reapplied. + Reset, + /// Restart: Teleport to the start zone. Restart, Spawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId), PracticeFly, @@ -23,32 +27,42 @@ pub enum InputInstruction{ pub enum Instruction{ Input(InputInstruction), Render, - Resize(winit::dpi::PhysicalSize,crate::settings::UserSettings), + 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::*; -pub struct MouseInterpolator{ + //TODO: move this or tab +pub struct MouseInterpolator<'a>{ //"PlayerController" timeline:std::collections::VecDeque>, last_mouse_time:Time,//this value is pre-transformed to simulation time mouse_blocking:bool, + //???? + bot_worker:crate::worker::QNWorker<'a,crate::bot_worker::Instruction>, + user_settings:crate::settings::UserSettings, //"Simulation" timer:Timer, physics:crate::physics::PhysicsContext, + } -impl MouseInterpolator{ - pub fn new( +impl MouseInterpolator<'_>{ + pub fn new<'a>( physics:crate::physics::PhysicsContext, - )->Self{ - Self{ + bot_worker:crate::worker::QNWorker<'a,crate::bot_worker::Instruction>, + user_settings:crate::settings::UserSettings, + )->MouseInterpolator<'a>{ + 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, + bot_worker, + user_settings, } } fn push_mouse_instruction(&mut self,ins:&TimedInstruction,m:glam::IVec2){ @@ -99,7 +113,7 @@ impl MouseInterpolator{ //do these really need to idle the physics? //sending None dumps the instruction queue Instruction::ChangeMap(_)=>Some(PhysicsInputInstruction::Idle), - Instruction::Resize(_,_)=>Some(PhysicsInputInstruction::Idle), + Instruction::Resize(_)=>Some(PhysicsInputInstruction::Idle), Instruction::Render=>Some(PhysicsInputInstruction::Idle), &Instruction::SetPaused(paused)=>{ if let Err(e)=self.timer.set_paused(ins.time,paused){ @@ -109,21 +123,25 @@ impl MouseInterpolator{ }, } } - fn update_mouse_blocking(&mut self,ins:&TimedInstruction)->bool{ + /// 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),phys_input_option:Option)->bool{ - if let Some(phys_input)=phys_input_option{ - //non-mouse event - self.timeline.push_back(TimedInstruction{ - time:self.timer.time(ins.time), - instruction:phys_input, - }); + fn handle_physics_input(&mut self,time:Time,phys_input:PhysicsInputInstruction)->bool{ + //non-mouse event + self.timeline.push_back(TimedInstruction{ + time:self.timer.time(time), + instruction:phys_input, + }); - //this returns the bool for us - self.update_mouse_blocking(ins) - }else{ - //mouse event - true - } + //this returns the bool for us + self.update_mouse_blocking(time) } - fn empty_queue(&mut self,bot_worker:&crate::worker::QNWorker<'_,crate::bot_worker::Instruction>){ + fn empty_queue(&mut self){ while let Some(instruction)=self.timeline.pop_front(){ //makeshift clone because requiring all TimedInstructions to be Clone is troublesome - bot_worker.send(crate::bot_worker::Instruction::Push{ + self.bot_worker.send(crate::bot_worker::Instruction::Push{ ins:TimedInstruction{ time:instruction.time, instruction:instruction.instruction.clone(), @@ -163,41 +176,69 @@ impl MouseInterpolator{ self.physics.run_input_instruction(instruction); } } - pub fn handle_instruction(&mut self,ins:&TimedInstruction,bot_worker:&crate::worker::QNWorker<'_,crate::bot_worker::Instruction>){ - let physics_input_option=self.map_instruction(ins); - let should_empty_queue=self.handle_physics_input(ins,physics_input_option); + pub fn handle_instruction(&mut self,ins:&TimedInstruction){ + let should_empty_queue=self.map_instruction(ins) + //should empty queue defaults to true + .map_or(true,|physics_input| + //this returns whether to empty the queue based on the input + self.handle_physics_input(ins.time,physics_input) + ); if should_empty_queue{ - self.empty_queue(bot_worker); + self.empty_queue(); } } pub fn get_render_stuff(&self,time:Time)->(crate::physics::PhysicsOutputState,Time,glam::IVec2){ (self.physics.output(),self.timer.time(time),self.physics.get_next_mouse().pos) } + 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); + + let simulation_time=self.timer.time(time); + //important! + //bots will not work properly without this exact restart + spawn setup + //reset the physics state to start a new run on the new map + self.handle_instruction(&TimedInstruction{ + time:simulation_time, + instruction:Instruction::Input(InputInstruction::Reset), + }); + //generate a spawn event so bots work properly on the first run + //no run started so does not invalidate the run + self.handle_instruction(&TimedInstruction{ + time:simulation_time, + instruction:Instruction::Input(InputInstruction::Spawn( + strafesnet_common::gameplay_modes::ModeId::MAIN, + strafesnet_common::gameplay_modes::StageId::FIRST, + )), + }); + } } } -pub fn new<'a>(physics:crate::physics::PhysicsContext,mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>,bot_worker:crate::worker::QNWorker<'a,crate::bot_worker::Instruction>)->crate::compat_worker::QNWorker<'a,TimedInstruction>{ - let mut interpolator=MouseInterpolator::new(physics); +pub fn new<'a>( + mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>, + bot_worker:crate::worker::QNWorker<'a,crate::bot_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,bot_worker); crate::compat_worker::QNWorker::new(move |ins:TimedInstruction|{ - interpolator.handle_instruction(&ins,&bot_worker); + interpolator.handle_instruction(&ins); match ins.instruction{ Instruction::Render=>{ let (physics_output,time,mouse_pos)=interpolator.get_render_stuff(ins.time); graphics_worker.send(crate::graphics_worker::Instruction::Render(physics_output,time,mouse_pos)).unwrap(); }, - Instruction::Resize(size,user_settings)=>{ + Instruction::Resize(size)=>{ graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,user_settings)).unwrap(); }, Instruction::ChangeMap(map)=>{ - physics.clear(); - physics.generate_models(&map); - //important! - //bots will not work properly without this exact restart + spawn setup - //reset the physics state to start a new run on the new map - physics.restart(); - //generate a spawn event so bots work properly on the first run - //no run started so does not invalidate the run - physics.spawn(); + interpolator.change_map(ins.time,&map); graphics_worker.send(crate::graphics_worker::Instruction::ChangeMap(map)).unwrap(); }, Instruction::Input(_)=>(),