Compare commits

...

13 Commits

Author SHA1 Message Date
ead0e33546 todo 2024-07-30 20:46:11 -07:00
b376c03bde done ish 2024-07-30 20:46:11 -07:00
d7dc08092a hehehehehe 2024-07-30 20:46:11 -07:00
d898d7cf67 wip 2024-07-30 20:46:11 -07:00
7f68fd9b21 more 2024-07-30 20:46:11 -07:00
0b8a433640 holy 2024-07-30 20:46:11 -07:00
9646804dcd aa 2024-07-30 20:46:11 -07:00
9cd72d5809 asdasdasd 2024-07-30 20:46:11 -07:00
9ecfe26a0c asd 2024-07-30 20:46:11 -07:00
fbb2ba369c redo data structures 2024-07-30 20:46:11 -07:00
d1f78b6e18 rewrite 2024-07-30 20:46:11 -07:00
8cade8134f use mem::replace where it is needed 2024-07-30 19:37:21 -07:00
3268d92d24 timers 2024-07-30 16:23:14 -07:00
5 changed files with 352 additions and 121 deletions

View File

@ -1,5 +1,6 @@
mod file; mod file;
mod setup; mod setup;
mod timer;
mod window; mod window;
mod worker; mod worker;
mod physics; mod physics;

View File

@ -104,7 +104,8 @@ impl InputState{
&self.next_mouse &self.next_mouse
} }
fn set_next_mouse(&mut self,next_mouse:MouseState){ fn set_next_mouse(&mut self,next_mouse:MouseState){
(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone()); //I like your functions magic language
self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse);
} }
fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){ fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){
(self.next_mouse,self.mouse)=(next_mouse,mouse); (self.next_mouse,self.mouse)=(next_mouse,mouse);

View File

@ -16,7 +16,10 @@ pub enum InputInstruction{
PracticeFly, PracticeFly,
} }
pub enum Instruction{ pub enum Instruction{
Input(InputInstruction), Passthrough(PassthroughInstruction),
Interpolate(InputInstruction),
}
pub enum PassthroughInstruction{
Render, Render,
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings), Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
GenerateModels(strafesnet_common::map::CompleteMap), GenerateModels(strafesnet_common::map::CompleteMap),
@ -24,140 +27,179 @@ pub enum Instruction{
//Graphics(crate::graphics_worker::Instruction), //Graphics(crate::graphics_worker::Instruction),
} }
pub struct MouseInterpolator{ pub struct MouseInterpolator{
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction>>, queue:std::collections::VecDeque<TimedInstruction<InputInstruction>>,
last_mouse_time:Time, }
mouse_blocking:bool, fn drain_queue(physics:&mut crate::physics::PhysicsContext,iterable:impl IntoIterator<Item=TimedInstruction<InputInstruction>>){
for ins in iterable{
let physics_input=match &ins.instruction{
InputInstruction::MoveMouse(_)=>panic!("Queue was confirmed to contain no MoveMouse events1"),
&InputInstruction::MoveForward(s)=>PhysicsInputInstruction::SetMoveForward(s),
&InputInstruction::MoveLeft(s)=>PhysicsInputInstruction::SetMoveLeft(s),
&InputInstruction::MoveBack(s)=>PhysicsInputInstruction::SetMoveBack(s),
&InputInstruction::MoveRight(s)=>PhysicsInputInstruction::SetMoveRight(s),
&InputInstruction::MoveUp(s)=>PhysicsInputInstruction::SetMoveUp(s),
&InputInstruction::MoveDown(s)=>PhysicsInputInstruction::SetMoveDown(s),
&InputInstruction::Jump(s)=>PhysicsInputInstruction::SetJump(s),
&InputInstruction::Zoom(s)=>PhysicsInputInstruction::SetZoom(s),
InputInstruction::Reset=>PhysicsInputInstruction::Reset,
InputInstruction::PracticeFly=>PhysicsInputInstruction::PracticeFly,
};
physics.run_input_instruction(TimedInstruction{
time:ins.time,
instruction:physics_input,
});
}
} }
impl MouseInterpolator{ impl MouseInterpolator{
fn push_mouse_instruction(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>,m:glam::IVec2){ fn handle_instruction(&mut self,physics:&mut crate::physics::PhysicsContext,ins:TimedInstruction<InputInstruction>){
if self.mouse_blocking{ //need to handle the case where mouse polling rate is less than 100hz
//tell the game state which is living in the past about its future //also the whole thing is probably wrong lol
self.timeline.push_front(TimedInstruction{ let is_inserting_mouse_instruction=matches!(ins.instruction,InputInstruction::MoveMouse(_));
time:self.last_mouse_time, self.queue.push_back(ins);
instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:ins.time,pos:m}), //We just pushed an element.
}); //The first element is guaranteed to exist.
}else{ let mut iter=self.queue.iter();
//mouse has just started moving again after being still for longer than 10ms. //find a mouse input
//replace the entire mouse interpolation state to avoid an intermediate state with identical m0.t m1.t timestamps which will divide by zero 'outer:loop{
self.timeline.push_front(TimedInstruction{ match iter.next(){
time:self.last_mouse_time, Some(ins0)=>{
instruction:PhysicsInputInstruction::ReplaceMouse( let physics_input=match &ins0.instruction{
MouseState{time:self.last_mouse_time,pos:physics.get_next_mouse().pos}, &InputInstruction::MoveMouse(mut mouse0)=>{
MouseState{time:ins.time,pos:m} //mouse instruction found.
), //enter a new loop with different behaviour
}); //we have to wait for the next mouse event
//delay physics execution until we have an interpolation target //so there is a before and after interpolation target
self.mouse_blocking=true; //write down ins0.time to appease the borrow checker
} let mut t0=ins0.time;
self.last_mouse_time=ins.time; 'inner:loop{
} match iter.next(){
/// returns the mapped physics input instruction Some(ins1)=>match &ins1.instruction{
/// may or may not mutate internal state XD! &InputInstruction::MoveMouse(mouse1)=>{
fn map_instruction(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>)->Option<PhysicsInputInstruction>{ //we found two mouse events to interpolate between
match &ins.instruction{ let consume_count=self.queue.len()-iter.len()-1;//don't consume the mouse1 instruction
Instruction::Input(input_instruction)=>match input_instruction{ //fire off a mouse instruction
&InputInstruction::MoveMouse(m)=>{ physics.run_input_instruction(TimedInstruction{
self.push_mouse_instruction(physics,ins,m); time:t0,
None instruction:PhysicsInputInstruction::SetNextMouse(
MouseState{time:ins1.time,pos:mouse1}
),
});
//update inner loop state
mouse0=mouse1;
t0=ins1.time;
//drain and handle the elements from the front
std::mem::drop(iter);
let mut hot_queue=self.queue.drain(0..consume_count);
hot_queue.next();
drain_queue(physics,hot_queue);
iter=self.queue.iter();
//keep looking for another mouse instruction in the inner loop
continue 'inner;
},
_=>if Time::from_millis(10)<ins1.time-t0{
//we have passed more than 10ms of instructions and have not seen a mouse event.
let consume_count=self.queue.len()-iter.len();
//run an event to extrapolate no movement from
let last_mouse=physics.get_next_mouse();
physics.run_input_instruction(TimedInstruction{
time:last_mouse.time,
instruction:PhysicsInputInstruction::SetNextMouse(
MouseState{time:ins1.time,pos:last_mouse.pos}
),
});
//drop the iterator so we can consume the queue up to this point
std::mem::drop(iter);
//consume queue up to the scanned point
let mut hot_queue=self.queue.drain(0..consume_count);
//the first element is always the last mouse instruction (last_mouse above)
hot_queue.next();
drain_queue(physics,hot_queue);
//make a new iterator starting from the new beginning
//and continue looping like nothing happened
iter=self.queue.iter();
continue 'outer;
},
},
None=>{
if is_inserting_mouse_instruction{
//the mouse started moving again after being still for over 10ms.
//replace the entire mouse state
physics.run_input_instruction(TimedInstruction{
time:physics.get_next_mouse().time,
instruction:PhysicsInputInstruction::ReplaceMouse(
physics.get_next_mouse().clone(),
MouseState{time:t0,pos:mouse0}
),
});
}
break 'outer;
}
}
}
},
&InputInstruction::MoveForward(s)=>PhysicsInputInstruction::SetMoveForward(s),
&InputInstruction::MoveLeft(s)=>PhysicsInputInstruction::SetMoveLeft(s),
&InputInstruction::MoveBack(s)=>PhysicsInputInstruction::SetMoveBack(s),
&InputInstruction::MoveRight(s)=>PhysicsInputInstruction::SetMoveRight(s),
&InputInstruction::MoveUp(s)=>PhysicsInputInstruction::SetMoveUp(s),
&InputInstruction::MoveDown(s)=>PhysicsInputInstruction::SetMoveDown(s),
&InputInstruction::Jump(s)=>PhysicsInputInstruction::SetJump(s),
&InputInstruction::Zoom(s)=>PhysicsInputInstruction::SetZoom(s),
InputInstruction::Reset=>PhysicsInputInstruction::Reset,
InputInstruction::PracticeFly=>PhysicsInputInstruction::PracticeFly,
};
//handle each event immediately, we are not waiting for mouse
physics.run_input_instruction(TimedInstruction{
time:ins0.time,
instruction:physics_input,
});
//drop it and pop it! consume one element and continue the loop
std::mem::drop(iter);
self.queue.pop_front();
iter=self.queue.iter();
}, },
&InputInstruction::MoveForward(s)=>Some(PhysicsInputInstruction::SetMoveForward(s)), None=>{
&InputInstruction::MoveLeft(s)=>Some(PhysicsInputInstruction::SetMoveLeft(s)), //if mouse0 is never found and the loop ends, we can drain the entire queue
&InputInstruction::MoveBack(s)=>Some(PhysicsInputInstruction::SetMoveBack(s)), //because we are not waiting for mouse events.
&InputInstruction::MoveRight(s)=>Some(PhysicsInputInstruction::SetMoveRight(s)), drain_queue(physics,self.queue.drain(..));
&InputInstruction::MoveUp(s)=>Some(PhysicsInputInstruction::SetMoveUp(s)), break 'outer;
&InputInstruction::MoveDown(s)=>Some(PhysicsInputInstruction::SetMoveDown(s)), }
&InputInstruction::Jump(s)=>Some(PhysicsInputInstruction::SetJump(s)),
&InputInstruction::Zoom(s)=>Some(PhysicsInputInstruction::SetZoom(s)),
InputInstruction::Reset=>Some(PhysicsInputInstruction::Reset),
InputInstruction::PracticeFly=>Some(PhysicsInputInstruction::PracticeFly),
},
Instruction::GenerateModels(_)=>Some(PhysicsInputInstruction::Idle),
Instruction::ClearModels=>Some(PhysicsInputInstruction::Idle),
Instruction::Resize(_,_)=>Some(PhysicsInputInstruction::Idle),
Instruction::Render=>Some(PhysicsInputInstruction::Idle),
}
}
fn update_mouse_blocking(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>)->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)<ins.time-physics.get_next_mouse().time{
//push an event to extrapolate no movement from
self.timeline.push_front(TimedInstruction{
time:self.last_mouse_time,
instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:ins.time,pos:physics.get_next_mouse().pos}),
});
self.last_mouse_time=ins.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;
true
}else{
false
} }
}else{
//keep this up to date so that it can be used as a known-timestamp
//that the mouse was not moving when the mouse starts moving again
self.last_mouse_time=ins.time;
true
}
}
/// returns whether or not to empty the instruction queue
fn handle_physics_input(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>,phys_input_option:Option<PhysicsInputInstruction>)->bool{
if let Some(phys_input)=phys_input_option{
//non-mouse event
self.timeline.push_back(TimedInstruction{
time:ins.time,
instruction:phys_input,
});
//this returns the bool for us
self.update_mouse_blocking(physics,ins)
}else{
//mouse event
true
}
}
fn empty_queue(&mut self,physics:&mut crate::physics::PhysicsContext){
while let Some(instruction)=self.timeline.pop_front(){
physics.run_input_instruction(instruction);
}
}
fn handle_instruction(&mut self,physics:&mut crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>){
let physics_input_option=self.map_instruction(physics,ins);
let should_empty_queue=self.handle_physics_input(physics,ins,physics_input_option);
if should_empty_queue{
self.empty_queue(physics);
} }
} }
} }
pub fn new(mut physics:crate::physics::PhysicsContext,mut graphics_worker:crate::compat_worker::INWorker<crate::graphics_worker::Instruction>)->crate::compat_worker::QNWorker<TimedInstruction<Instruction>>{ pub fn new(mut physics:crate::physics::PhysicsContext,mut graphics_worker:crate::compat_worker::INWorker<crate::graphics_worker::Instruction>)->crate::compat_worker::QNWorker<TimedInstruction<Instruction>>{
let mut interpolator=MouseInterpolator{ let mut interpolator=MouseInterpolator{
mouse_blocking:true, queue:std::collections::VecDeque::new(),
last_mouse_time:physics.get_next_mouse().time,
timeline:std::collections::VecDeque::new(),
}; };
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction>|{ crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction>|{
interpolator.handle_instruction(&mut physics,&ins); let passthrough_instruction=match ins.instruction{
match ins.instruction{ Instruction::Passthrough(passthrough_instruction)=>passthrough_instruction,
Instruction::Render=>{ Instruction::Interpolate(input_instruction)=>{
interpolator.handle_instruction(&mut physics,TimedInstruction{
instruction:input_instruction,
time:ins.time,
});
return;
},
};
match passthrough_instruction{
PassthroughInstruction::Render=>{
graphics_worker.send(crate::graphics_worker::Instruction::Render(physics.output(),ins.time,physics.get_next_mouse().pos)).unwrap(); graphics_worker.send(crate::graphics_worker::Instruction::Render(physics.output(),ins.time,physics.get_next_mouse().pos)).unwrap();
}, },
Instruction::Resize(size,user_settings)=>{ PassthroughInstruction::Resize(size,user_settings)=>{
graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,user_settings)).unwrap(); graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,user_settings)).unwrap();
}, },
Instruction::GenerateModels(map)=>{ PassthroughInstruction::GenerateModels(map)=>{
physics.generate_models(&map); physics.generate_models(&map);
physics.spawn(); physics.spawn();
graphics_worker.send(crate::graphics_worker::Instruction::GenerateModels(map)).unwrap(); graphics_worker.send(crate::graphics_worker::Instruction::GenerateModels(map)).unwrap();
}, },
Instruction::ClearModels=>{ PassthroughInstruction::ClearModels=>{
physics.clear(); physics.clear();
graphics_worker.send(crate::graphics_worker::Instruction::ClearModels).unwrap(); graphics_worker.send(crate::graphics_worker::Instruction::ClearModels).unwrap();
}, },
_=>(),
} }
}) })
} }

179
src/timer.rs Normal file
View File

@ -0,0 +1,179 @@
use strafesnet_common::integer::{Time,Ratio64};
pub trait TimerState:Copy{
fn get_time(&self,time:Time)->Time;
fn set_time(&mut self,time:Time,new_time:Time);
fn get_offset(&self)->Time;
fn set_offset(&mut self,offset:Time);
}
#[derive(Clone,Copy,Debug)]
struct Scaled{
scale:Ratio64,
offset:Time,
}
impl Scaled{
fn scale(&self,time:Time)->Time{
Time::raw(self.scale.mul_int(time.get()))
}
fn get_scale(&self)->Ratio64{
self.scale
}
fn set_scale(&mut self,time:Time,new_scale:Ratio64){
let new_time=self.get_time(time);
self.scale=new_scale;
self.set_time(time,new_time);
}
}
impl TimerState for Scaled{
fn get_time(&self,time:Time)->Time{
self.scale(time)+self.offset
}
fn set_time(&mut self,time:Time,new_time:Time){
self.offset=new_time-self.scale(time);
}
fn get_offset(&self)->Time{
self.offset
}
fn set_offset(&mut self,offset:Time){
self.offset=offset;
}
}
#[derive(Clone,Copy,Debug)]
struct Realtime{
offset:Time,
}
impl TimerState for Realtime{
fn get_time(&self,time:Time)->Time{
time+self.offset
}
fn set_time(&mut self,time:Time,new_time:Time){
self.offset=new_time-time;
}
fn get_offset(&self)->Time{
self.offset
}
fn set_offset(&mut self,offset:Time){
self.offset=offset;
}
}
#[derive(Clone,Debug)]
pub struct Timer<T>{
state:T,
paused:bool,
}
impl Timer<Realtime>{
pub fn realtime(offset:Time)->Self{
Self{
state:Realtime{offset},
paused:false,
}
}
pub fn realtime_paused(offset:Time)->Self{
Self{
state:Realtime{offset},
paused:true,
}
}
}
#[derive(Debug)]
pub enum Error{
AlreadyPaused,
AlreadyUnpaused,
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
impl Timer<Scaled>{
pub fn scaled(scale:Ratio64,offset:Time)->Self{
Self{
state:Scaled{scale,offset},
paused:false,
}
}
pub fn scaled_paused(scale:Ratio64,offset:Time)->Self{
Self{
state:Scaled{scale,offset},
paused:true,
}
}
pub fn get_scale(&mut self)->Ratio64{
self.state.get_scale()
}
pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){
self.state.set_scale(time,new_scale)
}
}
impl<T:TimerState> Timer<T>{
pub fn time(&self,time:Time)->Time{
match self.paused{
true=>self.state.get_offset(),
false=>self.state.get_time(time),
}
}
pub fn set_time(&mut self,time:Time,new_time:Time){
match self.paused{
true=>self.state.set_offset(new_time),
false=>self.state.set_time(time,new_time),
}
}
pub fn pause(&mut self,time:Time)->Result<(),Error>{
match self.paused{
true=>Err(Error::AlreadyPaused),
false=>{
let new_time=self.time(time);
self.state.set_offset(new_time);
self.paused=true;
Ok(())
},
}
}
pub fn unpause(&mut self,time:Time)->Result<(),Error>{
match self.paused{
true=>{
let new_time=self.time(time);
self.state.set_time(time,new_time);
self.paused=false;
Ok(())
},
false=>Err(Error::AlreadyUnpaused),
}
}
}
#[cfg(test)]
mod test{
use super::{Time,Timer,Error};
macro_rules! sec {
($s: expr) => {
Time::from_secs($s)
};
}
#[test]
fn test_timer()->Result<(),Error>{
//create a paused timer that reads 0s
let mut timer=Timer::realtime_paused(sec!(0));
//the paused timer at 1 second should read 0s
assert_eq!(timer.time(sec!(1)),sec!(0));
//unpause it after one second
timer.unpause(sec!(1))?;
//the timer at 6 seconds should read 5s
assert_eq!(timer.time(sec!(6)),sec!(5));
//pause the timer after 11 seconds
timer.pause(sec!(11))?;
//the paused timer at 20 seconds should read 10s
assert_eq!(timer.time(sec!(20)),sec!(10));
Ok(())
}
}

View File

@ -29,8 +29,12 @@ impl WindowContext<'_>{
winit::event::WindowEvent::DroppedFile(path)=>{ winit::event::WindowEvent::DroppedFile(path)=>{
match crate::file::load(path.as_path()){ match crate::file::load(path.as_path()){
Ok(map)=>{ Ok(map)=>{
self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::ClearModels}).unwrap(); self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::Passthrough(
self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::GenerateModels(map)}).unwrap(); crate::physics_worker::PassthroughInstruction::ClearModels
)}).unwrap();
self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::Passthrough(
crate::physics_worker::PassthroughInstruction::GenerateModels(map)
)}).unwrap();
}, },
Err(e)=>println!("Failed to load map: {e}"), Err(e)=>println!("Failed to load map: {e}"),
} }
@ -115,7 +119,7 @@ impl WindowContext<'_>{
}{ }{
self.physics_thread.send(TimedInstruction{ self.physics_thread.send(TimedInstruction{
time, time,
instruction:crate::physics_worker::Instruction::Input(input_instruction), instruction:crate::physics_worker::Instruction::Interpolate(input_instruction),
}).unwrap(); }).unwrap();
} }
}, },
@ -143,7 +147,7 @@ impl WindowContext<'_>{
self.mouse.pos+=delta; self.mouse.pos+=delta;
self.physics_thread.send(TimedInstruction{ self.physics_thread.send(TimedInstruction{
time, time,
instruction:crate::physics_worker::Instruction::Input(InputInstruction::MoveMouse(self.mouse.pos)), instruction:crate::physics_worker::Instruction::Interpolate(InputInstruction::MoveMouse(self.mouse.pos)),
}).unwrap(); }).unwrap();
}, },
winit::event::DeviceEvent::MouseWheel { winit::event::DeviceEvent::MouseWheel {
@ -153,7 +157,7 @@ impl WindowContext<'_>{
if false{//self.physics.style.use_scroll{ if false{//self.physics.style.use_scroll{
self.physics_thread.send(TimedInstruction{ self.physics_thread.send(TimedInstruction{
time, time,
instruction:crate::physics_worker::Instruction::Input(InputInstruction::Jump(true)),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump instruction:crate::physics_worker::Instruction::Interpolate(InputInstruction::Jump(true)),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump
}).unwrap(); }).unwrap();
} }
} }
@ -218,7 +222,9 @@ impl<'a> WindowContextSetup<'a>{
window_context.physics_thread.send( window_context.physics_thread.send(
TimedInstruction{ TimedInstruction{
time:ins.time, time:ins.time,
instruction:crate::physics_worker::Instruction::Resize(size,window_context.user_settings.clone()) instruction:crate::physics_worker::Instruction::Passthrough(
crate::physics_worker::PassthroughInstruction::Resize(size,window_context.user_settings.clone())
)
} }
).unwrap(); ).unwrap();
} }
@ -226,7 +232,9 @@ impl<'a> WindowContextSetup<'a>{
window_context.physics_thread.send( window_context.physics_thread.send(
TimedInstruction{ TimedInstruction{
time:ins.time, time:ins.time,
instruction:crate::physics_worker::Instruction::Render instruction:crate::physics_worker::Instruction::Passthrough(
crate::physics_worker::PassthroughInstruction::Render
)
} }
).unwrap(); ).unwrap();
} }