Compare commits

..

35 Commits

Author SHA1 Message Date
0dc462a3b1 comment infinite loop avoidance 2025-01-09 20:38:32 -08:00
ca003edbc3 reintroduce generics to Instruction traits 2025-01-09 20:11:00 -08:00
16abe23e97 push solve tweaks 2025-01-09 06:27:50 -08:00
67f8569178 push_solve infallible type signature 2025-01-09 05:56:11 -08:00
121c9c5258 resize immediately 2025-01-09 05:36:55 -08:00
411b997b87 use mold linker because it's faster 2025-01-09 05:36:55 -08:00
4d587b6c0b fixed_wide: clippy angry about derivable traits 2025-01-08 23:33:39 -08:00
6ff74f08ab roblox_emulator: deref returns correct type 2025-01-08 23:31:56 -08:00
08f419f931 rename crawl_fev to crawl 2025-01-08 21:09:52 -08:00
6066e82fd2 MeshQuery trait FEV associated types 2025-01-08 21:09:52 -08:00
ca8035cdfc fixed wide 2025-01-08 21:09:52 -08:00
ff5d954cfb push solve! 2025-01-08 18:17:40 -08:00
a967f31004 rename RawTime to AbsoluteTime 2025-01-08 01:15:52 -08:00
8ad5d28e51 impl AsRef<str> for FlagReason 2025-01-07 23:57:22 -08:00
ab05893813 A BUG HAS BEEN FOUND!!!! 2025-01-07 23:43:54 -08:00
2f7597146e fixup strafe client for strongly typed time 2025-01-07 23:43:54 -08:00
004e0d3776 common: session Time 2025-01-07 23:43:54 -08:00
120d8197b7 common 2025-01-07 23:43:54 -08:00
36ba73a892 timers 2025-01-07 23:43:54 -08:00
86cf7e74b1 typed Time 2025-01-07 22:36:58 -08:00
24787fede5 improve get_model_transform readability 2025-01-07 20:19:44 -08:00
3797408bc8 pull out named variables in checkpoint_check 2025-01-07 06:03:29 -08:00
47c9b77b00 style 2025-01-07 06:03:29 -08:00
479e657251 notes 2025-01-07 06:03:29 -08:00
63fbc94287 snf: demo file brainstorming 2025-01-06 23:14:08 -08:00
1318ae20ca snf: session file brainstorming 2025-01-06 23:14:08 -08:00
851d9c935d clear mode state in teleport_to_spawn 2025-01-06 21:50:43 -08:00
d0a190861c implement NoJump and jump_limit 2025-01-06 21:45:48 -08:00
4dca7fc369 try_increment_jump_count monolithic function 2025-01-06 21:45:48 -08:00
62dfe23539 prevent hitting side of spawn from updating current stage 2025-01-06 21:05:37 -08:00
3991cb5064 document unclear function 2025-01-06 21:05:37 -08:00
1dc2556d85 factor out immutable checkpoint_check logic 2025-01-06 21:05:37 -08:00
4f21985290 fix print grammar 2025-01-06 00:36:14 -08:00
ccce54c1a3 calculate title at compile time 2025-01-05 21:39:48 -08:00
02bb2d797c functions not needed 2025-01-05 03:46:15 -08:00
35 changed files with 1192 additions and 761 deletions

View File

@ -1,2 +1,6 @@
[registries.strafesnet] [registries.strafesnet]
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]

61
Cargo.lock generated
View File

@ -67,12 +67,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@ -367,20 +361,6 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "chrono"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "codespan-reporting" name = "codespan-reporting"
version = "0.11.1" version = "0.11.1"
@ -801,29 +781,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core 0.52.0",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "1.5.0" version = "1.5.0"
@ -2271,8 +2228,8 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
name = "strafe-client" name = "strafe-client"
version = "0.10.5" version = "0.10.5"
dependencies = [ dependencies = [
"arrayvec",
"bytemuck", "bytemuck",
"chrono",
"configparser", "configparser",
"ddsfile", "ddsfile",
"glam", "glam",
@ -2284,9 +2241,6 @@ dependencies = [
"strafesnet_deferred_loader", "strafesnet_deferred_loader",
"strafesnet_rbx_loader", "strafesnet_rbx_loader",
"strafesnet_snf", "strafesnet_snf",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"wgpu", "wgpu",
"winit", "winit",
] ]
@ -2938,7 +2892,7 @@ dependencies = [
"web-sys", "web-sys",
"wgpu-types", "wgpu-types",
"windows", "windows",
"windows-core 0.58.0", "windows-core",
] ]
[[package]] [[package]]
@ -2967,16 +2921,7 @@ version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [ dependencies = [
"windows-core 0.58.0", "windows-core",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]

View File

@ -18,13 +18,8 @@ bitflags::bitflags!{
const Use=1<<14;//Interact with object const Use=1<<14;//Interact with object
const PrimaryAction=1<<15;//LBM/Shoot/Melee const PrimaryAction=1<<15;//LBM/Shoot/Melee
const SecondaryAction=1<<16;//RMB/ADS/Block const SecondaryAction=1<<16;//RMB/ADS/Block
}
} const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits();
impl Controls{ const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits();
pub const fn wasd()->Self{
Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight)
}
pub const fn wasdqe()->Self{
Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown)
} }
} }

View File

@ -1,5 +1,5 @@
use crate::model; use crate::model;
use crate::integer::{Time,Planar64,Planar64Vec3}; use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3};
//you have this effect while in contact //you have this effect while in contact
#[derive(Clone,Hash,Eq,PartialEq)] #[derive(Clone,Hash,Eq,PartialEq)]
@ -31,7 +31,7 @@ pub enum Booster{
//Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more //Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more
Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity
Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
AirTime(Time),//increase airtime, invariant across mass and gravity changes AirTime(AbsoluteTime),//increase airtime, invariant across mass and gravity changes
Height(Planar64),//increase height, invariant across mass and gravity changes Height(Planar64),//increase height, invariant across mass and gravity changes
} }
impl Booster{ impl Booster{
@ -57,13 +57,13 @@ pub enum TrajectoryChoice{
#[derive(Clone,Hash,Eq,PartialEq)] #[derive(Clone,Hash,Eq,PartialEq)]
pub enum SetTrajectory{ pub enum SetTrajectory{
//Speed-type SetTrajectory //Speed-type SetTrajectory
AirTime(Time),//air time (relative to gravity direction) is invariant across mass and gravity changes AirTime(AbsoluteTime),//air time (relative to gravity direction) is invariant across mass and gravity changes
Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
//Velocity-type SetTrajectory //Velocity-type SetTrajectory
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
target_point:Planar64Vec3, target_point:Planar64Vec3,
time:Time,//short time = fast and direct, long time = launch high in the air, negative time = wrong way time:AbsoluteTime,//short time = fast and direct, long time = launch high in the air, negative time = wrong way
}, },
TargetPointSpeed{//launch at a fixed speed and land at a target point TargetPointSpeed{//launch at a fixed speed and land at a target point
target_point:Planar64Vec3, target_point:Planar64Vec3,

View File

@ -110,6 +110,7 @@ impl Stage{
pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){ pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
(self.ordered_checkpoints,self.unordered_checkpoints) (self.ordered_checkpoints,self.unordered_checkpoints)
} }
/// Returns true if the stage has no checkpoints.
#[inline] #[inline]
pub const fn is_empty(&self)->bool{ pub const fn is_empty(&self)->bool{
self.is_complete(0,0) self.is_complete(0,0)

View File

@ -1,7 +1,8 @@
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16 const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
use crate::integer::{int,vec3::int as int3,Time,Ratio64,Planar64,Planar64Vec3}; use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3};
use crate::controls_bitflag::Controls; use crate::controls_bitflag::Controls;
use crate::physics::Time as PhysicsTime;
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct StyleModifiers{ pub struct StyleModifiers{
@ -48,7 +49,7 @@ pub enum JumpCalculation{
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub enum JumpImpulse{ pub enum JumpImpulse{
Time(Time),//jump time is invariant across mass and gravity changes Time(AbsoluteTime),//jump time is invariant across mass and gravity changes
Height(Planar64),//jump height is invariant across mass and gravity changes Height(Planar64),//jump height is invariant across mass and gravity changes
Linear(Planar64),//jump velocity is invariant across mass and gravity changes Linear(Planar64),//jump velocity is invariant across mass and gravity changes
Energy(Planar64),// :) Energy(Planar64),// :)
@ -199,8 +200,8 @@ impl ControlsActivation{
} }
pub const fn full_3d()->Self{ pub const fn full_3d()->Self{
Self{ Self{
controls_mask:Controls::wasdqe(), controls_mask:Controls::WASDQE,
controls_intersects:Controls::wasdqe(), controls_intersects:Controls::WASDQE,
controls_contains:Controls::empty(), controls_contains:Controls::empty(),
} }
} }
@ -208,8 +209,8 @@ impl ControlsActivation{
//Normal //Normal
pub const fn full_2d()->Self{ pub const fn full_2d()->Self{
Self{ Self{
controls_mask:Controls::wasd(), controls_mask:Controls::WASD,
controls_intersects:Controls::wasd(), controls_intersects:Controls::WASD,
controls_contains:Controls::empty(), controls_contains:Controls::empty(),
} }
} }
@ -272,8 +273,8 @@ impl StrafeSettings{
false=>None, false=>None,
} }
} }
pub fn next_tick(&self,time:Time)->Time{ pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{
Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1)) PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
} }
pub const fn activates(&self,controls:Controls)->bool{ pub const fn activates(&self,controls:Controls)->bool{
self.enable.activates(controls) self.enable.activates(controls)
@ -435,7 +436,7 @@ impl StyleModifiers{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:None, air_accel_limit:None,
mv:int(3), mv:int(3),
tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(), tick_rate:Ratio64::new(64,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Energy(int(512)), impulse:JumpImpulse::Energy(int(512)),
@ -477,10 +478,10 @@ impl StyleModifiers{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:None, air_accel_limit:None,
mv:int(27)/10, mv:int(27)/10,
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Time(Time::from_micros(715_588)), impulse:JumpImpulse::Time(AbsoluteTime::from_micros(715_588)),
calculation:JumpCalculation::Max, calculation:JumpCalculation::Max,
limit_minimum:true, limit_minimum:true,
}), }),
@ -534,7 +535,7 @@ impl StyleModifiers{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:Some(Planar64::raw(150<<28)*100), air_accel_limit:Some(Planar64::raw(150<<28)*100),
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(), mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
@ -575,7 +576,7 @@ impl StyleModifiers{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()), air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
mv:(int(30)*VALVE_SCALE).fix_1(), mv:(int(30)*VALVE_SCALE).fix_1(),
tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(), tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),

View File

@ -1,35 +1,57 @@
use crate::integer::Time; use crate::integer::Time;
#[derive(Debug)] #[derive(Debug)]
pub struct TimedInstruction<I>{ pub struct TimedInstruction<I,T>{
pub time:Time, pub time:Time<T>,
pub instruction:I, pub instruction:I,
} }
/// Ensure all emitted instructions are processed before consuming external instructions
pub trait InstructionEmitter<I>{ pub trait InstructionEmitter<I>{
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<I>>; type TimeInner;
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
} }
/// Apply an atomic state update
pub trait InstructionConsumer<I>{ pub trait InstructionConsumer<I>{
fn process_instruction(&mut self, instruction:TimedInstruction<I>); type TimeInner;
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>);
} }
/// If the object produces its own instructions, allow exhaustively feeding them back in
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
where
Time<T>:Copy,
{
fn process_exhaustive(&mut self,time_limit:Time<T>){
while let Some(instruction)=self.next_instruction(time_limit){
self.process_instruction(instruction);
}
}
}
impl<I,T,X> InstructionFeedback<I,T> for X
where
Time<T>:Copy,
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
{}
//PROPER PRIVATE FIELDS!!! //PROPER PRIVATE FIELDS!!!
pub struct InstructionCollector<I>{ pub struct InstructionCollector<I,T>{
time:Time, time:Time<T>,
instruction:Option<I>, instruction:Option<I>,
} }
impl<I> InstructionCollector<I>{ impl<I,T> InstructionCollector<I,T>
pub const fn new(time:Time)->Self{ where Time<T>:Copy+PartialOrd,
{
pub const fn new(time:Time<T>)->Self{
Self{ Self{
time, time,
instruction:None instruction:None
} }
} }
#[inline] #[inline]
pub const fn time(&self)->Time{ pub const fn time(&self)->Time<T>{
self.time self.time
} }
pub fn collect(&mut self,instruction:Option<TimedInstruction<I>>){ pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
match instruction{ match instruction{
Some(unwrap_instruction)=>{ Some(unwrap_instruction)=>{
if unwrap_instruction.time<self.time { if unwrap_instruction.time<self.time {
@ -40,7 +62,7 @@ impl<I> InstructionCollector<I>{
None=>(), None=>(),
} }
} }
pub fn instruction(self)->Option<TimedInstruction<I>>{ pub fn instruction(self)->Option<TimedInstruction<I,T>>{
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR //STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
match self.instruction{ match self.instruction{
Some(instruction)=>Some(TimedInstruction{ Some(instruction)=>Some(TimedInstruction{

View File

@ -2,19 +2,25 @@ pub use fixed_wide::fixed::{Fixed,Fix};
pub use ratio_ops::ratio::{Ratio,Divide}; pub use ratio_ops::ratio::{Ratio,Divide};
//integer units //integer units
/// specific example of a "default" time type
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub struct Time(i64); pub enum TimeInner{}
impl Time{ pub type AbsoluteTime=Time<TimeInner>;
pub const MIN:Self=Self(i64::MIN);
pub const MAX:Self=Self(i64::MAX); #[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub const ZERO:Self=Self(0); pub struct Time<T>(i64,core::marker::PhantomData<T>);
pub const ONE_SECOND:Self=Self(1_000_000_000); impl<T> Time<T>{
pub const ONE_MILLISECOND:Self=Self(1_000_000); pub const MIN:Self=Self::raw(i64::MIN);
pub const ONE_MICROSECOND:Self=Self(1_000); pub const MAX:Self=Self::raw(i64::MAX);
pub const ONE_NANOSECOND:Self=Self(1); pub const ZERO:Self=Self::raw(0);
pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
pub const ONE_MICROSECOND:Self=Self::raw(1_000);
pub const ONE_NANOSECOND:Self=Self::raw(1);
#[inline] #[inline]
pub const fn raw(num:i64)->Self{ pub const fn raw(num:i64)->Self{
Self(num) Self(num,core::marker::PhantomData)
} }
#[inline] #[inline]
pub const fn get(self)->i64{ pub const fn get(self)->i64{
@ -22,19 +28,19 @@ impl Time{
} }
#[inline] #[inline]
pub const fn from_secs(num:i64)->Self{ pub const fn from_secs(num:i64)->Self{
Self(Self::ONE_SECOND.0*num) Self::raw(Self::ONE_SECOND.0*num)
} }
#[inline] #[inline]
pub const fn from_millis(num:i64)->Self{ pub const fn from_millis(num:i64)->Self{
Self(Self::ONE_MILLISECOND.0*num) Self::raw(Self::ONE_MILLISECOND.0*num)
} }
#[inline] #[inline]
pub const fn from_micros(num:i64)->Self{ pub const fn from_micros(num:i64)->Self{
Self(Self::ONE_MICROSECOND.0*num) Self::raw(Self::ONE_MICROSECOND.0*num)
} }
#[inline] #[inline]
pub const fn from_nanos(num:i64)->Self{ pub const fn from_nanos(num:i64)->Self{
Self(Self::ONE_NANOSECOND.0*num) Self::raw(Self::ONE_NANOSECOND.0*num)
} }
//should I have checked subtraction? force all time variables to be positive? //should I have checked subtraction? force all time variables to be positive?
#[inline] #[inline]
@ -45,14 +51,18 @@ impl Time{
pub const fn to_ratio(self)->Ratio<Planar64,Planar64>{ pub const fn to_ratio(self)->Ratio<Planar64,Planar64>{
Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000)) Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000))
} }
}
impl From<Planar64> for Time{
#[inline] #[inline]
fn from(value:Planar64)->Self{ pub const fn coerce<U>(self)->Time<U>{
Time((value*Planar64::raw(1_000_000_000)).fix_1().to_raw()) Time::raw(self.0)
} }
} }
impl<Num,Den,N1,T1> From<Ratio<Num,Den>> for Time impl<T> From<Planar64> for Time<T>{
#[inline]
fn from(value:Planar64)->Self{
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
}
}
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
where where
Num:core::ops::Mul<Planar64,Output=N1>, Num:core::ops::Mul<Planar64,Output=N1>,
N1:Divide<Den,Output=T1>, N1:Divide<Den,Output=T1>,
@ -60,34 +70,34 @@ impl<Num,Den,N1,T1> From<Ratio<Num,Den>> for Time
{ {
#[inline] #[inline]
fn from(value:Ratio<Num,Den>)->Self{ fn from(value:Ratio<Num,Den>)->Self{
Time((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw()) Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
} }
} }
impl std::fmt::Display for Time{ impl<T> std::fmt::Display for Time<T>{
#[inline] #[inline]
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0) write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
} }
} }
impl std::default::Default for Time{ impl<T> std::default::Default for Time<T>{
fn default()->Self{ fn default()->Self{
Self(0) Self::raw(0)
} }
} }
impl std::ops::Neg for Time{ impl<T> std::ops::Neg for Time<T>{
type Output=Time; type Output=Self;
#[inline] #[inline]
fn neg(self)->Self::Output { fn neg(self)->Self::Output {
Time(-self.0) Self::raw(-self.0)
} }
} }
macro_rules! impl_time_additive_operator { macro_rules! impl_time_additive_operator {
($trait:ty, $method:ident) => { ($trait:ty, $method:ident) => {
impl $trait for Time{ impl<T> $trait for Time<T>{
type Output=Time; type Output=Self;
#[inline] #[inline]
fn $method(self,rhs:Self)->Self::Output { fn $method(self,rhs:Self)->Self::Output {
Time(self.0.$method(rhs.0)) Self::raw(self.0.$method(rhs.0))
} }
} }
}; };
@ -97,7 +107,7 @@ impl_time_additive_operator!(core::ops::Sub,sub);
impl_time_additive_operator!(core::ops::Rem,rem); impl_time_additive_operator!(core::ops::Rem,rem);
macro_rules! impl_time_additive_assign_operator { macro_rules! impl_time_additive_assign_operator {
($trait:ty, $method:ident) => { ($trait:ty, $method:ident) => {
impl $trait for Time{ impl<T> $trait for Time<T>{
#[inline] #[inline]
fn $method(&mut self,rhs:Self){ fn $method(&mut self,rhs:Self){
self.0.$method(rhs.0) self.0.$method(rhs.0)
@ -108,53 +118,58 @@ macro_rules! impl_time_additive_assign_operator {
impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign); impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign);
impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign); impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign);
impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign); impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign);
impl std::ops::Mul for Time{ impl<T> std::ops::Mul for Time<T>{
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>; type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
#[inline] #[inline]
fn mul(self,rhs:Self)->Self::Output{ fn mul(self,rhs:Self)->Self::Output{
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2))) Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
} }
} }
impl std::ops::Div<i64> for Time{ impl<T> std::ops::Div<i64> for Time<T>{
type Output=Time; type Output=Self;
#[inline] #[inline]
fn div(self,rhs:i64)->Self::Output{ fn div(self,rhs:i64)->Self::Output{
Time(self.0/rhs) Self::raw(self.0/rhs)
} }
} }
impl std::ops::Mul<i64> for Time{ impl<T> std::ops::Mul<i64> for Time<T>{
type Output=Time; type Output=Self;
#[inline] #[inline]
fn mul(self,rhs:i64)->Self::Output{ fn mul(self,rhs:i64)->Self::Output{
Time(self.0*rhs) Self::raw(self.0*rhs)
} }
} }
impl core::ops::Mul<Time> for Planar64{ impl<T> core::ops::Mul<Time<T>> for Planar64{
type Output=Ratio<Fixed<2,64>,Planar64>; type Output=Ratio<Fixed<2,64>,Planar64>;
fn mul(self,rhs:Time)->Self::Output{ fn mul(self,rhs:Time<T>)->Self::Output{
Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000)) Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000))
} }
} }
#[test] #[cfg(test)]
fn time_from_planar64(){ mod test_time{
let a:Time=Planar64::from(1).into(); use super::*;
assert_eq!(a,Time::ONE_SECOND); type Time=super::AbsoluteTime;
} #[test]
#[test] fn time_from_planar64(){
fn time_from_ratio(){ let a:Time=Planar64::from(1).into();
let a:Time=Ratio::new(Planar64::from(1),Planar64::from(1)).into(); assert_eq!(a,Time::ONE_SECOND);
assert_eq!(a,Time::ONE_SECOND); }
} #[test]
#[test] fn time_from_ratio(){
fn time_squared(){ let a:Time=Ratio::new(Planar64::from(1),Planar64::from(1)).into();
let a=Time::from_secs(2); assert_eq!(a,Time::ONE_SECOND);
assert_eq!(a*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))*4,Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2)))); }
} #[test]
#[test] fn time_squared(){
fn time_times_planar64(){ let a=Time::from_secs(2);
let a=Time::from_secs(2); assert_eq!(a*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))*4,Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))));
let b=Planar64::from(2); }
assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000))); #[test]
fn time_times_planar64(){
let a=Time::from_secs(2);
let b=Planar64::from(2);
assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000)));
}
} }
#[inline] #[inline]

View File

@ -7,6 +7,7 @@ pub mod mouse;
pub mod timer; pub mod timer;
pub mod integer; pub mod integer;
pub mod physics; pub mod physics;
pub mod session;
pub mod updatable; pub mod updatable;
pub mod instruction; pub mod instruction;
pub mod gameplay_attributes; pub mod gameplay_attributes;

View File

@ -1,11 +1,11 @@
use crate::integer::Time; use crate::integer::Time;
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct MouseState{ pub struct MouseState<T>{
pub pos:glam::IVec2, pub pos:glam::IVec2,
pub time:Time, pub time:Time<T>,
} }
impl Default for MouseState{ impl<T> Default for MouseState<T>{
fn default()->Self{ fn default()->Self{
Self{ Self{
time:Time::ZERO, time:Time::ZERO,
@ -13,8 +13,10 @@ impl Default for MouseState{
} }
} }
} }
impl MouseState{ impl<T> MouseState<T>
pub fn lerp(&self,target:&MouseState,time:Time)->glam::IVec2{ where Time<T>:Copy,
{
pub fn lerp(&self,target:&MouseState<T>,time:Time<T>)->glam::IVec2{
let m0=self.pos.as_i64vec2(); let m0=self.pos.as_i64vec2();
let m1=target.pos.as_i64vec2(); let m1=target.pos.as_i64vec2();
//these are deltas //these are deltas

View File

@ -1,7 +1,11 @@
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>;
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub enum Instruction{ pub enum Instruction{
ReplaceMouse(crate::mouse::MouseState,crate::mouse::MouseState), ReplaceMouse(crate::mouse::MouseState<TimeInner>,crate::mouse::MouseState<TimeInner>),
SetNextMouse(crate::mouse::MouseState), SetNextMouse(crate::mouse::MouseState<TimeInner>),
SetMoveRight(bool), SetMoveRight(bool),
SetMoveUp(bool), SetMoveUp(bool),
SetMoveBack(bool), SetMoveBack(bool),

View File

@ -1,5 +1,10 @@
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused}; use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
use crate::integer::Time;
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>;
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug)]
pub enum FlagReason{ pub enum FlagReason{
@ -15,6 +20,11 @@ pub enum FlagReason{
} }
impl ToString for FlagReason{ impl ToString for FlagReason{
fn to_string(&self)->String{ fn to_string(&self)->String{
self.as_ref().to_owned()
}
}
impl AsRef<str> for FlagReason{
fn as_ref(&self)->&str{
match self{ match self{
FlagReason::Anticheat=>"Passed through anticheat zone.", FlagReason::Anticheat=>"Passed through anticheat zone.",
FlagReason::StyleChange=>"Changed style.", FlagReason::StyleChange=>"Changed style.",
@ -25,7 +35,7 @@ impl ToString for FlagReason{
FlagReason::Timescale=>"Timescale is not allowed in this style.", FlagReason::Timescale=>"Timescale is not allowed in this style.",
FlagReason::TimeTravel=>"Time travel is not allowed in this style.", FlagReason::TimeTravel=>"Time travel is not allowed in this style.",
FlagReason::Teleport=>"Illegal teleport.", FlagReason::Teleport=>"Illegal teleport.",
}.to_owned() }
} }
} }
@ -45,8 +55,8 @@ impl std::error::Error for Error{}
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug)]
enum RunState{ enum RunState{
Created, Created,
Started{timer:TimerFixed<Realtime,Unpaused>}, Started{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Unpaused>},
Finished{timer:TimerFixed<Realtime,Paused>}, Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>},
} }
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug)]
@ -62,14 +72,14 @@ impl Run{
flagged:None, flagged:None,
} }
} }
pub fn time(&self,time:Time)->Time{ pub fn time(&self,time:PhysicsTime)->Time{
match &self.state{ match &self.state{
RunState::Created=>Time::ZERO, RunState::Created=>Time::ZERO,
RunState::Started{timer}=>timer.time(time), RunState::Started{timer}=>timer.time(time),
RunState::Finished{timer}=>timer.time(time), RunState::Finished{timer}=>timer.time(time),
} }
} }
pub fn start(&mut self,time:Time)->Result<(),Error>{ pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
match &self.state{ match &self.state{
RunState::Created=>{ RunState::Created=>{
self.state=RunState::Started{ self.state=RunState::Started{
@ -81,7 +91,7 @@ impl Run{
RunState::Finished{..}=>Err(Error::AlreadyFinished), RunState::Finished{..}=>Err(Error::AlreadyFinished),
} }
} }
pub fn finish(&mut self,time:Time)->Result<(),Error>{ pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{
//this uses Copy //this uses Copy
match &self.state{ match &self.state{
RunState::Created=>Err(Error::NotStarted), RunState::Created=>Err(Error::NotStarted),

View File

@ -0,0 +1,3 @@
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>;

View File

@ -22,79 +22,106 @@ impl PauseState for Unpaused{
} }
} }
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
enum Inner{}
type InnerTime=Time<Inner>;
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug)]
pub struct Realtime{ pub struct Realtime<In,Out>{
offset:Time, offset:InnerTime,
_in:core::marker::PhantomData<In>,
_out:core::marker::PhantomData<Out>,
} }
impl Realtime{ impl<In,Out> Realtime<In,Out>{
pub const fn new(offset:Time)->Self{ pub const fn new(offset:InnerTime)->Self{
Self{offset} Self{
offset,
_in:core::marker::PhantomData,
_out:core::marker::PhantomData,
}
} }
} }
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug)]
pub struct Scaled{ pub struct Scaled<In,Out>{
scale:Ratio64, scale:Ratio64,
offset:Time, offset:InnerTime,
_in:core::marker::PhantomData<In>,
_out:core::marker::PhantomData<Out>,
} }
impl Scaled{ impl<In,Out> Scaled<In,Out>
pub const fn new(scale:Ratio64,offset:Time)->Self{ where Time<In>:Copy,
Self{scale,offset} {
pub const fn new(scale:Ratio64,offset:InnerTime)->Self{
Self{
scale,
offset,
_in:core::marker::PhantomData,
_out:core::marker::PhantomData,
}
} }
const fn with_scale(scale:Ratio64)->Self{ const fn with_scale(scale:Ratio64)->Self{
Self{scale,offset:Time::ZERO} Self::new(scale,InnerTime::ZERO)
} }
const fn scale(&self,time:Time)->Time{ const fn scale(&self,time:Time<In>)->InnerTime{
Time::raw(self.scale.mul_int(time.get())) InnerTime::raw(self.scale.mul_int(time.get()))
} }
const fn get_scale(&self)->Ratio64{ const fn get_scale(&self)->Ratio64{
self.scale self.scale
} }
fn set_scale(&mut self,time:Time,new_scale:Ratio64){ fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
let new_time=self.get_time(time); let new_time=self.get_time(time);
self.scale=new_scale; self.scale=new_scale;
self.set_time(time,new_time); self.set_time(time,new_time);
} }
} }
pub trait TimerState:Copy+std::fmt::Debug{ pub trait TimerState{
type In;
type Out;
fn identity()->Self; fn identity()->Self;
fn get_time(&self,time:Time)->Time; fn get_time(&self,time:Time<Self::In>)->Time<Self::Out>;
fn set_time(&mut self,time:Time,new_time:Time); fn set_time(&mut self,time:Time<Self::In>,new_time:Time<Self::Out>);
fn get_offset(&self)->Time; fn get_offset(&self)->InnerTime;
fn set_offset(&mut self,offset:Time); fn set_offset(&mut self,offset:InnerTime);
} }
impl TimerState for Realtime{ impl<In,Out> TimerState for Realtime<In,Out>{
type In=In;
type Out=Out;
fn identity()->Self{ fn identity()->Self{
Self{offset:Time::ZERO} Self::new(InnerTime::ZERO)
} }
fn get_time(&self,time:Time)->Time{ fn get_time(&self,time:Time<In>)->Time<Out>{
time+self.offset time.coerce()+self.offset.coerce()
} }
fn set_time(&mut self,time:Time,new_time:Time){ fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
self.offset=new_time-time; self.offset=new_time.coerce()-time.coerce();
} }
fn get_offset(&self)->Time{ fn get_offset(&self)->InnerTime{
self.offset self.offset
} }
fn set_offset(&mut self,offset:Time){ fn set_offset(&mut self,offset:InnerTime){
self.offset=offset; self.offset=offset;
} }
} }
impl TimerState for Scaled{ impl<In,Out> TimerState for Scaled<In,Out>
where Time<In>:Copy,
{
type In=In;
type Out=Out;
fn identity()->Self{ fn identity()->Self{
Self{scale:Ratio64::ONE,offset:Time::ZERO} Self::new(Ratio64::ONE,InnerTime::ZERO)
} }
fn get_time(&self,time:Time)->Time{ fn get_time(&self,time:Time<In>)->Time<Out>{
self.scale(time)+self.offset (self.scale(time)+self.offset).coerce()
} }
fn set_time(&mut self,time:Time,new_time:Time){ fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
self.offset=new_time-self.scale(time); self.offset=new_time.coerce()-self.scale(time);
} }
fn get_offset(&self)->Time{ fn get_offset(&self)->InnerTime{
self.offset self.offset
} }
fn set_offset(&mut self,offset:Time){ fn set_offset(&mut self,offset:InnerTime){
self.offset=offset; self.offset=offset;
} }
} }
@ -106,8 +133,10 @@ pub struct TimerFixed<T:TimerState,P:PauseState>{
} }
//scaled timer methods are generic across PauseState //scaled timer methods are generic across PauseState
impl<P:PauseState> TimerFixed<Scaled,P>{ impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
pub fn scaled(time:Time,new_time:Time,scale:Ratio64)->Self{ where Time<In>:Copy,
{
pub fn scaled(time:Time<In>,new_time:Time<Out>,scale:Ratio64)->Self{
let mut timer=Self{ let mut timer=Self{
state:Scaled::with_scale(scale), state:Scaled::with_scale(scale),
_paused:P::new(), _paused:P::new(),
@ -118,14 +147,16 @@ impl<P:PauseState> TimerFixed<Scaled,P>{
pub const fn get_scale(&self)->Ratio64{ pub const fn get_scale(&self)->Ratio64{
self.state.get_scale() self.state.get_scale()
} }
pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){ pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
self.state.set_scale(time,new_scale) self.state.set_scale(time,new_scale)
} }
} }
//pause and unpause is generic across TimerState //pause and unpause is generic across TimerState
impl<T:TimerState> TimerFixed<T,Paused>{ impl<T:TimerState> TimerFixed<T,Paused>
pub fn into_unpaused(self,time:Time)->TimerFixed<T,Unpaused>{ where Time<T::In>:Copy,
{
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
let new_time=self.time(time); let new_time=self.time(time);
let mut timer=TimerFixed{ let mut timer=TimerFixed{
state:self.state, state:self.state,
@ -135,8 +166,10 @@ impl<T:TimerState> TimerFixed<T,Paused>{
timer timer
} }
} }
impl<T:TimerState> TimerFixed<T,Unpaused>{ impl<T:TimerState> TimerFixed<T,Unpaused>
pub fn into_paused(self,time:Time)->TimerFixed<T,Paused>{ where Time<T::In>:Copy,
{
pub fn into_paused(self,time:Time<T::In>)->TimerFixed<T,Paused>{
let new_time=self.time(time); let new_time=self.time(time);
let mut timer=TimerFixed{ let mut timer=TimerFixed{
state:self.state, state:self.state,
@ -149,7 +182,7 @@ impl<T:TimerState> TimerFixed<T,Unpaused>{
//the new constructor and time queries are generic across both //the new constructor and time queries are generic across both
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{ impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
pub fn new(time:Time,new_time:Time)->Self{ pub fn new(time:Time<T::In>,new_time:Time<T::Out>)->Self{
let mut timer=Self{ let mut timer=Self{
state:T::identity(), state:T::identity(),
_paused:P::new(), _paused:P::new(),
@ -166,15 +199,15 @@ impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
pub fn into_state(self)->T{ pub fn into_state(self)->T{
self.state self.state
} }
pub fn time(&self,time:Time)->Time{ pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
match P::IS_PAUSED{ match P::IS_PAUSED{
true=>self.state.get_offset(), true=>self.state.get_offset().coerce(),
false=>self.state.get_time(time), false=>self.state.get_time(time),
} }
} }
pub fn set_time(&mut self,time:Time,new_time:Time){ pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
match P::IS_PAUSED{ match P::IS_PAUSED{
true=>self.state.set_offset(new_time), true=>self.state.set_offset(new_time.coerce()),
false=>self.state.set_time(time,new_time), false=>self.state.set_time(time,new_time),
} }
} }
@ -198,7 +231,11 @@ pub enum Timer<T:TimerState>{
Paused(TimerFixed<T,Paused>), Paused(TimerFixed<T,Paused>),
Unpaused(TimerFixed<T,Unpaused>), Unpaused(TimerFixed<T,Unpaused>),
} }
impl<T:TimerState> Timer<T>{ impl<T:TimerState> Timer<T>
where
T:Copy,
Time<T::In>:Copy,
{
pub fn from_state(state:T,paused:bool)->Self{ pub fn from_state(state:T,paused:bool)->Self{
match paused{ match paused{
true=>Self::Paused(TimerFixed::from_state(state)), true=>Self::Paused(TimerFixed::from_state(state)),
@ -211,32 +248,32 @@ impl<T:TimerState> Timer<T>{
Self::Unpaused(timer)=>(timer.into_state(),false), Self::Unpaused(timer)=>(timer.into_state(),false),
} }
} }
pub fn paused(time:Time,new_time:Time)->Self{ pub fn paused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
Self::Paused(TimerFixed::new(time,new_time)) Self::Paused(TimerFixed::new(time,new_time))
} }
pub fn unpaused(time:Time,new_time:Time)->Self{ pub fn unpaused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
Self::Unpaused(TimerFixed::new(time,new_time)) Self::Unpaused(TimerFixed::new(time,new_time))
} }
pub fn time(&self,time:Time)->Time{ pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
match self{ match self{
Self::Paused(timer)=>timer.time(time), Self::Paused(timer)=>timer.time(time),
Self::Unpaused(timer)=>timer.time(time), Self::Unpaused(timer)=>timer.time(time),
} }
} }
pub fn set_time(&mut self,time:Time,new_time:Time){ pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
match self{ match self{
Self::Paused(timer)=>timer.set_time(time,new_time), Self::Paused(timer)=>timer.set_time(time,new_time),
Self::Unpaused(timer)=>timer.set_time(time,new_time), Self::Unpaused(timer)=>timer.set_time(time,new_time),
} }
} }
pub fn pause(&mut self,time:Time)->Result<(),Error>{ pub fn pause(&mut self,time:Time<T::In>)->Result<(),Error>{
*self=match *self{ *self=match *self{
Self::Paused(_)=>return Err(Error::AlreadyPaused), Self::Paused(_)=>return Err(Error::AlreadyPaused),
Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)), Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
}; };
Ok(()) Ok(())
} }
pub fn unpause(&mut self,time:Time)->Result<(),Error>{ pub fn unpause(&mut self,time:Time<T::In>)->Result<(),Error>{
*self=match *self{ *self=match *self{
Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)), Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused), Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
@ -249,7 +286,7 @@ impl<T:TimerState> Timer<T>{
Self::Unpaused(_)=>false, Self::Unpaused(_)=>false,
} }
} }
pub fn set_paused(&mut self,time:Time,paused:bool)->Result<(),Error>{ pub fn set_paused(&mut self,time:Time<T::In>,paused:bool)->Result<(),Error>{
match paused{ match paused{
true=>self.pause(time), true=>self.pause(time),
false=>self.unpause(time), false=>self.unpause(time),
@ -257,14 +294,16 @@ impl<T:TimerState> Timer<T>{
} }
} }
//scaled timer methods are generic across PauseState //scaled timer methods are generic across PauseState
impl Timer<Scaled>{ impl<In,Out> Timer<Scaled<In,Out>>
where Time<In>:Copy,
{
pub const fn get_scale(&self)->Ratio64{ pub const fn get_scale(&self)->Ratio64{
match self{ match self{
Self::Paused(timer)=>timer.get_scale(), Self::Paused(timer)=>timer.get_scale(),
Self::Unpaused(timer)=>timer.get_scale(), Self::Unpaused(timer)=>timer.get_scale(),
} }
} }
pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){ pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
match self{ match self{
Self::Paused(timer)=>timer.set_scale(time,new_scale), Self::Paused(timer)=>timer.set_scale(time,new_scale),
Self::Unpaused(timer)=>timer.set_scale(time,new_scale), Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
@ -280,10 +319,15 @@ mod test{
Time::from_secs($s) Time::from_secs($s)
}; };
} }
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
enum Parent{}
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
enum Calculated{}
#[test] #[test]
fn test_timerfixed_scaled(){ fn test_timerfixed_scaled(){
//create a paused timer that reads 0s //create a paused timer that reads 0s
let timer=TimerFixed::<Scaled,Paused>::from_state(Scaled{scale:0.5f32.try_into().unwrap(),offset:sec!(0)}); let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
//the paused timer at 1 second should read 0s //the paused timer at 1 second should read 0s
assert_eq!(timer.time(sec!(1)),sec!(0)); assert_eq!(timer.time(sec!(1)),sec!(0));
@ -300,7 +344,7 @@ mod test{
#[test] #[test]
fn test_timer()->Result<(),Error>{ fn test_timer()->Result<(),Error>{
//create a paused timer that reads 0s //create a paused timer that reads 0s
let mut timer=Timer::<Realtime>::paused(sec!(0),sec!(0)); let mut timer=Timer::<Realtime<Parent,Calculated>>::paused(sec!(0),sec!(0));
//the paused timer at 1 second should read 0s //the paused timer at 1 second should read 0s
assert_eq!(timer.time(sec!(1)),sec!(0)); assert_eq!(timer.time(sec!(1)),sec!(0));

View File

@ -1,6 +1,6 @@
use bnum::{BInt,cast::As}; use bnum::{BInt,cast::As};
#[derive(Clone,Copy,Debug,Default,Hash)] #[derive(Clone,Copy,Debug,Default,Hash,PartialEq,PartialOrd,Ord)]
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled) /// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
/// N is the number of u64s to use /// N is the number of u64s to use
/// F is the number of fractional bits (always N*32 lol) /// F is the number of fractional bits (always N*32 lol)
@ -87,12 +87,6 @@ impl_from!(
i8,i16,i32,i64,i128,isize i8,i16,i32,i64,i128,isize
); );
impl<const N:usize,const F:usize> PartialEq for Fixed<N,F>{
#[inline]
fn eq(&self,other:&Self)->bool{
self.bits.eq(&other.bits)
}
}
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F> impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
where where
T:Copy, T:Copy,
@ -105,12 +99,6 @@ where
} }
impl<const N:usize,const F:usize> Eq for Fixed<N,F>{} impl<const N:usize,const F:usize> Eq for Fixed<N,F>{}
impl<const N:usize,const F:usize> PartialOrd for Fixed<N,F>{
#[inline]
fn partial_cmp(&self,other:&Self)->Option<std::cmp::Ordering>{
self.bits.partial_cmp(&other.bits)
}
}
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F> impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
where where
T:Copy, T:Copy,
@ -121,12 +109,6 @@ impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
self.bits.partial_cmp(&other.into()) self.bits.partial_cmp(&other.into())
} }
} }
impl<const N:usize,const F:usize> Ord for Fixed<N,F>{
#[inline]
fn cmp(&self,other:&Self)->std::cmp::Ordering{
self.bits.cmp(&other.bits)
}
}
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{ impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
type Output=Self; type Output=Self;

View File

@ -33,7 +33,7 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
// LMAO look at this function! // LMAO look at this function!
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{ pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?; let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
f(&mut *dom) f(*dom)
} }
fn coerce_float32(value:&mlua::Value)->Option<f32>{ fn coerce_float32(value:&mlua::Value)->Option<f32>{

View File

@ -7,21 +7,29 @@ pub enum Error{
/* /*
BLOCK_DEMO_HEADER: BLOCK_DEMO_HEADER:
u128 map_resource_id u32 num_maps
u64 map_header_block_id for map_id in 0..num_maps{
i64 simulation_time
u128 map_resource_id
u64 map_header_block_id
}
u32 num_bots u32 num_bots
for bot_id in 0..num_bots{ for bot_id in 0..num_bots{
i64 simulation_time
u128 bot_resource_id u128 bot_resource_id
u64 bot_header_block_id u64 bot_header_block_id
} }
//map loading timeline
//bot loading timeline //bot loading timeline
how to do worldstate for deathrun!? how to do worldstate for deathrun!?
- this is done in the client, there is no worldstate in the demo file
*/ */
pub struct StreamableDemo<R:BinReaderExt>{ pub struct StreamableDemo<R:BinReaderExt>{
map:Box<crate::map::StreamableMap<R>>, map:Vec<crate::map::StreamableMap<R>>,
bots:Vec<crate::bot::StreamableBot<R>>, bots:Vec<crate::bot::StreamableBot<R>>,
} }
impl<R:BinReaderExt> StreamableDemo<R>{ impl<R:BinReaderExt> StreamableDemo<R>{

2
lib/snf/src/session.rs Normal file
View File

@ -0,0 +1,2 @@
// a session is a recording of the client's inputs
// which should deterministically recreate a bot or whatever the client did

View File

@ -15,8 +15,8 @@ source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"] roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
[dependencies] [dependencies]
arrayvec = "0.7.6"
bytemuck = { version = "1.13.1", features = ["derive"] } bytemuck = { version = "1.13.1", features = ["derive"] }
chrono = "0.4.39"
configparser = "3.0.2" configparser = "3.0.2"
ddsfile = "0.5.1" ddsfile = "0.5.1"
glam = "0.29.0" glam = "0.29.0"
@ -28,8 +28,5 @@ strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true } strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true }
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true } strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true }
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true } strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
wasm-bindgen = "0.2.99"
wasm-bindgen-futures = "0.4.49"
web-sys = { version = "0.3.76", features = ["console"] }
wgpu = "23.0.1" wgpu = "23.0.1"
winit = "0.30.7" winit = "0.30.7"

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

@ -3,11 +3,11 @@ pub type INWorker<'a,Task>=CompatNWorker<'a,Task>;
pub struct CompatNWorker<'a,Task>{ pub struct CompatNWorker<'a,Task>{
data:std::marker::PhantomData<Task>, data:std::marker::PhantomData<Task>,
f:Box<dyn FnMut(Task)+'a>, f:Box<dyn FnMut(Task)+Send+'a>,
} }
impl<'a,Task> CompatNWorker<'a,Task>{ impl<'a,Task> CompatNWorker<'a,Task>{
pub fn new(f:impl FnMut(Task)+'a)->CompatNWorker<'a,Task>{ pub fn new(f:impl FnMut(Task)+Send+'a)->CompatNWorker<'a,Task>{
Self{ Self{
data:std::marker::PhantomData, data:std::marker::PhantomData,
f:Box::new(f), f:Box::new(f),

View File

@ -1,23 +1,34 @@
use crate::physics::Body; use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge};
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge,MinkowskiMesh,MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert}; use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
use strafesnet_common::integer::{Time,Fixed,Ratio}; use crate::physics::{Time,Body};
#[derive(Debug)] enum Transition<M:MeshQuery>{
enum Transition<F,E:DirectedEdge,V>{
Miss, Miss,
Next(FEV<F,E,V>,GigaTime), Next(FEV<M>,GigaTime),
Hit(F,GigaTime), Hit(M::Face,GigaTime),
} }
type MinkowskiFEV=FEV<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>; pub enum CrawlResult<M:MeshQuery>{
type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>; Miss(FEV<M>),
Hit(M::Face,GigaTime),
}
fn next_transition(fev:&MinkowskiFEV,body_time:GigaTime,mesh:&MinkowskiMesh,body:&Body,mut best_time:GigaTime)->MinkowskiTransition{ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
where
// This is hardcoded for MinkowskiMesh lol
M::Face:Copy,
M::Edge:Copy,
M::Vert:Copy,
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
{
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
//conflicting derivative means it crosses in the wrong direction. //conflicting derivative means it crosses in the wrong direction.
//if the transition time is equal to an already tested transition, do not replace the current best. //if the transition time is equal to an already tested transition, do not replace the current best.
let mut best_transition=MinkowskiTransition::Miss; let mut best_transition=Transition::Miss;
match fev{ match self{
&MinkowskiFEV::Face(face_id)=>{ &FEV::Face(face_id)=>{
//test own face collision time, ignoring roots with zero or conflicting derivative //test own face collision time, ignoring roots with zero or conflicting derivative
//n=face.normal d=face.dot //n=face.normal d=face.dot
//n.a t^2+n.v t+n.p-d==0 //n.a t^2+n.v t+n.p-d==0
@ -27,7 +38,7 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt; best_time=dt;
best_transition=MinkowskiTransition::Hit(face_id,dt); best_transition=Transition::Hit(face_id,dt);
break; break;
} }
} }
@ -41,14 +52,14 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){ for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt; best_time=dt;
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt); best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
break; break;
} }
} }
} }
//if none: //if none:
}, },
&MinkowskiFEV::Edge(edge_id)=>{ &FEV::Edge(edge_id)=>{
//test each face collision time, ignoring roots with zero or conflicting derivative //test each face collision time, ignoring roots with zero or conflicting derivative
let edge_n=mesh.edge_n(edge_id); let edge_n=mesh.edge_n(edge_id);
let edge_verts=mesh.edge_verts(edge_id); let edge_verts=mesh.edge_verts(edge_id);
@ -61,7 +72,7 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){ for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt; best_time=dt;
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Face(edge_face_id),dt); best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
break; break;
} }
} }
@ -74,14 +85,14 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4()); let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
best_time=dt; best_time=dt;
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Vert(vert_id),dt); best_transition=Transition::Next(FEV::Vert(vert_id),dt);
break; break;
} }
} }
} }
//if none: //if none:
}, },
&MinkowskiFEV::Vert(vert_id)=>{ &FEV::Vert(vert_id)=>{
//test each edge collision time, ignoring roots with zero or conflicting derivative //test each edge collision time, ignoring roots with zero or conflicting derivative
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){ for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
//edge is directed away from vertex, but we want the dot product to turn out negative //edge is directed away from vertex, but we want the dot product to turn out negative
@ -90,7 +101,7 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4()); let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
best_time=dt; best_time=dt;
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt); best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
break; break;
} }
} }
@ -100,28 +111,24 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
} }
best_transition best_transition
} }
pub enum CrawlResult<F,E:DirectedEdge,V>{ pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
Miss(FEV<F,E,V>), let mut body_time={
Hit(F,GigaTime), let r=(start_time-relative_body.time).to_ratio();
} Ratio::new(r.num.fix_4(),r.den.fix_4())
type MinkowskiCrawlResult=CrawlResult<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>; };
pub fn crawl_fev(mut fev:MinkowskiFEV,mesh:&MinkowskiMesh,relative_body:&Body,start_time:Time,time_limit:Time)->MinkowskiCrawlResult{ let time_limit={
let mut body_time={ let r=(time_limit-relative_body.time).to_ratio();
let r=(start_time-relative_body.time).to_ratio(); Ratio::new(r.num.fix_4(),r.den.fix_4())
Ratio::new(r.num.fix_4(),r.den.fix_4()) };
}; for _ in 0..20{
let time_limit={ match self.next_transition(body_time,mesh,relative_body,time_limit){
let r=(time_limit-relative_body.time).to_ratio(); Transition::Miss=>return CrawlResult::Miss(self),
Ratio::new(r.num.fix_4(),r.den.fix_4()) Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
}; Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
for _ in 0..20{ }
match next_transition(&fev,body_time,mesh,relative_body,time_limit){
Transition::Miss=>return CrawlResult::Miss(fev),
Transition::Next(next_fev,next_time)=>(fev,body_time)=(next_fev,next_time),
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
} }
//TODO: fix all bugs
//println!("Too many iterations! Using default behaviour instead of crashing...");
CrawlResult::Miss(self)
} }
//TODO: fix all bugs
//println!("Too many iterations! Using default behaviour instead of crashing...");
CrawlResult::Miss(fev)
} }

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{ pub struct GraphicsState{
pipelines:GraphicsPipelines, pipelines:GraphicsPipelines,
bind_groups:GraphicsBindGroups, bind_groups:GraphicsBindGroups,
@ -882,7 +876,7 @@ impl GraphicsState{
view:&wgpu::TextureView, view:&wgpu::TextureView,
device:&wgpu::Device, device:&wgpu::Device,
queue:&wgpu::Queue, 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 //TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input

View File

@ -1,5 +1,5 @@
pub enum Instruction{ pub enum Instruction{
Render(crate::graphics::FrameState), Render(crate::physics_worker::FrameState),
//UpdateModel(crate::graphics::GraphicsModelUpdate), //UpdateModel(crate::graphics::GraphicsModelUpdate),
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings), Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
ChangeMap(strafesnet_common::map::CompleteMap), ChangeMap(strafesnet_common::map::CompleteMap),
@ -14,9 +14,6 @@ WorkerDescription{
*/ */
//up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order //up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order
fn print(message:&str){
web_sys::console::log_1(&message.into());
}
pub fn new<'a>( pub fn new<'a>(
mut graphics:crate::graphics::GraphicsState, mut graphics:crate::graphics::GraphicsState,
mut config:wgpu::SurfaceConfiguration, mut config:wgpu::SurfaceConfiguration,
@ -24,7 +21,6 @@ pub fn new<'a>(
device:wgpu::Device, device:wgpu::Device,
queue:wgpu::Queue, queue:wgpu::Queue,
)->crate::compat_worker::INWorker<'a,Instruction>{ )->crate::compat_worker::INWorker<'a,Instruction>{
let mut resize=None;
crate::compat_worker::INWorker::new(move |ins:Instruction|{ crate::compat_worker::INWorker::new(move |ins:Instruction|{
match ins{ match ins{
Instruction::ChangeMap(map)=>{ Instruction::ChangeMap(map)=>{
@ -32,26 +28,15 @@ pub fn new<'a>(
graphics.generate_models(&device,&queue,&map); graphics.generate_models(&device,&queue,&map);
}, },
Instruction::Resize(size,user_settings)=>{ Instruction::Resize(size,user_settings)=>{
resize=Some((size,user_settings)); println!("Resizing to {:?}",size);
let t0=std::time::Instant::now();
config.width=size.width.max(1);
config.height=size.height.max(1);
surface.configure(&device,&config);
graphics.resize(&device,&config,&user_settings);
println!("Resize took {:?}",t0.elapsed());
} }
Instruction::Render(frame_state)=>{ Instruction::Render(frame_state)=>{
if let Some((size,user_settings))=resize.take(){
print(format!("Resizing to {:?}",size).as_str());
//let t0=std::time::Instant::now();
match size{
winit::dpi::PhysicalSize{width:2560,height:1440}=>{
config.width=size.width.clamp(1,2560);
config.height=size.height.clamp(1,1440);
},
_=>{
config.width=size.width.clamp(1,1280);
config.height=size.height.clamp(1,720);
}
}
surface.configure(&device,&config);
graphics.resize(&device,&config,&user_settings);
//println!("Resize took {:?}",t0.elapsed());
}
//this has to go deeper somehow //this has to go deeper somehow
let frame=match surface.get_current_texture(){ let frame=match surface.get_current_texture(){
Ok(frame)=>frame, Ok(frame)=>frame,

View File

@ -1,3 +1,4 @@
mod body;
mod file; mod file;
mod setup; mod setup;
mod window; mod window;
@ -5,6 +6,7 @@ mod worker;
mod physics; mod physics;
mod graphics; mod graphics;
mod settings; mod settings;
mod push_solve;
mod face_crawler; mod face_crawler;
mod compat_worker; mod compat_worker;
mod model_physics; mod model_physics;
@ -12,10 +14,8 @@ mod model_graphics;
mod physics_worker; mod physics_worker;
mod graphics_worker; mod graphics_worker;
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
fn main(){ fn main(){
let title=format!("Strafe Client v{}",env!("CARGO_PKG_VERSION")); setup::setup_and_start(TITLE);
#[cfg(target_arch="wasm32")]
wasm_bindgen_futures::spawn_local(setup::setup_and_start(title));
#[cfg(not(target_arch="wasm32"))]
pollster::block_on(setup::setup_and_start(title));
} }

View File

@ -3,6 +3,9 @@ use std::collections::{HashSet,HashMap};
use strafesnet_common::integer::vec3::Vector3; use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::model::{self,MeshId,PolygonIter}; use strafesnet_common::model::{self,MeshId,PolygonIter};
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio}; 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{ pub trait UndirectedEdge{
type DirectedEdge:Copy+DirectedEdge; type DirectedEdge:Copy+DirectedEdge;
@ -51,10 +54,10 @@ impl DirectedEdge for SubmeshDirectedEdgeId{
//Vertex <-> Edge <-> Face -> Collide //Vertex <-> Edge <-> Face -> Collide
#[derive(Debug)] #[derive(Debug)]
pub enum FEV<F,E:DirectedEdge,V>{ pub enum FEV<M:MeshQuery>{
Face(F), Face(M::Face),
Edge(E::UndirectedEdge), Edge(<M::Edge as DirectedEdge>::UndirectedEdge),
Vert(V), Vert(M::Vert),
} }
//use Unit32 #[repr(C)] for map files //use Unit32 #[repr(C)] for map files
@ -64,25 +67,28 @@ struct Face{
dot:Planar64, dot:Planar64,
} }
struct Vert(Planar64Vec3); struct Vert(Planar64Vec3);
pub trait MeshQuery<FACE:Clone,EDGE:Clone+DirectedEdge,VERT:Clone>{ pub trait MeshQuery{
type Face:Clone;
type Edge:Clone+DirectedEdge;
type Vert:Clone;
// Vertex must be Planar64Vec3 because it represents an actual position // Vertex must be Planar64Vec3 because it represents an actual position
type Normal; type Normal;
type Offset; type Offset;
fn edge_n(&self,edge_id:EDGE::UndirectedEdge)->Planar64Vec3{ fn edge_n(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Planar64Vec3{
let verts=self.edge_verts(edge_id); let verts=self.edge_verts(edge_id);
self.vert(verts[1].clone())-self.vert(verts[0].clone()) self.vert(verts[1].clone())-self.vert(verts[0].clone())
} }
fn directed_edge_n(&self,directed_edge_id:EDGE)->Planar64Vec3{ fn directed_edge_n(&self,directed_edge_id:Self::Edge)->Planar64Vec3{
let verts=self.edge_verts(directed_edge_id.as_undirected()); let verts=self.edge_verts(directed_edge_id.as_undirected());
(self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1) (self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1)
} }
fn vert(&self,vert_id:VERT)->Planar64Vec3; fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
fn face_nd(&self,face_id:FACE)->(Self::Normal,Self::Offset); fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
fn face_edges(&self,face_id:FACE)->Cow<Vec<EDGE>>; fn face_edges(&self,face_id:Self::Face)->Cow<Vec<Self::Edge>>;
fn edge_faces(&self,edge_id:EDGE::UndirectedEdge)->Cow<[FACE;2]>; fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>;
fn edge_verts(&self,edge_id:EDGE::UndirectedEdge)->Cow<[VERT;2]>; fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>;
fn vert_edges(&self,vert_id:VERT)->Cow<Vec<EDGE>>; fn vert_edges(&self,vert_id:Self::Vert)->Cow<Vec<Self::Edge>>;
fn vert_faces(&self,vert_id:VERT)->Cow<Vec<FACE>>; fn vert_faces(&self,vert_id:Self::Vert)->Cow<Vec<Self::Face>>;
} }
struct FaceRefs{ struct FaceRefs{
edges:Vec<SubmeshDirectedEdgeId>, edges:Vec<SubmeshDirectedEdgeId>,
@ -421,7 +427,10 @@ pub struct PhysicsMeshView<'a>{
data:&'a PhysicsMeshData, data:&'a PhysicsMeshData,
topology:&'a PhysicsMeshTopology, topology:&'a PhysicsMeshTopology,
} }
impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for PhysicsMeshView<'_>{ impl MeshQuery for PhysicsMeshView<'_>{
type Face=SubmeshFaceId;
type Edge=SubmeshDirectedEdgeId;
type Vert=SubmeshVertId;
type Normal=Planar64Vec3; type Normal=Planar64Vec3;
type Offset=Planar64; type Offset=Planar64;
fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){ fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){
@ -495,7 +504,10 @@ impl TransformedMesh<'_>{
) )
} }
} }
impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for TransformedMesh<'_>{ impl MeshQuery for TransformedMesh<'_>{
type Face=SubmeshFaceId;
type Edge=SubmeshDirectedEdgeId;
type Vert=SubmeshVertId;
type Normal=Vector3<Fixed<3,96>>; type Normal=Vector3<Fixed<3,96>>;
type Offset=Fixed<4,128>; type Offset=Fixed<4,128>;
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){ fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
@ -669,13 +681,13 @@ impl MinkowskiMesh<'_>{
} }
} }
/// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex /// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>{ fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiMesh>{
//start on any vertex //start on any vertex
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge //cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
//cross edge-face boundary if it's uncrossable //cross edge-face boundary if it's uncrossable
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){ match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){
//if a vert is returned, it is the closest point to the infinity point //if a vert is returned, it is the closest point to the infinity point
EV::Vert(vert_id)=>FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Vert(vert_id), EV::Vert(vert_id)=>FEV::Vert(vert_id),
EV::Edge(edge_id)=>{ EV::Edge(edge_id)=>{
//cross to face if the boundary is not crossable and we are on the wrong side //cross to face if the boundary is not crossable and we are on the wrong side
let edge_n=self.edge_n(edge_id); let edge_n=self.edge_n(edge_id);
@ -693,14 +705,14 @@ impl MinkowskiMesh<'_>{
//infinity_dir can always be treated as a velocity //infinity_dir can always be treated as a velocity
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){ if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
//both faces cannot pass this condition, return early if one does. //both faces cannot pass this condition, return early if one does.
return FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Face(face_id); return FEV::Face(face_id);
} }
} }
FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Edge(edge_id) FEV::Edge(edge_id)
}, },
} }
} }
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<MinkowskiMesh>>{
infinity_body.infinity_dir().map_or(None,|dir|{ infinity_body.infinity_dir().map_or(None,|dir|{
let infinity_fev=self.infinity_fev(-dir,infinity_body.position); let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
//a line is simpler to solve than a parabola //a line is simpler to solve than a parabola
@ -708,24 +720,24 @@ impl MinkowskiMesh<'_>{
infinity_body.acceleration=vec3::ZERO; infinity_body.acceleration=vec3::ZERO;
//crawl in from negative infinity along a tangent line to get the closest fev //crawl in from negative infinity along a tangent line to get the closest fev
// TODO: change crawl_fev args to delta time? Optional values? // 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 infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev), crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
crate::face_crawler::CrawlResult::Hit(_,_)=>None, 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|{ self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{
//continue forwards along the body parabola //continue forwards along the body parabola
match crate::face_crawler::crawl_fev(fev,self,relative_body,relative_body.time,time_limit){ match fev.crawl(self,relative_body,relative_body.time,time_limit){
crate::face_crawler::CrawlResult::Miss(_)=>None, crate::face_crawler::CrawlResult::Miss(_)=>None,
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)), crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
} }
}) })
} }
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 //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_position(time_limit),
-relative_body.extrapolated_velocity(time_limit), -relative_body.extrapolated_velocity(time_limit),
relative_body.acceleration, relative_body.acceleration,
@ -733,13 +745,13 @@ impl MinkowskiMesh<'_>{
); );
self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{ self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{
//continue backwards along the body parabola //continue backwards along the body parabola
match crate::face_crawler::crawl_fev(fev,self,&-relative_body.clone(),-time_limit,-relative_body.time){ match fev.crawl(self,&-relative_body.clone(),-time_limit,-relative_body.time){
crate::face_crawler::CrawlResult::Miss(_)=>None, crate::face_crawler::CrawlResult::Miss(_)=>None,
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,-time)),//no need to test -time<time_limit because of the first step crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,-time)),//no need to test -time<time_limit because of the first step
} }
}) })
} }
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) //no algorithm needed, there is only one state and two cases (Edge,None)
//determine when it passes an edge ("sliding off" case) //determine when it passes an edge ("sliding off" case)
let mut best_time={ let mut best_time={
@ -766,15 +778,15 @@ impl MinkowskiMesh<'_>{
} }
best_edge.map(|e|(e.as_undirected(),best_time)) 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); 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 infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
crate::face_crawler::CrawlResult::Miss(_)=>None, crate::face_crawler::CrawlResult::Miss(_)=>None,
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)), crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
} }
} }
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{ 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, //movement must escape the mesh forwards and backwards in time,
//otherwise the point is not inside the mesh //otherwise the point is not inside the mesh
self.infinity_in(infinity_body) self.infinity_in(infinity_body)
@ -784,9 +796,13 @@ impl MinkowskiMesh<'_>{
) )
} }
} }
impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiMesh<'_>{ impl MeshQuery for MinkowskiMesh<'_>{
type Face=MinkowskiFace;
type Edge=MinkowskiDirectedEdge;
type Vert=MinkowskiVert;
type Normal=Vector3<Fixed<3,96>>; type Normal=Vector3<Fixed<3,96>>;
type Offset=Fixed<4,128>; type Offset=Fixed<4,128>;
// TODO: relative d
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){ fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
match face_id{ match face_id{
MinkowskiFace::VertFace(v0,f1)=>{ MinkowskiFace::VertFace(v0,f1)=>{

View File

@ -5,15 +5,18 @@ use strafesnet_common::map;
use strafesnet_common::run; use strafesnet_common::run;
use strafesnet_common::aabb; use strafesnet_common::aabb;
use strafesnet_common::model::{MeshId,ModelId}; use strafesnet_common::model::{MeshId,ModelId};
use strafesnet_common::mouse::MouseState;
use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId}; use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId};
use strafesnet_common::gameplay_modes::{self,StageId}; use strafesnet_common::gameplay_modes::{self,StageId};
use strafesnet_common::gameplay_style::{self,StyleModifiers}; use strafesnet_common::gameplay_style::{self,StyleModifiers};
use strafesnet_common::controls_bitflag::Controls; use strafesnet_common::controls_bitflag::Controls;
use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,TimedInstruction}; use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,InstructionFeedback,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; use gameplay::ModeState;
pub type Body=crate::body::Body<TimeInner>;
type MouseState=strafesnet_common::mouse::MouseState<TimeInner>;
//external influence //external influence
//this is how you influence the physics from outside //this is how you influence the physics from outside
use strafesnet_common::physics::Instruction as PhysicsInputInstruction; use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
@ -21,7 +24,7 @@ use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
//internal influence //internal influence
//when the physics asks itself what happens next, this is how it's represented //when the physics asks itself what happens next, this is how it's represented
#[derive(Debug)] #[derive(Debug)]
enum PhysicsInternalInstruction{ pub enum PhysicsInternalInstruction{
CollisionStart(Collision,model_physics::GigaTime), CollisionStart(Collision,model_physics::GigaTime),
CollisionEnd(Collision,model_physics::GigaTime), CollisionEnd(Collision,model_physics::GigaTime),
StrafeTick, StrafeTick,
@ -29,32 +32,13 @@ enum PhysicsInternalInstruction{
// Water, // Water,
} }
#[derive(Debug)] #[derive(Debug)]
enum PhysicsInstruction{ pub enum PhysicsInstruction{
Internal(PhysicsInternalInstruction), Internal(PhysicsInternalInstruction),
//InputInstructions conditionally activate RefreshWalkTarget //InputInstructions conditionally activate RefreshWalkTarget
//(by doing what SetWalkTargetVelocity used to do and then flagging it) //(by doing what SetWalkTargetVelocity used to do and then flagging it)
Input(PhysicsInputInstruction), 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)] #[derive(Clone,Debug,Default)]
pub struct InputState{ pub struct InputState{
mouse:MouseState, mouse:MouseState,
@ -235,11 +219,11 @@ impl PhysicsModels{
} }
fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{ fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{
//ModelId can possibly be a decoration //ModelId can possibly be a decoration
self.contact_models.get(&ContactModelId::new(model_id.get())).map_or_else( match self.contact_models.get(&ContactModelId::new(model_id.get())){
||self.intersect_models.get(&IntersectModelId::new(model_id.get())) Some(model)=>Some(&model.transform),
None=>self.intersect_models.get(&IntersectModelId::new(model_id.get()))
.map(|model|&model.transform), .map(|model|&model.transform),
|model|Some(&model.transform) }
)
} }
fn contact_model(&self,model_id:ContactModelId)->&ContactModel{ fn contact_model(&self,model_id:ContactModelId)->&ContactModel{
&self.contact_models[&model_id] &self.contact_models[&model_id]
@ -326,6 +310,15 @@ impl std::default::Default for PhysicsCamera{
} }
mod gameplay{ mod gameplay{
use super::{gameplay_modes,HashSet,HashMap,ModelId}; use super::{gameplay_modes,HashSet,HashMap,ModelId};
pub enum JumpIncrementResult{
Allowed,
ExceededLimit,
}
impl JumpIncrementResult{
pub const fn is_allowed(self)->bool{
matches!(self,JumpIncrementResult::Allowed)
}
}
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct ModeState{ pub struct ModeState{
mode_id:gameplay_modes::ModeId, mode_id:gameplay_modes::ModeId,
@ -344,8 +337,14 @@ mod gameplay{
pub const fn get_next_ordered_checkpoint_id(&self)->gameplay_modes::CheckpointId{ pub const fn get_next_ordered_checkpoint_id(&self)->gameplay_modes::CheckpointId{
self.next_ordered_checkpoint_id self.next_ordered_checkpoint_id
} }
pub fn get_jump_count(&self,model_id:ModelId)->Option<u32>{ fn increment_jump_count(&mut self,model_id:ModelId)->u32{
self.jump_counts.get(&model_id).copied() *self.jump_counts.entry(model_id).and_modify(|c|*c+=1).or_insert(1)
}
pub fn try_increment_jump_count(&mut self,model_id:ModelId,jump_limit:Option<u8>)->JumpIncrementResult{
match jump_limit{
Some(jump_limit) if (jump_limit as u32)<self.increment_jump_count(model_id)=>JumpIncrementResult::ExceededLimit,
_=>JumpIncrementResult::Allowed,
}
} }
pub const fn ordered_checkpoint_count(&self)->u32{ pub const fn ordered_checkpoint_count(&self)->u32{
self.next_ordered_checkpoint_id.get() self.next_ordered_checkpoint_id.get()
@ -559,7 +558,7 @@ impl MoveState{
=>None, =>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 //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{
@ -726,8 +725,8 @@ impl Collision{
} }
#[derive(Clone,Debug,Default)] #[derive(Clone,Debug,Default)]
struct TouchingState{ struct TouchingState{
contacts:HashSet::<ContactCollision>, contacts:HashSet<ContactCollision>,
intersects:HashSet::<IntersectCollision>, intersects:HashSet<IntersectCollision>,
} }
impl TouchingState{ impl TouchingState{
fn clear(&mut self){ fn clear(&mut self){
@ -766,27 +765,29 @@ impl TouchingState{
a a
} }
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){ fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){
//TODO: trey push solve let contacts=self.contacts.iter().map(|contact|{
for contact in &self.contacts{
let n=contact_normal(models,hitbox_mesh,contact); let n=contact_normal(models,hitbox_mesh,contact);
let d=n.dot(*velocity); crate::push_solve::Contact{
if d.is_negative(){ position:vec3::ZERO,
*velocity-=(n*d/n.length_squared()).divide().fix_1(); velocity:n,
normal:n,
} }
} }).collect();
*velocity=crate::push_solve::push_solve(&contacts,*velocity);
} }
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){ fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){
//TODO: trey push solve let contacts=self.contacts.iter().map(|contact|{
for contact in &self.contacts{
let n=contact_normal(models,hitbox_mesh,contact); let n=contact_normal(models,hitbox_mesh,contact);
let d=n.dot(*acceleration); crate::push_solve::Contact{
if d.is_negative(){ position:vec3::ZERO,
*acceleration-=(n*d/n.length_squared()).divide().fix_1(); velocity:n,
normal:n,
} }
} }).collect();
*acceleration=crate::push_solve::push_solve(&contacts,*acceleration);
} }
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
let relative_body=VirtualBody::relative(&Body::ZERO,body).body(time); let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
for contact in &self.contacts{ for contact in &self.contacts{
//detect face slide off //detect face slide off
let model_mesh=models.contact_mesh(contact); let model_mesh=models.contact_mesh(contact);
@ -818,142 +819,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)] #[derive(Clone,Debug)]
pub struct PhysicsState{ pub struct PhysicsState{
time:Time, time:Time,
@ -1021,7 +886,7 @@ impl PhysicsState{
new_state.camera.sensitivity=self.camera.sensitivity; new_state.camera.sensitivity=self.camera.sensitivity;
*self=new_state; *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) self.move_state.next_move_instruction(&self.style.strafe,self.time)
} }
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){ fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
@ -1070,15 +935,24 @@ 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 // the physics consumes both PhysicsInputInstruction and PhysicsInternalInstruction,
impl instruction::InstructionConsumer<PhysicsInstruction> for PhysicsContext{ // but can only emit PhysicsInternalInstruction
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction>){ impl InstructionConsumer<PhysicsInternalInstruction> for PhysicsContext{
atomic_state_update(&mut self.state,&self.data,ins) type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInternalInstruction,TimeInner>){
atomic_internal_instruction(&mut self.state,&self.data,ins)
} }
} }
impl instruction::InstructionEmitter<PhysicsInternalInstruction> for PhysicsContext{ impl InstructionConsumer<PhysicsInputInstruction> for PhysicsContext{
//this little next instruction function can cache its return value and invalidate the cached value by watching the State. type TimeInner=TimeInner;
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{ fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInputInstruction,TimeInner>){
atomic_input_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionEmitter<PhysicsInternalInstruction> 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<PhysicsInternalInstruction,TimeInner>>{
next_instruction_internal(&self.state,&self.data,time_limit) next_instruction_internal(&self.state,&self.data,time_limit)
} }
} }
@ -1235,29 +1109,14 @@ impl PhysicsContext{
println!("Physics Objects: {}",model_count); println!("Physics Objects: {}",model_count);
} }
//tickless gaming pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction,TimeInner>){
fn run_internal_exhaustive(&mut self,time_limit:Time){ self.process_exhaustive(instruction.time);
//prepare is ommitted - everything is done via instructions. self.process_instruction(instruction);
while let Some(instruction)=self.next_instruction(time_limit){//collect
//process
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_internal_exhaustive(instruction.time);
self.process_instruction(TimedInstruction{
time:instruction.time,
instruction:PhysicsInstruction::Input(instruction.instruction),
});
} }
} }
//this is the one who asks //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 //JUST POLLING!!! NO MUTATION
let mut collector = instruction::InstructionCollector::new(time_limit); let mut collector = instruction::InstructionCollector::new(time_limit);
@ -1279,6 +1138,7 @@ impl PhysicsContext{
collector.collect(minkowski.predict_collision_in(relative_body,collector.time()) collector.collect(minkowski.predict_collision_in(relative_body,collector.time())
//temp (?) code to avoid collision loops //temp (?) code to avoid collision loops
.map_or(None,|(face,dt)|{ .map_or(None,|(face,dt)|{
// this must be rounded to avoid the infinite loop when hitting the start zone
let time=relative_body.time+dt.into(); let time=relative_body.time+dt.into();
if time<=state.time{None}else{Some((time,face,dt))}}) if time<=state.time{None}else{Some((time,face,dt))}})
.map(|(time,face,dt)| .map(|(time,face,dt)|
@ -1441,7 +1301,7 @@ enum TeleportToSpawnError{
NoModel, NoModel,
} }
fn teleport_to_spawn( fn teleport_to_spawn(
stage:&gameplay_modes::Stage, spawn_model_id:ModelId,
move_state:&mut MoveState, move_state:&mut MoveState,
body:&mut Body, body:&mut Body,
touching:&mut TouchingState, touching:&mut TouchingState,
@ -1456,14 +1316,78 @@ fn teleport_to_spawn(
input_state:&InputState, input_state:&InputState,
time:Time, time:Time,
)->Result<(),TeleportToSpawnError>{ )->Result<(),TeleportToSpawnError>{
//jump count and checkpoints are always reset on teleport_to_spawn.
//Map makers are expected to use tools to prevent
//multi-boosting on JumpLimit boosters such as spawning into a SetVelocity
mode_state.clear();
const EPSILON:Planar64=Planar64::raw((1<<32)/16); const EPSILON:Planar64=Planar64::raw((1<<32)/16);
let transform=models.get_model_transform(stage.spawn()).ok_or(TeleportToSpawnError::NoModel)?; let transform=models.get_model_transform(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?;
//TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation //TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation
let point=transform.vertex.transform_point3(vec3::Y).fix_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]); let point=transform.vertex.transform_point3(vec3::Y).fix_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]);
teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time); teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time);
Ok(()) Ok(())
} }
struct CheckpointCheckOutcome{
set_stage:Option<StageId>,
teleport_to_model:Option<ModelId>,
}
// stage_element.touch_result(mode,mode_state)
fn checkpoint_check(
mode_state:&ModeState,
stage_element:&gameplay_modes::StageElement,
mode:&gameplay_modes::Mode,
)->CheckpointCheckOutcome{
let current_stage_id=mode_state.get_stage_id();
let target_stage_id=stage_element.stage_id();
if current_stage_id<target_stage_id{
//checkpoint check
//check if current stage is complete
if let Some(current_stage)=mode.get_stage(current_stage_id){
if !current_stage.is_complete(mode_state.ordered_checkpoint_count(),mode_state.unordered_checkpoint_count()){
return CheckpointCheckOutcome{
set_stage:None,
teleport_to_model:Some(current_stage.spawn()),
};
}
}
//check if all between stages have no checkpoints required to pass them
for stage_id in current_stage_id.get()+1..target_stage_id.get(){
let stage_id=StageId::new(stage_id);
//check if none of the between stages has checkpoints, if they do teleport back to that stage
match mode.get_stage(stage_id){
Some(stage)=>if !stage.is_empty(){
return CheckpointCheckOutcome{
set_stage:Some(stage_id),
teleport_to_model:Some(stage.spawn()),
};
},
//no such stage! set to last existing stage
None=>return CheckpointCheckOutcome{
set_stage:Some(StageId::new(stage_id.get()-1)),
teleport_to_model:None,
},
}
};
//notably you do not get teleported for touching ordered checkpoints in the wrong order within the same stage.
return CheckpointCheckOutcome{
set_stage:Some(target_stage_id),
teleport_to_model:None,
};
}else if stage_element.force(){
//forced stage_element will set the stage_id even if the stage has already been passed
return CheckpointCheckOutcome{
set_stage:Some(target_stage_id),
teleport_to_model:None,
};
}
CheckpointCheckOutcome{
set_stage:None,
teleport_to_model:None,
}
}
fn run_teleport_behaviour( fn run_teleport_behaviour(
model_id:ModelId, model_id:ModelId,
wormhole:Option<&gameplay_attributes::Wormhole>, wormhole:Option<&gameplay_attributes::Wormhole>,
@ -1481,70 +1405,38 @@ fn run_teleport_behaviour(
input_state:&InputState, input_state:&InputState,
time:Time, time:Time,
){ ){
//TODO: jump count and checkpoints are always reset on teleport.
//Map makers are expected to use tools to prevent
//multi-boosting on JumpLimit boosters such as spawning into a SetVelocity
if let Some(mode)=mode{ if let Some(mode)=mode{
if let Some(stage_element)=mode.get_element(model_id){ if let Some(stage_element)=mode.get_element(model_id){
if let Some(stage)=mode.get_stage(stage_element.stage_id()){ if let Some(stage)=mode.get_stage(stage_element.stage_id()){
if mode_state.get_stage_id()<stage_element.stage_id(){ let CheckpointCheckOutcome{set_stage,teleport_to_model}=checkpoint_check(mode_state,stage_element,mode);
//checkpoint check if let Some(stage_id)=set_stage{
//check if current stage is complete mode_state.set_stage_id(stage_id);
if let Some(current_stage)=mode.get_stage(mode_state.get_stage_id()){
if !current_stage.is_complete(mode_state.ordered_checkpoint_count(),mode_state.unordered_checkpoint_count()){
//do the stage checkpoints have to be reset?
let _=teleport_to_spawn(current_stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
return;
}
} }
//check if all between stages have no checkpoints required to pass them if let Some(model_id)=teleport_to_model{
let mut loop_unbroken=true; let _=teleport_to_spawn(model_id,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
for stage_id in mode_state.get_stage_id().get()+1..stage_element.stage_id().get(){
let stage_id=StageId::new(stage_id);
//check if none of the between stages has checkpoints, if they do teleport back to that stage
match mode.get_stage(stage_id){
Some(stage)=>if !stage.is_empty(){
mode_state.set_stage_id(stage_id);
let _=teleport_to_spawn(stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
return;
},
None=>{
//no such stage! set to last existing stage and break loop
mode_state.set_stage_id(StageId::new(stage_id.get()-1));
loop_unbroken=false;
break;
},
}
};
//notably you do not get teleported for touching ordered checkpoints in the wrong order within the same stage.
if loop_unbroken{
mode_state.set_stage_id(stage_element.stage_id());
}
}else if stage_element.force(){
//forced stage_element will set the stage_id even if the stage has already been passed
mode_state.set_stage_id(stage_element.stage_id());
}
match stage_element.behaviour(){
gameplay_modes::StageElementBehaviour::SpawnAt=>(),
gameplay_modes::StageElementBehaviour::Trigger
|gameplay_modes::StageElementBehaviour::Teleport=>if let Some(mode_state_stage)=mode.get_stage(mode_state.get_stage_id()){
//I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird
let _=teleport_to_spawn(mode_state_stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
return; return;
}, }
gameplay_modes::StageElementBehaviour::Platform=>(), match stage_element.behaviour(){
gameplay_modes::StageElementBehaviour::Check=>(),//this is to run the checkpoint check behaviour without any other side effects gameplay_modes::StageElementBehaviour::SpawnAt=>(),
gameplay_modes::StageElementBehaviour::Checkpoint=>{ gameplay_modes::StageElementBehaviour::Trigger
//each of these checks if the model is actually a valid respective checkpoint object |gameplay_modes::StageElementBehaviour::Teleport=>if let Some(mode_state_stage)=mode.get_stage(mode_state.get_stage_id()){
//accumulate sequential ordered checkpoints //I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird
mode_state.accumulate_ordered_checkpoint(&stage,model_id); let _=teleport_to_spawn(mode_state_stage.spawn(),move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
//insert model id in accumulated unordered checkpoints return;
mode_state.accumulate_unordered_checkpoint(&stage,model_id); },
}, gameplay_modes::StageElementBehaviour::Platform=>(),
gameplay_modes::StageElementBehaviour::Check=>(),//this is to run the checkpoint check behaviour without any other side effects
gameplay_modes::StageElementBehaviour::Checkpoint=>{
//each of these checks if the model is actually a valid respective checkpoint object
//accumulate sequential ordered checkpoints
mode_state.accumulate_ordered_checkpoint(&stage,model_id);
//insert model id in accumulated unordered checkpoints
mode_state.accumulate_unordered_checkpoint(&stage,model_id);
},
}
} }
} }
} }
}
if let Some(&gameplay_attributes::Wormhole{destination_model})=wormhole{ if let Some(&gameplay_attributes::Wormhole{destination_model})=wormhole{
if let (Some(origin),Some(destination))=(models.get_model_transform(model_id),models.get_model_transform(destination_model)){ if let (Some(origin),Some(destination))=(models.get_model_transform(model_id),models.get_model_transform(destination_model)){
let point=body.position-origin.vertex.translation+destination.vertex.translation; let point=body.position-origin.vertex.translation+destination.vertex.translation;
@ -1554,6 +1446,18 @@ fn run_teleport_behaviour(
} }
} }
fn not_spawn_at(
mode:Option<&gameplay_modes::Mode>,
model_id:ModelId,
)->bool{
if let Some(mode)=mode{
if let Some(stage_element)=mode.get_element(model_id){
return stage_element.behaviour()!=gameplay_modes::StageElementBehaviour::SpawnAt;
}
}
true
}
fn collision_start_contact( fn collision_start_contact(
move_state:&mut MoveState, move_state:&mut MoveState,
body:&mut Body, body:&mut Body,
@ -1576,6 +1480,9 @@ fn collision_start_contact(
touching.insert(Collision::Contact(contact)); touching.insert(Collision::Contact(contact));
//clip v //clip v
set_velocity(body,touching,models,hitbox_mesh,incident_velocity); set_velocity(body,touching,models,hitbox_mesh,incident_velocity);
let mut allow_jump=true;
let model_id=contact.model_id.into();
let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id);
match &attr.contacting.contact_behaviour{ match &attr.contacting.contact_behaviour{
Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"), Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
@ -1596,9 +1503,10 @@ fn collision_start_contact(
let walk_state=ContactMoveState::ladder(ladder_settings,body,gravity,target_velocity,contact); let walk_state=ContactMoveState::ladder(ladder_settings,body,gravity,target_velocity,contact);
move_state.set_move_state(MoveState::Ladder(walk_state),body,touching,models,hitbox_mesh,style,camera,input_state); move_state.set_move_state(MoveState::Ladder(walk_state),body,touching,models,hitbox_mesh,style,camera,input_state);
}, },
Some(gameplay_attributes::ContactingBehaviour::NoJump)=>todo!("nyi"), Some(gameplay_attributes::ContactingBehaviour::NoJump)=>allow_jump=false,
None=>if let Some(walk_settings)=&style.walk{ None=>if let Some(walk_settings)=&style.walk{
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){ if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){
allow_run_teleport_behaviour=true;
//ground //ground
let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state); let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
let walk_state=ContactMoveState::ground(walk_settings,body,gravity,target_velocity,contact); let walk_state=ContactMoveState::ground(walk_settings,body,gravity,target_velocity,contact);
@ -1607,12 +1515,30 @@ fn collision_start_contact(
}, },
} }
//I love making functions with 10 arguments to dodge the borrow checker //I love making functions with 10 arguments to dodge the borrow checker
run_teleport_behaviour(contact.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time); if allow_run_teleport_behaviour{
if style.get_control(Controls::Jump,input_state.controls){ run_teleport_behaviour(model_id,attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
}
if allow_jump&&style.get_control(Controls::Jump,input_state.controls){
if let (Some(jump_settings),Some(walk_state))=(&style.jump,move_state.get_walk_state()){ if let (Some(jump_settings),Some(walk_state))=(&style.jump,move_state.get_walk_state()){
let jump_dir=walk_state.jump_direction.direction(models,hitbox_mesh,&walk_state.contact); let mut exceeded_jump_limit=false;
let jumped_velocity=jump_settings.jumped_velocity(style,jump_dir,body.velocity,attr.general.booster.as_ref()); if let Some(mode)=mode{
move_state.cull_velocity(jumped_velocity,body,touching,models,hitbox_mesh,style,camera,input_state); if let Some(stage_element)=mode.get_element(model_id){
if !mode_state.try_increment_jump_count(model_id,stage_element.jump_limit()).is_allowed(){
exceeded_jump_limit=true;
}
}
}
if exceeded_jump_limit{
if let Some(mode)=mode{
if let Some(spawn_model_id)=mode.get_spawn_model_id(mode_state.get_stage_id()){
let _=teleport_to_spawn(spawn_model_id,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
}
}
}else{
let jump_dir=walk_state.jump_direction.direction(models,hitbox_mesh,&walk_state.contact);
let jumped_velocity=jump_settings.jumped_velocity(style,jump_dir,body.velocity,attr.general.booster.as_ref());
move_state.cull_velocity(jumped_velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
}
} }
} }
match &attr.general.trajectory{ match &attr.general.trajectory{
@ -1724,7 +1650,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; state.time=ins.time;
let (should_advance_body,goober_time)=match ins.instruction{ let (should_advance_body,goober_time)=match ins.instruction{
PhysicsInternalInstruction::CollisionStart(_,dt) PhysicsInternalInstruction::CollisionStart(_,dt)
@ -1820,7 +1746,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; state.time=ins.time;
let should_advance_body=match ins.instruction{ let should_advance_body=match ins.instruction{
//the body may as well be a quantum wave function //the body may as well be a quantum wave function
@ -1914,7 +1840,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
if let Some(mode)=data.modes.get_mode(mode_id){ if let Some(mode)=data.modes.get_mode(mode_id){
if let Some(stage)=mode.get_stage(stage_id){ if let Some(stage)=mode.get_stage(stage_id){
let _=teleport_to_spawn( let _=teleport_to_spawn(
stage, stage.spawn(),
&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state, &mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,
mode, mode,
&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time &data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time
@ -1946,29 +1872,10 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
} }
} }
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)
|PhysicsInstruction::Internal(PhysicsInternalInstruction::ReachWalkTargetVelocity)=>(),
_=>println!("{}|{:?}",ins.time,ins.instruction),
}
if ins.time<state.time{
println!("@@@@ Time travel warning! state.time={} ins.time={}\nInstruction={:?}",state.time,ins.time,ins.instruction);
}
//idle is special, it is specifically a no-op to get Internal events to catch up to real time
match ins.instruction{
PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>(),
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}),
}
}
#[cfg(test)] #[cfg(test)]
mod test{ mod test{
use strafesnet_common::integer::{vec3::{self,int as int3},mat3}; use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
use crate::body::VirtualBody;
use super::*; use super::*;
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){ 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)); 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::mouse::MouseState;
use strafesnet_common::physics::Instruction as PhysicsInputInstruction; use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
use strafesnet_common::integer::Time; use strafesnet_common::physics::{Time as PhysicsTime,TimeInner as PhysicsTimeInner,Instruction as PhysicsInputInstruction};
use strafesnet_common::instruction::TimedInstruction; use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::timer::{Scaled,Timer,TimerState}; use strafesnet_common::timer::{Scaled,Timer,TimerState};
use mouse_interpolator::MouseInterpolator; use mouse_interpolator::MouseInterpolator;
pub struct FrameState{
pub body:crate::physics::Body,
pub camera:crate::physics::PhysicsCamera,
pub time:PhysicsTime,
}
#[derive(Debug)] #[derive(Debug)]
pub enum InputInstruction{ pub enum InputInstruction{
MoveMouse(glam::IVec2), MoveMouse(glam::IVec2),
@ -36,11 +43,11 @@ pub struct MouseInterpolator{
//"PlayerController" //"PlayerController"
user_settings:crate::settings::UserSettings, user_settings:crate::settings::UserSettings,
//"MouseInterpolator" //"MouseInterpolator"
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction>>, timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
last_mouse_time:Time,//this value is pre-transformed to simulation time last_mouse_time:PhysicsTime,
mouse_blocking:bool, mouse_blocking:bool,
//"Simulation" //"Simulation"
timer:Timer<Scaled>, timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
physics:crate::physics::PhysicsContext, physics:crate::physics::PhysicsContext,
} }
@ -58,7 +65,7 @@ impl MouseInterpolator{
user_settings, 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{ if self.mouse_blocking{
//tell the game state which is living in the past about its future //tell the game state which is living in the past about its future
self.timeline.push_front(TimedInstruction{ self.timeline.push_front(TimedInstruction{
@ -80,7 +87,7 @@ impl MouseInterpolator{
} }
self.last_mouse_time=self.timer.time(ins.time); 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 //This is always a non-mouse event
self.timeline.push_back(TimedInstruction{ self.timeline.push_back(TimedInstruction{
time:self.timer.time(time), time:self.timer.time(time),
@ -89,7 +96,7 @@ impl MouseInterpolator{
} }
/// returns should_empty_queue /// returns should_empty_queue
/// may or may not mutate internal state XD! /// 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; let mut update_mouse_blocking=true;
match &ins.instruction{ match &ins.instruction{
Instruction::Input(input_instruction)=>match input_instruction{ Instruction::Input(input_instruction)=>match input_instruction{
@ -126,7 +133,7 @@ impl MouseInterpolator{
Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle), Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle),
&Instruction::SetPaused(paused)=>{ &Instruction::SetPaused(paused)=>{
if let Err(e)=self.timer.set_paused(ins.time,paused){ if let Err(e)=self.timer.set_paused(ins.time,paused){
println!("Cannot pause: {e}"); println!("Cannot SetPaused: {e}");
} }
self.push(ins.time,PhysicsInputInstruction::Idle); self.push(ins.time,PhysicsInputInstruction::Idle);
}, },
@ -140,7 +147,7 @@ impl MouseInterpolator{
} }
} }
/// must check if self.mouse_blocking==true before calling! /// 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 //push an event to extrapolate no movement from
self.timeline.push_front(TimedInstruction{ self.timeline.push_front(TimedInstruction{
time:self.last_mouse_time, 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. //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; 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{ if self.mouse_blocking{
//assume the mouse has stopped moving after 10ms. //assume the mouse has stopped moving after 10ms.
//shitty mice are 125Hz which is 8ms so this should cover that. //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, //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 :( //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); self.unblock_mouse(time);
true true
}else{ }else{
@ -174,20 +181,20 @@ impl MouseInterpolator{
self.physics.run_input_instruction(instruction); 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); let should_empty_queue=self.map_instruction(ins);
if should_empty_queue{ if should_empty_queue{
self.empty_queue(); self.empty_queue();
} }
} }
pub fn get_frame_state(&self,time:Time)->crate::graphics::FrameState{ pub fn get_frame_state(&self,time:SessionTime)->FrameState{
crate::graphics::FrameState{ FrameState{
body:self.physics.camera_body(), body:self.physics.camera_body(),
camera:self.physics.camera(), camera:self.physics.camera(),
time:self.timer.time(time), 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 //dump any pending interpolation state
if self.mouse_blocking{ if self.mouse_blocking{
self.unblock_mouse(time); self.unblock_mouse(time);
@ -199,7 +206,7 @@ impl MouseInterpolator{
//use the standard input interface so the instructions are written out to bots //use the standard input interface so the instructions are written out to bots
self.handle_instruction(&TimedInstruction{ self.handle_instruction(&TimedInstruction{
time:self.timer.time(time), time,
instruction:Instruction::Input(InputInstruction::ResetAndSpawn( instruction:Instruction::Input(InputInstruction::ResetAndSpawn(
strafesnet_common::gameplay_modes::ModeId::MAIN, strafesnet_common::gameplay_modes::ModeId::MAIN,
strafesnet_common::gameplay_modes::StageId::FIRST, strafesnet_common::gameplay_modes::StageId::FIRST,
@ -215,13 +222,13 @@ impl MouseInterpolator{
pub fn new<'a>( pub fn new<'a>(
mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>, mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>,
user_settings:crate::settings::UserSettings, 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 physics=crate::physics::PhysicsContext::default();
let mut interpolator=MouseInterpolator::new( let mut interpolator=MouseInterpolator::new(
physics, physics,
user_settings 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); interpolator.handle_instruction(&ins);
match ins.instruction{ match ins.instruction{
Instruction::Render=>{ Instruction::Render=>{

View File

@ -0,0 +1,349 @@
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio};
// This algorithm is based on Lua code
// written by Trey Reynolds in 2021
// EPSILON=1/2^10
// A stack-allocated variable-size list that holds up to 4 elements
// Direct references are used instead of indices i0, i1, i2, i3
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
// hack to allow comparing ratios to zero
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
struct Ray{
origin:Planar64Vec3,
direction:Planar64Vec3,
}
impl Ray{
fn extrapolate<Num,Den,N1,T1>(&self,t: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>,
{
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
}
}
/// Information about a contact restriction
pub struct Contact{
pub position:Planar64Vec3,
pub velocity:Planar64Vec3,
pub normal:Planar64Vec3,
}
impl Contact{
fn relative_to(&self,point:Planar64Vec3)->Self{
Self{
position:self.position-point,
velocity:self.velocity,
normal:self.normal,
}
}
fn relative_dot(&self,direction:Planar64Vec3)->Fixed<2,64>{
(direction-self.velocity).dot(self.normal)
}
/// Calculate the time of intersection. (previously get_touch_time)
fn solve(&self,ray:&Ray)->Ratio<Fixed<2,64>,Fixed<2,64>>{
(self.position-ray.origin).dot(self.normal)/(ray.direction-self.velocity).dot(self.normal)
}
}
//note that this is horrible with fixed point arithmetic
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
let det=c0.normal.dot(c0.velocity);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
Some(c0.normal*d0/det)
}
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
let u0_u1=c0.velocity.cross(c1.velocity);
let n0_n1=c0.normal.cross(c1.normal);
let det=u0_u1.dot(n0_n1);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
let d1=c1.normal.dot(c1.position);
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
}
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
let n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
let d1=c1.normal.dot(c1.position);
let d2=c2.normal.dot(c2.position);
Some((c1.normal.cross(c2.normal)*d0+c2.normal.cross(c0.normal)*d1+c0.normal.cross(c1.normal)*d2)/det)
}
fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<Fixed<2,64>,Fixed<2,64>>;1]>{
let det=u0.dot(u0);
if det==Fixed::ZERO{
return None;
}
let s0=u0.dot(point)/det;
Some([s0])
}
fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<Fixed<4,128>,Fixed<4,128>>;2]>{
let u0_u1=u0.cross(u1);
let det=u0_u1.dot(u0_u1);
if det==Fixed::ZERO{
return None;
}
let s0=u0_u1.dot(point.cross(u1))/det;
let s1=u0_u1.dot(u0.cross(point))/det;
Some([s0,s1])
}
fn decompose3(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3,u2:Planar64Vec3)->Option<[Ratio<Fixed<3,96>,Fixed<3,96>>;3]>{
let det=u0.cross(u1).dot(u2);
if det==Fixed::ZERO{
return None;
}
let s0=point.cross(u1).dot(u2)/det;
let s1=u0.cross(point).dot(u2)/det;
let s2=u0.cross(u1).dot(point)/det;
Some([s0,s1,s2])
}
fn is_space_enclosed_2(
a:Planar64Vec3,
b:Planar64Vec3,
)->bool{
a.cross(b)==Vector3::new([Fixed::ZERO;3])
&&a.dot(b).is_negative()
}
fn is_space_enclosed_3(
a:Planar64Vec3,
b:Planar64Vec3,
c:Planar64Vec3
)->bool{
a.cross(b).dot(c)==Fixed::ZERO
&&{
let det_abac=a.cross(b).dot(a.cross(c));
let det_abbc=a.cross(b).dot(b.cross(c));
let det_acbc=a.cross(c).dot(b.cross(c));
return!( det_abac*det_abbc).is_positive()
&&!( det_abbc*det_acbc).is_positive()
&&!(-det_acbc*det_abac).is_positive()
||is_space_enclosed_2(a,b)
||is_space_enclosed_2(a,c)
||is_space_enclosed_2(b,c)
}
}
fn is_space_enclosed_4(
a:Planar64Vec3,
b:Planar64Vec3,
c:Planar64Vec3,
d:Planar64Vec3,
)->bool{
let det_abc=a.cross(b).dot(c);
let det_abd=a.cross(b).dot(d);
let det_acd=a.cross(c).dot(d);
let det_bcd=b.cross(c).dot(d);
return( det_abc*det_abd).is_negative()
&&(-det_abc*det_acd).is_negative()
&&( det_abd*det_acd).is_negative()
&&( det_abc*det_bcd).is_negative()
&&(-det_abd*det_bcd).is_negative()
&&( det_acd*det_bcd).is_negative()
||is_space_enclosed_3(a,b,c)
||is_space_enclosed_3(a,b,d)
||is_space_enclosed_3(a,c,d)
||is_space_enclosed_3(b,c,d)
}
const fn get_push_ray_0(point:Planar64Vec3)->Ray{
Ray{origin:point,direction:vec3::ZERO}
}
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
let direction=solve1(c0)?.divide().fix_1();
let [s0]=decompose1(direction,c0.velocity)?;
if s0.lt_ratio(RATIO_ZERO){
return None;
}
let origin=point+solve1(
&c0.relative_to(point),
)?.divide().fix_1();
Some(Ray{origin,direction})
}
fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
let direction=solve2(c0,c1)?.divide().fix_1();
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
return None;
}
let origin=point+solve2(
&c0.relative_to(point),
&c1.relative_to(point),
)?.divide().fix_1();
Some(Ray{origin,direction})
}
fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{
let direction=solve3(c0,c1,c2)?.divide().fix_1();
let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){
return None;
}
let origin=point+solve3(
&c0.relative_to(point),
&c1.relative_to(point),
&c2.relative_to(point),
)?.divide().fix_1();
Some(Ray{origin,direction})
}
const fn get_best_push_ray_and_conts_0<'a>(point:Planar64Vec3)->(Ray,Conts<'a>){
(get_push_ray_0(point),Conts::new_const())
}
fn get_best_push_ray_and_conts_1(point:Planar64Vec3,c0:&Contact)->Option<(Ray,Conts)>{
get_push_ray_1(point,c0)
.map(|ray|(ray,Conts::from_iter([c0])))
}
fn get_best_push_ray_and_conts_2<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact)->Option<(Ray,Conts<'a>)>{
if is_space_enclosed_2(c0.normal,c1.normal){
return None;
}
if let Some(ray)=get_push_ray_2(point,c0,c1){
return Some((ray,Conts::from_iter([c0,c1])));
}
if let Some(ray)=get_push_ray_1(point,c0){
if !c1.relative_dot(ray.direction).is_negative(){
return Some((ray,Conts::from_iter([c0])));
}
}
return None;
}
fn get_best_push_ray_and_conts_3<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact)->Option<(Ray,Conts<'a>)>{
if is_space_enclosed_3(c0.normal,c1.normal,c2.normal){
return None;
}
if let Some(ray)=get_push_ray_3(point,c0,c1,c2){
return Some((ray,Conts::from_iter([c0,c1,c2])));
}
if let Some(ray)=get_push_ray_2(point,c0,c1){
if !c2.relative_dot(ray.direction).is_negative(){
return Some((ray,Conts::from_iter([c0,c1])));
}
}
if let Some(ray)=get_push_ray_2(point,c0,c2){
if !c1.relative_dot(ray.direction).is_negative(){
return Some((ray,Conts::from_iter([c0,c2])));
}
}
if let Some(ray)=get_push_ray_1(point,c0){
if !c1.relative_dot(ray.direction).is_negative()
&&!c2.relative_dot(ray.direction).is_negative(){
return Some((ray,Conts::from_iter([c0])));
}
}
return None;
}
fn get_best_push_ray_and_conts_4<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact,c3:&'a Contact)->Option<(Ray,Conts<'a>)>{
if is_space_enclosed_4(c0.normal,c1.normal,c2.normal,c3.normal){
return None;
}
let (ray012,conts012)=get_best_push_ray_and_conts_3(point,c0,c1,c2)?;
let (ray013,conts013)=get_best_push_ray_and_conts_3(point,c0,c1,c3)?;
let (ray023,conts023)=get_best_push_ray_and_conts_3(point,c0,c2,c3)?;
let err012=c3.relative_dot(ray012.direction);
let err013=c2.relative_dot(ray013.direction);
let err023=c1.relative_dot(ray023.direction);
let best_err=err012.max(err013).max(err023);
if best_err==err012{
return Some((ray012,conts012))
}else if best_err==err013{
return Some((ray013,conts013))
}else if best_err==err023{
return Some((ray023,conts023))
}
unreachable!()
}
fn get_best_push_ray_and_conts<'a>(
point:Planar64Vec3,
conts:&[&'a Contact],
)->Option<(Ray,Conts<'a>)>{
match conts{
&[c0,c1,c2,c3]=>get_best_push_ray_and_conts_4(point,c0,c1,c2,c3),
&[c0,c1,c2]=>get_best_push_ray_and_conts_3(point,c0,c1,c2),
&[c0,c1]=>get_best_push_ray_and_conts_2(point,c0,c1),
&[c0]=>get_best_push_ray_and_conts_1(point,c0),
&[]=>Some(get_best_push_ray_and_conts_0(point)),
_=>unreachable!(),
}
}
fn get_first_touch<'a>(contacts:&'a Vec<Contact>,ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
contacts.iter()
.filter(|&contact|
!conts.iter().any(|&c|std::ptr::eq(c,contact))
&&contact.relative_dot(ray.direction).is_negative()
)
.map(|contact|(contact.solve(ray),contact))
.min_by_key(|&(t,_)|t)
}
pub fn push_solve(contacts:&Vec<Contact>,point:Planar64Vec3)->Planar64Vec3{
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
loop{
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
Some((t,cont))=>(t,cont),
None=>return ray.origin,
};
if RATIO_ZERO.le_ratio(next_t){
return ray.origin;
}
//push_front
if conts.len()==conts.capacity(){
//this is a dead case, new_conts never has more than 3 elements
conts.rotate_right(1);
conts[0]=next_cont;
}else{
conts.push(next_cont);
conts.rotate_right(1);
}
let meet_point=ray.extrapolate(next_t);
match get_best_push_ray_and_conts(meet_point,conts.as_slice()){
Some((new_ray,new_conts))=>(ray,conts)=(new_ray,new_conts),
None=>return meet_point,
}
}
}
#[cfg(test)]
mod tests{
use super::*;
#[test]
fn test_push_solve(){
let contacts=vec![
Contact{
position:vec3::ZERO,
velocity:vec3::Y,
normal:vec3::Y,
}
];
assert_eq!(
vec3::ZERO,
push_solve(&contacts,vec3::NEG_Y)
);
}
}

View File

@ -1,6 +1,7 @@
use crate::window::WindowInstruction; use crate::window::WindowInstruction;
use strafesnet_common::instruction::TimedInstruction; use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::integer; use strafesnet_common::integer;
use strafesnet_common::session::TimeInner as SessionTimeInner;
fn optional_features()->wgpu::Features{ fn optional_features()->wgpu::Features{
wgpu::Features::TEXTURE_COMPRESSION_ASTC wgpu::Features::TEXTURE_COMPRESSION_ASTC
@ -32,16 +33,6 @@ fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Resul
use winit::platform::windows::WindowBuilderExtWindows; use winit::platform::windows::WindowBuilderExtWindows;
builder=builder.with_no_redirection_bitmap(true); builder=builder.with_no_redirection_bitmap(true);
} }
#[cfg(target_arch="wasm32")]
{
use wasm_bindgen::JsCast;
use winit::platform::web::WindowAttributesExtWebSys;
let canvas=web_sys::window().unwrap()
.document().unwrap()
.get_element_by_id("canvas").unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
attr=attr.with_canvas(Some(canvas));
}
event_loop.create_window(attr) event_loop.create_window(attr)
} }
fn create_instance()->SetupContextPartial1{ fn create_instance()->SetupContextPartial1{
@ -71,18 +62,36 @@ struct SetupContextPartial2<'a>{
surface:wgpu::Surface<'a>, surface:wgpu::Surface<'a>,
} }
impl<'a> SetupContextPartial2<'a>{ impl<'a> SetupContextPartial2<'a>{
async fn pick_adapter(self)->SetupContextPartial3<'a>{ fn pick_adapter(self)->SetupContextPartial3<'a>{
let adapter; let adapter;
//TODO: prefer adapter that implements optional features //TODO: prefer adapter that implements optional features
//let optional_features=optional_features(); //let optional_features=optional_features();
let required_features=required_features(); let required_features=required_features();
let chosen_adapter=self.instance.request_adapter(&wgpu::RequestAdapterOptions{ //no helper function smh gotta write it myself
power_preference:wgpu::PowerPreference::HighPerformance, let adapters=self.instance.enumerate_adapters(self.backends);
force_fallback_adapter:false,
compatible_surface:Some(&self.surface), let mut chosen_adapter=None;
}).await; let mut chosen_adapter_score=0;
for adapter in adapters {
if !adapter.is_surface_supported(&self.surface) {
continue;
}
let score=match adapter.get_info().device_type{
wgpu::DeviceType::IntegratedGpu=>3,
wgpu::DeviceType::DiscreteGpu=>4,
wgpu::DeviceType::VirtualGpu=>2,
wgpu::DeviceType::Other|wgpu::DeviceType::Cpu=>1,
};
let adapter_features=adapter.features();
if chosen_adapter_score<score&&adapter_features.contains(required_features) {
chosen_adapter_score=score;
chosen_adapter=Some(adapter);
}
}
if let Some(maybe_chosen_adapter)=chosen_adapter{ if let Some(maybe_chosen_adapter)=chosen_adapter{
adapter=maybe_chosen_adapter; adapter=maybe_chosen_adapter;
@ -121,7 +130,7 @@ struct SetupContextPartial3<'a>{
adapter:wgpu::Adapter, adapter:wgpu::Adapter,
} }
impl<'a> SetupContextPartial3<'a>{ impl<'a> SetupContextPartial3<'a>{
async fn request_device(self)->SetupContextPartial4<'a>{ fn request_device(self)->SetupContextPartial4<'a>{
let optional_features=optional_features(); let optional_features=optional_features();
let required_features=required_features(); let required_features=required_features();
@ -129,7 +138,7 @@ impl<'a> SetupContextPartial3<'a>{
let needed_limits=required_limits().using_resolution(self.adapter.limits()); let needed_limits=required_limits().using_resolution(self.adapter.limits());
let trace_dir=std::env::var("WGPU_TRACE"); let trace_dir=std::env::var("WGPU_TRACE");
let (device, queue)=self.adapter let (device, queue)=pollster::block_on(self.adapter
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
@ -138,7 +147,7 @@ impl<'a> SetupContextPartial3<'a>{
memory_hints:wgpu::MemoryHints::Performance, memory_hints:wgpu::MemoryHints::Performance,
}, },
trace_dir.ok().as_ref().map(std::path::Path::new), trace_dir.ok().as_ref().map(std::path::Path::new),
).await ))
.expect("Unable to find a suitable GPU adapter!"); .expect("Unable to find a suitable GPU adapter!");
SetupContextPartial4{ SetupContextPartial4{
@ -184,20 +193,20 @@ pub struct SetupContext<'a>{
pub config:wgpu::SurfaceConfiguration, pub config:wgpu::SurfaceConfiguration,
} }
pub async fn setup_and_start(title:String){ pub fn setup_and_start(title:&str){
let event_loop=winit::event_loop::EventLoop::new().unwrap(); let event_loop=winit::event_loop::EventLoop::new().unwrap();
println!("Initializing the surface..."); println!("Initializing the surface...");
let partial_1=create_instance(); let partial_1=create_instance();
let window=create_window(title.as_str(),&event_loop).unwrap(); let window=create_window(title,&event_loop).unwrap();
let partial_2=partial_1.create_surface(&window).unwrap(); let partial_2=partial_1.create_surface(&window).unwrap();
let partial_3=partial_2.pick_adapter().await; let partial_3=partial_2.pick_adapter();
let partial_4=partial_3.request_device().await; let partial_4=partial_3.request_device();
let size=window.inner_size(); let size=window.inner_size();
@ -220,17 +229,17 @@ pub async fn setup_and_start(title:String){
}; };
println!("Entering event loop..."); println!("Entering event loop...");
let root_time=chrono::Utc::now(); let root_time=std::time::Instant::now();
run_event_loop(event_loop,window_thread,root_time).unwrap(); run_event_loop(event_loop,window_thread,root_time).unwrap();
} }
fn run_event_loop( fn run_event_loop(
event_loop:winit::event_loop::EventLoop<()>, 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:chrono::DateTime<chrono::Utc>, root_time:std::time::Instant
)->Result<(),winit::error::EventLoopError>{ )->Result<(),winit::error::EventLoopError>{
event_loop.run(move |event,elwt|{ event_loop.run(move |event,elwt|{
let time=integer::Time::from_nanos((chrono::Utc::now()-root_time).num_nanoseconds().unwrap()); let time=integer::Time::from_nanos(root_time.elapsed().as_nanos() as i64);
// *control_flow=if cfg!(feature="metal-auto-capture"){ // *control_flow=if cfg!(feature="metal-auto-capture"){
// winit::event_loop::ControlFlow::Exit // winit::event_loop::ControlFlow::Exit
// }else{ // }else{

View File

@ -1,6 +1,7 @@
use crate::physics_worker::InputInstruction; use crate::physics_worker::InputInstruction;
use strafesnet_common::integer; use strafesnet_common::integer;
use strafesnet_common::instruction::TimedInstruction; use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
pub enum WindowInstruction{ pub enum WindowInstruction{
Resize(winit::dpi::PhysicalSize<u32>), Resize(winit::dpi::PhysicalSize<u32>),
@ -13,17 +14,17 @@ pub enum WindowInstruction{
//holds thread handles to dispatch to //holds thread handles to dispatch to
struct WindowContext<'a>{ struct WindowContext<'a>{
manual_mouse_lock:bool, 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, screen_size:glam::UVec2,
window:&'a winit::window::Window, 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<'_>{ impl WindowContext<'_>{
fn get_middle_of_screen(&self)->winit::dpi::PhysicalPosition<u32>{ fn get_middle_of_screen(&self)->winit::dpi::PhysicalPosition<u32>{
winit::dpi::PhysicalPosition::new(self.screen_size.x/2,self.screen_size.y/2) 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{ match event{
winit::event::WindowEvent::DroppedFile(path)=>{ winit::event::WindowEvent::DroppedFile(path)=>{
match crate::file::load(path.as_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{ match event{
winit::event::DeviceEvent::MouseMotion{ winit::event::DeviceEvent::MouseMotion{
delta,//these (f64,f64) are integers on my machine delta,//these (f64,f64) are integers on my machine
@ -161,7 +162,7 @@ impl WindowContext<'_>{
pub fn worker<'a>( pub fn worker<'a>(
window:&'a winit::window::Window, window:&'a winit::window::Window,
setup_context:crate::setup::SetupContext<'a>, setup_context:crate::setup::SetupContext<'a>,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction>>{ )->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction,SessionTimeInner>>{
// WindowContextSetup::new // WindowContextSetup::new
let user_settings=crate::settings::read_user_settings(); let user_settings=crate::settings::read_user_settings();
@ -184,7 +185,7 @@ pub fn worker<'a>(
}; };
//WindowContextSetup::into_worker //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{ match ins.instruction{
WindowInstruction::RequestRedraw=>{ WindowInstruction::RequestRedraw=>{
window_context.window.request_redraw(); window_context.window.request_redraw();

View File

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

View File

@ -1 +0,0 @@
/dist

View File

@ -1,2 +0,0 @@
[build]
target = "index.html"

View File

@ -1,37 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Strafe Client</title>
<base data-trunk-public-url />
<style type="text/css">
body {
margin: 0px;
background: #fff;
width: 100%;
height: 100%;
}
.root {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.main-canvas {
margin: 0;
/* This allows the flexbox to grow to max size, this is needed for WebGPU */
flex: 1;
/* This forces CSS to ignore the width/height of the canvas, this is needed for WebGL */
contain: size;
}
</style>
</head>
<body>
<div class="root">
<canvas class="main-canvas" id="canvas"></canvas>
</div>
<link data-trunk rel="rust" href="../strafe-client/Cargo.toml"/>
</body>
</html>