refactor physics to use shared context for multiple simulations

This commit is contained in:
Quaternions 2025-01-16 18:51:22 -08:00
parent 28499800cb
commit 2faa61225f
3 changed files with 65 additions and 56 deletions

View File

@ -829,17 +829,7 @@ pub struct PhysicsState{
//a start zone. If you change mode, a new run is created.
run:run::Run,
}
//random collection of contextual data that doesn't belong in PhysicsState
pub struct PhysicsData{
//permanent map data
bvh:bvh::BvhNode<ConvexMeshId>,
//transient map/environment data (open world loads/unloads parts of this data)
models:PhysicsModels,
//semi-transient data
modes:gameplay_modes::Modes,
//cached calculations
hitbox_mesh:HitboxMesh,
}
impl Default for PhysicsState{
fn default()->Self{
Self{
@ -856,19 +846,18 @@ impl Default for PhysicsState{
}
}
}
impl Default for PhysicsData{
fn default()->Self{
Self{
bvh:bvh::BvhNode::default(),
models:Default::default(),
modes:Default::default(),
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
}
}
}
impl PhysicsState{
fn clear(&mut self){
pub fn camera_body(&self)->Body{
Body{
position:self.body.position+self.style.camera_offset,
..self.body
}
}
pub const fn camera(&self)->PhysicsCamera{
self.camera
}
pub fn clear(&mut self){
self.touching.clear();
}
fn reset_to_default(&mut self){
@ -917,46 +906,67 @@ impl PhysicsState{
// });
// }
}
#[derive(Default)]
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.
// shared geometry for simulations
pub struct PhysicsData{
//permanent map data
bvh:bvh::BvhNode<ConvexMeshId>,
//transient map/environment data (open world loads/unloads parts of this data)
models:PhysicsModels,
//semi-transient data
modes:gameplay_modes::Modes,
//cached calculations
hitbox_mesh:HitboxMesh,
}
impl Default for PhysicsData{
fn default()->Self{
Self{
bvh:bvh::BvhNode::default(),
models:Default::default(),
modes:Default::default(),
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
}
}
}
// the collection of information required to run physics
pub struct PhysicsContext<'a>{
state:&'a mut PhysicsState,//this captures the entire state of the physics.
data:&'a PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
}
// the physics consumes both Instruction and PhysicsInternalInstruction,
// but can only emit PhysicsInternalInstruction
impl InstructionConsumer<InternalInstruction> for PhysicsContext{
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){
atomic_internal_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionConsumer<Instruction> for PhysicsContext{
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){
atomic_input_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionEmitter<InternalInstruction> for PhysicsContext{
impl InstructionEmitter<InternalInstruction> 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<TimedInstruction<InternalInstruction,TimeInner>>{
next_instruction_internal(&self.state,&self.data,time_limit)
}
}
impl PhysicsContext{
pub fn camera_body(&self)->Body{
Body{
position:self.state.body.position+self.state.style.camera_offset,
..self.state.body
impl PhysicsContext<'_>{
pub fn run_input_instruction(
state:&mut PhysicsState,
data:&PhysicsData,
instruction:TimedInstruction<Instruction,TimeInner>
){
let mut context=PhysicsContext{state,data};
context.process_exhaustive(instruction.time);
context.process_instruction(instruction);
}
}
pub const fn camera(&self)->PhysicsCamera{
self.state.camera
}
impl PhysicsData{
/// use with caution, this is the only non-instruction way to mess with physics
pub fn generate_models(&mut self,map:&map::CompleteMap){
self.state.clear();
let mut modes=map.modes.clone();
for mode in &mut modes.modes{
mode.denormalize_data();
@ -1087,17 +1097,12 @@ impl PhysicsContext{
(IntersectAttributesId::new(attr_id as u32),attr)
).collect(),
};
self.data.bvh=bvh;
self.data.models=models;
self.data.modes=modes;
self.bvh=bvh;
self.models=models;
self.modes=modes;
//hitbox_mesh is unchanged
println!("Physics Objects: {}",model_count);
}
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<Instruction,TimeInner>){
self.process_exhaustive(instruction.time);
self.process_instruction(instruction);
}
}
//this is the one who asks

View File

@ -21,7 +21,7 @@ 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<Instruction,SessionTimeInner>>{
let physics=crate::physics::PhysicsContext::default();
let physics=crate::physics::PhysicsState::default();
let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO);
let simulation=Simulation::new(timer,physics);
let mut session=Session::new(

View File

@ -14,6 +14,7 @@ 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::physics::{PhysicsContext,PhysicsData};
use crate::settings::UserSettings;
pub enum Instruction<'a>{
@ -60,12 +61,12 @@ pub struct FrameState{
pub struct Simulation{
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
physics:crate::physics::PhysicsContext,
physics:crate::physics::PhysicsState,
}
impl Simulation{
pub const fn new(
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
physics:crate::physics::PhysicsContext,
physics:crate::physics::PhysicsState,
)->Self{
Self{
timer,
@ -106,12 +107,12 @@ impl Replay{
simulation,
}
}
pub fn advance(&mut self,time_limit:SessionTime){
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
let mut time=self.simulation.timer.time(time_limit);
loop{
if let Some(ins)=self.recording.instructions.get(self.last_instruction_id){
if ins.time<time{
self.simulation.physics.run_input_instruction(ins.clone());
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
self.last_instruction_id+=1;
}else{
break;
@ -144,6 +145,7 @@ pub struct Session{
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
view_state:ViewState,
//gui:GuiState
geometry_shared:crate::physics::PhysicsData,
simulation:Simulation,
// below fields not included in lite session
recording:Recording,
@ -158,6 +160,7 @@ impl Session{
Self{
user_settings,
mouse_interpolator:MouseInterpolator::new(),
geometry_shared:Default::default(),
simulation,
view_state:ViewState::Play,
recording:Default::default(),
@ -168,7 +171,8 @@ impl Session{
self.recording.clear();
}
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
self.simulation.physics.generate_models(map);
self.simulation.physics.clear();
self.geometry_shared.generate_models(map);
}
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
match &self.view_state{
@ -339,7 +343,7 @@ impl InstructionConsumer<Instruction<'_>> for Session{
// this just refreshes the replays
for replay in self.replays.values_mut(){
// TODO: filter idles from recording, inject new idles in real time
replay.advance(ins.time);
replay.advance(&self.geometry_shared,ins.time);
}
}
};
@ -355,7 +359,7 @@ impl InstructionConsumer<StepInstruction> for Session{
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
//record
self.recording.instructions.push(instruction.clone());
self.simulation.physics.run_input_instruction(instruction);
PhysicsContext::run_input_instruction(&mut self.simulation.physics,&self.geometry_shared,instruction);
}
}
}