From 76a9b33fb24546aa12305995ee971cd527b84f11 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 1 Aug 2024 11:47:20 -0700 Subject: [PATCH] refactor physics instruction processing --- src/physics.rs | 172 ++++++++++++++++++++++++++++++++----------------- src/worker.rs | 4 +- 2 files changed, 116 insertions(+), 60 deletions(-) diff --git a/src/physics.rs b/src/physics.rs index e0f28ae..872efae 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -13,8 +13,10 @@ use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer use strafesnet_common::integer::{self,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2}; use gameplay::ModeState; +//internal influence +//when the physics asks itself what happens next, this is how it's represented #[derive(Debug)] -pub enum PhysicsInstruction { +enum PhysicsInternalInstruction{ CollisionStart(Collision), CollisionEnd(Collision), StrafeTick, @@ -25,12 +27,11 @@ pub enum PhysicsInstruction { // bool,//true = Trigger; false = teleport // bool,//true = Force // ) - //InputInstructions conditionally activate RefreshWalkTarget (by doing what SetWalkTargetVelocity used to do and then flagging it) - Input(PhysicsInputInstruction), - SetSensitivity(Ratio64Vec2), } +//external influence +//this is how you influence the physics from outside #[derive(Debug)] -pub enum PhysicsInputInstruction { +pub enum PhysicsInputInstruction{ ReplaceMouse(MouseState,MouseState), SetNextMouse(MouseState), SetMoveRight(bool), @@ -47,6 +48,14 @@ pub enum PhysicsInputInstruction { //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(Ratio64Vec2), +} +#[derive(Debug)] +enum PhysicsInstruction{ + Internal(PhysicsInternalInstruction), + //InputInstructions conditionally activate RefreshWalkTarget + //(by doing what SetWalkTargetVelocity used to do and then flagging it) + Input(PhysicsInputInstruction), } #[derive(Clone,Copy,Debug,Default,Hash)] @@ -559,13 +568,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:PhysicsInstruction::ReachWalkTargetVelocity + instruction:PhysicsInternalInstruction::ReachWalkTargetVelocity }), TransientAcceleration::Unreachable{acceleration:_} |TransientAcceleration::Reached @@ -575,7 +584,7 @@ impl MoveState{ TimedInstruction{ time:strafe.next_tick(time), //only poll the physics if there is a before and after mouse event - instruction:PhysicsInstruction::StrafeTick + instruction:PhysicsInternalInstruction::StrafeTick } }), MoveState::Water=>None,//TODO @@ -769,7 +778,7 @@ impl TouchingState{ } } } - 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=VirtualBody::relative(&Body::default(),body).body(time); for contact in &self.contacts{ //detect face slide off @@ -778,7 +787,7 @@ impl TouchingState{ collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{ TimedInstruction{ time, - instruction:PhysicsInstruction::CollisionEnd( + instruction:PhysicsInternalInstruction::CollisionEnd( Collision::Contact(ContactCollision{convex_mesh_id:contact.convex_mesh_id,face_id:contact.face_id}) ), } @@ -791,7 +800,7 @@ impl TouchingState{ collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{ TimedInstruction{ time, - instruction:PhysicsInstruction::CollisionEnd( + instruction:PhysicsInternalInstruction::CollisionEnd( Collision::Intersect(IntersectCollision{convex_mesh_id:intersect.convex_mesh_id}) ), } @@ -950,15 +959,11 @@ impl Default for PhysicsData{ } } -impl PhysicsState { +impl PhysicsState{ fn clear(&mut self){ self.touching.clear(); } - fn advance_time(&mut self, time: Time){ - self.body.advance_time(time); - self.time=time; - } - fn next_move_instruction(&self)->Option>{ + fn next_move_instruction(&self)->Option>{ self.move_state.next_move_instruction(&self.style.strafe,self.time) } //lmao idk this is convenient @@ -1031,31 +1036,33 @@ 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 the generic PhysicsInstruction, but can only emit the more narrow PhysicsInternalInstruction impl instruction::InstructionConsumer for PhysicsContext{ fn process_instruction(&mut self,ins:TimedInstruction){ atomic_state_update(&mut self.state,&self.data,ins) } } -impl instruction::InstructionEmitter for PhysicsContext{ +impl instruction::InstructionEmitter for PhysicsContext{ //this little next instruction function can cache its return value and invalidate the cached value by watching the State. - fn next_instruction(&self,time_limit:Time)->Option>{ - literally_next_instruction_but_with_context(&self.state,&self.data,time_limit) + fn next_instruction(&self,time_limit:Time)->Option>{ + next_instruction_internal(&self.state,&self.data,time_limit) } } impl PhysicsContext{ pub fn clear(&mut self){ self.state.clear(); } + //TODO: remove non-standard interfaces to process_instruction pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){ - self.process_instruction(TimedInstruction{ + self.run_input_instruction(TimedInstruction{ time:self.state.time, - instruction:PhysicsInstruction::SetSensitivity(user_settings.calculate_sensitivity()), + instruction:PhysicsInputInstruction::SetSensitivity(user_settings.calculate_sensitivity()), }); } pub fn spawn(&mut self){ - self.process_instruction(TimedInstruction{ + self.run_input_instruction(TimedInstruction{ time:self.state.time, - instruction:PhysicsInstruction::Input(PhysicsInputInstruction::Reset), + instruction:PhysicsInputInstruction::Reset, }); } pub const fn output(&self)->PhysicsOutputState{ @@ -1141,16 +1148,19 @@ impl PhysicsContext{ } //tickless gaming - fn run(&mut self,time_limit:Time){ + fn run_internal_exhaustive(&mut self,time_limit:Time){ //prepare is ommitted - everything is done via instructions. while let Some(instruction)=self.next_instruction(time_limit){//collect //process - self.process_instruction(instruction); + self.process_instruction(TimedInstruction{ + time:instruction.time, + instruction:PhysicsInstruction::Internal(instruction.instruction), + }); //write hash lol } } pub fn run_input_instruction(&mut self,instruction:TimedInstruction){ - self.run(instruction.time); + self.run_internal_exhaustive(instruction.time); self.process_instruction(TimedInstruction{ time:instruction.time, instruction:PhysicsInstruction::Input(instruction.instruction), @@ -1158,7 +1168,8 @@ impl PhysicsContext{ } } - fn literally_next_instruction_but_with_context(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option>{ + //this is the one who asks + fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option>{ //JUST POLLING!!! NO MUTATION let mut collector = instruction::InstructionCollector::new(time_limit); @@ -1180,7 +1191,7 @@ impl PhysicsContext{ //temp (?) code to avoid collision loops .map_or(None,|(face,time)|if time==state.time{None}else{Some((face,time))}) .map(|(face,time)|{ - TimedInstruction{time,instruction:PhysicsInstruction::CollisionStart(match data.models.attr(convex_mesh_id.model_id){ + TimedInstruction{time,instruction:PhysicsInternalInstruction::CollisionStart(match data.models.attr(convex_mesh_id.model_id){ PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{convex_mesh_id,face_id:face}), PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{convex_mesh_id}), })} @@ -1316,27 +1327,18 @@ fn run_teleport_behaviour(wormhole:&Option,models } } - fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ - match &ins.instruction{ - PhysicsInstruction::Input(PhysicsInputInstruction::Idle) - |PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) - |PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_)) - |PhysicsInstruction::StrafeTick=>(), - _=>println!("{}|{:?}",ins.time,ins.instruction), - } - //selectively update body - match &ins.instruction{ - PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>state.time=ins.time,//idle simply updates time - PhysicsInstruction::Input(_) - |PhysicsInstruction::ReachWalkTargetVelocity - |PhysicsInstruction::CollisionStart(_) - |PhysicsInstruction::CollisionEnd(_) - |PhysicsInstruction::StrafeTick - |PhysicsInstruction::SetSensitivity(_) - =>state.advance_time(ins.time), - } +fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ + let should_advance_body=match ins.instruction{ + PhysicsInternalInstruction::CollisionStart(_) + |PhysicsInternalInstruction::CollisionEnd(_) + |PhysicsInternalInstruction::StrafeTick + |PhysicsInternalInstruction::ReachWalkTargetVelocity=>true, + }; + if should_advance_body{ + state.body.advance_time(state.time); + } match ins.instruction{ - PhysicsInstruction::CollisionStart(collision)=>{ + PhysicsInternalInstruction::CollisionStart(collision)=>{ let convex_mesh_id=collision.convex_mesh_id(); match (data.models.attr(convex_mesh_id.model_id),&collision){ (PhysicsCollisionAttributes::Contact{contacting,general},&Collision::Contact(contact))=>{ @@ -1445,7 +1447,7 @@ fn run_teleport_behaviour(wormhole:&Option,models _=>panic!("invalid pair"), } }, - PhysicsInstruction::CollisionEnd(collision)=>{ + PhysicsInternalInstruction::CollisionEnd(collision)=>{ match (data.models.attr(collision.convex_mesh_id().model_id),&collision){ (PhysicsCollisionAttributes::Contact{contacting:_,general:_},&Collision::Contact(contact))=>{ state.touching.remove(&collision);//remove contact before calling contact_constrain_acceleration @@ -1478,7 +1480,7 @@ fn run_teleport_behaviour(wormhole:&Option,models _=>panic!("invalid pair"), } }, - PhysicsInstruction::StrafeTick=>{ + PhysicsInternalInstruction::StrafeTick=>{ //TODO make this less huge if let Some(strafe_settings)=&state.style.strafe{ let controls=state.input_state.controls; @@ -1496,7 +1498,7 @@ fn run_teleport_behaviour(wormhole:&Option,models } } } - PhysicsInstruction::ReachWalkTargetVelocity=>{ + PhysicsInternalInstruction::ReachWalkTargetVelocity=>{ match &mut state.move_state{ MoveState::Air |MoveState::Water @@ -1520,10 +1522,48 @@ fn run_teleport_behaviour(wormhole:&Option,models } } }, - PhysicsInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity, - PhysicsInstruction::Input(input_instruction)=>{ + } + } + +fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ + 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::SetZoom(..) + |PhysicsInputInstruction::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(..)=>{ + //technically this could be refined further + //and only advance if you are moving relative to the contact + //but this is good enough for now + match &state.move_state{ + MoveState::Fly + |MoveState::Water + |MoveState::Walk(_) + |MoveState::Ladder(_)=>true, + MoveState::Air=>false, + } + }, + //the body must be updated unconditionally + PhysicsInputInstruction::PracticeFly=>true, + }; + if should_advance_body{ + state.body.advance_time(state.time); + } + //TODO: UNTAB let mut b_refresh_walk_target=true; - match input_instruction{ + 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); @@ -1539,7 +1579,6 @@ fn run_teleport_behaviour(wormhole:&Option,models 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)=>{ - b_refresh_walk_target=false; 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{ @@ -1548,6 +1587,7 @@ fn run_teleport_behaviour(wormhole:&Option,models state.cull_velocity(&data,jumped_velocity); } } + b_refresh_walk_target=false; }, PhysicsInputInstruction::SetZoom(s)=>{ state.input_state.set_control(Controls::Zoom,s); @@ -1577,13 +1617,29 @@ fn run_teleport_behaviour(wormhole:&Option,models } b_refresh_walk_target=false; }, - PhysicsInputInstruction::Idle=>{b_refresh_walk_target=false;},//literally idle! + PhysicsInputInstruction::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); } - }, +} + + fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ + match &ins.instruction{ + PhysicsInstruction::Input(PhysicsInputInstruction::Idle) + |PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) + |PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_)) + |PhysicsInstruction::Internal(PhysicsInternalInstruction::StrafeTick)=>(), + _=>println!("{}|{:?}",ins.time,ins.instruction), + } + state.time=ins.time; + match ins.instruction{ + PhysicsInstruction::Internal(instruction)=>atomic_internal_instruction(state,data,TimedInstruction{time:ins.time,instruction}), + PhysicsInstruction::Input(instruction)=>atomic_input_instruction(state,data,TimedInstruction{time:ins.time,instruction}), } } diff --git a/src/worker.rs b/src/worker.rs index b0a3b43..325a3b9 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -190,7 +190,7 @@ mod test{ for _ in 0..5 { let task = instruction::TimedInstruction{ time:integer::Time::ZERO, - instruction:physics::PhysicsInstruction::StrafeTick, + instruction:physics::PhysicsInputInstruction::Idle, }; worker.send(task).unwrap(); } @@ -204,7 +204,7 @@ mod test{ // Send a new task let task = instruction::TimedInstruction{ time:integer::Time::ZERO, - instruction:physics::PhysicsInstruction::StrafeTick, + instruction:physics::PhysicsInputInstruction::Idle, }; worker.send(task).unwrap();