refactor physics instruction processing
This is an important engine upgrade: idle events do not donate their timestamp to engine objects and pollute the timeline with unnecessary game ticks that can be represented as analytic continuations of previous game ticks. This means that all "render" tick updates can be dropped from bot timelines. In other words, progressing the physics simulation is invariant to differing subdivisions of an overall time advancement with no external input.
This commit is contained in:
parent
e04e754abb
commit
755adeaefd
170
src/physics.rs
170
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<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
|
||||
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<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);
|
||||
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<TimedInstruction<PhysicsInstruction>>{
|
||||
fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction>>{
|
||||
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<PhysicsInstruction> for PhysicsContext{
|
||||
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction>){
|
||||
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.
|
||||
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInstruction>>{
|
||||
literally_next_instruction_but_with_context(&self.state,&self.data,time_limit)
|
||||
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
|
||||
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<PhysicsInputInstruction>){
|
||||
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<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
|
||||
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<gameplay_attributes::Wormhole>,models
|
||||
}
|
||||
}
|
||||
|
||||
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::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<PhysicsInternalInstruction>){
|
||||
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<gameplay_attributes::Wormhole>,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<gameplay_attributes::Wormhole>,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<gameplay_attributes::Wormhole>,models
|
||||
}
|
||||
}
|
||||
}
|
||||
PhysicsInstruction::ReachWalkTargetVelocity=>{
|
||||
PhysicsInternalInstruction::ReachWalkTargetVelocity=>{
|
||||
match &mut state.move_state{
|
||||
MoveState::Air
|
||||
|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;
|
||||
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<gameplay_attributes::Wormhole>,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<gameplay_attributes::Wormhole>,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<gameplay_attributes::Wormhole>,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<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}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user