fixup strafe client for strongly typed time

This commit is contained in:
Quaternions 2025-01-07 22:54:58 -08:00
parent 004e0d3776
commit 2f7597146e
11 changed files with 240 additions and 220 deletions

160
strafe-client/src/body.rs Normal file
View File

@ -0,0 +1,160 @@
use strafesnet_common::aabb;
use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3};
#[derive(Clone,Copy,Debug,Hash)]
pub struct Body<T>{
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
pub time:Time<T>,//nanoseconds x xxxxD!
}
impl<T> std::ops::Neg for Body<T>{
type Output=Self;
fn neg(self)->Self::Output{
Self{
position:self.position,
velocity:-self.velocity,
acceleration:self.acceleration,
time:-self.time,
}
}
}
impl<T> Body<T>
where Time<T>:Copy,
{
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
Self{
position,
velocity,
acceleration,
time,
}
}
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time;
self.position
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
}
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time;
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
}
pub fn advance_time(&mut self,time:Time<T>){
self.position=self.extrapolated_position(time);
self.velocity=self.extrapolated_velocity(time);
self.time=time;
}
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
// Why?
// All of this can be removed with const generics because the type can be specified as
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
// which is known to implement all the necessary traits
Num:Copy,
Den:Copy+core::ops::Mul<i64,Output=D1>,
D1:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<D1,Output=N2>,
N1:core::ops::Add<N2,Output=N3>,
Num:core::ops::Mul<N3,Output=N4>,
Den:core::ops::Mul<D1,Output=D2>,
D2:Copy,
Planar64:core::ops::Mul<D2,Output=N4>,
N4:integer::Divide<D2,Output=T1>,
T1:integer::Fix<Planar64>,
{
// a*dt^2/2 + v*dt + p
// (a*dt/2+v)*dt+p
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
.map(|elem|elem.divide().fix())+self.position
}
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
// a*dt + v
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
}
pub fn advance_time_ratio_dt(&mut self,dt:crate::model_physics::GigaTime){
self.position=self.extrapolated_position_ratio_dt(dt);
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
self.time+=dt.into();
}
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
if self.velocity==vec3::ZERO{
if self.acceleration==vec3::ZERO{
None
}else{
Some(self.acceleration)
}
}else{
Some(self.velocity)
}
}
pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time<T>,t1:Time<T>){
aabb.grow(self.extrapolated_position(t0));
aabb.grow(self.extrapolated_position(t1));
//v+a*t==0
//goober code
if !self.acceleration.x.is_zero(){
let t=-self.velocity.x/self.acceleration.x;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
if !self.acceleration.y.is_zero(){
let t=-self.velocity.y/self.acceleration.y;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
if !self.acceleration.z.is_zero(){
let t=-self.velocity.z/self.acceleration.z;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
}
}
impl<T> std::fmt::Display for Body<T>{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
}
}
pub struct VirtualBody<'a,T>{
body0:&'a Body<T>,
body1:&'a Body<T>,
}
impl<T> VirtualBody<'_,T>
where Time<T>:Copy,
{
pub const fn relative<'a>(body0:&'a Body<T>,body1:&'a Body<T>)->VirtualBody<'a,T>{
//(p0,v0,a0,t0)
//(p1,v1,a1,t1)
VirtualBody{
body0,
body1,
}
}
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
}
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
}
pub fn acceleration(&self)->Planar64Vec3{
self.body1.acceleration-self.body0.acceleration
}
pub fn body(&self,time:Time<T>)->Body<T>{
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
}
}

View File

@ -1,6 +1,6 @@
use crate::physics::Body;
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge,MinkowskiMesh,MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert};
use strafesnet_common::integer::{Time,Fixed,Ratio};
use strafesnet_common::integer::{Fixed,Ratio};
use crate::physics::{Time,Body};
#[derive(Debug)]
enum Transition<F,E:DirectedEdge,V>{

View File

@ -98,12 +98,6 @@ impl std::default::Default for GraphicsCamera{
}
}
pub struct FrameState{
pub body:crate::physics::Body,
pub camera:crate::physics::PhysicsCamera,
pub time:integer::Time,
}
pub struct GraphicsState{
pipelines:GraphicsPipelines,
bind_groups:GraphicsBindGroups,
@ -882,7 +876,7 @@ impl GraphicsState{
view:&wgpu::TextureView,
device:&wgpu::Device,
queue:&wgpu::Queue,
frame_state:FrameState,
frame_state:crate::physics_worker::FrameState,
){
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input

View File

@ -1,5 +1,5 @@
pub enum Instruction{
Render(crate::graphics::FrameState),
Render(crate::physics_worker::FrameState),
//UpdateModel(crate::graphics::GraphicsModelUpdate),
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
ChangeMap(strafesnet_common::map::CompleteMap),

View File

@ -1,3 +1,4 @@
mod body;
mod file;
mod setup;
mod window;

View File

@ -3,6 +3,9 @@ use std::collections::{HashSet,HashMap};
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::model::{self,MeshId,PolygonIter};
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
use strafesnet_common::physics::Time;
type Body=crate::body::Body<strafesnet_common::physics::TimeInner>;
pub trait UndirectedEdge{
type DirectedEdge:Copy+DirectedEdge;
@ -700,7 +703,7 @@ impl MinkowskiMesh<'_>{
},
}
}
fn closest_fev_not_inside(&self,mut infinity_body:crate::physics::Body)->Option<FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>>{
fn closest_fev_not_inside(&self,mut infinity_body:Body)->Option<FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>>{
infinity_body.infinity_dir().map_or(None,|dir|{
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
//a line is simpler to solve than a parabola
@ -708,13 +711,13 @@ impl MinkowskiMesh<'_>{
infinity_body.acceleration=vec3::ZERO;
//crawl in from negative infinity along a tangent line to get the closest fev
// TODO: change crawl_fev args to delta time? Optional values?
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,Time::MIN/4,infinity_body.time){
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
crate::face_crawler::CrawlResult::Hit(_,_)=>None,
}
})
}
pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
pub fn predict_collision_in(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{
//continue forwards along the body parabola
match crate::face_crawler::crawl_fev(fev,self,relative_body,relative_body.time,time_limit){
@ -723,9 +726,9 @@ impl MinkowskiMesh<'_>{
}
})
}
pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
pub fn predict_collision_out(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
//create an extrapolated body at time_limit
let infinity_body=crate::physics::Body::new(
let infinity_body=Body::new(
relative_body.extrapolated_position(time_limit),
-relative_body.extrapolated_velocity(time_limit),
relative_body.acceleration,
@ -739,7 +742,7 @@ impl MinkowskiMesh<'_>{
}
})
}
pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
pub fn predict_collision_face_out(&self,relative_body:&Body,time_limit:Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
//no algorithm needed, there is only one state and two cases (Edge,None)
//determine when it passes an edge ("sliding off" case)
let mut best_time={
@ -766,15 +769,15 @@ impl MinkowskiMesh<'_>{
}
best_edge.map(|e|(e.as_undirected(),best_time))
}
fn infinity_in(&self,infinity_body:crate::physics::Body)->Option<(MinkowskiFace,GigaTime)>{
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,Time::MIN/4,infinity_body.time){
crate::face_crawler::CrawlResult::Miss(_)=>None,
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
}
}
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
let infinity_body=crate::physics::Body::new(point,vec3::Y,vec3::ZERO,integer::Time::ZERO);
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
//movement must escape the mesh forwards and backwards in time,
//otherwise the point is not inside the mesh
self.infinity_in(infinity_body)

View File

@ -5,15 +5,18 @@ use strafesnet_common::map;
use strafesnet_common::run;
use strafesnet_common::aabb;
use strafesnet_common::model::{MeshId,ModelId};
use strafesnet_common::mouse::MouseState;
use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId};
use strafesnet_common::gameplay_modes::{self,StageId};
use strafesnet_common::gameplay_style::{self,StyleModifiers};
use strafesnet_common::controls_bitflag::Controls;
use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,TimedInstruction};
use strafesnet_common::integer::{self,vec3,mat3,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2};
use strafesnet_common::integer::{self,vec3,mat3,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2};
pub use strafesnet_common::physics::{Time,TimeInner};
use gameplay::ModeState;
pub type Body=crate::body::Body<TimeInner>;
type MouseState=strafesnet_common::mouse::MouseState<TimeInner>;
//external influence
//this is how you influence the physics from outside
use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
@ -21,7 +24,7 @@ use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
//internal influence
//when the physics asks itself what happens next, this is how it's represented
#[derive(Debug)]
enum PhysicsInternalInstruction{
pub enum PhysicsInternalInstruction{
CollisionStart(Collision,model_physics::GigaTime),
CollisionEnd(Collision,model_physics::GigaTime),
StrafeTick,
@ -29,32 +32,13 @@ enum PhysicsInternalInstruction{
// Water,
}
#[derive(Debug)]
enum PhysicsInstruction{
pub 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,Hash)]
pub struct Body{
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
pub time:Time,//nanoseconds x xxxxD!
}
impl std::ops::Neg for Body{
type Output=Self;
fn neg(self)->Self::Output{
Self{
position:self.position,
velocity:-self.velocity,
acceleration:self.acceleration,
time:-self.time,
}
}
}
#[derive(Clone,Debug,Default)]
pub struct InputState{
mouse:MouseState,
@ -574,7 +558,7 @@ impl MoveState{
=>None,
}
}
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
//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{
@ -800,8 +784,8 @@ impl TouchingState{
}
}
}
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::ZERO,body).body(time);
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction,TimeInner>,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
let model_mesh=models.contact_mesh(contact);
@ -833,142 +817,6 @@ impl TouchingState{
}
}
impl Body{
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{
Self{
position,
velocity,
acceleration,
time,
}
}
pub fn extrapolated_position(&self,time:Time)->Planar64Vec3{
let dt=time-self.time;
self.position
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
}
pub fn extrapolated_velocity(&self,time:Time)->Planar64Vec3{
let dt=time-self.time;
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
}
pub fn advance_time(&mut self,time:Time){
self.position=self.extrapolated_position(time);
self.velocity=self.extrapolated_velocity(time);
self.time=time;
}
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
// Why?
// All of this can be removed with const generics because the type can be specified as
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
// which is known to implement all the necessary traits
Num:Copy,
Den:Copy+core::ops::Mul<i64,Output=D1>,
D1:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<D1,Output=N2>,
N1:core::ops::Add<N2,Output=N3>,
Num:core::ops::Mul<N3,Output=N4>,
Den:core::ops::Mul<D1,Output=D2>,
D2:Copy,
Planar64:core::ops::Mul<D2,Output=N4>,
N4:integer::Divide<D2,Output=T1>,
T1:integer::Fix<Planar64>,
{
// a*dt^2/2 + v*dt + p
// (a*dt/2+v)*dt+p
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
.map(|elem|elem.divide().fix())+self.position
}
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
// a*dt + v
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
}
pub fn advance_time_ratio_dt(&mut self,dt:model_physics::GigaTime){
self.position=self.extrapolated_position_ratio_dt(dt);
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
self.time+=dt.into();
}
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
if self.velocity==vec3::ZERO{
if self.acceleration==vec3::ZERO{
None
}else{
Some(self.acceleration)
}
}else{
Some(self.velocity)
}
}
pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time,t1:Time){
aabb.grow(self.extrapolated_position(t0));
aabb.grow(self.extrapolated_position(t1));
//v+a*t==0
//goober code
if !self.acceleration.x.is_zero(){
let t=-self.velocity.x/self.acceleration.x;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
if !self.acceleration.y.is_zero(){
let t=-self.velocity.y/self.acceleration.y;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
if !self.acceleration.z.is_zero(){
let t=-self.velocity.z/self.acceleration.z;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
}
}
impl std::fmt::Display for Body{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
}
}
struct VirtualBody<'a>{
body0:&'a Body,
body1:&'a Body,
}
impl VirtualBody<'_>{
const fn relative<'a>(body0:&'a Body,body1:&'a Body)->VirtualBody<'a>{
//(p0,v0,a0,t0)
//(p1,v1,a1,t1)
VirtualBody{
body0,
body1,
}
}
fn extrapolated_position(&self,time:Time)->Planar64Vec3{
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
}
fn extrapolated_velocity(&self,time:Time)->Planar64Vec3{
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
}
fn acceleration(&self)->Planar64Vec3{
self.body1.acceleration-self.body0.acceleration
}
fn body(&self,time:Time)->Body{
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
}
}
#[derive(Clone,Debug)]
pub struct PhysicsState{
time:Time,
@ -1036,7 +884,7 @@ impl PhysicsState{
new_state.camera.sensitivity=self.camera.sensitivity;
*self=new_state;
}
fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction>>{
fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
self.move_state.next_move_instruction(&self.style.strafe,self.time)
}
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
@ -1086,14 +934,18 @@ pub struct PhysicsContext{
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>){
impl instruction::InstructionConsumer for PhysicsContext{
type Instruction=PhysicsInstruction;
type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction,TimeInner>){
atomic_state_update(&mut self.state,&self.data,ins)
}
}
impl instruction::InstructionEmitter<PhysicsInternalInstruction> for PhysicsContext{
impl instruction::InstructionEmitter for PhysicsContext{
type Instruction=PhysicsInternalInstruction;
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<PhysicsInternalInstruction>>{
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
next_instruction_internal(&self.state,&self.data,time_limit)
}
}
@ -1262,7 +1114,7 @@ impl PhysicsContext{
//write hash lol
}
}
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction>){
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction,TimeInner>){
self.run_internal_exhaustive(instruction.time);
self.process_instruction(TimedInstruction{
time:instruction.time,
@ -1272,7 +1124,7 @@ impl PhysicsContext{
}
//this is the one who asks
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
//JUST POLLING!!! NO MUTATION
let mut collector = instruction::InstructionCollector::new(time_limit);
@ -1805,7 +1657,7 @@ fn collision_end_intersect(
}
}
}
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction>){
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction,TimeInner>){
state.time=ins.time;
let (should_advance_body,goober_time)=match ins.instruction{
PhysicsInternalInstruction::CollisionStart(_,dt)
@ -1901,7 +1753,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
}
}
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction>){
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction,TimeInner>){
state.time=ins.time;
let should_advance_body=match ins.instruction{
//the body may as well be a quantum wave function
@ -2027,7 +1879,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
}
}
fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction>){
fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction,TimeInner>){
match &ins.instruction{
PhysicsInstruction::Input(PhysicsInputInstruction::Idle)
|PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_))
@ -2050,6 +1902,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
#[cfg(test)]
mod test{
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
use crate::body::VirtualBody;
use super::*;
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO));

View File

@ -1,10 +1,17 @@
use strafesnet_common::mouse::MouseState;
use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
use strafesnet_common::integer::Time;
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;
pub struct FrameState{
pub body:crate::physics::Body,
pub camera:crate::physics::PhysicsCamera,
pub time:PhysicsTime,
}
#[derive(Debug)]
pub enum InputInstruction{
MoveMouse(glam::IVec2),
@ -36,11 +43,11 @@ pub struct MouseInterpolator{
//"PlayerController"
user_settings:crate::settings::UserSettings,
//"MouseInterpolator"
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction>>,
last_mouse_time:Time,//this value is pre-transformed to simulation time
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
last_mouse_time:PhysicsTime,
mouse_blocking:bool,
//"Simulation"
timer:Timer<Scaled>,
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
physics:crate::physics::PhysicsContext,
}
@ -58,7 +65,7 @@ impl MouseInterpolator{
user_settings,
}
}
fn push_mouse_instruction(&mut self,ins:&TimedInstruction<Instruction>,m:glam::IVec2){
fn push_mouse_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>,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{
@ -80,7 +87,7 @@ impl MouseInterpolator{
}
self.last_mouse_time=self.timer.time(ins.time);
}
fn push(&mut self,time:Time,phys_input:PhysicsInputInstruction){
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),
@ -89,7 +96,7 @@ impl MouseInterpolator{
}
/// returns should_empty_queue
/// may or may not mutate internal state XD!
fn map_instruction(&mut self,ins:&TimedInstruction<Instruction>)->bool{
fn map_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>)->bool{
let mut update_mouse_blocking=true;
match &ins.instruction{
Instruction::Input(input_instruction)=>match input_instruction{
@ -140,7 +147,7 @@ impl MouseInterpolator{
}
}
/// must check if self.mouse_blocking==true before calling!
fn unblock_mouse(&mut self,time:Time){
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,
@ -150,13 +157,13 @@ impl MouseInterpolator{
//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:Time)->bool{
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 Time::from_millis(10)<self.timer.time(time)-self.physics.get_next_mouse().time{
if PhysicsTime::from_millis(10)<self.timer.time(time)-self.physics.get_next_mouse().time{
self.unblock_mouse(time);
true
}else{
@ -174,20 +181,20 @@ impl MouseInterpolator{
self.physics.run_input_instruction(instruction);
}
}
pub fn handle_instruction(&mut self,ins:&TimedInstruction<Instruction>){
pub fn handle_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>){
let should_empty_queue=self.map_instruction(ins);
if should_empty_queue{
self.empty_queue();
}
}
pub fn get_frame_state(&self,time:Time)->crate::graphics::FrameState{
crate::graphics::FrameState{
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:Time,map:&strafesnet_common::map::CompleteMap){
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);
@ -215,13 +222,13 @@ impl MouseInterpolator{
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>>{
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{
let physics=crate::physics::PhysicsContext::default();
let mut interpolator=MouseInterpolator::new(
physics,
user_settings
);
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction>|{
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{
interpolator.handle_instruction(&ins);
match ins.instruction{
Instruction::Render=>{

View File

@ -1,6 +1,7 @@
use crate::window::WindowInstruction;
use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::integer;
use strafesnet_common::session::TimeInner as SessionTimeInner;
fn optional_features()->wgpu::Features{
wgpu::Features::TEXTURE_COMPRESSION_ASTC
@ -234,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<TimedInstruction<WindowInstruction>>,
mut window_thread:crate::compat_worker::QNWorker<TimedInstruction<WindowInstruction,SessionTimeInner>>,
root_time:std::time::Instant
)->Result<(),winit::error::EventLoopError>{
event_loop.run(move |event,elwt|{

View File

@ -1,6 +1,7 @@
use crate::physics_worker::InputInstruction;
use strafesnet_common::integer;
use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
pub enum WindowInstruction{
Resize(winit::dpi::PhysicalSize<u32>),
@ -13,17 +14,17 @@ pub enum WindowInstruction{
//holds thread handles to dispatch to
struct WindowContext<'a>{
manual_mouse_lock:bool,
mouse:strafesnet_common::mouse::MouseState,//std::sync::Arc<std::sync::Mutex<>>
mouse:strafesnet_common::mouse::MouseState<SessionTimeInner>,//std::sync::Arc<std::sync::Mutex<>>
screen_size:glam::UVec2,
window:&'a winit::window::Window,
physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<crate::physics_worker::Instruction>>,
physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<crate::physics_worker::Instruction,SessionTimeInner>>,
}
impl WindowContext<'_>{
fn get_middle_of_screen(&self)->winit::dpi::PhysicalPosition<u32>{
winit::dpi::PhysicalPosition::new(self.screen_size.x/2,self.screen_size.y/2)
}
fn window_event(&mut self,time:integer::Time,event:winit::event::WindowEvent){
fn window_event(&mut self,time:SessionTime,event:winit::event::WindowEvent){
match event{
winit::event::WindowEvent::DroppedFile(path)=>{
match crate::file::load(path.as_path()){
@ -122,7 +123,7 @@ impl WindowContext<'_>{
}
}
fn device_event(&mut self,time:integer::Time,event: winit::event::DeviceEvent){
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
@ -161,7 +162,7 @@ impl WindowContext<'_>{
pub fn worker<'a>(
window:&'a winit::window::Window,
setup_context:crate::setup::SetupContext<'a>,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction>>{
)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction,SessionTimeInner>>{
// WindowContextSetup::new
let user_settings=crate::settings::read_user_settings();
@ -184,7 +185,7 @@ pub fn worker<'a>(
};
//WindowContextSetup::into_worker
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction>|{
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction,SessionTimeInner>|{
match ins.instruction{
WindowInstruction::RequestRedraw=>{
window_context.window.request_redraw();

View File

@ -176,20 +176,20 @@ impl<'a,Task:Send+'a> INWorker<'a,Task>{
#[cfg(test)]
mod test{
use super::{thread,QRWorker};
use crate::physics;
type Body=crate::physics::Body;
use strafesnet_common::{integer,instruction};
#[test]//How to run this test with printing: cargo test --release -- --nocapture
fn test_worker() {
// Create the worker thread
let test_body=physics::Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO);
let worker=QRWorker::new(physics::Body::ZERO,
|_|physics::Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO)
let test_body=Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO);
let worker=QRWorker::new(Body::ZERO,
|_|Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO)
);
// Send tasks to the worker
for _ in 0..5 {
let task = instruction::TimedInstruction{
time:integer::Time::ZERO,
time:strafesnet_common::physics::Time::ZERO,
instruction:strafesnet_common::physics::Instruction::Idle,
};
worker.send(task).unwrap();