refactor physics instruction processing

This commit is contained in:
Quaternions 2024-08-01 11:47:20 -07:00
parent e04e754abb
commit 76a9b33fb2
2 changed files with 116 additions and 60 deletions

View File

@ -13,8 +13,10 @@ use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer
use strafesnet_common::integer::{self,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2}; use strafesnet_common::integer::{self,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2};
use gameplay::ModeState; use gameplay::ModeState;
//internal influence
//when the physics asks itself what happens next, this is how it's represented
#[derive(Debug)] #[derive(Debug)]
pub enum PhysicsInstruction { enum PhysicsInternalInstruction{
CollisionStart(Collision), CollisionStart(Collision),
CollisionEnd(Collision), CollisionEnd(Collision),
StrafeTick, StrafeTick,
@ -25,12 +27,11 @@ pub enum PhysicsInstruction {
// bool,//true = Trigger; false = teleport // bool,//true = Trigger; false = teleport
// bool,//true = Force // 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)] #[derive(Debug)]
pub enum PhysicsInputInstruction { pub enum PhysicsInputInstruction{
ReplaceMouse(MouseState,MouseState), ReplaceMouse(MouseState,MouseState),
SetNextMouse(MouseState), SetNextMouse(MouseState),
SetMoveRight(bool), SetMoveRight(bool),
@ -47,6 +48,14 @@ pub enum PhysicsInputInstruction {
//for interpolation / networking / playback reasons, most playback heads will always want //for interpolation / networking / playback reasons, most playback heads will always want
//to be 1 instruction ahead to generate the next state for interpolation. //to be 1 instruction ahead to generate the next state for interpolation.
PracticeFly, 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)] #[derive(Clone,Copy,Debug,Default,Hash)]
@ -559,13 +568,13 @@ impl MoveState{
=>None, =>None,
} }
} }
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInstruction>>{ fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
//check if you have a valid walk state and create an instruction //check if you have a valid walk state and create an instruction
match self{ match self{
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{ MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
&TransientAcceleration::Reachable{acceleration:_,time}=>Some(TimedInstruction{ &TransientAcceleration::Reachable{acceleration:_,time}=>Some(TimedInstruction{
time, time,
instruction:PhysicsInstruction::ReachWalkTargetVelocity instruction:PhysicsInternalInstruction::ReachWalkTargetVelocity
}), }),
TransientAcceleration::Unreachable{acceleration:_} TransientAcceleration::Unreachable{acceleration:_}
|TransientAcceleration::Reached |TransientAcceleration::Reached
@ -575,7 +584,7 @@ impl MoveState{
TimedInstruction{ TimedInstruction{
time:strafe.next_tick(time), time:strafe.next_tick(time),
//only poll the physics if there is a before and after mouse event //only poll the physics if there is a before and after mouse event
instruction:PhysicsInstruction::StrafeTick instruction:PhysicsInternalInstruction::StrafeTick
} }
}), }),
MoveState::Water=>None,//TODO MoveState::Water=>None,//TODO
@ -769,7 +778,7 @@ impl TouchingState{
} }
} }
} }
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInstruction>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
let relative_body=VirtualBody::relative(&Body::default(),body).body(time); let relative_body=VirtualBody::relative(&Body::default(),body).body(time);
for contact in &self.contacts{ for contact in &self.contacts{
//detect face slide off //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)|{ collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{
TimedInstruction{ TimedInstruction{
time, time,
instruction:PhysicsInstruction::CollisionEnd( instruction:PhysicsInternalInstruction::CollisionEnd(
Collision::Contact(ContactCollision{convex_mesh_id:contact.convex_mesh_id,face_id:contact.face_id}) 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)|{ collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{
TimedInstruction{ TimedInstruction{
time, time,
instruction:PhysicsInstruction::CollisionEnd( instruction:PhysicsInternalInstruction::CollisionEnd(
Collision::Intersect(IntersectCollision{convex_mesh_id:intersect.convex_mesh_id}) 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){ fn clear(&mut self){
self.touching.clear(); self.touching.clear();
} }
fn advance_time(&mut self, time: Time){ fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction>>{
self.body.advance_time(time);
self.time=time;
}
fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInstruction>>{
self.move_state.next_move_instruction(&self.style.strafe,self.time) self.move_state.next_move_instruction(&self.style.strafe,self.time)
} }
//lmao idk this is convenient //lmao idk this is convenient
@ -1031,31 +1036,33 @@ pub struct PhysicsContext{
state:PhysicsState,//this captures the entire state of the physics. 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. 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<PhysicsInstruction> for PhysicsContext{ impl instruction::InstructionConsumer<PhysicsInstruction> for PhysicsContext{
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction>){ fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction>){
atomic_state_update(&mut self.state,&self.data,ins) atomic_state_update(&mut self.state,&self.data,ins)
} }
} }
impl instruction::InstructionEmitter<PhysicsInstruction> for PhysicsContext{ impl instruction::InstructionEmitter<PhysicsInternalInstruction> for PhysicsContext{
//this little next instruction function can cache its return value and invalidate the cached value by watching the State. //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<TimedInstruction<PhysicsInstruction>>{ fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
literally_next_instruction_but_with_context(&self.state,&self.data,time_limit) next_instruction_internal(&self.state,&self.data,time_limit)
} }
} }
impl PhysicsContext{ impl PhysicsContext{
pub fn clear(&mut self){ pub fn clear(&mut self){
self.state.clear(); self.state.clear();
} }
//TODO: remove non-standard interfaces to process_instruction
pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){ 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, time:self.state.time,
instruction:PhysicsInstruction::SetSensitivity(user_settings.calculate_sensitivity()), instruction:PhysicsInputInstruction::SetSensitivity(user_settings.calculate_sensitivity()),
}); });
} }
pub fn spawn(&mut self){ pub fn spawn(&mut self){
self.process_instruction(TimedInstruction{ self.run_input_instruction(TimedInstruction{
time:self.state.time, time:self.state.time,
instruction:PhysicsInstruction::Input(PhysicsInputInstruction::Reset), instruction:PhysicsInputInstruction::Reset,
}); });
} }
pub const fn output(&self)->PhysicsOutputState{ pub const fn output(&self)->PhysicsOutputState{
@ -1141,16 +1148,19 @@ impl PhysicsContext{
} }
//tickless gaming //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. //prepare is ommitted - everything is done via instructions.
while let Some(instruction)=self.next_instruction(time_limit){//collect while let Some(instruction)=self.next_instruction(time_limit){//collect
//process //process
self.process_instruction(instruction); self.process_instruction(TimedInstruction{
time:instruction.time,
instruction:PhysicsInstruction::Internal(instruction.instruction),
});
//write hash lol //write hash lol
} }
} }
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction>){ pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction>){
self.run(instruction.time); self.run_internal_exhaustive(instruction.time);
self.process_instruction(TimedInstruction{ self.process_instruction(TimedInstruction{
time:instruction.time, time:instruction.time,
instruction:PhysicsInstruction::Input(instruction.instruction), 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<TimedInstruction<PhysicsInstruction>>{ //this is the one who asks
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
//JUST POLLING!!! NO MUTATION //JUST POLLING!!! NO MUTATION
let mut collector = instruction::InstructionCollector::new(time_limit); let mut collector = instruction::InstructionCollector::new(time_limit);
@ -1180,7 +1191,7 @@ impl PhysicsContext{
//temp (?) code to avoid collision loops //temp (?) code to avoid collision loops
.map_or(None,|(face,time)|if time==state.time{None}else{Some((face,time))}) .map_or(None,|(face,time)|if time==state.time{None}else{Some((face,time))})
.map(|(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::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{convex_mesh_id,face_id:face}),
PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{convex_mesh_id}), PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{convex_mesh_id}),
})} })}
@ -1316,27 +1327,18 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
} }
} }
fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction>){ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction>){
match &ins.instruction{ let should_advance_body=match ins.instruction{
PhysicsInstruction::Input(PhysicsInputInstruction::Idle) PhysicsInternalInstruction::CollisionStart(_)
|PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) |PhysicsInternalInstruction::CollisionEnd(_)
|PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_)) |PhysicsInternalInstruction::StrafeTick
|PhysicsInstruction::StrafeTick=>(), |PhysicsInternalInstruction::ReachWalkTargetVelocity=>true,
_=>println!("{}|{:?}",ins.time,ins.instruction), };
} if should_advance_body{
//selectively update body state.body.advance_time(state.time);
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),
}
match ins.instruction{ match ins.instruction{
PhysicsInstruction::CollisionStart(collision)=>{ PhysicsInternalInstruction::CollisionStart(collision)=>{
let convex_mesh_id=collision.convex_mesh_id(); let convex_mesh_id=collision.convex_mesh_id();
match (data.models.attr(convex_mesh_id.model_id),&collision){ match (data.models.attr(convex_mesh_id.model_id),&collision){
(PhysicsCollisionAttributes::Contact{contacting,general},&Collision::Contact(contact))=>{ (PhysicsCollisionAttributes::Contact{contacting,general},&Collision::Contact(contact))=>{
@ -1445,7 +1447,7 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
_=>panic!("invalid pair"), _=>panic!("invalid pair"),
} }
}, },
PhysicsInstruction::CollisionEnd(collision)=>{ PhysicsInternalInstruction::CollisionEnd(collision)=>{
match (data.models.attr(collision.convex_mesh_id().model_id),&collision){ match (data.models.attr(collision.convex_mesh_id().model_id),&collision){
(PhysicsCollisionAttributes::Contact{contacting:_,general:_},&Collision::Contact(contact))=>{ (PhysicsCollisionAttributes::Contact{contacting:_,general:_},&Collision::Contact(contact))=>{
state.touching.remove(&collision);//remove contact before calling contact_constrain_acceleration state.touching.remove(&collision);//remove contact before calling contact_constrain_acceleration
@ -1478,7 +1480,7 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
_=>panic!("invalid pair"), _=>panic!("invalid pair"),
} }
}, },
PhysicsInstruction::StrafeTick=>{ PhysicsInternalInstruction::StrafeTick=>{
//TODO make this less huge //TODO make this less huge
if let Some(strafe_settings)=&state.style.strafe{ if let Some(strafe_settings)=&state.style.strafe{
let controls=state.input_state.controls; let controls=state.input_state.controls;
@ -1496,7 +1498,7 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
} }
} }
} }
PhysicsInstruction::ReachWalkTargetVelocity=>{ PhysicsInternalInstruction::ReachWalkTargetVelocity=>{
match &mut state.move_state{ match &mut state.move_state{
MoveState::Air MoveState::Air
|MoveState::Water |MoveState::Water
@ -1520,10 +1522,48 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
} }
} }
}, },
PhysicsInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity, }
PhysicsInstruction::Input(input_instruction)=>{ }
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction>){
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; let mut b_refresh_walk_target=true;
match input_instruction{ match ins.instruction{
PhysicsInputInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity,
PhysicsInputInstruction::SetNextMouse(m)=>{ PhysicsInputInstruction::SetNextMouse(m)=>{
state.camera.move_mouse(state.input_state.mouse_delta()); state.camera.move_mouse(state.input_state.mouse_delta());
state.input_state.set_next_mouse(m); state.input_state.set_next_mouse(m);
@ -1539,7 +1579,6 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
PhysicsInputInstruction::SetMoveUp(s)=>state.input_state.set_control(Controls::MoveUp,s), PhysicsInputInstruction::SetMoveUp(s)=>state.input_state.set_control(Controls::MoveUp,s),
PhysicsInputInstruction::SetMoveDown(s)=>state.input_state.set_control(Controls::MoveDown,s), PhysicsInputInstruction::SetMoveDown(s)=>state.input_state.set_control(Controls::MoveDown,s),
PhysicsInputInstruction::SetJump(s)=>{ PhysicsInputInstruction::SetJump(s)=>{
b_refresh_walk_target=false;
state.input_state.set_control(Controls::Jump,s); state.input_state.set_control(Controls::Jump,s);
if let Some(walk_state)=state.move_state.get_walk_state(){ if let Some(walk_state)=state.move_state.get_walk_state(){
if let Some(jump_settings)=&state.style.jump{ if let Some(jump_settings)=&state.style.jump{
@ -1548,6 +1587,7 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
state.cull_velocity(&data,jumped_velocity); state.cull_velocity(&data,jumped_velocity);
} }
} }
b_refresh_walk_target=false;
}, },
PhysicsInputInstruction::SetZoom(s)=>{ PhysicsInputInstruction::SetZoom(s)=>{
state.input_state.set_control(Controls::Zoom,s); state.input_state.set_control(Controls::Zoom,s);
@ -1577,13 +1617,29 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
} }
b_refresh_walk_target=false; 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{ if b_refresh_walk_target{
state.apply_input_and_body(data); state.apply_input_and_body(data);
state.cull_velocity(data,state.body.velocity); state.cull_velocity(data,state.body.velocity);
} }
}, }
fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction>){
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}),
} }
} }

View File

@ -190,7 +190,7 @@ mod test{
for _ in 0..5 { for _ in 0..5 {
let task = instruction::TimedInstruction{ let task = instruction::TimedInstruction{
time:integer::Time::ZERO, time:integer::Time::ZERO,
instruction:physics::PhysicsInstruction::StrafeTick, instruction:physics::PhysicsInputInstruction::Idle,
}; };
worker.send(task).unwrap(); worker.send(task).unwrap();
} }
@ -204,7 +204,7 @@ mod test{
// Send a new task // Send a new task
let task = instruction::TimedInstruction{ let task = instruction::TimedInstruction{
time:integer::Time::ZERO, time:integer::Time::ZERO,
instruction:physics::PhysicsInstruction::StrafeTick, instruction:physics::PhysicsInputInstruction::Idle,
}; };
worker.send(task).unwrap(); worker.send(task).unwrap();