238 lines
8.0 KiB
Rust
238 lines
8.0 KiB
Rust
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<PhysicsInputInstruction,PhysicsTimeInner>;
|
|
type TimedUnbufferedInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
|
|
type DoubleTimedUnbufferedInstruction=TimedInstruction<TimedUnbufferedInstruction,SessionTimeInner>;
|
|
|
|
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<PhysicsTimeInner>),
|
|
Buffered(SessionTime,MouseState<PhysicsTimeInner>),
|
|
}
|
|
|
|
pub struct MouseInterpolator{
|
|
buffer_state:BufferState,
|
|
// double timestamped timeline?
|
|
buffer:std::collections::VecDeque<TimedPhysicsInstruction>,
|
|
output:std::collections::VecDeque<TimedPhysicsInstruction>,
|
|
}
|
|
// Maybe MouseInterpolator manipulation is better expressed using impls
|
|
// and called from Instruction trait impls in session
|
|
impl InstructionConsumer<TimedUnbufferedInstruction> for MouseInterpolator{
|
|
type TimeInner=SessionTimeInner;
|
|
fn process_instruction(&mut self,ins:DoubleTimedUnbufferedInstruction){
|
|
self.push_unbuffered_input(ins)
|
|
}
|
|
}
|
|
impl InstructionEmitter<StepInstruction> for MouseInterpolator{
|
|
type TimeInner=SessionTimeInner;
|
|
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
|
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<MouseInstruction,PhysicsTimeInner>){
|
|
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<SessionTime>{
|
|
match &self.buffer_state{
|
|
BufferState::Unbuffered=>None,
|
|
BufferState::Initializing(time,_mouse_state)
|
|
|BufferState::Buffered(time,_mouse_state)=>{
|
|
let timeout=*time+MOUSE_TIMEOUT;
|
|
(timeout<time_limit).then_some(timeout)
|
|
}
|
|
}
|
|
}
|
|
fn timeout_mouse(&mut self,timeout_time:PhysicsTime){
|
|
// the state always changes to unbuffered
|
|
let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
|
|
match buffer_state{
|
|
BufferState::Unbuffered=>(),
|
|
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<TimedInstruction<StepInstruction,SessionTimeInner>>{
|
|
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<StepInstruction,PhysicsTimeInner>)->Option<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>{
|
|
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::<strafesnet_common::timer::Scaled<SessionTimeInner,PhysicsTimeInner>>::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);
|
|
}
|
|
}
|