diff --git a/Cargo.lock b/Cargo.lock index 0a553ac..34a5c36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2019,6 +2019,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "replace_with" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690" + [[package]] name = "rmp" version = "0.8.14" @@ -2236,6 +2242,7 @@ dependencies = [ "id", "parking_lot", "pollster", + "replace_with", "strafesnet_bsp_loader", "strafesnet_common", "strafesnet_deferred_loader", diff --git a/lib/common/src/physics.rs b/lib/common/src/physics.rs index dc6be13..5f5fd77 100644 --- a/lib/common/src/physics.rs +++ b/lib/common/src/physics.rs @@ -1,11 +1,34 @@ +use crate::mouse::MouseState; + #[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] pub enum TimeInner{} pub type Time=crate::integer::Time; #[derive(Clone,Debug)] pub enum Instruction{ - ReplaceMouse(crate::mouse::MouseState,crate::mouse::MouseState), - SetNextMouse(crate::mouse::MouseState), + Mouse(MouseInstruction), + Other(OtherInstruction), +} +impl Instruction{ + pub const IDLE:Self=Self::Other(OtherInstruction::Other(OtherOtherInstruction::Idle)); +} +#[derive(Clone,Debug)] +pub enum OtherInstruction{ + SetControl(SetControlInstruction), + Mode(ModeInstruction), + Other(OtherOtherInstruction), +} +#[derive(Clone,Debug)] +pub enum MouseInstruction{ + /// Replace the entire interpolation state to avoid dividing by zero when replacing twice + ReplaceMouse{ + m0:MouseState, + m1:MouseState, + }, + SetNextMouse(MouseState), +} +#[derive(Clone,Debug)] +pub enum SetControlInstruction{ SetMoveRight(bool), SetMoveUp(bool), SetMoveBack(bool), @@ -14,6 +37,9 @@ pub enum Instruction{ SetMoveForward(bool), SetJump(bool), SetZoom(bool), +} +#[derive(Clone,Debug)] +pub enum ModeInstruction{ /// Reset: fully replace the physics state. /// This forgets all inputs and settings which need to be reapplied. Reset, @@ -22,10 +48,11 @@ pub enum Instruction{ /// Spawn: Teleport to a specific mode's spawn /// Sets current mode & spawn Spawn(crate::gameplay_modes::ModeId,crate::gameplay_modes::StageId), +} +#[derive(Clone,Debug)] +pub enum OtherOtherInstruction{ + /// Idle: there were no input events, but the simulation is safe to advance to this timestep 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. PracticeFly, SetSensitivity(crate::integer::Ratio64Vec2), } diff --git a/strafe-client/Cargo.toml b/strafe-client/Cargo.toml index 19ce177..a631d0a 100644 --- a/strafe-client/Cargo.toml +++ b/strafe-client/Cargo.toml @@ -23,6 +23,7 @@ glam = "0.29.0" id = { version = "0.1.0", registry = "strafesnet" } parking_lot = "0.12.1" pollster = "0.4.0" +replace_with = "0.1.7" strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true } strafesnet_common = { path = "../lib/common", registry = "strafesnet" } strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true } diff --git a/strafe-client/src/graphics.rs b/strafe-client/src/graphics.rs index b870ad2..8002da7 100644 --- a/strafe-client/src/graphics.rs +++ b/strafe-client/src/graphics.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::collections::{HashSet,HashMap}; use strafesnet_common::map; -use strafesnet_common::integer; use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; use wgpu::{util::DeviceExt,AstcBlock,AstcChannel}; use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex}; @@ -876,7 +875,7 @@ impl GraphicsState{ view:&wgpu::TextureView, device:&wgpu::Device, queue:&wgpu::Queue, - frame_state:crate::physics_worker::FrameState, + frame_state:crate::session::FrameState, ){ //TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input diff --git a/strafe-client/src/graphics_worker.rs b/strafe-client/src/graphics_worker.rs index dba4203..76d6eca 100644 --- a/strafe-client/src/graphics_worker.rs +++ b/strafe-client/src/graphics_worker.rs @@ -1,5 +1,5 @@ pub enum Instruction{ - Render(crate::physics_worker::FrameState), + Render(crate::session::FrameState), //UpdateModel(crate::graphics::GraphicsModelUpdate), Resize(winit::dpi::PhysicalSize,crate::settings::UserSettings), ChangeMap(strafesnet_common::map::CompleteMap), @@ -14,13 +14,13 @@ WorkerDescription{ */ //up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order -pub fn new<'a>( +pub fn new( mut graphics:crate::graphics::GraphicsState, mut config:wgpu::SurfaceConfiguration, - surface:wgpu::Surface<'a>, + surface:wgpu::Surface, device:wgpu::Device, queue:wgpu::Queue, -)->crate::compat_worker::INWorker<'a,Instruction>{ +)->crate::compat_worker::INWorker<'_,Instruction>{ crate::compat_worker::INWorker::new(move |ins:Instruction|{ match ins{ Instruction::ChangeMap(map)=>{ diff --git a/strafe-client/src/main.rs b/strafe-client/src/main.rs index faae714..968c630 100644 --- a/strafe-client/src/main.rs +++ b/strafe-client/src/main.rs @@ -4,6 +4,7 @@ mod setup; mod window; mod worker; mod physics; +mod session; mod graphics; mod settings; mod push_solve; @@ -13,6 +14,7 @@ mod model_physics; mod model_graphics; mod physics_worker; mod graphics_worker; +mod mouse_interpolator; const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION")); diff --git a/strafe-client/src/mouse_interpolator.rs b/strafe-client/src/mouse_interpolator.rs new file mode 100644 index 0000000..8b26fe1 --- /dev/null +++ b/strafe-client/src/mouse_interpolator.rs @@ -0,0 +1,245 @@ +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}, + m0:mouse_state, + }, + }); + } + BufferState::Buffered(_time,mouse_state)=>{ + // convert to BufferState::Unbuffered + // use the first instruction which should be a mouse instruction + // to push a ReplaceMouse instruction + // duplicate the current mouse + self.push_mouse_and_flush_buffer(TimedInstruction{ + // This should be simulation_timer.time(timeout) + // but the timer is not accessible from this scope + // and it's just here to say that the mouse isn't moving anyways. + // I think this is a divide by zero bug, two identical mouse_states will occupy the interpolation state + time:mouse_state.time, + instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,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 + + // push buffered mouse instruction and flush buffer to output + if self.get_mouse_timedout_at(ins.time).is_some(){ + self.timeout_mouse(ins.instruction.time); + } + // replace_with allows the enum variant to safely be replaced from behind a mutable reference + 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); + } +} diff --git a/strafe-client/src/physics.rs b/strafe-client/src/physics.rs index 16bbe48..9d309b1 100644 --- a/strafe-client/src/physics.rs +++ b/strafe-client/src/physics.rs @@ -19,25 +19,18 @@ type MouseState=strafesnet_common::mouse::MouseState; //external influence //this is how you influence the physics from outside -use strafesnet_common::physics::Instruction as PhysicsInputInstruction; +use strafesnet_common::physics::{Instruction,OtherInstruction,MouseInstruction,ModeInstruction,OtherOtherInstruction,SetControlInstruction}; //internal influence //when the physics asks itself what happens next, this is how it's represented #[derive(Debug)] -pub enum PhysicsInternalInstruction{ +pub enum InternalInstruction{ CollisionStart(Collision,model_physics::GigaTime), CollisionEnd(Collision,model_physics::GigaTime), StrafeTick, ReachWalkTargetVelocity, // Water, } -#[derive(Debug)] -pub enum PhysicsInstruction{ - Internal(PhysicsInternalInstruction), - //InputInstructions conditionally activate RefreshWalkTarget - //(by doing what SetWalkTargetVelocity used to do and then flagging it) - Input(PhysicsInputInstruction), -} #[derive(Clone,Debug,Default)] pub struct InputState{ @@ -558,13 +551,13 @@ impl MoveState{ =>None, } } - fn next_move_instruction(&self,strafe:&Option,time:Time)->Option>{ + fn next_move_instruction(&self,strafe:&Option,time:Time)->Option>{ //check if you have a valid walk state and create an instruction match self{ MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{ &TransientAcceleration::Reachable{acceleration:_,time}=>Some(TimedInstruction{ time, - instruction:PhysicsInternalInstruction::ReachWalkTargetVelocity + instruction:InternalInstruction::ReachWalkTargetVelocity }), TransientAcceleration::Unreachable{acceleration:_} |TransientAcceleration::Reached @@ -574,7 +567,7 @@ impl MoveState{ TimedInstruction{ time:strafe.next_tick(time), //only poll the physics if there is a before and after mouse event - instruction:PhysicsInternalInstruction::StrafeTick + instruction:InternalInstruction::StrafeTick } }), MoveState::Water=>None,//TODO @@ -786,7 +779,7 @@ impl TouchingState{ }).collect(); *acceleration=crate::push_solve::push_solve(&contacts,*acceleration); } - fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ + fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time); for contact in &self.contacts{ //detect face slide off @@ -795,7 +788,7 @@ impl TouchingState{ collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{ TimedInstruction{ time:relative_body.time+time.into(), - instruction:PhysicsInternalInstruction::CollisionEnd( + instruction:InternalInstruction::CollisionEnd( Collision::Contact(*contact), time ), @@ -809,7 +802,7 @@ impl TouchingState{ collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{ TimedInstruction{ time:relative_body.time+time.into(), - instruction:PhysicsInternalInstruction::CollisionEnd( + instruction:InternalInstruction::CollisionEnd( Collision::Intersect(*intersect), time ), @@ -882,11 +875,9 @@ impl PhysicsState{ self.touching.clear(); } fn reset_to_default(&mut self){ - let mut new_state=Self::default(); - new_state.camera.sensitivity=self.camera.sensitivity; - *self=new_state; + *self=Self::default(); } - fn next_move_instruction(&self)->Option>{ + fn next_move_instruction(&self)->Option>{ self.move_state.next_move_instruction(&self.style.strafe,self.time) } fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){ @@ -935,24 +926,24 @@ pub struct PhysicsContext{ state:PhysicsState,//this captures the entire state of the physics. data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state. } -// the physics consumes both PhysicsInputInstruction and PhysicsInternalInstruction, +// the physics consumes both Instruction and PhysicsInternalInstruction, // but can only emit PhysicsInternalInstruction -impl InstructionConsumer for PhysicsContext{ +impl InstructionConsumer for PhysicsContext{ type TimeInner=TimeInner; - fn process_instruction(&mut self,ins:TimedInstruction){ + fn process_instruction(&mut self,ins:TimedInstruction){ atomic_internal_instruction(&mut self.state,&self.data,ins) } } -impl InstructionConsumer for PhysicsContext{ +impl InstructionConsumer for PhysicsContext{ type TimeInner=TimeInner; - fn process_instruction(&mut self,ins:TimedInstruction){ + fn process_instruction(&mut self,ins:TimedInstruction){ atomic_input_instruction(&mut self.state,&self.data,ins) } } -impl InstructionEmitter for PhysicsContext{ +impl InstructionEmitter for PhysicsContext{ type TimeInner=TimeInner; //this little next instruction function could cache its return value and invalidate the cached value by watching the State. - fn next_instruction(&self,time_limit:Time)->Option>{ + fn next_instruction(&self,time_limit:Time)->Option>{ next_instruction_internal(&self.state,&self.data,time_limit) } } @@ -1109,14 +1100,14 @@ impl PhysicsContext{ println!("Physics Objects: {}",model_count); } - pub fn run_input_instruction(&mut self,instruction:TimedInstruction){ + pub fn run_input_instruction(&mut self,instruction:TimedInstruction){ self.process_exhaustive(instruction.time); self.process_instruction(instruction); } } //this is the one who asks - fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option>{ + fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option>{ //JUST POLLING!!! NO MUTATION let mut collector = instruction::InstructionCollector::new(time_limit); @@ -1140,11 +1131,11 @@ impl PhysicsContext{ .map_or(None,|(face,dt)|{ // this must be rounded to avoid the infinite loop when hitting the start zone let time=relative_body.time+dt.into(); - if time<=state.time{None}else{Some((time,face,dt))}}) - .map(|(time,face,dt)| + (state.time){ +fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ state.time=ins.time; let (should_advance_body,goober_time)=match ins.instruction{ - PhysicsInternalInstruction::CollisionStart(_,dt) - |PhysicsInternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)), - PhysicsInternalInstruction::StrafeTick - |PhysicsInternalInstruction::ReachWalkTargetVelocity=>(true,None), + InternalInstruction::CollisionStart(_,dt) + |InternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)), + InternalInstruction::StrafeTick + |InternalInstruction::ReachWalkTargetVelocity=>(true,None), }; if should_advance_body{ match goober_time{ @@ -1665,7 +1656,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim } } match ins.instruction{ - PhysicsInternalInstruction::CollisionStart(collision,_)=>{ + InternalInstruction::CollisionStart(collision,_)=>{ let mode=data.modes.get_mode(state.mode_state.get_mode_id()); match collision{ Collision::Contact(contact)=>collision_start_contact( @@ -1686,7 +1677,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim ), } }, - PhysicsInternalInstruction::CollisionEnd(collision,_)=>match collision{ + InternalInstruction::CollisionEnd(collision,_)=>match collision{ Collision::Contact(contact)=>collision_end_contact( &mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state, data.models.contact_attr(contact.model_id), @@ -1701,7 +1692,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim state.time ), }, - PhysicsInternalInstruction::StrafeTick=>{ + InternalInstruction::StrafeTick=>{ //TODO make this less huge if let Some(strafe_settings)=&state.style.strafe{ let controls=state.input_state.controls; @@ -1719,7 +1710,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim } } } - PhysicsInternalInstruction::ReachWalkTargetVelocity=>{ + InternalInstruction::ReachWalkTargetVelocity=>{ match &mut state.move_state{ MoveState::Air |MoveState::Water @@ -1746,27 +1737,18 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim } } -fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ +fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ state.time=ins.time; let should_advance_body=match ins.instruction{ //the body may as well be a quantum wave function //as far as these instruction are concerned (they don't care where it is) - PhysicsInputInstruction::SetSensitivity(..) - |PhysicsInputInstruction::Reset - |PhysicsInputInstruction::Restart - |PhysicsInputInstruction::Spawn(..) - |PhysicsInputInstruction::SetZoom(..) - |PhysicsInputInstruction::Idle=>false, + Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(..))) + |Instruction::Other(OtherInstruction::Mode(_)) + |Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetZoom(..))) + |Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::Idle))=>false, //these controls only update the body if you are on the ground - PhysicsInputInstruction::SetNextMouse(..) - |PhysicsInputInstruction::ReplaceMouse(..) - |PhysicsInputInstruction::SetMoveForward(..) - |PhysicsInputInstruction::SetMoveLeft(..) - |PhysicsInputInstruction::SetMoveBack(..) - |PhysicsInputInstruction::SetMoveRight(..) - |PhysicsInputInstruction::SetMoveUp(..) - |PhysicsInputInstruction::SetMoveDown(..) - |PhysicsInputInstruction::SetJump(..)=>{ + Instruction::Mouse(_) + |Instruction::Other(OtherInstruction::SetControl(_))=>{ match &state.move_state{ MoveState::Fly |MoveState::Water @@ -1776,100 +1758,101 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI } }, //the body must be updated unconditionally - PhysicsInputInstruction::PracticeFly=>true, + Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::PracticeFly))=>true, }; if should_advance_body{ state.body.advance_time(state.time); } - //TODO: UNTAB - let mut b_refresh_walk_target=true; - match ins.instruction{ - PhysicsInputInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity, - PhysicsInputInstruction::SetNextMouse(m)=>{ - state.camera.move_mouse(state.input_state.mouse_delta()); - state.input_state.set_next_mouse(m); - }, - PhysicsInputInstruction::ReplaceMouse(m0,m1)=>{ - state.camera.move_mouse(m0.pos-state.input_state.mouse.pos); - state.input_state.replace_mouse(m0,m1); - }, - PhysicsInputInstruction::SetMoveForward(s)=>state.input_state.set_control(Controls::MoveForward,s), - PhysicsInputInstruction::SetMoveLeft(s)=>state.input_state.set_control(Controls::MoveLeft,s), - PhysicsInputInstruction::SetMoveBack(s)=>state.input_state.set_control(Controls::MoveBackward,s), - PhysicsInputInstruction::SetMoveRight(s)=>state.input_state.set_control(Controls::MoveRight,s), - PhysicsInputInstruction::SetMoveUp(s)=>state.input_state.set_control(Controls::MoveUp,s), - PhysicsInputInstruction::SetMoveDown(s)=>state.input_state.set_control(Controls::MoveDown,s), - PhysicsInputInstruction::SetJump(s)=>{ - state.input_state.set_control(Controls::Jump,s); - if let Some(walk_state)=state.move_state.get_walk_state(){ - if let Some(jump_settings)=&state.style.jump{ - let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact); - let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref(); - let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option); - state.cull_velocity(&data,jumped_velocity); - } - } - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::SetZoom(s)=>{ - state.input_state.set_control(Controls::Zoom,s); - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::Reset=>{ - //totally reset physics state - state.reset_to_default(); - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::Restart=>{ - //teleport to start zone - let mode=data.modes.get_mode(state.mode_state.get_mode_id()); - let spawn_point=mode.and_then(|mode| - //TODO: spawn at the bottom of the start zone plus the hitbox size - //TODO: set camera andles to face the same way as the start zone - data.models.get_model_transform(mode.get_start().into()).map(|transform| - transform.vertex.translation - ) - ).unwrap_or(vec3::ZERO); - set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time); - set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); - state.set_move_state(data,MoveState::Air); - b_refresh_walk_target=false; - } - PhysicsInputInstruction::Spawn(mode_id,stage_id)=>{ - //spawn at a particular stage - if let Some(mode)=data.modes.get_mode(mode_id){ - if let Some(stage)=mode.get_stage(stage_id){ - let _=teleport_to_spawn( - stage.spawn(), - &mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state, - mode, - &data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time - ); - } - } - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::PracticeFly=>{ - match &state.move_state{ - MoveState::Fly=>{ - state.set_move_state(data,MoveState::Air); - }, - _=>{ - state.set_move_state(data,MoveState::Fly); - }, - } - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::Idle=>{ - //literally idle! - b_refresh_walk_target=false; - }, + + let mut b_refresh_walk_target=true; + match ins.instruction{ + Instruction::Mouse(MouseInstruction::SetNextMouse(m))=>{ + state.camera.move_mouse(state.input_state.mouse_delta()); + state.input_state.set_next_mouse(m); + }, + Instruction::Mouse(MouseInstruction::ReplaceMouse{m0,m1})=>{ + state.camera.move_mouse(m0.pos-state.input_state.mouse.pos); + state.input_state.replace_mouse(m0,m1); + }, + Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(sensitivity)))=>state.camera.sensitivity=sensitivity, + Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveForward(s)))=>state.input_state.set_control(Controls::MoveForward,s), + Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveLeft(s)))=>state.input_state.set_control(Controls::MoveLeft,s), + Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveBack(s)))=>state.input_state.set_control(Controls::MoveBackward,s), + Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveRight(s)))=>state.input_state.set_control(Controls::MoveRight,s), + Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveUp(s)))=>state.input_state.set_control(Controls::MoveUp,s), + Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveDown(s)))=>state.input_state.set_control(Controls::MoveDown,s), + Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetJump(s)))=>{ + state.input_state.set_control(Controls::Jump,s); + if let Some(walk_state)=state.move_state.get_walk_state(){ + if let Some(jump_settings)=&state.style.jump{ + let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact); + let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref(); + let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option); + state.cull_velocity(&data,jumped_velocity); } - if b_refresh_walk_target{ - state.apply_input_and_body(data); - state.cull_velocity(data,state.body.velocity); - //also check if accelerating away from surface + } + b_refresh_walk_target=false; + }, + Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetZoom(s)))=>{ + state.input_state.set_control(Controls::Zoom,s); + b_refresh_walk_target=false; + }, + Instruction::Other(OtherInstruction::Mode(ModeInstruction::Reset))=>{ + //totally reset physics state + state.reset_to_default(); + b_refresh_walk_target=false; + }, + Instruction::Other(OtherInstruction::Mode(ModeInstruction::Restart))=>{ + //teleport to start zone + let mode=data.modes.get_mode(state.mode_state.get_mode_id()); + let spawn_point=mode.and_then(|mode| + //TODO: spawn at the bottom of the start zone plus the hitbox size + //TODO: set camera andles to face the same way as the start zone + data.models.get_model_transform(mode.get_start().into()).map(|transform| + transform.vertex.translation + ) + ).unwrap_or(vec3::ZERO); + set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time); + set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); + state.set_move_state(data,MoveState::Air); + b_refresh_walk_target=false; + } + // Spawn does not necessarily imply reset + Instruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(mode_id,stage_id)))=>{ + //spawn at a particular stage + if let Some(mode)=data.modes.get_mode(mode_id){ + if let Some(stage)=mode.get_stage(stage_id){ + let _=teleport_to_spawn( + stage.spawn(), + &mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state, + mode, + &data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time + ); } + } + b_refresh_walk_target=false; + }, + Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::PracticeFly))=>{ + match &state.move_state{ + MoveState::Fly=>{ + state.set_move_state(data,MoveState::Air); + }, + _=>{ + state.set_move_state(data,MoveState::Fly); + }, + } + b_refresh_walk_target=false; + }, + Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::Idle))=>{ + //literally idle! + b_refresh_walk_target=false; + }, + } + if b_refresh_walk_target{ + state.apply_input_and_body(data); + state.cull_velocity(data,state.body.velocity); + //also check if accelerating away from surface + } } #[cfg(test)] diff --git a/strafe-client/src/physics_worker.rs b/strafe-client/src/physics_worker.rs index 1e7ffa3..225b07a 100644 --- a/strafe-client/src/physics_worker.rs +++ b/strafe-client/src/physics_worker.rs @@ -1,249 +1,67 @@ -use strafesnet_common::mouse::MouseState; +use crate::graphics_worker::Instruction as GraphicsInstruction; +use crate::session::{SessionInputInstruction,Instruction as SessionInstruction,Session,Simulation}; +use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer}; +use strafesnet_common::physics::Time as PhysicsTime; use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; -use strafesnet_common::physics::{Time as PhysicsTime,TimeInner as PhysicsTimeInner,Instruction as PhysicsInputInstruction}; -use strafesnet_common::instruction::TimedInstruction; -use strafesnet_common::timer::{Scaled,Timer,TimerState}; -use mouse_interpolator::MouseInterpolator; +use strafesnet_common::timer::Timer; - -pub struct FrameState{ - pub body:crate::physics::Body, - pub camera:crate::physics::PhysicsCamera, - pub time:PhysicsTime, -} - -#[derive(Debug)] -pub enum InputInstruction{ - MoveMouse(glam::IVec2), - MoveRight(bool), - MoveUp(bool), - MoveBack(bool), - MoveLeft(bool), - MoveDown(bool), - MoveForward(bool), - Jump(bool), - Zoom(bool), - ResetAndRestart, - ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId), - PracticeFly, -} pub enum Instruction{ - Input(InputInstruction), + Input(SessionInputInstruction), + SetPaused(bool), Render, Resize(winit::dpi::PhysicalSize), ChangeMap(strafesnet_common::map::CompleteMap), - //SetPaused is not an InputInstruction: the physics doesn't know that it's paused. - SetPaused(bool), - //Graphics(crate::graphics_worker::Instruction), } -mod mouse_interpolator{ - use super::*; - //TODO: move this or tab -pub struct MouseInterpolator{ - //"PlayerController" - user_settings:crate::settings::UserSettings, - //"MouseInterpolator" - timeline:std::collections::VecDeque>, - last_mouse_time:PhysicsTime, - mouse_blocking:bool, - //"Simulation" - timer:Timer>, - physics:crate::physics::PhysicsContext, -} -impl MouseInterpolator{ - pub fn new( - physics:crate::physics::PhysicsContext, - user_settings:crate::settings::UserSettings, - )->MouseInterpolator{ - MouseInterpolator{ - mouse_blocking:true, - last_mouse_time:physics.get_next_mouse().time, - timeline:std::collections::VecDeque::new(), - timer:Timer::from_state(Scaled::identity(),false), - physics, - user_settings, - } - } - fn push_mouse_instruction(&mut self,ins:&TimedInstruction,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:self.timer.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:self.physics.get_next_mouse().pos}, - MouseState{time:self.timer.time(ins.time),pos:m} - ), - }); - //delay physics execution until we have an interpolation target - self.mouse_blocking=true; - } - self.last_mouse_time=self.timer.time(ins.time); - } - fn push(&mut self,time:SessionTime,phys_input:PhysicsInputInstruction){ - //This is always a non-mouse event - self.timeline.push_back(TimedInstruction{ - time:self.timer.time(time), - instruction:phys_input, - }); - } - /// returns should_empty_queue - /// may or may not mutate internal state XD! - fn map_instruction(&mut self,ins:&TimedInstruction)->bool{ - let mut update_mouse_blocking=true; - match &ins.instruction{ - Instruction::Input(input_instruction)=>match input_instruction{ - &InputInstruction::MoveMouse(m)=>{ - if !self.timer.is_paused(){ - self.push_mouse_instruction(ins,m); - } - update_mouse_blocking=false; - }, - &InputInstruction::MoveForward(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveForward(s)), - &InputInstruction::MoveLeft(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveLeft(s)), - &InputInstruction::MoveBack(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveBack(s)), - &InputInstruction::MoveRight(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveRight(s)), - &InputInstruction::MoveUp(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveUp(s)), - &InputInstruction::MoveDown(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveDown(s)), - &InputInstruction::Jump(s)=>self.push(ins.time,PhysicsInputInstruction::SetJump(s)), - &InputInstruction::Zoom(s)=>self.push(ins.time,PhysicsInputInstruction::SetZoom(s)), - &InputInstruction::ResetAndSpawn(mode_id,stage_id)=>{ - self.push(ins.time,PhysicsInputInstruction::Reset); - self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity())); - self.push(ins.time,PhysicsInputInstruction::Spawn(mode_id,stage_id)); - }, - InputInstruction::ResetAndRestart=>{ - self.push(ins.time,PhysicsInputInstruction::Reset); - self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity())); - self.push(ins.time,PhysicsInputInstruction::Restart); - }, - InputInstruction::PracticeFly=>self.push(ins.time,PhysicsInputInstruction::PracticeFly), - }, - //do these really need to idle the physics? - //sending None dumps the instruction queue - Instruction::ChangeMap(_)=>self.push(ins.time,PhysicsInputInstruction::Idle), - Instruction::Resize(_)=>self.push(ins.time,PhysicsInputInstruction::Idle), - Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle), - &Instruction::SetPaused(paused)=>{ - if let Err(e)=self.timer.set_paused(ins.time,paused){ - println!("Cannot SetPaused: {e}"); - } - self.push(ins.time,PhysicsInputInstruction::Idle); - }, - } - if update_mouse_blocking{ - //this returns the bool for us - self.update_mouse_blocking(ins.time) - }else{ - //do flush that queue - true - } - } - /// must check if self.mouse_blocking==true before calling! - fn unblock_mouse(&mut self,time:SessionTime){ - //push an event to extrapolate no movement from - self.timeline.push_front(TimedInstruction{ - time:self.last_mouse_time, - instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(time),pos:self.physics.get_next_mouse().pos}), - }); - self.last_mouse_time=self.timer.time(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; - } - fn update_mouse_blocking(&mut self,time:SessionTime)->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 PhysicsTime::from_millis(10)){ - let should_empty_queue=self.map_instruction(ins); - if should_empty_queue{ - self.empty_queue(); - } - } - pub fn get_frame_state(&self,time:SessionTime)->FrameState{ - FrameState{ - body:self.physics.camera_body(), - camera:self.physics.camera(), - time:self.timer.time(time), - } - } - pub fn change_map(&mut self,time:SessionTime,map:&strafesnet_common::map::CompleteMap){ - //dump any pending interpolation state - if self.mouse_blocking{ - self.unblock_mouse(time); - } - self.empty_queue(); - - //doing it like this to avoid doing PhysicsInstruction::ChangeMap(Rc) - self.physics.generate_models(&map); - - //use the standard input interface so the instructions are written out to bots - self.handle_instruction(&TimedInstruction{ - time, - instruction:Instruction::Input(InputInstruction::ResetAndSpawn( - strafesnet_common::gameplay_modes::ModeId::MAIN, - strafesnet_common::gameplay_modes::StageId::FIRST, - )), - }); - } - pub const fn user_settings(&self)->&crate::settings::UserSettings{ - &self.user_settings - } -} -} +const SESSION_INSTRUCTION_IDLE:SessionInstruction=SessionInstruction::Input(SessionInputInstruction::Other(strafesnet_common::physics::OtherOtherInstruction::Idle)); pub fn new<'a>( mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>, user_settings:crate::settings::UserSettings, )->crate::compat_worker::QNWorker<'a,TimedInstruction>{ let physics=crate::physics::PhysicsContext::default(); - let mut interpolator=MouseInterpolator::new( - physics, - user_settings + let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO); + let simulation=Simulation::new(timer,physics); + let mut session=Session::new( + user_settings, + simulation, ); crate::compat_worker::QNWorker::new(move |ins:TimedInstruction|{ - interpolator.handle_instruction(&ins); + // excruciating pain + macro_rules! run_session_instruction{ + ($time:expr,$instruction:expr)=>{ + session.process_instruction(TimedInstruction{ + time:$time, + instruction:$instruction, + }); + }; + } + macro_rules! run_graphics_worker_instruction{ + ($instruction:expr)=>{ + graphics_worker.send($instruction).unwrap(); + }; + } match ins.instruction{ + Instruction::Input(unbuffered_instruction)=>{ + run_session_instruction!(ins.time,SessionInstruction::Input(unbuffered_instruction)); + }, + Instruction::SetPaused(paused)=>{ + run_session_instruction!(ins.time,SessionInstruction::SetPaused(paused)); + }, Instruction::Render=>{ - let frame_state=interpolator.get_frame_state(ins.time); - graphics_worker.send(crate::graphics_worker::Instruction::Render(frame_state)).unwrap(); + run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE); + let frame_state=session.get_frame_state(ins.time); + run_graphics_worker_instruction!(GraphicsInstruction::Render(frame_state)); }, - Instruction::Resize(size)=>{ - graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,interpolator.user_settings().clone())).unwrap(); + Instruction::Resize(physical_size)=>{ + run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE); + let user_settings=session.user_settings().clone(); + run_graphics_worker_instruction!(GraphicsInstruction::Resize(physical_size,user_settings)); }, - Instruction::ChangeMap(map)=>{ - interpolator.change_map(ins.time,&map); - graphics_worker.send(crate::graphics_worker::Instruction::ChangeMap(map)).unwrap(); + Instruction::ChangeMap(complete_map)=>{ + run_session_instruction!(ins.time,SessionInstruction::ChangeMap(&complete_map)); + run_graphics_worker_instruction!(GraphicsInstruction::ChangeMap(complete_map)); }, - Instruction::Input(_)=>(), - Instruction::SetPaused(_)=>(), } }) } diff --git a/strafe-client/src/session.rs b/strafe-client/src/session.rs new file mode 100644 index 0000000..fce8439 --- /dev/null +++ b/strafe-client/src/session.rs @@ -0,0 +1,193 @@ +use strafesnet_common::gameplay_modes::{ModeId,StageId}; +use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction}; +// session represents the non-hardware state of the client. +// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state. +use strafesnet_common::physics::{ + ModeInstruction,OtherInstruction,OtherOtherInstruction, + Instruction as PhysicsInputInstruction, + TimeInner as PhysicsTimeInner, + Time as PhysicsTime +}; +use strafesnet_common::timer::{Scaled,Timer}; +use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime}; + +use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction}; +use crate::settings::UserSettings; + +pub enum Instruction<'a>{ + Input(SessionInputInstruction), + SetPaused(bool), + ChangeMap(&'a strafesnet_common::map::CompleteMap), + //Graphics(crate::graphics_worker::Instruction), +} + +pub enum SessionInputInstruction{ + Mouse(glam::IVec2), + SetControl(strafesnet_common::physics::SetControlInstruction), + Mode(ImplicitModeInstruction), + Other(strafesnet_common::physics::OtherOtherInstruction), +} +/// Implicit mode instruction are fed separately to session. +/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction +#[derive(Clone,Debug)] +pub enum ImplicitModeInstruction{ + ResetAndRestart, + ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId), +} + +pub struct FrameState{ + pub body:crate::physics::Body, + pub camera:crate::physics::PhysicsCamera, + pub time:PhysicsTime, +} + +pub struct Simulation{ + timer:Timer>, + physics:crate::physics::PhysicsContext, +} +impl Simulation{ + pub const fn new( + timer:Timer>, + physics:crate::physics::PhysicsContext, + )->Self{ + Self{ + timer, + physics, + } + } + pub fn get_frame_state(&self,time:SessionTime)->FrameState{ + FrameState{ + body:self.physics.camera_body(), + camera:self.physics.camera(), + time:self.timer.time(time), + } + } +} + +pub struct Replay{ + last_instruction_id:usize, + instructions:Vec, + simulation:Simulation, +} +impl Replay{ + pub const fn new( + instructions:Vec, + simulation:Simulation, + )->Self{ + Self{ + last_instruction_id:0, + instructions, + simulation, + } + } +} + +pub struct Session{ + user_settings:UserSettings, + mouse_interpolator:crate::mouse_interpolator::MouseInterpolator, + //gui:GuiState + simulation:Simulation, + replays:Vec, +} +impl Session{ + pub fn new( + user_settings:UserSettings, + simulation:Simulation, + )->Self{ + Self{ + user_settings, + mouse_interpolator:MouseInterpolator::new(), + simulation, + replays:Vec::new(), + } + } + fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){ + self.simulation.physics.generate_models(map); + } + pub fn get_frame_state(&self,time:SessionTime)->FrameState{ + self.simulation.get_frame_state(time) + } + pub fn user_settings(&self)->&UserSettings{ + &self.user_settings + } +} + +// mouseinterpolator consumes RawInputInstruction +// mouseinterpolator emits PhysicsInputInstruction +// mouseinterpolator consumes DoStep to move on to the next emitted instruction +// Session comsumes SessionInstruction -> forwards RawInputInstruction to mouseinterpolator +// Session consumes DoStep -> forwards DoStep to mouseinterpolator +// Session emits DoStep + +impl InstructionConsumer> for Session{ + type TimeInner=SessionTimeInner; + fn process_instruction(&mut self,ins:TimedInstruction){ + macro_rules! run_mouse_interpolator_instruction{ + ($instruction:expr)=>{ + self.mouse_interpolator.process_instruction(TimedInstruction{ + time:ins.time, + instruction:TimedInstruction{ + time:self.simulation.timer.time(ins.time), + instruction:$instruction, + }, + }); + }; + } + match ins.instruction{ + // send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime + Instruction::Input(SessionInputInstruction::Mouse(pos))=>{ + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos)); + }, + Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{ + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::SetControl(set_control_instruction))); + }, + Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{ + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset))); + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())))); + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Restart))); + }, + Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{ + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset))); + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())))); + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id)))); + }, + Instruction::Input(SessionInputInstruction::Other(other_other_instruction))=>{ + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(other_other_instruction))); + }, + Instruction::SetPaused(paused)=>{ + // don't flush the buffered instructions in the mouse interpolator + // until the mouse is confirmed to be not moving at a later time + // what if they pause for 5ms lmao + _=self.simulation.timer.set_paused(ins.time,paused); + } + Instruction::ChangeMap(complete_map)=>{ + self.change_map(complete_map); + // ResetAndSpawn + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset))); + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())))); + run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(ModeId::MAIN,StageId::FIRST)))); + }, + }; + // run all buffered instruction produced + self.process_exhaustive(ins.time); + } +} +impl InstructionConsumer for Session{ + type TimeInner=SessionTimeInner; + fn process_instruction(&mut self,ins:TimedInstruction){ + // ins.time ignored??? + let ins_retimed=TimedInstruction{ + time:self.simulation.timer.time(ins.time), + instruction:ins.instruction, + }; + if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins_retimed){ + self.simulation.physics.run_input_instruction(instruction); + } + } +} +impl InstructionEmitter for Session{ + type TimeInner=SessionTimeInner; + fn next_instruction(&self,time_limit:SessionTime)->Option>{ + self.mouse_interpolator.next_instruction(time_limit) + } +} diff --git a/strafe-client/src/setup.rs b/strafe-client/src/setup.rs index af81dd7..3b244c2 100644 --- a/strafe-client/src/setup.rs +++ b/strafe-client/src/setup.rs @@ -1,4 +1,4 @@ -use crate::window::WindowInstruction; +use crate::window::Instruction; use strafesnet_common::instruction::TimedInstruction; use strafesnet_common::integer; use strafesnet_common::session::TimeInner as SessionTimeInner; @@ -224,7 +224,7 @@ pub fn setup_and_start(title:&str){ let path=std::path::PathBuf::from(arg); window_thread.send(TimedInstruction{ time:integer::Time::ZERO, - instruction:WindowInstruction::WindowEvent(winit::event::WindowEvent::DroppedFile(path)), + instruction:Instruction::WindowEvent(winit::event::WindowEvent::DroppedFile(path)), }).unwrap(); }; @@ -235,7 +235,7 @@ pub fn setup_and_start(title:&str){ fn run_event_loop( event_loop:winit::event_loop::EventLoop<()>, - mut window_thread:crate::compat_worker::QNWorker>, + mut window_thread:crate::compat_worker::QNWorker>, root_time:std::time::Instant )->Result<(),winit::error::EventLoopError>{ event_loop.run(move |event,elwt|{ @@ -247,7 +247,7 @@ fn run_event_loop( // }; match event{ winit::event::Event::AboutToWait=>{ - window_thread.send(TimedInstruction{time,instruction:WindowInstruction::RequestRedraw}).unwrap(); + window_thread.send(TimedInstruction{time,instruction:Instruction::RequestRedraw}).unwrap(); } winit::event::Event::WindowEvent { event: @@ -259,7 +259,7 @@ fn run_event_loop( winit::event::WindowEvent::Resized(size),//ignoring scale factor changed for now because mutex bruh window_id:_, } => { - window_thread.send(TimedInstruction{time,instruction:WindowInstruction::Resize(size)}).unwrap(); + window_thread.send(TimedInstruction{time,instruction:Instruction::Resize(size)}).unwrap(); } winit::event::Event::WindowEvent{event,..}=>match event{ winit::event::WindowEvent::KeyboardInput{ @@ -275,17 +275,17 @@ fn run_event_loop( elwt.exit(); } winit::event::WindowEvent::RedrawRequested=>{ - window_thread.send(TimedInstruction{time,instruction:WindowInstruction::Render}).unwrap(); + window_thread.send(TimedInstruction{time,instruction:Instruction::Render}).unwrap(); } _=>{ - window_thread.send(TimedInstruction{time,instruction:WindowInstruction::WindowEvent(event)}).unwrap(); + window_thread.send(TimedInstruction{time,instruction:Instruction::WindowEvent(event)}).unwrap(); } }, winit::event::Event::DeviceEvent{ event, .. } => { - window_thread.send(TimedInstruction{time,instruction:WindowInstruction::DeviceEvent(event)}).unwrap(); + window_thread.send(TimedInstruction{time,instruction:Instruction::DeviceEvent(event)}).unwrap(); }, _=>{} } diff --git a/strafe-client/src/window.rs b/strafe-client/src/window.rs index 20a9027..941eb71 100644 --- a/strafe-client/src/window.rs +++ b/strafe-client/src/window.rs @@ -1,9 +1,10 @@ -use crate::physics_worker::InputInstruction; -use strafesnet_common::integer; use strafesnet_common::instruction::TimedInstruction; use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; +use strafesnet_common::physics::{OtherInstruction,OtherOtherInstruction,SetControlInstruction}; +use crate::physics_worker::Instruction as PhysicsWorkerInstruction; +use crate::session::SessionInputInstruction; -pub enum WindowInstruction{ +pub enum Instruction{ Resize(winit::dpi::PhysicalSize), WindowEvent(winit::event::WindowEvent), DeviceEvent(winit::event::DeviceEvent), @@ -14,10 +15,10 @@ pub enum WindowInstruction{ //holds thread handles to dispatch to struct WindowContext<'a>{ manual_mouse_lock:bool, - mouse:strafesnet_common::mouse::MouseState,//std::sync::Arc> + mouse_pos:glam::DVec2, screen_size:glam::UVec2, window:&'a winit::window::Window, - physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction>, + physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction>, } impl WindowContext<'_>{ @@ -28,7 +29,7 @@ impl WindowContext<'_>{ match event{ 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::ChangeMap(map)}).unwrap(), + Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(), Err(e)=>println!("Failed to load map: {e}"), } }, @@ -36,7 +37,7 @@ impl WindowContext<'_>{ //pause unpause self.physics_thread.send(TimedInstruction{ time, - instruction:crate::physics_worker::Instruction::SetPaused(!state), + instruction:PhysicsWorkerInstruction::SetPaused(!state), }).unwrap(); //recalculate pressed keys on focus }, @@ -91,29 +92,29 @@ impl WindowContext<'_>{ }, (keycode,state)=>{ let s=state.is_pressed(); - if let Some(input_instruction)=match keycode{ - winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(InputInstruction::Jump(s)), + if let Some(session_input_instruction)=match keycode{ + winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(s))), winit::keyboard::Key::Character(key)=>match key.as_str(){ - "W"|"w"=>Some(InputInstruction::MoveForward(s)), - "A"|"a"=>Some(InputInstruction::MoveLeft(s)), - "S"|"s"=>Some(InputInstruction::MoveBack(s)), - "D"|"d"=>Some(InputInstruction::MoveRight(s)), - "E"|"e"=>Some(InputInstruction::MoveUp(s)), - "Q"|"q"=>Some(InputInstruction::MoveDown(s)), - "Z"|"z"=>Some(InputInstruction::Zoom(s)), - "R"|"r"=>if s{ + "W"|"w"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveForward(s))), + "A"|"a"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveLeft(s))), + "S"|"s"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveBack(s))), + "D"|"d"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveRight(s))), + "E"|"e"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveUp(s))), + "Q"|"q"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveDown(s))), + "Z"|"z"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetZoom(s))), + "R"|"r"=>s.then(||{ //mouse needs to be reset since the position is absolute - self.mouse=strafesnet_common::mouse::MouseState::default(); - Some(InputInstruction::ResetAndRestart) - }else{None}, - "F"|"f"=>if s{Some(InputInstruction::PracticeFly)}else{None}, + self.mouse_pos=glam::DVec2::ZERO; + SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndRestart) + }), + "F"|"f"=>s.then_some(SessionInputInstruction::Other(OtherOtherInstruction::PracticeFly)), _=>None, }, _=>None, }{ self.physics_thread.send(TimedInstruction{ time, - instruction:crate::physics_worker::Instruction::Input(input_instruction), + instruction:PhysicsWorkerInstruction::Input(session_input_instruction), }).unwrap(); } }, @@ -126,7 +127,7 @@ impl WindowContext<'_>{ fn device_event(&mut self,time:SessionTime,event: winit::event::DeviceEvent){ match event{ winit::event::DeviceEvent::MouseMotion{ - delta,//these (f64,f64) are integers on my machine + delta, }=>{ if self.manual_mouse_lock{ match self.window.set_cursor_position(self.get_middle_of_screen()){ @@ -134,14 +135,10 @@ impl WindowContext<'_>{ Err(e)=>println!("Could not set cursor position: {:?}",e), } } - //do not step the physics because the mouse polling rate is higher than the physics can run. - //essentially the previous input will be overwritten until a true step runs - //which is fine because they run all the time. - let delta=glam::ivec2(delta.0 as i32,delta.1 as i32); - self.mouse.pos+=delta; + self.mouse_pos+=glam::dvec2(delta.0,delta.1); self.physics_thread.send(TimedInstruction{ time, - instruction:crate::physics_worker::Instruction::Input(InputInstruction::MoveMouse(self.mouse.pos)), + instruction:PhysicsWorkerInstruction::Input(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())), }).unwrap(); }, winit::event::DeviceEvent::MouseWheel { @@ -151,7 +148,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:PhysicsWorkerInstruction::Input(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(true))),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump }).unwrap(); } }, @@ -162,7 +159,7 @@ impl WindowContext<'_>{ pub fn worker<'a>( window:&'a winit::window::Window, setup_context:crate::setup::SetupContext<'a>, -)->crate::compat_worker::QNWorker<'a,TimedInstruction>{ +)->crate::compat_worker::QNWorker<'a,TimedInstruction>{ // WindowContextSetup::new let user_settings=crate::settings::read_user_settings(); @@ -174,7 +171,7 @@ pub fn worker<'a>( let graphics_thread=crate::graphics_worker::new(graphics,setup_context.config,setup_context.surface,setup_context.device,setup_context.queue); let mut window_context=WindowContext{ manual_mouse_lock:false, - mouse:strafesnet_common::mouse::MouseState::default(), + mouse_pos:glam::DVec2::ZERO, //make sure to update this!!!!! screen_size, window, @@ -185,30 +182,30 @@ pub fn worker<'a>( }; //WindowContextSetup::into_worker - crate::compat_worker::QNWorker::new(move |ins:TimedInstruction|{ + crate::compat_worker::QNWorker::new(move |ins:TimedInstruction|{ match ins.instruction{ - WindowInstruction::RequestRedraw=>{ + Instruction::RequestRedraw=>{ window_context.window.request_redraw(); } - WindowInstruction::WindowEvent(window_event)=>{ + Instruction::WindowEvent(window_event)=>{ window_context.window_event(ins.time,window_event); }, - WindowInstruction::DeviceEvent(device_event)=>{ + Instruction::DeviceEvent(device_event)=>{ window_context.device_event(ins.time,device_event); }, - WindowInstruction::Resize(size)=>{ + Instruction::Resize(size)=>{ window_context.physics_thread.send( TimedInstruction{ time:ins.time, - instruction:crate::physics_worker::Instruction::Resize(size) + instruction:PhysicsWorkerInstruction::Resize(size) } ).unwrap(); } - WindowInstruction::Render=>{ + Instruction::Render=>{ window_context.physics_thread.send( TimedInstruction{ time:ins.time, - instruction:crate::physics_worker::Instruction::Render + instruction:PhysicsWorkerInstruction::Render } ).unwrap(); } diff --git a/strafe-client/src/worker.rs b/strafe-client/src/worker.rs index ada128a..52bb351 100644 --- a/strafe-client/src/worker.rs +++ b/strafe-client/src/worker.rs @@ -190,7 +190,7 @@ mod test{ for _ in 0..5 { let task = instruction::TimedInstruction{ time:strafesnet_common::physics::Time::ZERO, - instruction:strafesnet_common::physics::Instruction::Idle, + instruction:strafesnet_common::physics::Instruction::IDLE, }; worker.send(task).unwrap(); } @@ -204,7 +204,7 @@ mod test{ // Send a new task let task = instruction::TimedInstruction{ time:integer::Time::ZERO, - instruction:strafesnet_common::physics::Instruction::Idle, + instruction:strafesnet_common::physics::Instruction::IDLE, }; worker.send(task).unwrap();