Compare commits

..

9 Commits

Author SHA1 Message Date
64e1e762a1 this is very cool but it won't work because mouse timestamps will be identical while paused 2023-10-10 15:43:41 -07:00
ad862ae8c9 unit test 2023-10-10 15:43:41 -07:00
0ee17ac3d9 timers 2023-10-10 15:43:41 -07:00
e2af6fc4ed sort enums like normalid 2023-10-10 15:33:32 -07:00
bdc0dd1b3b move keyboard input to WindowEvent to fix Wayland 2023-10-10 02:45:19 -07:00
95fb316a23 add fullscreen hotkey 2023-10-09 20:39:15 -07:00
9dec53d764 implement config 2023-10-09 19:48:15 -07:00
3552491a9a calculators 2023-10-09 19:48:15 -07:00
dd13a066d0 settings module 2023-10-09 19:47:38 -07:00
4 changed files with 318 additions and 58 deletions

@ -7,6 +7,7 @@ use instruction::{TimedInstruction, InstructionConsumer};
mod bvh;
mod aabb;
mod model;
mod timers;
mod zeroes;
mod worker;
mod physics;
@ -918,51 +919,23 @@ impl framework::Example for GlobalState {
#[allow(clippy::single_match)]
fn update(&mut self, window: &winit::window::Window, device: &wgpu::Device, queue: &wgpu::Queue, event: winit::event::WindowEvent) {
let time=self.start_time.elapsed().as_nanos() as i64;
match event {
winit::event::WindowEvent::DroppedFile(path) => self.load_file(path,device,queue),
winit::event::WindowEvent::Focused(state)=>{
//pause unpause
//recalculate pressed keys on focus
}
_=>(),
}
}
fn device_event(&mut self, window: &winit::window::Window, event: winit::event::DeviceEvent) {
//there's no way this is the best way get a timestamp.
let time=self.start_time.elapsed().as_nanos() as i64;
match event {
winit::event::DeviceEvent::Key(winit::event::KeyboardInput {
state,
scancode: keycode,
},
winit::event::WindowEvent::KeyboardInput {
input:winit::event::KeyboardInput{state, virtual_keycode,..},
..
}) => {
}=>{
let s=match state {
winit::event::ElementState::Pressed => true,
winit::event::ElementState::Released => false,
};
if let Some(input_instruction)=match keycode {
17=>Some(InputInstruction::MoveForward(s)),//W
30=>Some(InputInstruction::MoveLeft(s)),//A
31=>Some(InputInstruction::MoveBack(s)),//S
32=>Some(InputInstruction::MoveRight(s)),//D
18=>Some(InputInstruction::MoveUp(s)),//E
16=>Some(InputInstruction::MoveDown(s)),//Q
57=>Some(InputInstruction::Jump(s)),//Space
44=>Some(InputInstruction::Zoom(s)),//Z
19=>if s{Some(InputInstruction::Reset)}else{None},//R
01=>{//Esc
if s{
self.manual_mouse_lock=false;
match window.set_cursor_grab(winit::window::CursorGrabMode::None){
Ok(())=>(),
Err(e)=>println!("Could not release cursor: {:?}",e),
}
window.set_cursor_visible(true);
}
None
},
15=>{//Tab
match virtual_keycode{
Some(winit::event::VirtualKeyCode::Tab)=>{
if s{
self.manual_mouse_lock=false;
match window.set_cursor_position(winit::dpi::PhysicalPosition::new(self.graphics.camera.screen_size.x as f32/2.0, self.graphics.camera.screen_size.y as f32/2.0)){
@ -990,16 +963,56 @@ impl framework::Example for GlobalState {
}
}
window.set_cursor_visible(s);
None
},
_ => {println!("scancode {}",keycode);None},
}{
self.physics_thread.send(TimedInstruction{
time,
instruction:input_instruction,
}).unwrap();
Some(winit::event::VirtualKeyCode::F11)=>{
if s{
if window.fullscreen().is_some(){
window.set_fullscreen(None);
}else{
window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
}
}
},
Some(winit::event::VirtualKeyCode::Escape)=>{
if s{
self.manual_mouse_lock=false;
match window.set_cursor_grab(winit::window::CursorGrabMode::None){
Ok(())=>(),
Err(e)=>println!("Could not release cursor: {:?}",e),
}
window.set_cursor_visible(true);
}
},
Some(keycode)=>{
if let Some(input_instruction)=match keycode {
winit::event::VirtualKeyCode::W => Some(InputInstruction::MoveForward(s)),
winit::event::VirtualKeyCode::A => Some(InputInstruction::MoveLeft(s)),
winit::event::VirtualKeyCode::S => Some(InputInstruction::MoveBack(s)),
winit::event::VirtualKeyCode::D => Some(InputInstruction::MoveRight(s)),
winit::event::VirtualKeyCode::E => Some(InputInstruction::MoveUp(s)),
winit::event::VirtualKeyCode::Q => Some(InputInstruction::MoveDown(s)),
winit::event::VirtualKeyCode::Space => Some(InputInstruction::Jump(s)),
winit::event::VirtualKeyCode::Z => Some(InputInstruction::Zoom(s)),
winit::event::VirtualKeyCode::R => if s{Some(InputInstruction::Reset)}else{None},
_ => None,
}{
self.physics_thread.send(TimedInstruction{
time,
instruction:input_instruction,
}).unwrap();
}
},
_=>(),
}
},
_=>(),
}
}
fn device_event(&mut self, window: &winit::window::Window, event: winit::event::DeviceEvent) {
//there's no way this is the best way get a timestamp.
let time=self.start_time.elapsed().as_nanos() as i64;
match event {
winit::event::DeviceEvent::MouseMotion {
delta,//these (f64,f64) are integers on my machine
} => {

@ -19,12 +19,12 @@ pub enum PhysicsInstruction {
pub enum PhysicsInputInstruction {
ReplaceMouse(MouseState,MouseState),
SetNextMouse(MouseState),
SetMoveForward(bool),
SetMoveLeft(bool),
SetMoveBack(bool),
SetMoveRight(bool),
SetMoveUp(bool),
SetMoveBack(bool),
SetMoveLeft(bool),
SetMoveDown(bool),
SetMoveForward(bool),
SetJump(bool),
SetZoom(bool),
Reset,
@ -33,21 +33,22 @@ pub enum PhysicsInputInstruction {
#[derive(Debug)]
pub enum InputInstruction {
MoveMouse(glam::IVec2),
MoveForward(bool),
MoveLeft(bool),
MoveBack(bool),
MoveRight(bool),
MoveUp(bool),
MoveBack(bool),
MoveLeft(bool),
MoveDown(bool),
MoveForward(bool),
Jump(bool),
Zoom(bool),
Reset,
SetPaused(bool),
Idle,
//Idle: there were no input events, but the simulation is safe to advance to this timestep
//for interpolation / networking / playback reasons, most playback heads will always want
//to be 1 instruction ahead to generate the next state for interpolation.
}
#[derive(Clone)]
#[derive(Clone,Debug)]
pub struct Body {
position: glam::Vec3,//I64 where 2^32 = 1 u
velocity: glam::Vec3,//I64 where 2^32 = 1 u/s
@ -453,6 +454,7 @@ impl PhysicsState {
pub fn into_worker(mut self)->crate::worker::CompatWorker<TimedInstruction<InputInstruction>,PhysicsOutputState,Box<dyn FnMut(TimedInstruction<InputInstruction>)->PhysicsOutputState>>{
let mut mouse_blocking=true;
let mut last_mouse_time=self.next_mouse.time;
let mut simulation_timer=crate::timers::UnscaledTimer::unpaused();
let mut timeline=std::collections::VecDeque::new();
crate::worker::CompatWorker::new(self.output(),Box::new(move |ins:TimedInstruction<InputInstruction>|{
if if let Some(phys_input)=match ins.instruction{
@ -460,17 +462,17 @@ impl PhysicsState {
if mouse_blocking{
//tell the game state which is living in the past about its future
timeline.push_front(TimedInstruction{
time:last_mouse_time,
time:simulation_timer.time(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
timeline.push_front(TimedInstruction{
time:last_mouse_time,
time:simulation_timer.time(last_mouse_time),
instruction:PhysicsInputInstruction::ReplaceMouse(
MouseState{time:last_mouse_time,pos:self.next_mouse.pos},
MouseState{time:ins.time,pos:m}
MouseState{time:simulation_timer.time(last_mouse_time),pos:self.next_mouse.pos},
MouseState{time:simulation_timer.time(ins.time),pos:m}
),
});
//delay physics execution until we have an interpolation target
@ -479,6 +481,14 @@ impl PhysicsState {
last_mouse_time=ins.time;
None
},
InputInstruction::SetPaused(s)=>{
if s{
simulation_timer.pause(ins.time);
}else{
simulation_timer.unpause(ins.time);
}
Some(PhysicsInputInstruction::Idle)
}
InputInstruction::MoveForward(s)=>Some(PhysicsInputInstruction::SetMoveForward(s)),
InputInstruction::MoveLeft(s)=>Some(PhysicsInputInstruction::SetMoveLeft(s)),
InputInstruction::MoveBack(s)=>Some(PhysicsInputInstruction::SetMoveBack(s)),
@ -492,7 +502,7 @@ impl PhysicsState {
}{
//non-mouse event
timeline.push_back(TimedInstruction{
time:ins.time,
time:simulation_timer.time(ins.time),
instruction:phys_input,
});
@ -504,7 +514,7 @@ impl PhysicsState {
if 10_000_000<ins.time-self.next_mouse.time{
//push an event to extrapolate no movement from
timeline.push_front(TimedInstruction{
time:last_mouse_time,
time:simulation_timer.time(last_mouse_time),
instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:ins.time,pos:self.next_mouse.pos}),
});
last_mouse_time=ins.time;
@ -526,9 +536,10 @@ impl PhysicsState {
}{
//empty queue
while let Some(instruction)=timeline.pop_front(){
self.run(instruction.time);
let simulation_time=simulation_timer.time(instruction.time);
self.run(simulation_time);
self.process_instruction(TimedInstruction{
time:instruction.time,
time:simulation_time,
instruction:PhysicsInstruction::Input(instruction.instruction),
});
}

@ -27,7 +27,6 @@ impl Default for Sensitivity{
}
}
#[derive(Default)]
pub struct UserSettings{
fov:Fov,

237
src/timers.rs Normal file

@ -0,0 +1,237 @@
type TIME=crate::physics::TIME;
#[derive(Clone)]
pub struct Timescale{
num:i64,
den:std::num::NonZeroU64,
}
#[derive(Clone)]
pub struct Paused{}
#[derive(Clone)]
pub struct Unpaused{}
#[derive(Clone)]
pub struct PausedScaled{scale:Timescale}
#[derive(Clone)]
pub struct UnpausedScaled{scale:Timescale}
pub trait TimerState{}
impl TimerState for Paused{}
impl TimerState for Unpaused{}
impl TimerState for PausedScaled{}
impl TimerState for UnpausedScaled{}
pub trait IsPaused{}
impl IsPaused for Paused{}
impl IsPaused for PausedScaled{}
pub trait IsUnpaused{}
impl IsUnpaused for Unpaused{}
impl IsUnpaused for UnpausedScaled{}
pub trait IsScaled{}
impl IsScaled for PausedScaled{}
impl IsScaled for UnpausedScaled{}
pub trait IsUnscaled{}
impl IsUnscaled for Paused{}
impl IsUnscaled for Unpaused{}
//scaled timer wrapper
enum Scaled{
Paused(Timer<PausedScaled>),
Unpaused(Timer<UnpausedScaled>),
}
pub struct ScaledTimer{
timer:Scaled,
}
impl ScaledTimer{
pub fn unpaused()->Self{
Self{
timer:Scaled::Unpaused(unpaused_scaled(Timescale{num:1,den:std::num::NonZeroU64::new(1).unwrap()}))
}
}
pub fn time(&self,time:TIME)->TIME{
match &self.timer{
Scaled::Paused(timer)=>timer.time(),
Scaled::Unpaused(timer)=>timer.time(time),
}
}
pub fn pause(&mut self,time:TIME){
match &self.timer{
Scaled::Paused(_)=>(),
Scaled::Unpaused(timer)=>self.timer=Scaled::Paused(timer.clone().pause(time)),
};
}
pub fn unpause(&mut self,time:TIME){
match &self.timer{
Scaled::Paused(timer)=>self.timer=Scaled::Unpaused(timer.clone().unpause(time)),
Scaled::Unpaused(_)=>(),
};
}
}
//unscaled timer wrapper
enum Unscaled{
Paused(Timer<Paused>),
Unpaused(Timer<Unpaused>),
}
pub struct UnscaledTimer{
timer:Unscaled,
}
impl UnscaledTimer{
pub fn unpaused()->Self{
Self{
timer:Unscaled::Unpaused(unpaused())
}
}
pub fn time(&self,time:TIME)->TIME{
match &self.timer{
Unscaled::Paused(timer)=>timer.time(),
Unscaled::Unpaused(timer)=>timer.time(time),
}
}
pub fn pause(&mut self,time:TIME){
match &self.timer{
Unscaled::Paused(_)=>(),
Unscaled::Unpaused(timer)=>self.timer=Unscaled::Paused(timer.clone().pause(time)),
};
}
pub fn unpause(&mut self,time:TIME){
match &self.timer{
Unscaled::Paused(timer)=>self.timer=Unscaled::Unpaused(timer.clone().unpause(time)),
Unscaled::Unpaused(_)=>(),
};
}
}
#[derive(Clone)]
pub struct Timer<State:TimerState>{
offset:crate::physics::TIME,
state:State,
}
fn get_offset(time:TIME,write_time:TIME)->TIME{
write_time-time
}
fn get_offset_scaled(time:TIME,write_time:TIME,scale:&Timescale)->TIME{
write_time-time*scale.num/scale.den.get() as i64
}
fn paused()->Timer<Paused>{
Timer{
offset:0,
state:Paused{},
}
}
fn unpaused()->Timer<Unpaused>{
Timer{
offset:0,
state:Unpaused{},
}
}
fn paused_scaled(scale:Timescale)->Timer<PausedScaled>{
Timer{
offset:0,
state:PausedScaled{scale},
}
}
fn unpaused_scaled(scale:Timescale)->Timer<UnpausedScaled>{
Timer{
offset:0,
state:UnpausedScaled{scale},
}
}
impl Timer<Paused>{
pub fn time(&self)->TIME{
self.offset
}
pub fn unpause(self,time:TIME)->Timer<Unpaused>{
Timer{
offset:get_offset(time,self.time()),
state:Unpaused{},
}
}
pub fn set_time(&mut self,time:TIME,write_time:TIME){
self.offset=get_offset(time,write_time);
}
pub fn set_scale(self,time:TIME,scale:Timescale)->Timer<PausedScaled>{
Timer{
offset:get_offset_scaled(time,self.time(),&scale),
state:PausedScaled{scale},
}
}
}
impl Timer<Unpaused>{
pub fn time(&self,time:TIME)->TIME{
self.offset+time
}
pub fn pause(self,time:TIME)->Timer<Paused>{
Timer{
offset:self.time(time),
state:Paused{},
}
}
pub fn set_time(&mut self,time:TIME,write_time:TIME){
self.offset=get_offset(time,write_time);
}
pub fn set_scale(self,time:TIME,scale:Timescale)->Timer<UnpausedScaled>{
Timer{
offset:get_offset_scaled(time,self.time(time),&scale),
state:UnpausedScaled{scale},
}
}
}
impl Timer<PausedScaled>{
pub fn time(&self)->TIME{
self.offset
}
pub fn unpause(self,time:TIME)->Timer<UnpausedScaled>{
Timer{
offset:get_offset_scaled(time,self.time(),&self.state.scale),
state:UnpausedScaled{scale:self.state.scale},
}
}
pub fn set_time(&mut self,time:TIME,write_time:TIME){
self.offset=get_offset_scaled(time,write_time,&self.state.scale);
}
pub fn set_scale(self,time:TIME,scale:Timescale)->Timer<PausedScaled>{
Timer{
offset:get_offset_scaled(time,self.time(),&scale),
state:PausedScaled{scale},
}
}
}
impl Timer<UnpausedScaled>{
pub fn time(&self,time:TIME)->TIME{
self.offset+time*self.state.scale.num/self.state.scale.den.get() as i64
}
pub fn pause(self,time:TIME)->Timer<PausedScaled>{
Timer{
offset:self.time(time),
state:PausedScaled{scale:self.state.scale},
}
}
pub fn set_time(&mut self,time:TIME,write_time:TIME){
self.offset=get_offset_scaled(time,write_time,&self.state.scale);
}
pub fn set_scale(self,time:TIME,scale:Timescale)->Timer<UnpausedScaled>{
Timer{
offset:get_offset_scaled(time,self.time(time),&scale),
//self.offset+time*self.state.scale.num/self.state.scale.den.get() as i64-time*scale.num/scale.den.get() as i64
state:UnpausedScaled{scale},
}
}
}
#[test]
fn test_timer_unscaled(){
const ONE_SECOND:TIME=1_000_000_000;
let run_prepare=paused();
let run_start=run_prepare.unpause(ONE_SECOND);
let run_finish=run_start.pause(11*ONE_SECOND);
assert_eq!(run_finish.time(),10*ONE_SECOND);
}