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 setup;
mod timer;
mod window;
mod worker;
mod physics;

View File

@ -104,7 +104,8 @@ impl InputState{
&self.next_mouse
}
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){
(self.next_mouse,self.mouse)=(next_mouse,mouse);

View File

@ -16,7 +16,10 @@ pub enum InputInstruction{
PracticeFly,
}
pub enum Instruction{
Input(InputInstruction),
Passthrough(PassthroughInstruction),
Interpolate(InputInstruction),
}
pub enum PassthroughInstruction{
Render,
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
GenerateModels(strafesnet_common::map::CompleteMap),
@ -24,140 +27,179 @@ pub enum Instruction{
//Graphics(crate::graphics_worker::Instruction),
}
pub struct MouseInterpolator{
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction>>,
last_mouse_time:Time,
mouse_blocking:bool,
queue:std::collections::VecDeque<TimedInstruction<InputInstruction>>,
}
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{
fn push_mouse_instruction(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>,m:glam::IVec2){
if self.mouse_blocking{
//tell the game state which is living in the past about its future
self.timeline.push_front(TimedInstruction{
time:self.last_mouse_time,
instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:ins.time,pos:m}),
});
}else{
//mouse has just started moving again after being still for longer than 10ms.
//replace the entire mouse interpolation state to avoid an intermediate state with identical m0.t m1.t timestamps which will divide by zero
self.timeline.push_front(TimedInstruction{
time:self.last_mouse_time,
instruction:PhysicsInputInstruction::ReplaceMouse(
MouseState{time:self.last_mouse_time,pos:physics.get_next_mouse().pos},
MouseState{time:ins.time,pos:m}
fn handle_instruction(&mut self,physics:&mut crate::physics::PhysicsContext,ins:TimedInstruction<InputInstruction>){
//need to handle the case where mouse polling rate is less than 100hz
//also the whole thing is probably wrong lol
let is_inserting_mouse_instruction=matches!(ins.instruction,InputInstruction::MoveMouse(_));
self.queue.push_back(ins);
//We just pushed an element.
//The first element is guaranteed to exist.
let mut iter=self.queue.iter();
//find a mouse input
'outer:loop{
match iter.next(){
Some(ins0)=>{
let physics_input=match &ins0.instruction{
&InputInstruction::MoveMouse(mut mouse0)=>{
//mouse instruction found.
//enter a new loop with different behaviour
//we have to wait for the next mouse event
//so there is a before and after interpolation target
//write down ins0.time to appease the borrow checker
let mut t0=ins0.time;
'inner:loop{
match iter.next(){
Some(ins1)=>match &ins1.instruction{
&InputInstruction::MoveMouse(mouse1)=>{
//we found two mouse events to interpolate between
let consume_count=self.queue.len()-iter.len()-1;//don't consume the mouse1 instruction
//fire off a mouse instruction
physics.run_input_instruction(TimedInstruction{
time:t0,
instruction:PhysicsInputInstruction::SetNextMouse(
MouseState{time:ins1.time,pos:mouse1}
),
});
//delay physics execution until we have an interpolation target
self.mouse_blocking=true;
}
self.last_mouse_time=ins.time;
}
/// returns the mapped physics input instruction
/// may or may not mutate internal state XD!
fn map_instruction(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>)->Option<PhysicsInputInstruction>{
match &ins.instruction{
Instruction::Input(input_instruction)=>match input_instruction{
&InputInstruction::MoveMouse(m)=>{
self.push_mouse_instruction(physics,ins,m);
None
//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;
},
&InputInstruction::MoveForward(s)=>Some(PhysicsInputInstruction::SetMoveForward(s)),
&InputInstruction::MoveLeft(s)=>Some(PhysicsInputInstruction::SetMoveLeft(s)),
&InputInstruction::MoveBack(s)=>Some(PhysicsInputInstruction::SetMoveBack(s)),
&InputInstruction::MoveRight(s)=>Some(PhysicsInputInstruction::SetMoveRight(s)),
&InputInstruction::MoveUp(s)=>Some(PhysicsInputInstruction::SetMoveUp(s)),
&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),
_=>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;
},
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}),
},
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}
),
});
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
break 'outer;
}
}
/// 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,
}
},
&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,
});
//this returns the bool for us
self.update_mouse_blocking(physics,ins)
}else{
//mouse event
true
//drop it and pop it! consume one element and continue the loop
std::mem::drop(iter);
self.queue.pop_front();
iter=self.queue.iter();
},
None=>{
//if mouse0 is never found and the loop ends, we can drain the entire queue
//because we are not waiting for mouse events.
drain_queue(physics,self.queue.drain(..));
break 'outer;
}
}
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>>{
let mut interpolator=MouseInterpolator{
mouse_blocking:true,
last_mouse_time:physics.get_next_mouse().time,
timeline:std::collections::VecDeque::new(),
queue:std::collections::VecDeque::new(),
};
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction>|{
interpolator.handle_instruction(&mut physics,&ins);
match ins.instruction{
Instruction::Render=>{
let passthrough_instruction=match ins.instruction{
Instruction::Passthrough(passthrough_instruction)=>passthrough_instruction,
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();
},
Instruction::Resize(size,user_settings)=>{
PassthroughInstruction::Resize(size,user_settings)=>{
graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,user_settings)).unwrap();
},
Instruction::GenerateModels(map)=>{
PassthroughInstruction::GenerateModels(map)=>{
physics.generate_models(&map);
physics.spawn();
graphics_worker.send(crate::graphics_worker::Instruction::GenerateModels(map)).unwrap();
},
Instruction::ClearModels=>{
PassthroughInstruction::ClearModels=>{
physics.clear();
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)=>{
match crate::file::load(path.as_path()){
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::GenerateModels(map)}).unwrap();
self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::Passthrough(
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}"),
}
@ -115,7 +119,7 @@ impl WindowContext<'_>{
}{
self.physics_thread.send(TimedInstruction{
time,
instruction:crate::physics_worker::Instruction::Input(input_instruction),
instruction:crate::physics_worker::Instruction::Interpolate(input_instruction),
}).unwrap();
}
},
@ -143,7 +147,7 @@ impl WindowContext<'_>{
self.mouse.pos+=delta;
self.physics_thread.send(TimedInstruction{
time,
instruction:crate::physics_worker::Instruction::Input(InputInstruction::MoveMouse(self.mouse.pos)),
instruction:crate::physics_worker::Instruction::Interpolate(InputInstruction::MoveMouse(self.mouse.pos)),
}).unwrap();
},
winit::event::DeviceEvent::MouseWheel {
@ -153,7 +157,7 @@ impl WindowContext<'_>{
if false{//self.physics.style.use_scroll{
self.physics_thread.send(TimedInstruction{
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();
}
}
@ -218,7 +222,9 @@ impl<'a> WindowContextSetup<'a>{
window_context.physics_thread.send(
TimedInstruction{
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();
}
@ -226,7 +232,9 @@ impl<'a> WindowContextSetup<'a>{
window_context.physics_thread.send(
TimedInstruction{
time:ins.time,
instruction:crate::physics_worker::Instruction::Render
instruction:crate::physics_worker::Instruction::Passthrough(
crate::physics_worker::PassthroughInstruction::Render
)
}
).unwrap();
}