use strafesnet_common::mouse::MouseState; use strafesnet_common::physics::{ Instruction as PhysicsInputInstruction, TimeInner as PhysicsTimeInner, Time as PhysicsTime, MouseInstruction, OtherInstruction, }; use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction}; type TimedPhysicsInstruction=TimedInstruction; type TimedUnbufferedInstruction=TimedInstruction; type DoubleTimedUnbufferedInstruction=TimedInstruction; const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10); /// To be fed into MouseInterpolator #[derive(Clone,Debug)] pub enum Instruction{ MoveMouse(glam::IVec2), Other(OtherInstruction), } pub 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:DoubleTimedUnbufferedInstruction){ self.push_unbuffered_input(ins) } } 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:PhysicsInputInstruction::Mouse(ins.instruction), }); // 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}), }); }, } } pub fn push_unbuffered_input(&mut self,ins:DoubleTimedUnbufferedInstruction){ // 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.instruction.instruction{ Instruction::MoveMouse(pos)=>{ let next_mouse_state=MouseState{pos,time:ins.instruction.time}; match buffer_state{ BufferState::Unbuffered=>{ ((None,None),BufferState::Initializing(ins.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(ins.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(ins.time,next_mouse_state)) }, } }, Instruction::Other(other_instruction)=>((None,Some(TimedInstruction{ time:ins.instruction.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:PhysicsInputInstruction::Other(ins.instruction), }; if matches!(self.buffer_state,BufferState::Unbuffered){ self.output.push_back(instruction); }else{ self.buffer.push_back(instruction); } } } pub 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(TimedInstruction{ time:$time, instruction:TimedInstruction{ time:timer.time($time), instruction:$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,Instruction::MoveMouse(glam::ivec2(0,0))); t+=SessionTime::from_millis(5); push!(t,Instruction::MoveMouse(glam::ivec2(0,0))); t+=SessionTime::from_millis(5); push!(t,Instruction::MoveMouse(glam::ivec2(0,0))); t+=SessionTime::from_millis(1); } }