use strafesnet_common::mouse::MouseState; use strafesnet_common::physics::{ MouseInstruction,SetControlInstruction,ModeInstruction,MiscInstruction, Instruction as PhysicsInstruction, TimeInner as PhysicsTimeInner, Time as PhysicsTime, }; use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction}; type TimedSelfInstruction=TimedInstruction; type DoubleTimedSelfInstruction=TimedInstruction; type TimedPhysicsInstruction=TimedInstruction; const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10); /// To be fed into MouseInterpolator #[derive(Clone,Debug)] pub(crate) enum Instruction{ MoveMouse(glam::IVec2), SetControl(SetControlInstruction), Mode(ModeInstruction), Misc(MiscInstruction), Idle, } #[derive(Clone,Debug)] enum UnbufferedInstruction{ MoveMouse(glam::IVec2), NonMouse(NonMouseInstruction), } #[derive(Clone,Debug)] enum BufferedInstruction{ Mouse(MouseInstruction), NonMouse(NonMouseInstruction), } #[derive(Clone,Debug)] pub(crate) enum NonMouseInstruction{ SetControl(SetControlInstruction), Mode(ModeInstruction), Misc(MiscInstruction), Idle, } impl From for UnbufferedInstruction{ #[inline] fn from(value:Instruction)->Self{ match value{ Instruction::MoveMouse(mouse_instruction)=>UnbufferedInstruction::MoveMouse(mouse_instruction), Instruction::SetControl(set_control_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::SetControl(set_control_instruction)), Instruction::Mode(mode_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Mode(mode_instruction)), Instruction::Misc(misc_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Misc(misc_instruction)), Instruction::Idle=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Idle), } } } impl From for PhysicsInstruction{ #[inline] fn from(value:BufferedInstruction)->Self{ match value{ BufferedInstruction::Mouse(mouse_instruction)=>PhysicsInstruction::Mouse(mouse_instruction), BufferedInstruction::NonMouse(non_mouse_instruction)=>match non_mouse_instruction{ NonMouseInstruction::SetControl(set_control_instruction)=>PhysicsInstruction::SetControl(set_control_instruction), NonMouseInstruction::Mode(mode_instruction)=>PhysicsInstruction::Mode(mode_instruction), NonMouseInstruction::Misc(misc_instruction)=>PhysicsInstruction::Misc(misc_instruction), NonMouseInstruction::Idle=>PhysicsInstruction::Idle, }, } } } pub(crate) enum StepInstruction{ Pop, Timeout, } #[derive(Clone,Debug)] enum BufferState{ Unbuffered, Initializing(SessionTime,MouseState), Buffered(SessionTime,MouseState), } pub struct MouseInterpolator{ buffer_state:BufferState, // double timestamped timeline? buffer:std::collections::VecDeque, output:std::collections::VecDeque, } // Maybe MouseInterpolator manipulation is better expressed using impls // and called from Instruction trait impls in session impl InstructionConsumer for MouseInterpolator{ type TimeInner=SessionTimeInner; fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){ self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into()) } } impl InstructionEmitter for MouseInterpolator{ type TimeInner=SessionTimeInner; fn next_instruction(&self,time_limit:SessionTime)->Option>{ self.buffered_instruction_with_timeout(time_limit) } } impl MouseInterpolator{ pub fn new()->MouseInterpolator{ MouseInterpolator{ buffer_state:BufferState::Unbuffered, buffer:std::collections::VecDeque::new(), output:std::collections::VecDeque::new(), } } fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction){ self.buffer.push_front(TimedInstruction{ time:ins.time, instruction:BufferedInstruction::Mouse(ins.instruction).into(), }); // flush buffer to output if self.output.len()==0{ // swap buffers core::mem::swap(&mut self.buffer,&mut self.output); }else{ // append buffer contents to output self.output.append(&mut self.buffer); } } fn get_mouse_timedout_at(&self,time_limit:SessionTime)->Option{ match &self.buffer_state{ BufferState::Unbuffered=>None, BufferState::Initializing(time,_mouse_state) |BufferState::Buffered(time,_mouse_state)=>{ let timeout=*time+MOUSE_TIMEOUT; (timeout(), BufferState::Initializing(_time,mouse_state)=>{ // only a single mouse move was sent in 10ms, this is very much an edge case! self.push_mouse_and_flush_buffer(TimedInstruction{ time:mouse_state.time, instruction:MouseInstruction::ReplaceMouse{ m1:MouseState{pos:mouse_state.pos,time:timeout_time}, m0:mouse_state, }, }); } BufferState::Buffered(_time,mouse_state)=>{ // duplicate the currently buffered mouse state but at a later (future, from the physics perspective) time self.push_mouse_and_flush_buffer(TimedInstruction{ time:mouse_state.time, instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time:timeout_time}), }); }, } } fn push_unbuffered_input(&mut self,session_time:SessionTime,physics_time:PhysicsTime,ins:UnbufferedInstruction){ // new input // if there is zero instruction buffered, it means the mouse is not moving // case 1: unbuffered // no mouse event is buffered // - ins is mouse event? change to buffered // - ins other -> write to timeline // case 2: buffered // a mouse event is buffered, and exists within the last 10ms // case 3: stop // a mouse event is buffered, but no mouse events have transpired within 10ms // replace_with allows the enum variant to safely be replaced // from behind a mutable reference, but a panic in the closure means that // the entire program terminates rather than completing an unwind. let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{ match ins{ UnbufferedInstruction::MoveMouse(pos)=>{ let next_mouse_state=MouseState{pos,time:physics_time}; match buffer_state{ BufferState::Unbuffered=>{ ((None,None),BufferState::Initializing(session_time,next_mouse_state)) }, BufferState::Initializing(_time,mouse_state)=>{ let ins_mouse=TimedInstruction{ time:mouse_state.time, instruction:MouseInstruction::ReplaceMouse{ m0:mouse_state, m1:next_mouse_state.clone(), }, }; ((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state)) }, BufferState::Buffered(_time,mouse_state)=>{ let ins_mouse=TimedInstruction{ time:mouse_state.time, instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()), }; ((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state)) }, } }, UnbufferedInstruction::NonMouse(other_instruction)=>((None,Some(TimedInstruction{ time:physics_time, instruction:other_instruction, })),buffer_state), } }); if let Some(ins)=ins_mouse{ self.push_mouse_and_flush_buffer(ins); } if let Some(ins)=ins_other{ let instruction=TimedInstruction{ time:ins.time, instruction:BufferedInstruction::NonMouse(ins.instruction).into(), }; if matches!(self.buffer_state,BufferState::Unbuffered){ self.output.push_back(instruction); }else{ self.buffer.push_back(instruction); } } } fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option>{ match self.get_mouse_timedout_at(time_limit){ Some(timeout)=>Some(TimedInstruction{ time:timeout, instruction:StepInstruction::Timeout, }), None=>(self.output.len()!=0).then_some(TimedInstruction{ // this timestamp should not matter time:time_limit, instruction:StepInstruction::Pop, }), } } pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction)->Option{ match ins.instruction{ StepInstruction::Pop=>(), StepInstruction::Timeout=>self.timeout_mouse(ins.time), } self.output.pop_front() } } #[cfg(test)] mod test{ use super::*; #[test] fn test(){ let mut interpolator=MouseInterpolator::new(); let timer=strafesnet_common::timer::Timer::>::unpaused(SessionTime::ZERO,PhysicsTime::from_secs(1000)); macro_rules! push{ ($time:expr,$ins:expr)=>{ println!("in={:?}",$ins); interpolator.push_unbuffered_input( $time, timer.time($time), $ins, ); while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){ let ins_retimed=TimedInstruction{ time:timer.time(ins.time), instruction:ins.instruction, }; let out=interpolator.pop_buffered_instruction(ins_retimed); println!("out={out:?}"); } }; } // test each buffer_state transition let mut t=SessionTime::ZERO; push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0))); t+=SessionTime::from_millis(5); push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0))); t+=SessionTime::from_millis(5); push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0))); t+=SessionTime::from_millis(1); } }