Compare commits
65 Commits
instructio
...
headless
| Author | SHA1 | Date | |
|---|---|---|---|
| 3805921e36 | |||
| 2f276d3828 | |||
| a54055271c | |||
| 6d77b5661e | |||
| fe440249c3 | |||
| 6fdccb8b32 | |||
| 4db38c6496 | |||
| 32c16778c8 | |||
| 5810a55209 | |||
| 5a2231221d | |||
| 091da88b5c | |||
| 045e540020 | |||
| c14f8975fc | |||
| 8e228033e0 | |||
| eaecbc5b73 | |||
| 91a1b3d65e | |||
| e1d51ff2da | |||
| c5f2395b3a | |||
| 77aa83d6ac | |||
| 184b12a9cc | |||
| 9ba3484e25 | |||
| adcd7db4f1 | |||
| 710670d78e | |||
| 70e1514ef9 | |||
| 45e9b3d0ce | |||
| 1eac734c35 | |||
| 25e1312fe4 | |||
| cc8f6b059d | |||
| 4cd287dbb8 | |||
| 40ea0e6c7d | |||
| b45b92c627 | |||
| e90f53a111 | |||
| 6beb6c5f9a | |||
| a1507d4f26 | |||
| 77db5a7a6b | |||
| 75c8bc2bbb | |||
| d49a6b2f0a | |||
| 19778ac7aa | |||
| 5b62052222 | |||
| 5a8bc141d3 | |||
| 83a067320b | |||
| 2faa61225f | |||
| 28499800cb | |||
| 57cc49dc1a | |||
| d517b78a8c | |||
| e6d1d69241 | |||
| b28fa25279 | |||
| 713b235816 | |||
| d2002383cb | |||
| 52f7de809d | |||
| 4efe1209b8 | |||
| 15a9136fc4 | |||
| 035736e7af | |||
| 7f9a16a56d | |||
| 814e573d91 | |||
| 6fa0f1c83e | |||
| 870cb56dac | |||
| 1aac2a9303 | |||
| 38661b3a68 | |||
| 7ad76740d4 | |||
| c2d6af8bda | |||
| fbacef83b9 | |||
| 2a9e848541 | |||
| 3413ec8740 | |||
| 168d6708d1 |
@@ -1,6 +1,2 @@
|
|||||||
[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"]
|
|
||||||
|
|||||||
396
Cargo.lock
generated
396
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,19 @@
|
|||||||
use crate::integer::Time;
|
use crate::integer::Time;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct TimedInstruction<I,T>{
|
pub struct TimedInstruction<I,T>{
|
||||||
pub time:Time<T>,
|
pub time:Time<T>,
|
||||||
pub instruction:I,
|
pub instruction:I,
|
||||||
}
|
}
|
||||||
|
impl<I,T> TimedInstruction<I,T>{
|
||||||
|
#[inline]
|
||||||
|
pub fn set_time<TimeInner>(self,new_time:Time<TimeInner>)->TimedInstruction<I,TimeInner>{
|
||||||
|
TimedInstruction{
|
||||||
|
time:new_time,
|
||||||
|
instruction:self.instruction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensure all emitted instructions are processed before consuming external instructions
|
/// Ensure all emitted instructions are processed before consuming external instructions
|
||||||
pub trait InstructionEmitter<I>{
|
pub trait InstructionEmitter<I>{
|
||||||
@@ -21,6 +30,7 @@ pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+Instruction
|
|||||||
where
|
where
|
||||||
Time<T>:Copy,
|
Time<T>:Copy,
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
fn process_exhaustive(&mut self,time_limit:Time<T>){
|
fn process_exhaustive(&mut self,time_limit:Time<T>){
|
||||||
while let Some(instruction)=self.next_instruction(time_limit){
|
while let Some(instruction)=self.next_instruction(time_limit){
|
||||||
self.process_instruction(instruction);
|
self.process_instruction(instruction);
|
||||||
@@ -33,42 +43,6 @@ impl<I,T,X> InstructionFeedback<I,T> for X
|
|||||||
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
|
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
|
||||||
{}
|
{}
|
||||||
|
|
||||||
pub struct InstructionCache<S,I,T>{
|
|
||||||
instruction_machine:S,
|
|
||||||
cached_instruction:Option<TimedInstruction<I,T>>,
|
|
||||||
time_limit:Time<T>,
|
|
||||||
}
|
|
||||||
impl<S,I,T> InstructionCache<S,I,T>
|
|
||||||
where
|
|
||||||
Time<T>:Copy+Ord,
|
|
||||||
Option<TimedInstruction<I,T>>:Clone,
|
|
||||||
S:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
|
|
||||||
{
|
|
||||||
pub fn new(
|
|
||||||
instruction_machine:S,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
instruction_machine,
|
|
||||||
cached_instruction:None,
|
|
||||||
time_limit:Time::MIN,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn next_instruction_cached(&mut self,time_limit:Time<T>)->Option<TimedInstruction<I,T>>{
|
|
||||||
if time_limit<self.time_limit{
|
|
||||||
return self.cached_instruction.clone();
|
|
||||||
}
|
|
||||||
let next_instruction=self.instruction_machine.next_instruction(time_limit);
|
|
||||||
self.cached_instruction=next_instruction.clone();
|
|
||||||
self.time_limit=time_limit;
|
|
||||||
next_instruction
|
|
||||||
}
|
|
||||||
pub fn process_instruction(&mut self,instruction:TimedInstruction<I,T>){
|
|
||||||
// invalidate cache
|
|
||||||
self.time_limit=Time::MIN;
|
|
||||||
self.instruction_machine.process_instruction(instruction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//PROPER PRIVATE FIELDS!!!
|
//PROPER PRIVATE FIELDS!!!
|
||||||
pub struct InstructionCollector<I,T>{
|
pub struct InstructionCollector<I,T>{
|
||||||
time:Time<T>,
|
time:Time<T>,
|
||||||
@@ -77,6 +51,7 @@ pub struct InstructionCollector<I,T>{
|
|||||||
impl<I,T> InstructionCollector<I,T>
|
impl<I,T> InstructionCollector<I,T>
|
||||||
where Time<T>:Copy+PartialOrd,
|
where Time<T>:Copy+PartialOrd,
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
pub const fn new(time:Time<T>)->Self{
|
pub const fn new(time:Time<T>)->Self{
|
||||||
Self{
|
Self{
|
||||||
time,
|
time,
|
||||||
@@ -87,25 +62,21 @@ impl<I,T> InstructionCollector<I,T>
|
|||||||
pub const fn time(&self)->Time<T>{
|
pub const fn time(&self)->Time<T>{
|
||||||
self.time
|
self.time
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
||||||
match instruction{
|
if let Some(ins)=instruction{
|
||||||
Some(unwrap_instruction)=>{
|
if ins.time<self.time{
|
||||||
if unwrap_instruction.time<self.time {
|
self.time=ins.time;
|
||||||
self.time=unwrap_instruction.time;
|
self.instruction=Some(ins.instruction);
|
||||||
self.instruction=Some(unwrap_instruction.instruction);
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
None=>(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn instruction(self)->Option<TimedInstruction<I,T>>{
|
#[inline]
|
||||||
|
pub fn take(self)->Option<TimedInstruction<I,T>>{
|
||||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||||
match self.instruction{
|
self.instruction.map(|instruction|TimedInstruction{
|
||||||
Some(instruction)=>Some(TimedInstruction{
|
time:self.time,
|
||||||
time:self.time,
|
instruction
|
||||||
instruction
|
})
|
||||||
}),
|
|
||||||
None=>None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::mouse::MouseState;
|
use crate::mouse::MouseState;
|
||||||
|
use crate::gameplay_modes::{ModeId,StageId};
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
pub enum TimeInner{}
|
pub enum TimeInner{}
|
||||||
@@ -7,16 +8,14 @@ pub type Time=crate::integer::Time<TimeInner>;
|
|||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Mouse(MouseInstruction),
|
Mouse(MouseInstruction),
|
||||||
Other(OtherInstruction),
|
|
||||||
}
|
|
||||||
impl Instruction{
|
|
||||||
pub const IDLE:Self=Self::Other(OtherInstruction::Other(OtherOtherInstruction::Idle));
|
|
||||||
}
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
pub enum OtherInstruction{
|
|
||||||
SetControl(SetControlInstruction),
|
SetControl(SetControlInstruction),
|
||||||
Mode(ModeInstruction),
|
Mode(ModeInstruction),
|
||||||
Other(OtherOtherInstruction),
|
Misc(MiscInstruction),
|
||||||
|
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
impl Instruction{
|
||||||
|
pub const IDLE:Self=Self::Idle;
|
||||||
}
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum MouseInstruction{
|
pub enum MouseInstruction{
|
||||||
@@ -44,15 +43,14 @@ pub enum ModeInstruction{
|
|||||||
/// This forgets all inputs and settings which need to be reapplied.
|
/// This forgets all inputs and settings which need to be reapplied.
|
||||||
Reset,
|
Reset,
|
||||||
/// Restart: Teleport to the start zone.
|
/// Restart: Teleport to the start zone.
|
||||||
Restart,
|
/// This runs when you press R or teleport to a bonus
|
||||||
|
Restart(ModeId),
|
||||||
/// Spawn: Teleport to a specific mode's spawn
|
/// Spawn: Teleport to a specific mode's spawn
|
||||||
/// Sets current mode & spawn
|
/// This runs when the map loads to put you at the map lobby
|
||||||
Spawn(crate::gameplay_modes::ModeId,crate::gameplay_modes::StageId),
|
Spawn(ModeId,StageId),
|
||||||
}
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum OtherOtherInstruction{
|
pub enum MiscInstruction{
|
||||||
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
|
|
||||||
Idle,
|
|
||||||
PracticeFly,
|
PracticeFly,
|
||||||
SetSensitivity(crate::integer::Ratio64Vec2),
|
SetSensitivity(crate::integer::Ratio64Vec2),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ impl Run{
|
|||||||
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||||
@@ -110,4 +110,10 @@ impl Run{
|
|||||||
self.flagged=Some(flag_reason);
|
self.flagged=Some(flag_reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_finish_time(&self)->Option<Time>{
|
||||||
|
match &self.state{
|
||||||
|
RunState::Finished{timer}=>Some(timer.time()),
|
||||||
|
_=>None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ impl<T:TimerState> TimerFixed<T,Paused>
|
|||||||
where Time<T::In>:Copy,
|
where Time<T::In>:Copy,
|
||||||
{
|
{
|
||||||
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
||||||
let new_time=self.time(time);
|
let new_time=self.time();
|
||||||
let mut timer=TimerFixed{
|
let mut timer=TimerFixed{
|
||||||
state:self.state,
|
state:self.state,
|
||||||
_paused:Unpaused,
|
_paused:Unpaused,
|
||||||
@@ -165,6 +165,9 @@ impl<T:TimerState> TimerFixed<T,Paused>
|
|||||||
timer.set_time(time,new_time);
|
timer.set_time(time,new_time);
|
||||||
timer
|
timer
|
||||||
}
|
}
|
||||||
|
pub fn time(&self)->Time<T::Out>{
|
||||||
|
self.state.get_offset().coerce()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<T:TimerState> TimerFixed<T,Unpaused>
|
impl<T:TimerState> TimerFixed<T,Unpaused>
|
||||||
where Time<T::In>:Copy,
|
where Time<T::In>:Copy,
|
||||||
@@ -178,6 +181,9 @@ impl<T:TimerState> TimerFixed<T,Unpaused>
|
|||||||
timer.set_time(time,new_time);
|
timer.set_time(time,new_time);
|
||||||
timer
|
timer
|
||||||
}
|
}
|
||||||
|
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||||
|
self.state.get_time(time)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//the new constructor and time queries are generic across both
|
//the new constructor and time queries are generic across both
|
||||||
@@ -199,12 +205,6 @@ 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<T::In>)->Time<T::Out>{
|
|
||||||
match P::IS_PAUSED{
|
|
||||||
true=>self.state.get_offset().coerce(),
|
|
||||||
false=>self.state.get_time(time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
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.coerce()),
|
true=>self.state.set_offset(new_time.coerce()),
|
||||||
@@ -256,7 +256,7 @@ impl<T:TimerState> Timer<T>
|
|||||||
}
|
}
|
||||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
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(),
|
||||||
Self::Unpaused(timer)=>timer.time(time),
|
Self::Unpaused(timer)=>timer.time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,7 +329,7 @@ mod test{
|
|||||||
//create a paused timer that reads 0s
|
//create a paused timer that reads 0s
|
||||||
let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),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!(0));
|
||||||
|
|
||||||
//unpause it after one second
|
//unpause it after one second
|
||||||
let timer=timer.into_unpaused(sec!(1));
|
let timer=timer.into_unpaused(sec!(1));
|
||||||
@@ -339,7 +339,7 @@ mod test{
|
|||||||
//pause the timer after 11 seconds
|
//pause the timer after 11 seconds
|
||||||
let timer=timer.into_paused(sec!(11));
|
let timer=timer.into_paused(sec!(11));
|
||||||
//the paused timer at 20 seconds should read 5s
|
//the paused timer at 20 seconds should read 5s
|
||||||
assert_eq!(timer.time(sec!(20)),sec!(5));
|
assert_eq!(timer.time(),sec!(5));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_timer()->Result<(),Error>{
|
fn test_timer()->Result<(),Error>{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use bnum::{BInt,cast::As};
|
use bnum::{BInt,cast::As};
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,PartialOrd,Ord)]
|
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,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)
|
||||||
@@ -97,7 +97,6 @@ where
|
|||||||
self.bits.eq(&other.into())
|
self.bits.eq(&other.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const N:usize,const F:usize> Eq for Fixed<N,F>{}
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
macro_rules! type_from_lua_userdata{
|
macro_rules! type_from_lua_userdata{
|
||||||
($asd:ident)=>{
|
($ty:ident)=>{
|
||||||
impl mlua::FromLua for $asd{
|
impl mlua::FromLua for $ty{
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||||
match value{
|
match value{
|
||||||
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
|
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
macro_rules! type_from_lua_userdata_lua_lifetime{
|
macro_rules! type_from_lua_userdata_lua_lifetime{
|
||||||
($asd:ident)=>{
|
($ty:ident)=>{
|
||||||
impl mlua::FromLua for $asd<'static>{
|
impl mlua::FromLua for $ty<'static>{
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||||
match value{
|
match value{
|
||||||
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
|
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
|
|||||||
match delay.classify(){
|
match delay.classify(){
|
||||||
std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
|
std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
|
||||||
// cases where the number is too large to schedule
|
// cases where the number is too large to schedule
|
||||||
std::num::FpCategory::Infinite=>return Ok(()),
|
std::num::FpCategory::Infinite
|
||||||
std::num::FpCategory::Normal=>if (u64::MAX as f64)<delay{
|
|std::num::FpCategory::Normal if (u64::MAX as f64)<delay=>{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
_=>(),
|
_=>(),
|
||||||
|
|||||||
@@ -1,98 +1,347 @@
|
|||||||
use binrw::{BinReaderExt, binrw};
|
use binrw::{binrw,BinReaderExt,BinWrite,BinWriterExt};
|
||||||
|
|
||||||
|
use crate::newtypes;
|
||||||
|
use crate::file::BlockId;
|
||||||
|
use strafesnet_common::physics::Time;
|
||||||
|
|
||||||
|
const VERSION:u32=0;
|
||||||
|
|
||||||
|
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error{
|
pub enum Error{
|
||||||
InvalidHeader,
|
InvalidHeader(binrw::Error),
|
||||||
InvalidSegment(binrw::Error),
|
InvalidSegment(binrw::Error),
|
||||||
|
SegmentConvert(newtypes::integer::RatioError),
|
||||||
|
InstructionConvert(newtypes::physics::InstructionConvert),
|
||||||
|
InstructionWrite(binrw::Error),
|
||||||
InvalidSegmentId(SegmentId),
|
InvalidSegmentId(SegmentId),
|
||||||
|
InvalidData(binrw::Error),
|
||||||
|
IO(std::io::Error),
|
||||||
File(crate::file::Error),
|
File(crate::file::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bot files are simply the sequence of instructions that the physics received during the run.
|
||||||
|
// The instructions are partitioned into timestamped blocks for ease of streaming.
|
||||||
|
//
|
||||||
|
// Keyframe information required for efficient seeking
|
||||||
|
// is part of a different file, and is generated from this file.
|
||||||
|
|
||||||
/* block types
|
/* block types
|
||||||
|
|
||||||
BLOCK_BOT_HEADER:
|
BLOCK_BOT_HEADER:
|
||||||
u128 map_resource_uuid //which map is this bot running
|
// Segments are laid out in chronological order,
|
||||||
//don't include style info in bot header because it's in the simulation state
|
// but block_id is not necessarily in ascending order.
|
||||||
//blocks are laid out in chronological order, but indices may jump around.
|
//
|
||||||
u64 num_segments
|
// This is to place the final segment close to the start of the file,
|
||||||
|
// which allows the duration of the bot to be conveniently calculated
|
||||||
|
// from the first and last instruction timestamps.
|
||||||
|
//
|
||||||
|
// Use exact physics version for replay playback
|
||||||
|
// Use highest compatible physics version for verification
|
||||||
|
u32 physics_version
|
||||||
|
u32 num_segments
|
||||||
for _ in 0..num_segments{
|
for _ in 0..num_segments{
|
||||||
i64 time //simulation_state timestamp
|
i64 time
|
||||||
u64 block_id
|
u32 instruction_count
|
||||||
|
u32 block_id
|
||||||
}
|
}
|
||||||
|
|
||||||
BLOCK_BOT_SEGMENT:
|
BLOCK_BOT_SEGMENT:
|
||||||
//format version indicates what version of these structures to use
|
// segments can potentially be losslessly compressed!
|
||||||
SimulationState simulation_state //SimulationState is just under ClientState which includes Play/Pause events that the simulation doesn't know about.
|
for _ in 0..instruction_count{
|
||||||
//to read, greedily decode instructions until eof
|
// TODO: delta encode as much as possible (time,mousepos)
|
||||||
loop{
|
i64 time
|
||||||
//delta encode as much as possible (time,mousepos)
|
physics::Instruction instruction
|
||||||
//strafe ticks are implied
|
|
||||||
//physics can be implied in an input-only bot file
|
|
||||||
TimedInstruction<SimulationInstruction> instruction
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//error hiding mock code
|
#[binrw]
|
||||||
mod simulation{
|
#[brw(little)]
|
||||||
#[super::binrw]
|
struct SegmentHeader{
|
||||||
#[brw(little)]
|
time:i64,
|
||||||
pub struct State{}
|
instruction_count:u32,
|
||||||
#[super::binrw]
|
block_id:BlockId,
|
||||||
#[brw(little)]
|
}
|
||||||
pub struct Instruction{}
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
struct Header{
|
||||||
|
physics_version:u32,
|
||||||
|
num_segments:u32,
|
||||||
|
#[br(count=num_segments)]
|
||||||
|
segments:Vec<SegmentHeader>,
|
||||||
}
|
}
|
||||||
// mod instruction{
|
|
||||||
// #[super::binrw]
|
|
||||||
// #[brw(little)]
|
|
||||||
// pub struct TimedInstruction<Instruction:binrw::BinRead+binrw::BinWrite>{
|
|
||||||
// time:u64,
|
|
||||||
// instruction:Instruction
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// mod timeline{
|
|
||||||
// #[super::binrw]
|
|
||||||
// #[brw(little)]
|
|
||||||
// pub struct Timeline<Instruction:binrw::BinRead+binrw::BinWrite>{
|
|
||||||
// #[bw(try_calc(u32::try_from(instructions.len())))]
|
|
||||||
// instruction_count:u32,
|
|
||||||
// #[br(count=instruction_count)]
|
|
||||||
// instructions:Vec<super::instruction::TimedInstruction<Instruction>>
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
//serious code
|
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
#[derive(Clone,Copy,Debug,id::Id)]
|
#[derive(Clone,Copy,Debug,id::Id)]
|
||||||
pub struct SegmentId(u32);
|
pub struct SegmentId(u32);
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[brw(little)]
|
|
||||||
pub struct Segment{
|
pub struct Segment{
|
||||||
state:simulation::State,
|
pub instructions:Vec<TimedPhysicsInstruction>
|
||||||
//#[bw(try_calc(u32::try_from(instructions.len())))]
|
}
|
||||||
//instruction_count:u32,
|
|
||||||
//#[br(count=instruction_count)]
|
|
||||||
//instructions:Vec<instruction::TimedInstruction<simulation::Instruction>>
|
|
||||||
|
|
||||||
//please remember that strafe ticks are implicit! 33% smaller bot files
|
#[derive(Clone,Copy,Debug)]
|
||||||
|
pub struct SegmentInfo{
|
||||||
|
/// time of the first instruction in this segment.
|
||||||
|
time:Time,
|
||||||
|
instruction_count:u32,
|
||||||
|
/// How many total instructions in segments up to and including this segment
|
||||||
|
/// Alternatively, the id of the first instruction be in the _next_ segment
|
||||||
|
instructions_subtotal:u64,
|
||||||
|
block_id:BlockId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StreamableBot<R:BinReaderExt>{
|
pub struct StreamableBot<R:BinReaderExt>{
|
||||||
file:crate::file::File<R>,
|
file:crate::file::File<R>,
|
||||||
//timeline:timeline::Timeline<SegmentId>,
|
segment_map:Vec<SegmentInfo>,
|
||||||
segment_id_to_block_id:Vec<crate::file::BlockId>,
|
|
||||||
}
|
}
|
||||||
impl<R:BinReaderExt> StreamableBot<R>{
|
impl<R:BinReaderExt> StreamableBot<R>{
|
||||||
pub(crate) fn new(file:crate::file::File<R>)->Result<Self,Error>{
|
pub(crate) fn new(mut file:crate::file::File<R>)->Result<Self,Error>{
|
||||||
Err(Error::InvalidHeader)
|
//assume the file seek is in the right place to start reading header
|
||||||
|
let header:Header=file.data_mut().read_le().map_err(Error::InvalidHeader)?;
|
||||||
|
let mut instructions_subtotal=0;
|
||||||
|
let segment_map=header.segments.into_iter().map(|SegmentHeader{time,instruction_count,block_id}|{
|
||||||
|
instructions_subtotal+=instruction_count as u64;
|
||||||
|
SegmentInfo{
|
||||||
|
time:Time::raw(time),
|
||||||
|
instruction_count,
|
||||||
|
instructions_subtotal,
|
||||||
|
block_id,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
Ok(Self{
|
||||||
|
file,
|
||||||
|
segment_map,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn load_segment(&mut self,segment_id:SegmentId)->Result<Segment,Error>{
|
fn get_segment_info(&self,segment_id:SegmentId)->Result<SegmentInfo,Error>{
|
||||||
let block_id=*self.segment_id_to_block_id.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?;
|
Ok(*self.segment_map.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?)
|
||||||
let mut block=self.file.block_reader(block_id).map_err(Error::File)?;
|
}
|
||||||
let segment=block.read_le().map_err(Error::InvalidSegment)?;
|
pub fn find_segments_instruction_range(&self,start_instruction:u64,end_instruction:u64)->&[SegmentInfo]{
|
||||||
|
let start=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<start_instruction);
|
||||||
|
let end=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<end_instruction);
|
||||||
|
&self.segment_map[start..=end]
|
||||||
|
}
|
||||||
|
// pub fn find_segments_time_range(&self,start_time:Time,end_time:Time)->&[SegmentInfo]{
|
||||||
|
// // TODO: This is off by one, both should be one less
|
||||||
|
// let start=self.segment_map.partition_point(|segment_info|segment_info.time<start_time);
|
||||||
|
// let end=self.segment_map.partition_point(|segment_info|segment_info.time<end_time);
|
||||||
|
// &self.segment_map[start..=end]
|
||||||
|
// }
|
||||||
|
fn append_to_segment(&mut self,segment_info:SegmentInfo,segment:&mut Segment)->Result<(),Error>{
|
||||||
|
let mut block=self.file.block_reader(segment_info.block_id).map_err(Error::File)?;
|
||||||
|
for _ in 0..segment_info.instruction_count{
|
||||||
|
let instruction:newtypes::physics::TimedInstruction=block.read_le().map_err(Error::InvalidSegment)?;
|
||||||
|
segment.instructions.push(instruction.try_into().map_err(Error::SegmentConvert)?);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn load_segment(&mut self,segment_info:SegmentInfo)->Result<Segment,Error>{
|
||||||
|
let mut segment=Segment{
|
||||||
|
instructions:Vec::with_capacity(segment_info.instruction_count as usize),
|
||||||
|
};
|
||||||
|
self.append_to_segment(segment_info,&mut segment)?;
|
||||||
|
Ok(segment)
|
||||||
|
}
|
||||||
|
pub fn read_all(&mut self)->Result<Segment,Error>{
|
||||||
|
let mut segment=Segment{
|
||||||
|
instructions:Vec::new(),
|
||||||
|
};
|
||||||
|
for i in 0..self.segment_map.len(){
|
||||||
|
let segment_info=self.segment_map[i];
|
||||||
|
self.append_to_segment(segment_info,&mut segment)?;
|
||||||
|
}
|
||||||
Ok(segment)
|
Ok(segment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_BLOCK_SIZE:usize=64*1024;//64 kB
|
||||||
|
pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:impl IntoIterator<Item=TimedPhysicsInstruction>)->Result<(),Error>{
|
||||||
|
// decide which instructions to put in which segment
|
||||||
|
// write segment 1 to block 1
|
||||||
|
// write segment N to block 2
|
||||||
|
// write rest of segments
|
||||||
|
// 1 2 3 4 5
|
||||||
|
// becomes
|
||||||
|
// [1 5] 2 3 4
|
||||||
|
struct SegmentHeaderInfo{
|
||||||
|
time:Time,
|
||||||
|
instruction_count:u32,
|
||||||
|
range:core::ops::Range<usize>
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut segment_header_infos=Vec::new();
|
||||||
|
let mut raw_segments=std::io::Cursor::new(Vec::new());
|
||||||
|
|
||||||
|
// block info
|
||||||
|
let mut start_time=Time::ZERO;
|
||||||
|
let mut start_position=raw_segments.position() as usize;
|
||||||
|
let mut instruction_count=0;
|
||||||
|
|
||||||
|
let mut last_position=start_position;
|
||||||
|
|
||||||
|
let mut iter=instructions.into_iter();
|
||||||
|
|
||||||
|
macro_rules! collect_instruction{
|
||||||
|
($instruction:expr)=>{
|
||||||
|
let time=$instruction.time;
|
||||||
|
let instruction_writable:newtypes::physics::TimedInstruction=$instruction.try_into().map_err(Error::InstructionConvert)?;
|
||||||
|
instruction_writable.write_le(&mut raw_segments).map_err(Error::InstructionWrite)?;
|
||||||
|
instruction_count+=1;
|
||||||
|
let position=raw_segments.position() as usize;
|
||||||
|
// exceeds max block size
|
||||||
|
if MAX_BLOCK_SIZE<position-last_position{
|
||||||
|
segment_header_infos.push(SegmentHeaderInfo{
|
||||||
|
time:start_time,
|
||||||
|
instruction_count,
|
||||||
|
range:start_position..last_position,
|
||||||
|
});
|
||||||
|
start_position=last_position;
|
||||||
|
instruction_count=0;
|
||||||
|
start_time=time;
|
||||||
|
}
|
||||||
|
last_position=position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unroll one loop iteration to grab the starting time
|
||||||
|
if let Some(instruction)=iter.next(){
|
||||||
|
start_time=instruction.time;
|
||||||
|
collect_instruction!(instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
for instruction in iter{
|
||||||
|
collect_instruction!(instruction);
|
||||||
|
}
|
||||||
|
//last block, whatever size it happens to be
|
||||||
|
{
|
||||||
|
let final_position=raw_segments.position() as usize;
|
||||||
|
segment_header_infos.push(SegmentHeaderInfo{
|
||||||
|
time:start_time,
|
||||||
|
instruction_count,
|
||||||
|
range:start_position..final_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// drop cursor
|
||||||
|
let raw_segments=raw_segments.into_inner();
|
||||||
|
|
||||||
|
let num_segments=segment_header_infos.len();
|
||||||
|
|
||||||
|
// segments list is in chronological order
|
||||||
|
let make_segment_header=|block_id,&SegmentHeaderInfo{time,instruction_count,range:ref _range}|SegmentHeader{
|
||||||
|
time:time.get(),
|
||||||
|
instruction_count,
|
||||||
|
block_id,
|
||||||
|
};
|
||||||
|
let segments=if 2<num_segments{
|
||||||
|
let mut segments=Vec::with_capacity(num_segments);
|
||||||
|
// segment 1 is second block
|
||||||
|
if let Some(seg)=segment_header_infos.first(){
|
||||||
|
segments.push(make_segment_header(BlockId::new(1),seg));
|
||||||
|
}
|
||||||
|
// rest of segments start at fourth block
|
||||||
|
for (i,seg) in segment_header_infos[1..num_segments-1].iter().enumerate(){
|
||||||
|
make_segment_header(BlockId::new(3+i as u32),seg);
|
||||||
|
}
|
||||||
|
// last segment is third block
|
||||||
|
if let Some(seg)=segment_header_infos.last(){
|
||||||
|
segments.push(make_segment_header(BlockId::new(2),seg));
|
||||||
|
}
|
||||||
|
segments
|
||||||
|
}else{
|
||||||
|
// all segments in order
|
||||||
|
segment_header_infos.iter().enumerate().map(|(i,seg)|
|
||||||
|
make_segment_header(BlockId::new(1+i as u32),seg)
|
||||||
|
).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let header=Header{
|
||||||
|
physics_version,
|
||||||
|
num_segments:num_segments as u32,
|
||||||
|
segments,
|
||||||
|
};
|
||||||
|
|
||||||
|
// map header is +1
|
||||||
|
let block_count=1+num_segments as u32;
|
||||||
|
|
||||||
|
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
|
||||||
|
// block_location is one longer than block_count
|
||||||
|
let mut block_location=Vec::with_capacity(1+block_count as usize);
|
||||||
|
|
||||||
|
//probe header length
|
||||||
|
let mut bot_header_data=Vec::new();
|
||||||
|
binrw::BinWrite::write_le(&header,&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?;
|
||||||
|
|
||||||
|
// the first block location is the map header
|
||||||
|
block_location.push(offset);
|
||||||
|
offset+=bot_header_data.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
|
||||||
|
// priming includes file header + first 3 blocks [bot header, first segment, last segment]
|
||||||
|
let priming=if 2<num_segments{
|
||||||
|
// segment 1 is block 2
|
||||||
|
if let Some(seg)=segment_header_infos.first(){
|
||||||
|
offset+=seg.range.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
}
|
||||||
|
// last segment is block 3
|
||||||
|
if let Some(seg)=segment_header_infos.last(){
|
||||||
|
offset+=seg.range.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
let priming=offset;
|
||||||
|
|
||||||
|
// rest of segments
|
||||||
|
for seg in &segment_header_infos[1..num_segments-1]{
|
||||||
|
offset+=seg.range.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
}
|
||||||
|
priming
|
||||||
|
}else{
|
||||||
|
// all segments in order
|
||||||
|
for seg in &segment_header_infos{
|
||||||
|
offset+=seg.range.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
}
|
||||||
|
offset
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_header=crate::file::Header{
|
||||||
|
fourcc:crate::file::FourCC::Bot,
|
||||||
|
version:VERSION,
|
||||||
|
priming,
|
||||||
|
resource:0,
|
||||||
|
block_count,
|
||||||
|
block_location,
|
||||||
|
};
|
||||||
|
|
||||||
|
// write file header
|
||||||
|
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
||||||
|
// write bot header
|
||||||
|
writer.write(&bot_header_data).map_err(Error::IO)?;
|
||||||
|
|
||||||
|
// write blocks
|
||||||
|
if 2<num_segments{
|
||||||
|
// segment 1 is block 2
|
||||||
|
if let Some(seg)=segment_header_infos.first(){
|
||||||
|
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||||
|
}
|
||||||
|
// last segment is block 3
|
||||||
|
if let Some(seg)=segment_header_infos.last(){
|
||||||
|
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||||
|
}
|
||||||
|
// rest of segments
|
||||||
|
for seg in &segment_header_infos[1..num_segments-1]{
|
||||||
|
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// all segments in order
|
||||||
|
for seg in segment_header_infos{
|
||||||
|
writer.write(&raw_segments[seg.range]).map_err(Error::IO)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,6 +73,16 @@ pub struct Header{
|
|||||||
#[br(count=block_count+1)]
|
#[br(count=block_count+1)]
|
||||||
pub block_location:Vec<u64>,
|
pub block_location:Vec<u64>,
|
||||||
}
|
}
|
||||||
|
impl Header{
|
||||||
|
pub const fn calculate_size(block_count:u32)->usize{
|
||||||
|
4 // fourcc
|
||||||
|
+4 // version
|
||||||
|
+8 // priming
|
||||||
|
+16 // resource
|
||||||
|
+4 // block_count
|
||||||
|
+(block_count as usize+1)*8 // block_location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ for model_id in 0..num_models{
|
|||||||
//if you hash the resource itself and set the first 8 bits to this, that's the resource uuid
|
//if you hash the resource itself and set the first 8 bits to this, that's the resource uuid
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little,repr=u8)]
|
#[brw(little,repr=u8)]
|
||||||
|
#[repr(u8)]
|
||||||
enum ResourceType{
|
enum ResourceType{
|
||||||
Mesh,
|
Mesh,
|
||||||
Texture,
|
Texture,
|
||||||
@@ -411,28 +412,26 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
|
|||||||
attributes:map.attributes.into_iter().map(Into::into).collect(),
|
attributes:map.attributes.into_iter().map(Into::into).collect(),
|
||||||
render_configs:map.render_configs.into_iter().map(Into::into).collect(),
|
render_configs:map.render_configs.into_iter().map(Into::into).collect(),
|
||||||
};
|
};
|
||||||
let mut file_header=crate::file::Header{
|
//probe header length
|
||||||
|
let mut map_header_data=Vec::new();
|
||||||
|
binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?;
|
||||||
|
|
||||||
|
// calculate final file header
|
||||||
|
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
|
||||||
|
offset+=map_header_data.len() as u64;
|
||||||
|
// priming includes map header
|
||||||
|
let priming=offset;
|
||||||
|
for position in &mut block_location{
|
||||||
|
*position+=offset;
|
||||||
|
}
|
||||||
|
let file_header=crate::file::Header{
|
||||||
fourcc:crate::file::FourCC::Map,
|
fourcc:crate::file::FourCC::Map,
|
||||||
version:0,
|
version:0,
|
||||||
priming:0,
|
priming,
|
||||||
resource:0,
|
resource:0,
|
||||||
block_count,
|
block_count,
|
||||||
block_location,
|
block_location,
|
||||||
};
|
};
|
||||||
//probe header length
|
|
||||||
let mut file_header_data=Vec::new();
|
|
||||||
binrw::BinWrite::write_le(&file_header,&mut std::io::Cursor::new(&mut file_header_data)).map_err(Error::InvalidData)?;
|
|
||||||
let mut map_header_data=Vec::new();
|
|
||||||
binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?;
|
|
||||||
|
|
||||||
//update file header according to probe data
|
|
||||||
let mut offset=file_header_data.len() as u64;
|
|
||||||
file_header.priming=offset;
|
|
||||||
file_header.block_location[0]=offset;
|
|
||||||
offset+=map_header_data.len() as u64;
|
|
||||||
for position in &mut file_header.block_location[1..]{
|
|
||||||
*position+=offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
//write (updated) file header
|
//write (updated) file header
|
||||||
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
pub const fn flag(b:bool,mask:u8)->u8{
|
pub const fn flag(b:bool,mask:u8)->u8{
|
||||||
(-(b as i8) as u8)&mask
|
(-(b as i8) as u8)&mask
|
||||||
}
|
}
|
||||||
|
pub fn bool_from_u8(value:u8)->bool{
|
||||||
|
value!=0
|
||||||
|
}
|
||||||
|
pub fn bool_into_u8(value:&bool)->u8{
|
||||||
|
*value as u8
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,23 @@ pub struct Ratio64Vec2{
|
|||||||
pub x:Ratio64,
|
pub x:Ratio64,
|
||||||
pub y:Ratio64,
|
pub y:Ratio64,
|
||||||
}
|
}
|
||||||
|
impl TryInto<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
|
||||||
|
type Error=RatioError;
|
||||||
|
fn try_into(self)->Result<strafesnet_common::integer::Ratio64Vec2,Self::Error>{
|
||||||
|
Ok(strafesnet_common::integer::Ratio64Vec2{
|
||||||
|
x:self.x.try_into()?,
|
||||||
|
y:self.y.try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
|
||||||
|
fn from(value:strafesnet_common::integer::Ratio64Vec2)->Self{
|
||||||
|
Self{
|
||||||
|
x:value.x.into(),
|
||||||
|
y:value.y.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Angle32=i32;
|
pub type Angle32=i32;
|
||||||
pub type Planar64=i64;
|
pub type Planar64=i64;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
mod common;
|
mod common;
|
||||||
pub mod aabb;
|
pub mod aabb;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
pub mod mouse;
|
||||||
pub mod integer;
|
pub mod integer;
|
||||||
|
pub mod physics;
|
||||||
pub mod gameplay_modes;
|
pub mod gameplay_modes;
|
||||||
pub mod gameplay_style;
|
pub mod gameplay_style;
|
||||||
pub mod gameplay_attributes;
|
pub mod gameplay_attributes;
|
||||||
25
lib/snf/src/newtypes/mouse.rs
Normal file
25
lib/snf/src/newtypes/mouse.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use super::integer::Time;
|
||||||
|
|
||||||
|
#[binrw::binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
pub struct MouseState{
|
||||||
|
pub pos:[i32;2],
|
||||||
|
pub time:Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Into<strafesnet_common::mouse::MouseState<T>> for MouseState{
|
||||||
|
fn into(self)->strafesnet_common::mouse::MouseState<T>{
|
||||||
|
strafesnet_common::mouse::MouseState{
|
||||||
|
pos:self.pos.into(),
|
||||||
|
time:strafesnet_common::integer::Time::raw(self.time),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> From<strafesnet_common::mouse::MouseState<T>> for MouseState{
|
||||||
|
fn from(value:strafesnet_common::mouse::MouseState<T>)->Self{
|
||||||
|
Self{
|
||||||
|
pos:value.pos.to_array(),
|
||||||
|
time:value.time.get(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
156
lib/snf/src/newtypes/physics.rs
Normal file
156
lib/snf/src/newtypes/physics.rs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
use super::integer::Time;
|
||||||
|
use super::common::{bool_from_u8,bool_into_u8};
|
||||||
|
|
||||||
|
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>;
|
||||||
|
|
||||||
|
#[binrw::binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
pub struct TimedInstruction{
|
||||||
|
pub time:Time,
|
||||||
|
pub instruction:Instruction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<TimedPhysicsInstruction> for TimedInstruction{
|
||||||
|
type Error=super::integer::RatioError;
|
||||||
|
fn try_into(self)->Result<TimedPhysicsInstruction,Self::Error>{
|
||||||
|
Ok(strafesnet_common::instruction::TimedInstruction{
|
||||||
|
time:strafesnet_common::integer::Time::raw(self.time),
|
||||||
|
instruction:self.instruction.try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<TimedPhysicsInstruction> for TimedInstruction{
|
||||||
|
type Error=super::physics::InstructionConvert;
|
||||||
|
fn try_from(value:TimedPhysicsInstruction)->Result<Self,Self::Error>{
|
||||||
|
Ok(Self{
|
||||||
|
time:value.time.get(),
|
||||||
|
instruction:value.instruction.try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[binrw::binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
pub enum Instruction{
|
||||||
|
#[brw(magic=0u8)]
|
||||||
|
ReplaceMouse{
|
||||||
|
m0:super::mouse::MouseState,
|
||||||
|
m1:super::mouse::MouseState
|
||||||
|
},
|
||||||
|
#[brw(magic=1u8)]
|
||||||
|
SetNextMouse(super::mouse::MouseState),
|
||||||
|
#[brw(magic=2u8)]
|
||||||
|
SetMoveRight(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=3u8)]
|
||||||
|
SetMoveUp(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=4u8)]
|
||||||
|
SetMoveBack(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=5u8)]
|
||||||
|
SetMoveLeft(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=6u8)]
|
||||||
|
SetMoveDown(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=7u8)]
|
||||||
|
SetMoveForward(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=8u8)]
|
||||||
|
SetJump(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=9u8)]
|
||||||
|
SetZoom(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=10u8)]
|
||||||
|
Reset,
|
||||||
|
#[brw(magic=11u8)]
|
||||||
|
Restart(super::gameplay_modes::ModeId),
|
||||||
|
#[brw(magic=12u8)]
|
||||||
|
Spawn(super::gameplay_modes::ModeId,super::gameplay_modes::StageId),
|
||||||
|
#[brw(magic=13u8)]
|
||||||
|
PracticeFly,
|
||||||
|
#[brw(magic=14u8)]
|
||||||
|
SetSensitivity(super::integer::Ratio64Vec2),
|
||||||
|
#[brw(magic=255u8)]
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InstructionConvert{
|
||||||
|
/// This is an instruction that can be dropped when serializing
|
||||||
|
DropInstruction,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for InstructionConvert{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for InstructionConvert{}
|
||||||
|
impl TryInto<strafesnet_common::physics::Instruction> for Instruction{
|
||||||
|
type Error=super::integer::RatioError;
|
||||||
|
fn try_into(self)->Result<strafesnet_common::physics::Instruction,Self::Error>{
|
||||||
|
Ok(match self{
|
||||||
|
Instruction::ReplaceMouse{m0,m1}=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
|
||||||
|
Instruction::SetNextMouse(m)=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m.into())),
|
||||||
|
Instruction::SetMoveRight(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state.into())),
|
||||||
|
Instruction::SetMoveUp(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state.into())),
|
||||||
|
Instruction::SetMoveBack(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state.into())),
|
||||||
|
Instruction::SetMoveLeft(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state.into())),
|
||||||
|
Instruction::SetMoveDown(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state.into())),
|
||||||
|
Instruction::SetMoveForward(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state.into())),
|
||||||
|
Instruction::SetJump(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state.into())),
|
||||||
|
Instruction::SetZoom(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state.into())),
|
||||||
|
Instruction::Reset=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset),
|
||||||
|
Instruction::Restart(mode_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(strafesnet_common::gameplay_modes::ModeId::new(mode_id))),
|
||||||
|
Instruction::Spawn(mode_id,stage_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(
|
||||||
|
strafesnet_common::gameplay_modes::ModeId::new(mode_id),
|
||||||
|
strafesnet_common::gameplay_modes::StageId::new(stage_id),
|
||||||
|
)),
|
||||||
|
Instruction::PracticeFly=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly),
|
||||||
|
Instruction::SetSensitivity(sensitivity)=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity.try_into()?)),
|
||||||
|
Instruction::Idle=>strafesnet_common::physics::Instruction::Idle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<strafesnet_common::physics::Instruction> for Instruction{
|
||||||
|
type Error=InstructionConvert;
|
||||||
|
fn try_from(value:strafesnet_common::physics::Instruction)->Result<Self,Self::Error>{
|
||||||
|
match value{
|
||||||
|
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0,m1})=>Ok(Instruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
|
||||||
|
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m))=>Ok(Instruction::SetNextMouse(m.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state))=>Ok(Instruction::SetMoveRight(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state))=>Ok(Instruction::SetMoveUp(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state))=>Ok(Instruction::SetMoveBack(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state))=>Ok(Instruction::SetMoveLeft(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state))=>Ok(Instruction::SetMoveDown(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state))=>Ok(Instruction::SetMoveForward(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state))=>Ok(Instruction::SetJump(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state))=>Ok(Instruction::SetZoom(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset)=>Ok(Instruction::Reset),
|
||||||
|
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(mode_id))=>Ok(Instruction::Restart(mode_id.get())),
|
||||||
|
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(mode_id,stage_id))=>Ok(Instruction::Spawn(
|
||||||
|
mode_id.get(),
|
||||||
|
stage_id.get(),
|
||||||
|
)),
|
||||||
|
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly)=>Ok(Instruction::PracticeFly),
|
||||||
|
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity))=>Ok(Instruction::SetSensitivity(sensitivity.into())),
|
||||||
|
strafesnet_common::physics::Instruction::Idle=>Ok(Instruction::Idle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafe-client"
|
name = "strafe-client"
|
||||||
version = "0.10.5"
|
version = "0.11.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "Custom"
|
license = "Custom"
|
||||||
@@ -29,5 +29,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 }
|
||||||
wgpu = "23.0.1"
|
wgpu = "24.0.0"
|
||||||
winit = "0.30.7"
|
winit = "0.30.7"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ pub enum ReadError{
|
|||||||
StrafesNET(strafesnet_snf::Error),
|
StrafesNET(strafesnet_snf::Error),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
StrafesNETMap(strafesnet_snf::map::Error),
|
StrafesNETMap(strafesnet_snf::map::Error),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
StrafesNETBot(strafesnet_snf::bot::Error),
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
UnknownFileFormat,
|
UnknownFileFormat,
|
||||||
}
|
}
|
||||||
@@ -20,28 +22,39 @@ impl std::fmt::Display for ReadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for ReadError{}
|
impl std::error::Error for ReadError{}
|
||||||
|
|
||||||
pub enum DataStructure{
|
pub enum ReadFormat{
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
Roblox(strafesnet_rbx_loader::Model),
|
Roblox(strafesnet_rbx_loader::Model),
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
Source(strafesnet_bsp_loader::Bsp),
|
Source(strafesnet_bsp_loader::Bsp),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
StrafesNET(strafesnet_common::map::CompleteMap),
|
SNFM(strafesnet_common::map::CompleteMap),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
SNFB(strafesnet_snf::bot::Segment),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R:Read+std::io::Seek>(input:R)->Result<DataStructure,ReadError>{
|
pub fn read<R:Read+std::io::Seek>(input:R)->Result<ReadFormat,ReadError>{
|
||||||
let mut buf=std::io::BufReader::new(input);
|
let mut buf=std::io::BufReader::new(input);
|
||||||
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
|
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?[0..4].to_owned();
|
||||||
match &peek[0..4]{
|
// reading the entire file is way faster than round tripping the disk constantly
|
||||||
|
let mut entire_file=Vec::new();
|
||||||
|
buf.read_to_end(&mut entire_file).map_err(ReadError::Io)?;
|
||||||
|
let cursor=std::io::Cursor::new(entire_file);
|
||||||
|
match peek.as_slice(){
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
b"<rob"=>Ok(DataStructure::Roblox(strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?)),
|
b"<rob"=>Ok(ReadFormat::Roblox(strafesnet_rbx_loader::read(cursor).map_err(ReadError::Roblox)?)),
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
b"VBSP"=>Ok(DataStructure::Source(strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?)),
|
b"VBSP"=>Ok(ReadFormat::Source(strafesnet_bsp_loader::read(cursor).map_err(ReadError::Source)?)),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
b"SNFM"=>Ok(DataStructure::StrafesNET(
|
b"SNFM"=>Ok(ReadFormat::SNFM(
|
||||||
strafesnet_snf::read_map(buf).map_err(ReadError::StrafesNET)?
|
strafesnet_snf::read_map(cursor).map_err(ReadError::StrafesNET)?
|
||||||
.into_complete_map().map_err(ReadError::StrafesNETMap)?
|
.into_complete_map().map_err(ReadError::StrafesNETMap)?
|
||||||
)),
|
)),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
b"SNFB"=>Ok(ReadFormat::SNFB(
|
||||||
|
strafesnet_snf::read_bot(cursor).map_err(ReadError::StrafesNET)?
|
||||||
|
.read_all().map_err(ReadError::StrafesNETBot)?
|
||||||
|
)),
|
||||||
_=>Err(ReadError::UnknownFileFormat),
|
_=>Err(ReadError::UnknownFileFormat),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,14 +72,23 @@ impl std::fmt::Display for LoadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for LoadError{}
|
impl std::error::Error for LoadError{}
|
||||||
|
|
||||||
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
pub enum LoadFormat{
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
Map(strafesnet_common::map::CompleteMap),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
Bot(strafesnet_snf::bot::Segment),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<LoadFormat,LoadError>{
|
||||||
//blocking because it's simpler...
|
//blocking because it's simpler...
|
||||||
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
||||||
match read(file).map_err(LoadError::ReadError)?{
|
match read(file).map_err(LoadError::ReadError)?{
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
DataStructure::StrafesNET(map)=>Ok(map),
|
ReadFormat::SNFB(bot)=>Ok(LoadFormat::Bot(bot)),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
ReadFormat::SNFM(map)=>Ok(LoadFormat::Map(map)),
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
DataStructure::Roblox(model)=>{
|
ReadFormat::Roblox(model)=>{
|
||||||
let mut place=model.into_place();
|
let mut place=model.into_place();
|
||||||
place.run_scripts();
|
place.run_scripts();
|
||||||
|
|
||||||
@@ -99,10 +121,10 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::Co
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(map)
|
Ok(LoadFormat::Map(map))
|
||||||
},
|
},
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
DataStructure::Source(bsp)=>{
|
ReadFormat::Source(bsp)=>{
|
||||||
let mut loader=strafesnet_deferred_loader::source_legacy();
|
let mut loader=strafesnet_deferred_loader::source_legacy();
|
||||||
|
|
||||||
let (texture_loader,mesh_loader)=loader.get_inner_mut();
|
let (texture_loader,mesh_loader)=loader.get_inner_mut();
|
||||||
@@ -138,7 +160,7 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::Co
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(map)
|
Ok(LoadFormat::Map(map))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,11 +84,11 @@ pub trait MeshQuery{
|
|||||||
}
|
}
|
||||||
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
|
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
|
||||||
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
|
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
|
||||||
fn face_edges(&self,face_id:Self::Face)->Cow<Vec<Self::Edge>>;
|
fn face_edges(&self,face_id:Self::Face)->Cow<[Self::Edge]>;
|
||||||
fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>;
|
fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>;
|
||||||
fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>;
|
fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>;
|
||||||
fn vert_edges(&self,vert_id:Self::Vert)->Cow<Vec<Self::Edge>>;
|
fn vert_edges(&self,vert_id:Self::Vert)->Cow<[Self::Edge]>;
|
||||||
fn vert_faces(&self,vert_id:Self::Vert)->Cow<Vec<Self::Face>>;
|
fn vert_faces(&self,vert_id:Self::Vert)->Cow<[Self::Face]>;
|
||||||
}
|
}
|
||||||
struct FaceRefs{
|
struct FaceRefs{
|
||||||
edges:Vec<SubmeshDirectedEdgeId>,
|
edges:Vec<SubmeshDirectedEdgeId>,
|
||||||
@@ -279,15 +279,12 @@ struct EdgePool{
|
|||||||
}
|
}
|
||||||
impl EdgePool{
|
impl EdgePool{
|
||||||
fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){
|
fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){
|
||||||
let edge_id=if let Some(&edge_id)=self.edge_id_from_guy.get(&edge_ref_verts){
|
let edge_id=*self.edge_id_from_guy.entry(edge_ref_verts.clone()).or_insert_with(||{
|
||||||
edge_id
|
|
||||||
}else{
|
|
||||||
let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32);
|
let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32);
|
||||||
self.edge_guys.push((edge_ref_verts.clone(),EdgeRefFaces::new()));
|
self.edge_guys.push((edge_ref_verts,EdgeRefFaces::new()));
|
||||||
self.edge_id_from_guy.insert(edge_ref_verts,edge_id);
|
|
||||||
edge_id
|
edge_id
|
||||||
};
|
});
|
||||||
(&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id.get() as usize)}.1,edge_id)
|
(&mut self.edge_guys[edge_id.get() as usize].1,edge_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,16 +319,11 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
|||||||
let mut submesh_verts=Vec::new();
|
let mut submesh_verts=Vec::new();
|
||||||
let mut submesh_vert_id_from_mesh_vert_id=HashMap::<MeshVertId,SubmeshVertId>::new();
|
let mut submesh_vert_id_from_mesh_vert_id=HashMap::<MeshVertId,SubmeshVertId>::new();
|
||||||
//lazy closure
|
//lazy closure
|
||||||
let mut get_submesh_vert_id=|vert_id:MeshVertId|{
|
let mut get_submesh_vert_id=|vert_id:MeshVertId|*submesh_vert_id_from_mesh_vert_id.entry(vert_id).or_insert_with(||{
|
||||||
if let Some(&submesh_vert_id)=submesh_vert_id_from_mesh_vert_id.get(&vert_id){
|
let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32);
|
||||||
submesh_vert_id
|
submesh_verts.push(vert_id);
|
||||||
}else{
|
submesh_vert_id
|
||||||
let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32);
|
});
|
||||||
submesh_verts.push(vert_id);
|
|
||||||
submesh_vert_id_from_mesh_vert_id.insert(vert_id,submesh_vert_id);
|
|
||||||
submesh_vert_id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut edge_pool=EdgePool::default();
|
let mut edge_pool=EdgePool::default();
|
||||||
let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()];
|
let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()];
|
||||||
let mut face_ref_guys=Vec::new();
|
let mut face_ref_guys=Vec::new();
|
||||||
@@ -364,10 +356,10 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
|||||||
edge_ref_faces.push(!is_sorted as usize,submesh_face_id);
|
edge_ref_faces.push(!is_sorted as usize,submesh_face_id);
|
||||||
//index edges & face into vertices
|
//index edges & face into vertices
|
||||||
{
|
{
|
||||||
let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert0_id.get() as usize)};
|
let vert_ref_guy=&mut vert_ref_guys[submesh_vert0_id.get() as usize];
|
||||||
vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted));
|
vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted));
|
||||||
vert_ref_guy.faces.insert(submesh_face_id);
|
vert_ref_guy.faces.insert(submesh_face_id);
|
||||||
unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert1_id.get() as usize)}.edges.insert(edge_id.as_directed(!is_sorted));
|
vert_ref_guys[submesh_vert1_id.get() as usize].edges.insert(edge_id.as_directed(!is_sorted));
|
||||||
}
|
}
|
||||||
//return directed_edge_id
|
//return directed_edge_id
|
||||||
edge_id.as_directed(is_sorted)
|
edge_id.as_directed(is_sorted)
|
||||||
@@ -442,7 +434,7 @@ impl MeshQuery for PhysicsMeshView<'_>{
|
|||||||
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
|
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
|
||||||
self.data.verts[vert_idx].0
|
self.data.verts[vert_idx].0
|
||||||
}
|
}
|
||||||
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<Vec<SubmeshDirectedEdgeId>>{
|
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{
|
||||||
Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges)
|
Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges)
|
||||||
}
|
}
|
||||||
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{
|
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{
|
||||||
@@ -451,10 +443,10 @@ impl MeshQuery for PhysicsMeshView<'_>{
|
|||||||
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{
|
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{
|
||||||
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts)
|
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts)
|
||||||
}
|
}
|
||||||
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshDirectedEdgeId>>{
|
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{
|
||||||
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges)
|
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges)
|
||||||
}
|
}
|
||||||
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshFaceId>>{
|
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{
|
||||||
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces)
|
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -520,7 +512,7 @@ impl MeshQuery for TransformedMesh<'_>{
|
|||||||
self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1()
|
self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1()
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<Vec<SubmeshDirectedEdgeId>>{
|
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{
|
||||||
self.view.face_edges(face_id)
|
self.view.face_edges(face_id)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -532,11 +524,11 @@ impl MeshQuery for TransformedMesh<'_>{
|
|||||||
self.view.edge_verts(edge_id)
|
self.view.edge_verts(edge_id)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshDirectedEdgeId>>{
|
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{
|
||||||
self.view.vert_edges(vert_id)
|
self.view.vert_edges(vert_id)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshFaceId>>{
|
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{
|
||||||
self.view.vert_faces(vert_id)
|
self.view.vert_faces(vert_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -712,7 +704,21 @@ impl MinkowskiMesh<'_>{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn closest_fev_not_inside(&self,mut infinity_body:Body)->Option<FEV<MinkowskiMesh>>{
|
// TODO: fundamentally improve this algorithm.
|
||||||
|
// All it needs to do is find the closest point on the mesh
|
||||||
|
// and return the FEV which the point resides on.
|
||||||
|
//
|
||||||
|
// What it actually does is use the above functions to trace a ray in from infinity,
|
||||||
|
// crawling the closest point along the mesh surface until the ray reaches
|
||||||
|
// the starting point to discover the final FEV.
|
||||||
|
//
|
||||||
|
// The actual collision prediction probably does a single test
|
||||||
|
// and then immediately returns with 0 FEV transitions on average,
|
||||||
|
// because of the strict time_limit constraint.
|
||||||
|
//
|
||||||
|
// Most of the calculation time is just calculating the starting point
|
||||||
|
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
|
||||||
|
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Time,)->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
|
||||||
@@ -720,40 +726,39 @@ 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 infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
|
match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,start_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:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_in(&self,relative_body:&Body,start_time:Time,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(),start_time).map_or(None,|fev|{
|
||||||
//continue forwards along the body parabola
|
//continue forwards along the body parabola
|
||||||
match fev.crawl(self,relative_body,relative_body.time,time_limit){
|
match fev.crawl(self,relative_body,start_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:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_out(&self,relative_body:&Body,start_time:Time,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
//create an extrapolated body at time_limit
|
//create an extrapolated body at time_limit
|
||||||
let infinity_body=Body::new(
|
let infinity_body=-relative_body.clone();
|
||||||
relative_body.extrapolated_position(time_limit),
|
self.closest_fev_not_inside(infinity_body,-time_limit).map_or(None,|fev|{
|
||||||
-relative_body.extrapolated_velocity(time_limit),
|
|
||||||
relative_body.acceleration,
|
|
||||||
-time_limit,
|
|
||||||
);
|
|
||||||
self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{
|
|
||||||
//continue backwards along the body parabola
|
//continue backwards along the body parabola
|
||||||
match fev.crawl(self,&-relative_body.clone(),-time_limit,-relative_body.time){
|
match fev.crawl(self,&infinity_body,-time_limit,-start_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:&Body,time_limit:Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
|
pub fn predict_collision_face_out(&self,relative_body:&Body,start_time:Time,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 start_time={
|
||||||
|
let r=(start_time-relative_body.time).to_ratio();
|
||||||
|
Ratio::new(r.num,r.den)
|
||||||
|
};
|
||||||
let mut best_time={
|
let mut best_time={
|
||||||
let r=(time_limit-relative_body.time).to_ratio();
|
let r=(time_limit-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())
|
||||||
@@ -769,7 +774,7 @@ impl MinkowskiMesh<'_>{
|
|||||||
//WARNING! d outside of *2
|
//WARNING! d outside of *2
|
||||||
//WARNING: truncated precision
|
//WARNING: truncated precision
|
||||||
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){
|
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){
|
||||||
if Ratio::new(Planar64::ZERO,Planar64::EPSILON).le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if start_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_edge=Some(directed_edge_id);
|
best_edge=Some(directed_edge_id);
|
||||||
break;
|
break;
|
||||||
@@ -832,7 +837,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn face_edges(&self,face_id:MinkowskiFace)->Cow<Vec<MinkowskiDirectedEdge>>{
|
fn face_edges(&self,face_id:MinkowskiFace)->Cow<[MinkowskiDirectedEdge]>{
|
||||||
match face_id{
|
match face_id{
|
||||||
MinkowskiFace::VertFace(v0,f1)=>{
|
MinkowskiFace::VertFace(v0,f1)=>{
|
||||||
Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{
|
Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{
|
||||||
@@ -933,7 +938,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn vert_edges(&self,vert_id:MinkowskiVert)->Cow<Vec<MinkowskiDirectedEdge>>{
|
fn vert_edges(&self,vert_id:MinkowskiVert)->Cow<[MinkowskiDirectedEdge]>{
|
||||||
match vert_id{
|
match vert_id{
|
||||||
MinkowskiVert::VertVert(v0,v1)=>{
|
MinkowskiVert::VertVert(v0,v1)=>{
|
||||||
let mut edges=Vec::new();
|
let mut edges=Vec::new();
|
||||||
@@ -975,7 +980,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow<Vec<MinkowskiFace>>{
|
fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow<[MinkowskiFace]>{
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,74 @@
|
|||||||
use strafesnet_common::mouse::MouseState;
|
use strafesnet_common::mouse::MouseState;
|
||||||
use strafesnet_common::physics::{
|
use strafesnet_common::physics::{
|
||||||
Instruction as PhysicsInputInstruction,
|
MouseInstruction,SetControlInstruction,ModeInstruction,MiscInstruction,
|
||||||
|
Instruction as PhysicsInstruction,
|
||||||
TimeInner as PhysicsTimeInner,
|
TimeInner as PhysicsTimeInner,
|
||||||
Time as PhysicsTime,
|
Time as PhysicsTime,
|
||||||
MouseInstruction,
|
|
||||||
OtherInstruction,
|
|
||||||
};
|
};
|
||||||
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
|
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
|
||||||
|
|
||||||
type TimedPhysicsInstruction=TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>;
|
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
|
||||||
type TimedUnbufferedInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
|
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTimeInner>;
|
||||||
type DoubleTimedUnbufferedInstruction=TimedInstruction<TimedUnbufferedInstruction,SessionTimeInner>;
|
|
||||||
|
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTimeInner>;
|
||||||
|
|
||||||
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
|
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
|
||||||
|
|
||||||
/// To be fed into MouseInterpolator
|
/// To be fed into MouseInterpolator
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum Instruction{
|
pub(crate) enum Instruction{
|
||||||
MoveMouse(glam::IVec2),
|
MoveMouse(glam::IVec2),
|
||||||
Other(OtherInstruction),
|
SetControl(SetControlInstruction),
|
||||||
|
Mode(ModeInstruction),
|
||||||
|
Misc(MiscInstruction),
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
enum UnbufferedInstruction{
|
||||||
|
MoveMouse(glam::IVec2),
|
||||||
|
NonMouse(NonMouseInstruction),
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
enum BufferedInstruction{
|
||||||
|
Mouse(MouseInstruction),
|
||||||
|
NonMouse(NonMouseInstruction),
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub(crate) enum NonMouseInstruction{
|
||||||
|
SetControl(SetControlInstruction),
|
||||||
|
Mode(ModeInstruction),
|
||||||
|
Misc(MiscInstruction),
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
impl From<Instruction> for UnbufferedInstruction{
|
||||||
|
#[inline]
|
||||||
|
fn from(value:Instruction)->Self{
|
||||||
|
match value{
|
||||||
|
Instruction::MoveMouse(mouse_instruction)=>UnbufferedInstruction::MoveMouse(mouse_instruction),
|
||||||
|
Instruction::SetControl(set_control_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::SetControl(set_control_instruction)),
|
||||||
|
Instruction::Mode(mode_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Mode(mode_instruction)),
|
||||||
|
Instruction::Misc(misc_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Misc(misc_instruction)),
|
||||||
|
Instruction::Idle=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Idle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<BufferedInstruction> for PhysicsInstruction{
|
||||||
|
#[inline]
|
||||||
|
fn from(value:BufferedInstruction)->Self{
|
||||||
|
match value{
|
||||||
|
BufferedInstruction::Mouse(mouse_instruction)=>PhysicsInstruction::Mouse(mouse_instruction),
|
||||||
|
BufferedInstruction::NonMouse(non_mouse_instruction)=>match non_mouse_instruction{
|
||||||
|
NonMouseInstruction::SetControl(set_control_instruction)=>PhysicsInstruction::SetControl(set_control_instruction),
|
||||||
|
NonMouseInstruction::Mode(mode_instruction)=>PhysicsInstruction::Mode(mode_instruction),
|
||||||
|
NonMouseInstruction::Misc(misc_instruction)=>PhysicsInstruction::Misc(misc_instruction),
|
||||||
|
NonMouseInstruction::Idle=>PhysicsInstruction::Idle,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum StepInstruction{
|
pub(crate) enum StepInstruction{
|
||||||
Pop,
|
Pop,
|
||||||
Timeout,
|
Timeout,
|
||||||
}
|
}
|
||||||
@@ -42,10 +88,10 @@ pub struct MouseInterpolator{
|
|||||||
}
|
}
|
||||||
// Maybe MouseInterpolator manipulation is better expressed using impls
|
// Maybe MouseInterpolator manipulation is better expressed using impls
|
||||||
// and called from Instruction trait impls in session
|
// and called from Instruction trait impls in session
|
||||||
impl InstructionConsumer<TimedUnbufferedInstruction> for MouseInterpolator{
|
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{
|
||||||
type TimeInner=SessionTimeInner;
|
type TimeInner=SessionTimeInner;
|
||||||
fn process_instruction(&mut self,ins:DoubleTimedUnbufferedInstruction){
|
fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){
|
||||||
self.push_unbuffered_input(ins)
|
self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionEmitter<StepInstruction> for MouseInterpolator{
|
impl InstructionEmitter<StepInstruction> for MouseInterpolator{
|
||||||
@@ -65,7 +111,7 @@ impl MouseInterpolator{
|
|||||||
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){
|
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){
|
||||||
self.buffer.push_front(TimedInstruction{
|
self.buffer.push_front(TimedInstruction{
|
||||||
time:ins.time,
|
time:ins.time,
|
||||||
instruction:PhysicsInputInstruction::Mouse(ins.instruction),
|
instruction:BufferedInstruction::Mouse(ins.instruction).into(),
|
||||||
});
|
});
|
||||||
// flush buffer to output
|
// flush buffer to output
|
||||||
if self.output.len()==0{
|
if self.output.len()==0{
|
||||||
@@ -86,7 +132,8 @@ impl MouseInterpolator{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn timeout_mouse(&mut self,time:PhysicsTime){
|
fn timeout_mouse(&mut self,timeout_time:PhysicsTime){
|
||||||
|
// the state always changes to unbuffered
|
||||||
let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
|
let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
|
||||||
match buffer_state{
|
match buffer_state{
|
||||||
BufferState::Unbuffered=>(),
|
BufferState::Unbuffered=>(),
|
||||||
@@ -95,28 +142,21 @@ impl MouseInterpolator{
|
|||||||
self.push_mouse_and_flush_buffer(TimedInstruction{
|
self.push_mouse_and_flush_buffer(TimedInstruction{
|
||||||
time:mouse_state.time,
|
time:mouse_state.time,
|
||||||
instruction:MouseInstruction::ReplaceMouse{
|
instruction:MouseInstruction::ReplaceMouse{
|
||||||
m1:MouseState{pos:mouse_state.pos,time},
|
m1:MouseState{pos:mouse_state.pos,time:timeout_time},
|
||||||
m0:mouse_state,
|
m0:mouse_state,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
BufferState::Buffered(_time,mouse_state)=>{
|
BufferState::Buffered(_time,mouse_state)=>{
|
||||||
// convert to BufferState::Unbuffered
|
// duplicate the currently buffered mouse state but at a later (future, from the physics perspective) time
|
||||||
// use the first instruction which should be a mouse instruction
|
|
||||||
// to push a ReplaceMouse instruction
|
|
||||||
// duplicate the current mouse
|
|
||||||
self.push_mouse_and_flush_buffer(TimedInstruction{
|
self.push_mouse_and_flush_buffer(TimedInstruction{
|
||||||
// This should be simulation_timer.time(timeout)
|
|
||||||
// but the timer is not accessible from this scope
|
|
||||||
// and it's just here to say that the mouse isn't moving anyways.
|
|
||||||
// I think this is a divide by zero bug, two identical mouse_states will occupy the interpolation state
|
|
||||||
time:mouse_state.time,
|
time:mouse_state.time,
|
||||||
instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time}),
|
instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time:timeout_time}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn push_unbuffered_input(&mut self,ins:DoubleTimedUnbufferedInstruction){
|
fn push_unbuffered_input(&mut self,session_time:SessionTime,physics_time:PhysicsTime,ins:UnbufferedInstruction){
|
||||||
// new input
|
// new input
|
||||||
// if there is zero instruction buffered, it means the mouse is not moving
|
// if there is zero instruction buffered, it means the mouse is not moving
|
||||||
// case 1: unbuffered
|
// case 1: unbuffered
|
||||||
@@ -128,18 +168,16 @@ impl MouseInterpolator{
|
|||||||
// case 3: stop
|
// case 3: stop
|
||||||
// a mouse event is buffered, but no mouse events have transpired within 10ms
|
// a mouse event is buffered, but no mouse events have transpired within 10ms
|
||||||
|
|
||||||
// push buffered mouse instruction and flush buffer to output
|
// replace_with allows the enum variant to safely be replaced
|
||||||
if self.get_mouse_timedout_at(ins.time).is_some(){
|
// from behind a mutable reference, but a panic in the closure means that
|
||||||
self.timeout_mouse(ins.instruction.time);
|
// the entire program terminates rather than completing an unwind.
|
||||||
}
|
|
||||||
// replace_with allows the enum variant to safely be replaced from behind a mutable reference
|
|
||||||
let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{
|
let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{
|
||||||
match ins.instruction.instruction{
|
match ins{
|
||||||
Instruction::MoveMouse(pos)=>{
|
UnbufferedInstruction::MoveMouse(pos)=>{
|
||||||
let next_mouse_state=MouseState{pos,time:ins.instruction.time};
|
let next_mouse_state=MouseState{pos,time:physics_time};
|
||||||
match buffer_state{
|
match buffer_state{
|
||||||
BufferState::Unbuffered=>{
|
BufferState::Unbuffered=>{
|
||||||
((None,None),BufferState::Initializing(ins.time,next_mouse_state))
|
((None,None),BufferState::Initializing(session_time,next_mouse_state))
|
||||||
},
|
},
|
||||||
BufferState::Initializing(_time,mouse_state)=>{
|
BufferState::Initializing(_time,mouse_state)=>{
|
||||||
let ins_mouse=TimedInstruction{
|
let ins_mouse=TimedInstruction{
|
||||||
@@ -149,19 +187,19 @@ impl MouseInterpolator{
|
|||||||
m1:next_mouse_state.clone(),
|
m1:next_mouse_state.clone(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
((Some(ins_mouse),None),BufferState::Buffered(ins.time,next_mouse_state))
|
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
|
||||||
},
|
},
|
||||||
BufferState::Buffered(_time,mouse_state)=>{
|
BufferState::Buffered(_time,mouse_state)=>{
|
||||||
let ins_mouse=TimedInstruction{
|
let ins_mouse=TimedInstruction{
|
||||||
time:mouse_state.time,
|
time:mouse_state.time,
|
||||||
instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()),
|
instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()),
|
||||||
};
|
};
|
||||||
((Some(ins_mouse),None),BufferState::Buffered(ins.time,next_mouse_state))
|
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Instruction::Other(other_instruction)=>((None,Some(TimedInstruction{
|
UnbufferedInstruction::NonMouse(other_instruction)=>((None,Some(TimedInstruction{
|
||||||
time:ins.instruction.time,
|
time:physics_time,
|
||||||
instruction:other_instruction,
|
instruction:other_instruction,
|
||||||
})),buffer_state),
|
})),buffer_state),
|
||||||
}
|
}
|
||||||
@@ -172,7 +210,7 @@ impl MouseInterpolator{
|
|||||||
if let Some(ins)=ins_other{
|
if let Some(ins)=ins_other{
|
||||||
let instruction=TimedInstruction{
|
let instruction=TimedInstruction{
|
||||||
time:ins.time,
|
time:ins.time,
|
||||||
instruction:PhysicsInputInstruction::Other(ins.instruction),
|
instruction:BufferedInstruction::NonMouse(ins.instruction).into(),
|
||||||
};
|
};
|
||||||
if matches!(self.buffer_state,BufferState::Unbuffered){
|
if matches!(self.buffer_state,BufferState::Unbuffered){
|
||||||
self.output.push_back(instruction);
|
self.output.push_back(instruction);
|
||||||
@@ -181,7 +219,7 @@ impl MouseInterpolator{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{
|
fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{
|
||||||
match self.get_mouse_timedout_at(time_limit){
|
match self.get_mouse_timedout_at(time_limit){
|
||||||
Some(timeout)=>Some(TimedInstruction{
|
Some(timeout)=>Some(TimedInstruction{
|
||||||
time:timeout,
|
time:timeout,
|
||||||
@@ -194,7 +232,7 @@ impl MouseInterpolator{
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>{
|
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedPhysicsInstruction>{
|
||||||
match ins.instruction{
|
match ins.instruction{
|
||||||
StepInstruction::Pop=>(),
|
StepInstruction::Pop=>(),
|
||||||
StepInstruction::Timeout=>self.timeout_mouse(ins.time),
|
StepInstruction::Timeout=>self.timeout_mouse(ins.time),
|
||||||
@@ -215,13 +253,11 @@ mod test{
|
|||||||
macro_rules! push{
|
macro_rules! push{
|
||||||
($time:expr,$ins:expr)=>{
|
($time:expr,$ins:expr)=>{
|
||||||
println!("in={:?}",$ins);
|
println!("in={:?}",$ins);
|
||||||
interpolator.push_unbuffered_input(TimedInstruction{
|
interpolator.push_unbuffered_input(
|
||||||
time:$time,
|
$time,
|
||||||
instruction:TimedInstruction{
|
timer.time($time),
|
||||||
time:timer.time($time),
|
$ins,
|
||||||
instruction:$ins,
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){
|
while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){
|
||||||
let ins_retimed=TimedInstruction{
|
let ins_retimed=TimedInstruction{
|
||||||
time:timer.time(ins.time),
|
time:timer.time(ins.time),
|
||||||
@@ -235,11 +271,11 @@ mod test{
|
|||||||
|
|
||||||
// test each buffer_state transition
|
// test each buffer_state transition
|
||||||
let mut t=SessionTime::ZERO;
|
let mut t=SessionTime::ZERO;
|
||||||
push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
|
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||||
t+=SessionTime::from_millis(5);
|
t+=SessionTime::from_millis(5);
|
||||||
push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
|
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||||
t+=SessionTime::from_millis(5);
|
t+=SessionTime::from_millis(5);
|
||||||
push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
|
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||||
t+=SessionTime::from_millis(1);
|
t+=SessionTime::from_millis(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,51 @@ use strafesnet_common::integer::{self,vec3,mat3,Planar64,Planar64Vec3,Planar64Ma
|
|||||||
pub use strafesnet_common::physics::{Time,TimeInner};
|
pub use strafesnet_common::physics::{Time,TimeInner};
|
||||||
use gameplay::ModeState;
|
use gameplay::ModeState;
|
||||||
|
|
||||||
|
// Physics bug fixes can easily desync all bots.
|
||||||
|
//
|
||||||
|
// When replaying a bot, use the exact physics version which it was recorded with.
|
||||||
|
//
|
||||||
|
// When validating a new bot, use the latest compatible physics version
|
||||||
|
// from the compatiblity matrix, since it may include bugfixes
|
||||||
|
// for things like clipping through walls with surgical precision
|
||||||
|
// i.e. without breaking bots which don't exploit the bug.
|
||||||
|
//
|
||||||
|
// Compatible physics versions should be determined empirically via leaderboard resimulation.
|
||||||
|
// Compatible physics versions should result in an identical leaderboard state,
|
||||||
|
// or the only bots which fail are ones exploiting a surgically patched bug.
|
||||||
|
#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
||||||
|
pub struct PhysicsVersion(u32);
|
||||||
|
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
|
||||||
|
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
|
||||||
|
let compat=[0];
|
||||||
|
|
||||||
|
let mut input_version=0;
|
||||||
|
while input_version<compat.len(){
|
||||||
|
// compatible version must be greater that or equal to the input version
|
||||||
|
assert!(input_version as u32<=compat[input_version]);
|
||||||
|
// compatible version must be a version that exists
|
||||||
|
assert!(compat[input_version]<=VERSION.0);
|
||||||
|
input_version+=1;
|
||||||
|
}
|
||||||
|
compat
|
||||||
|
};
|
||||||
|
pub enum PhysicsVersionError{
|
||||||
|
UnknownPhysicsVersion,
|
||||||
|
}
|
||||||
|
pub const fn get_latest_compatible_version(PhysicsVersion(version):PhysicsVersion)->Result<PhysicsVersion,PhysicsVersionError>{
|
||||||
|
if (version as usize)<LATEST_COMPATIBLE_VERSION.len(){
|
||||||
|
Ok(PhysicsVersion(LATEST_COMPATIBLE_VERSION[version as usize]))
|
||||||
|
}else{
|
||||||
|
Err(PhysicsVersionError::UnknownPhysicsVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Body=crate::body::Body<TimeInner>;
|
pub type Body=crate::body::Body<TimeInner>;
|
||||||
type MouseState=strafesnet_common::mouse::MouseState<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,OtherInstruction,MouseInstruction,ModeInstruction,OtherOtherInstruction,SetControlInstruction};
|
use strafesnet_common::physics::{Instruction,MouseInstruction,ModeInstruction,MiscInstruction,SetControlInstruction};
|
||||||
|
|
||||||
//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
|
||||||
@@ -39,9 +78,6 @@ pub struct InputState{
|
|||||||
controls:strafesnet_common::controls_bitflag::Controls,
|
controls:strafesnet_common::controls_bitflag::Controls,
|
||||||
}
|
}
|
||||||
impl InputState{
|
impl InputState{
|
||||||
pub const fn get_next_mouse(&self)->&MouseState{
|
|
||||||
&self.next_mouse
|
|
||||||
}
|
|
||||||
fn set_next_mouse(&mut self,next_mouse:MouseState){
|
fn set_next_mouse(&mut self,next_mouse:MouseState){
|
||||||
//I like your functions magic language
|
//I like your functions magic language
|
||||||
self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse);
|
self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse);
|
||||||
@@ -693,18 +729,18 @@ struct IntersectModel{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
|
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
|
||||||
struct ContactCollision{
|
pub struct ContactCollision{
|
||||||
face_id:model_physics::MinkowskiFace,
|
face_id:model_physics::MinkowskiFace,
|
||||||
model_id:ContactModelId,
|
model_id:ContactModelId,
|
||||||
submesh_id:PhysicsSubmeshId,
|
submesh_id:PhysicsSubmeshId,
|
||||||
}
|
}
|
||||||
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
|
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
|
||||||
struct IntersectCollision{
|
pub struct IntersectCollision{
|
||||||
model_id:IntersectModelId,
|
model_id:IntersectModelId,
|
||||||
submesh_id:PhysicsSubmeshId,
|
submesh_id:PhysicsSubmeshId,
|
||||||
}
|
}
|
||||||
#[derive(Debug,Clone,Eq,Hash,PartialEq)]
|
#[derive(Debug,Clone,Eq,Hash,PartialEq)]
|
||||||
enum Collision{
|
pub enum Collision{
|
||||||
Contact(ContactCollision),
|
Contact(ContactCollision),
|
||||||
Intersect(IntersectCollision),
|
Intersect(IntersectCollision),
|
||||||
}
|
}
|
||||||
@@ -779,13 +815,14 @@ impl TouchingState{
|
|||||||
}).collect();
|
}).collect();
|
||||||
*acceleration=crate::push_solve::push_solve(&contacts,*acceleration);
|
*acceleration=crate::push_solve::push_solve(&contacts,*acceleration);
|
||||||
}
|
}
|
||||||
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
|
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){
|
||||||
let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
|
// let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
|
||||||
|
let relative_body=body;
|
||||||
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);
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
||||||
collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{
|
collector.collect(minkowski.predict_collision_face_out(&relative_body,start_time,collector.time(),contact.face_id).map(|(_face,time)|{
|
||||||
TimedInstruction{
|
TimedInstruction{
|
||||||
time:relative_body.time+time.into(),
|
time:relative_body.time+time.into(),
|
||||||
instruction:InternalInstruction::CollisionEnd(
|
instruction:InternalInstruction::CollisionEnd(
|
||||||
@@ -799,7 +836,7 @@ impl TouchingState{
|
|||||||
//detect model collision in reverse
|
//detect model collision in reverse
|
||||||
let model_mesh=models.intersect_mesh(intersect);
|
let model_mesh=models.intersect_mesh(intersect);
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
||||||
collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{
|
collector.collect(minkowski.predict_collision_out(&relative_body,start_time,collector.time()).map(|(_face,time)|{
|
||||||
TimedInstruction{
|
TimedInstruction{
|
||||||
time:relative_body.time+time.into(),
|
time:relative_body.time+time.into(),
|
||||||
instruction:InternalInstruction::CollisionEnd(
|
instruction:InternalInstruction::CollisionEnd(
|
||||||
@@ -832,17 +869,7 @@ pub struct PhysicsState{
|
|||||||
//a start zone. If you change mode, a new run is created.
|
//a start zone. If you change mode, a new run is created.
|
||||||
run:run::Run,
|
run:run::Run,
|
||||||
}
|
}
|
||||||
//random collection of contextual data that doesn't belong in PhysicsState
|
|
||||||
pub struct PhysicsData{
|
|
||||||
//permanent map data
|
|
||||||
bvh:bvh::BvhNode<ConvexMeshId>,
|
|
||||||
//transient map/environment data (open world loads/unloads parts of this data)
|
|
||||||
models:PhysicsModels,
|
|
||||||
//semi-transient data
|
|
||||||
modes:gameplay_modes::Modes,
|
|
||||||
//cached calculations
|
|
||||||
hitbox_mesh:HitboxMesh,
|
|
||||||
}
|
|
||||||
impl Default for PhysicsState{
|
impl Default for PhysicsState{
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self{
|
Self{
|
||||||
@@ -859,19 +886,24 @@ impl Default for PhysicsState{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Default for PhysicsData{
|
|
||||||
fn default()->Self{
|
|
||||||
Self{
|
|
||||||
bvh:bvh::BvhNode::default(),
|
|
||||||
models:Default::default(),
|
|
||||||
modes:Default::default(),
|
|
||||||
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhysicsState{
|
impl PhysicsState{
|
||||||
fn clear(&mut self){
|
pub fn camera_body(&self)->Body{
|
||||||
|
Body{
|
||||||
|
position:self.body.position+self.style.camera_offset,
|
||||||
|
..self.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn camera(&self)->PhysicsCamera{
|
||||||
|
self.camera
|
||||||
|
}
|
||||||
|
pub const fn mode(&self)->gameplay_modes::ModeId{
|
||||||
|
self.mode_state.get_mode_id()
|
||||||
|
}
|
||||||
|
pub fn get_finish_time(&self)->Option<run::Time>{
|
||||||
|
self.run.get_finish_time()
|
||||||
|
}
|
||||||
|
pub fn clear(&mut self){
|
||||||
self.touching.clear();
|
self.touching.clear();
|
||||||
}
|
}
|
||||||
fn reset_to_default(&mut self){
|
fn reset_to_default(&mut self){
|
||||||
@@ -920,49 +952,67 @@ impl PhysicsState{
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
// shared geometry for simulations
|
||||||
#[derive(Default)]
|
pub struct PhysicsData{
|
||||||
pub struct PhysicsContext{
|
//permanent map data
|
||||||
state:PhysicsState,//this captures the entire state of the physics.
|
bvh:bvh::BvhNode<ConvexMeshId>,
|
||||||
data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
|
//transient map/environment data (open world loads/unloads parts of this data)
|
||||||
|
models:PhysicsModels,
|
||||||
|
//semi-transient data
|
||||||
|
modes:gameplay_modes::Modes,
|
||||||
|
//cached calculations
|
||||||
|
hitbox_mesh:HitboxMesh,
|
||||||
|
}
|
||||||
|
impl Default for PhysicsData{
|
||||||
|
fn default()->Self{
|
||||||
|
Self{
|
||||||
|
bvh:bvh::BvhNode::default(),
|
||||||
|
models:Default::default(),
|
||||||
|
modes:Default::default(),
|
||||||
|
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the collection of information required to run physics
|
||||||
|
pub struct PhysicsContext<'a>{
|
||||||
|
state:&'a mut PhysicsState,//this captures the entire state of the physics.
|
||||||
|
data:&'a PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
|
||||||
}
|
}
|
||||||
// the physics consumes both Instruction and PhysicsInternalInstruction,
|
// the physics consumes both Instruction and PhysicsInternalInstruction,
|
||||||
// but can only emit PhysicsInternalInstruction
|
// but can only emit PhysicsInternalInstruction
|
||||||
impl InstructionConsumer<InternalInstruction> for PhysicsContext{
|
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
|
||||||
type TimeInner=TimeInner;
|
type TimeInner=TimeInner;
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){
|
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){
|
||||||
atomic_internal_instruction(&mut self.state,&self.data,ins)
|
atomic_internal_instruction(&mut self.state,&self.data,ins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionConsumer<Instruction> for PhysicsContext{
|
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
|
||||||
type TimeInner=TimeInner;
|
type TimeInner=TimeInner;
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){
|
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){
|
||||||
atomic_input_instruction(&mut self.state,&self.data,ins)
|
atomic_input_instruction(&mut self.state,&self.data,ins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionEmitter<InternalInstruction> for PhysicsContext{
|
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
|
||||||
type TimeInner=TimeInner;
|
type TimeInner=TimeInner;
|
||||||
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
|
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
|
||||||
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
|
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
|
||||||
next_instruction_internal(&self.state,&self.data,time_limit)
|
next_instruction_internal(&self.state,&self.data,time_limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PhysicsContext{
|
impl PhysicsContext<'_>{
|
||||||
pub fn camera_body(&self)->Body{
|
pub fn run_input_instruction(
|
||||||
Body{
|
state:&mut PhysicsState,
|
||||||
position:self.state.body.position+self.state.style.camera_offset,
|
data:&PhysicsData,
|
||||||
..self.state.body
|
instruction:TimedInstruction<Instruction,TimeInner>
|
||||||
}
|
){
|
||||||
}
|
let mut context=PhysicsContext{state,data};
|
||||||
pub const fn camera(&self)->PhysicsCamera{
|
context.process_exhaustive(instruction.time);
|
||||||
self.state.camera
|
context.process_instruction(instruction);
|
||||||
}
|
|
||||||
pub const fn get_next_mouse(&self)->&MouseState{
|
|
||||||
self.state.input_state.get_next_mouse()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl PhysicsData{
|
||||||
/// use with caution, this is the only non-instruction way to mess with physics
|
/// use with caution, this is the only non-instruction way to mess with physics
|
||||||
pub fn generate_models(&mut self,map:&map::CompleteMap){
|
pub fn generate_models(&mut self,map:&map::CompleteMap){
|
||||||
self.state.clear();
|
|
||||||
let mut modes=map.modes.clone();
|
let mut modes=map.modes.clone();
|
||||||
for mode in &mut modes.modes{
|
for mode in &mut modes.modes{
|
||||||
mode.denormalize_data();
|
mode.denormalize_data();
|
||||||
@@ -1093,23 +1143,18 @@ impl PhysicsContext{
|
|||||||
(IntersectAttributesId::new(attr_id as u32),attr)
|
(IntersectAttributesId::new(attr_id as u32),attr)
|
||||||
).collect(),
|
).collect(),
|
||||||
};
|
};
|
||||||
self.data.bvh=bvh;
|
self.bvh=bvh;
|
||||||
self.data.models=models;
|
self.models=models;
|
||||||
self.data.modes=modes;
|
self.modes=modes;
|
||||||
//hitbox_mesh is unchanged
|
//hitbox_mesh is unchanged
|
||||||
println!("Physics Objects: {}",model_count);
|
println!("Physics Objects: {}",model_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<Instruction,TimeInner>){
|
|
||||||
self.process_exhaustive(instruction.time);
|
|
||||||
self.process_instruction(instruction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is the one who asks
|
//this is the one who asks
|
||||||
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
|
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,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);
|
||||||
|
|
||||||
collector.collect(state.next_move_instruction());
|
collector.collect(state.next_move_instruction());
|
||||||
|
|
||||||
@@ -1126,7 +1171,7 @@ impl PhysicsContext{
|
|||||||
//no checks are needed because of the time limits.
|
//no checks are needed because of the time limits.
|
||||||
let model_mesh=data.models.mesh(convex_mesh_id);
|
let model_mesh=data.models.mesh(convex_mesh_id);
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
|
||||||
collector.collect(minkowski.predict_collision_in(relative_body,collector.time())
|
collector.collect(minkowski.predict_collision_in(relative_body,state.time,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
|
// this must be rounded to avoid the infinite loop when hitting the start zone
|
||||||
@@ -1143,7 +1188,7 @@ impl PhysicsContext{
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
collector.instruction()
|
collector.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1742,13 +1787,13 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
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
|
||||||
//as far as these instruction are concerned (they don't care where it is)
|
//as far as these instruction are concerned (they don't care where it is)
|
||||||
Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(..)))
|
Instruction::Misc(MiscInstruction::SetSensitivity(..))
|
||||||
|Instruction::Other(OtherInstruction::Mode(_))
|
|Instruction::Mode(_)
|
||||||
|Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetZoom(..)))
|
|Instruction::SetControl(SetControlInstruction::SetZoom(..))
|
||||||
|Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::Idle))=>false,
|
|Instruction::Idle=>false,
|
||||||
//these controls only update the body if you are on the ground
|
//these controls only update the body if you are on the ground
|
||||||
Instruction::Mouse(_)
|
Instruction::Mouse(_)
|
||||||
|Instruction::Other(OtherInstruction::SetControl(_))=>{
|
|Instruction::SetControl(_)=>{
|
||||||
match &state.move_state{
|
match &state.move_state{
|
||||||
MoveState::Fly
|
MoveState::Fly
|
||||||
|MoveState::Water
|
|MoveState::Water
|
||||||
@@ -1758,7 +1803,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
//the body must be updated unconditionally
|
//the body must be updated unconditionally
|
||||||
Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::PracticeFly))=>true,
|
Instruction::Misc(MiscInstruction::PracticeFly)=>true,
|
||||||
};
|
};
|
||||||
if should_advance_body{
|
if should_advance_body{
|
||||||
state.body.advance_time(state.time);
|
state.body.advance_time(state.time);
|
||||||
@@ -1774,14 +1819,14 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
state.camera.move_mouse(m0.pos-state.input_state.mouse.pos);
|
state.camera.move_mouse(m0.pos-state.input_state.mouse.pos);
|
||||||
state.input_state.replace_mouse(m0,m1);
|
state.input_state.replace_mouse(m0,m1);
|
||||||
},
|
},
|
||||||
Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(sensitivity)))=>state.camera.sensitivity=sensitivity,
|
Instruction::Misc(MiscInstruction::SetSensitivity(sensitivity))=>state.camera.sensitivity=sensitivity,
|
||||||
Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveForward(s)))=>state.input_state.set_control(Controls::MoveForward,s),
|
Instruction::SetControl(SetControlInstruction::SetMoveForward(s))=>state.input_state.set_control(Controls::MoveForward,s),
|
||||||
Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveLeft(s)))=>state.input_state.set_control(Controls::MoveLeft,s),
|
Instruction::SetControl(SetControlInstruction::SetMoveLeft(s))=>state.input_state.set_control(Controls::MoveLeft,s),
|
||||||
Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveBack(s)))=>state.input_state.set_control(Controls::MoveBackward,s),
|
Instruction::SetControl(SetControlInstruction::SetMoveBack(s))=>state.input_state.set_control(Controls::MoveBackward,s),
|
||||||
Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveRight(s)))=>state.input_state.set_control(Controls::MoveRight,s),
|
Instruction::SetControl(SetControlInstruction::SetMoveRight(s))=>state.input_state.set_control(Controls::MoveRight,s),
|
||||||
Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveUp(s)))=>state.input_state.set_control(Controls::MoveUp,s),
|
Instruction::SetControl(SetControlInstruction::SetMoveUp(s))=>state.input_state.set_control(Controls::MoveUp,s),
|
||||||
Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveDown(s)))=>state.input_state.set_control(Controls::MoveDown,s),
|
Instruction::SetControl(SetControlInstruction::SetMoveDown(s))=>state.input_state.set_control(Controls::MoveDown,s),
|
||||||
Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetJump(s)))=>{
|
Instruction::SetControl(SetControlInstruction::SetJump(s))=>{
|
||||||
state.input_state.set_control(Controls::Jump,s);
|
state.input_state.set_control(Controls::Jump,s);
|
||||||
if let Some(walk_state)=state.move_state.get_walk_state(){
|
if let Some(walk_state)=state.move_state.get_walk_state(){
|
||||||
if let Some(jump_settings)=&state.style.jump{
|
if let Some(jump_settings)=&state.style.jump{
|
||||||
@@ -1793,18 +1838,18 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
}
|
}
|
||||||
b_refresh_walk_target=false;
|
b_refresh_walk_target=false;
|
||||||
},
|
},
|
||||||
Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetZoom(s)))=>{
|
Instruction::SetControl(SetControlInstruction::SetZoom(s))=>{
|
||||||
state.input_state.set_control(Controls::Zoom,s);
|
state.input_state.set_control(Controls::Zoom,s);
|
||||||
b_refresh_walk_target=false;
|
b_refresh_walk_target=false;
|
||||||
},
|
},
|
||||||
Instruction::Other(OtherInstruction::Mode(ModeInstruction::Reset))=>{
|
Instruction::Mode(ModeInstruction::Reset)=>{
|
||||||
//totally reset physics state
|
//totally reset physics state
|
||||||
state.reset_to_default();
|
state.reset_to_default();
|
||||||
b_refresh_walk_target=false;
|
b_refresh_walk_target=false;
|
||||||
},
|
},
|
||||||
Instruction::Other(OtherInstruction::Mode(ModeInstruction::Restart))=>{
|
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
|
||||||
//teleport to start zone
|
//teleport to mode start zone
|
||||||
let mode=data.modes.get_mode(state.mode_state.get_mode_id());
|
let mode=data.modes.get_mode(mode_id);
|
||||||
let spawn_point=mode.and_then(|mode|
|
let spawn_point=mode.and_then(|mode|
|
||||||
//TODO: spawn at the bottom of the start zone plus the hitbox size
|
//TODO: spawn at the bottom of the start zone plus the hitbox size
|
||||||
//TODO: set camera andles to face the same way as the start zone
|
//TODO: set camera andles to face the same way as the start zone
|
||||||
@@ -1818,7 +1863,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
b_refresh_walk_target=false;
|
b_refresh_walk_target=false;
|
||||||
}
|
}
|
||||||
// Spawn does not necessarily imply reset
|
// Spawn does not necessarily imply reset
|
||||||
Instruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(mode_id,stage_id)))=>{
|
Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{
|
||||||
//spawn at a particular stage
|
//spawn at a particular stage
|
||||||
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){
|
||||||
@@ -1832,7 +1877,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
}
|
}
|
||||||
b_refresh_walk_target=false;
|
b_refresh_walk_target=false;
|
||||||
},
|
},
|
||||||
Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::PracticeFly))=>{
|
Instruction::Misc(MiscInstruction::PracticeFly)=>{
|
||||||
match &state.move_state{
|
match &state.move_state{
|
||||||
MoveState::Fly=>{
|
MoveState::Fly=>{
|
||||||
state.set_move_state(data,MoveState::Air);
|
state.set_move_state(data,MoveState::Air);
|
||||||
@@ -1843,7 +1888,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
}
|
}
|
||||||
b_refresh_walk_target=false;
|
b_refresh_walk_target=false;
|
||||||
},
|
},
|
||||||
Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::Idle))=>{
|
Instruction::Idle=>{
|
||||||
//literally idle!
|
//literally idle!
|
||||||
b_refresh_walk_target=false;
|
b_refresh_walk_target=false;
|
||||||
},
|
},
|
||||||
@@ -1857,8 +1902,9 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test{
|
mod test{
|
||||||
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
|
use crate::file;
|
||||||
use crate::body::VirtualBody;
|
use crate::body::VirtualBody;
|
||||||
|
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
|
||||||
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));
|
||||||
@@ -1866,7 +1912,7 @@ mod test{
|
|||||||
let hitbox_mesh=h1.transformed_mesh();
|
let hitbox_mesh=h1.transformed_mesh();
|
||||||
let platform_mesh=h0.transformed_mesh();
|
let platform_mesh=h0.transformed_mesh();
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
|
||||||
let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10));
|
let collision=minkowski.predict_collision_in(&relative_body,Time::ZERO,Time::from_secs(10));
|
||||||
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
|
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
|
||||||
}
|
}
|
||||||
fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){
|
fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){
|
||||||
@@ -1884,7 +1930,7 @@ mod test{
|
|||||||
let hitbox_mesh=h1.transformed_mesh();
|
let hitbox_mesh=h1.transformed_mesh();
|
||||||
let platform_mesh=h0.transformed_mesh();
|
let platform_mesh=h0.transformed_mesh();
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
|
||||||
let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10));
|
let collision=minkowski.predict_collision_in(&relative_body,Time::ZERO,Time::from_secs(10));
|
||||||
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
|
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
|
||||||
}
|
}
|
||||||
fn test_collision(relative_body:Body,expected_collision_time:Option<Time>){
|
fn test_collision(relative_body:Body,expected_collision_time:Option<Time>){
|
||||||
@@ -2071,4 +2117,197 @@ mod test{
|
|||||||
Time::ZERO
|
Time::ZERO
|
||||||
),None);
|
),None);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn run_replay(){
|
||||||
|
println!("loading map file..");
|
||||||
|
let map=file::load("../tools/bhop_maps/5692113331.snfm");
|
||||||
|
println!("loading bot file..");
|
||||||
|
let bot=file::load("../tools/replays/534s+997497968ns.snfb");
|
||||||
|
if let (Ok(file::LoadFormat::Map(map)),Ok(file::LoadFormat::Bot(bot)))=(map,bot){
|
||||||
|
// create recording
|
||||||
|
let mut physics_data=PhysicsData::default();
|
||||||
|
println!("generating models..");
|
||||||
|
physics_data.generate_models(&map);
|
||||||
|
println!("simulating...");
|
||||||
|
let mut physics=PhysicsState::default();
|
||||||
|
for ins in bot.instructions{
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics,&physics_data,ins);
|
||||||
|
}
|
||||||
|
match physics.get_finish_time(){
|
||||||
|
Some(time)=>println!("finish time:{}",time),
|
||||||
|
None=>println!("simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
panic!("missing files");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum DeterminismResult{
|
||||||
|
Deterministic,
|
||||||
|
NonDeterministic,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DetErr{
|
||||||
|
Load(file::LoadError),
|
||||||
|
IO(std::io::Error),
|
||||||
|
}
|
||||||
|
impl From<file::LoadError> for DetErr{
|
||||||
|
fn from(value:file::LoadError)->Self{
|
||||||
|
Self::Load(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<std::io::Error> for DetErr{
|
||||||
|
fn from(value:std::io::Error)->Self{
|
||||||
|
Self::IO(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn run_bot_on_map(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->DeterminismResult{
|
||||||
|
// create default physics state
|
||||||
|
let mut physics_deterministic=PhysicsState::default();
|
||||||
|
// create a second physics state
|
||||||
|
let mut physics_filtered=PhysicsState::default();
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
println!("simulating...");
|
||||||
|
|
||||||
|
for (i,ins) in bot.instructions.into_iter().enumerate(){
|
||||||
|
let state_deterministic=physics_deterministic.clone();
|
||||||
|
let state_filtered=physics_filtered.clone();
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics_deterministic,&physics_data,ins.clone());
|
||||||
|
match ins{
|
||||||
|
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
|
||||||
|
other=>{
|
||||||
|
// run
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics_filtered,&physics_data,other.clone());
|
||||||
|
// check if position matches
|
||||||
|
let b0=physics_deterministic.camera_body();
|
||||||
|
let b1=physics_filtered.camera_body();
|
||||||
|
if b0.position!=b1.position{
|
||||||
|
println!("instruction #{i}={:?}",other);
|
||||||
|
println!("desync at instruction #{}",i);
|
||||||
|
println!("deterministic state0:\n{state_deterministic:?}");
|
||||||
|
println!("filtered state0:\n{state_filtered:?}");
|
||||||
|
println!("deterministic state1:\n{:?}",physics_deterministic);
|
||||||
|
println!("filtered state1:\n{:?}",physics_filtered);
|
||||||
|
return DeterminismResult::NonDeterministic;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match physics_deterministic.get_finish_time(){
|
||||||
|
Some(time)=>println!("[with idle] finish time:{}",time),
|
||||||
|
None=>println!("[with idle] simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
match physics_filtered.get_finish_time(){
|
||||||
|
Some(time)=>println!("[filtered] finish time:{}",time),
|
||||||
|
None=>println!("[filtered] simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
DeterminismResult::Deterministic
|
||||||
|
}
|
||||||
|
type LeSend=Result<Option<DeterminismResult>,file::LoadError>;
|
||||||
|
fn do_thread<'a>(s:&'a std::thread::Scope<'a,'_>,file_path:std::path::PathBuf,send:std::sync::mpsc::Sender<LeSend>,physics_data:&'a PhysicsData){
|
||||||
|
s.spawn(move ||{
|
||||||
|
let result=match file::load(file_path.as_path()){
|
||||||
|
Ok(file::LoadFormat::Bot(bot))=>{
|
||||||
|
println!("Running {:?}",file_path.file_stem());
|
||||||
|
Ok(Some(run_bot_on_map(bot,physics_data)))
|
||||||
|
},
|
||||||
|
Ok(_)=>{
|
||||||
|
println!("Provided bot file is not a bot file!");
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Err(e)=>{
|
||||||
|
println!("Load error");
|
||||||
|
Err(e)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// send when thread is complete
|
||||||
|
send.send(result).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn get_file_path(dir_entry:std::fs::DirEntry)->Result<Option<std::path::PathBuf>,std::io::Error>{
|
||||||
|
Ok(dir_entry.file_type()?.is_file().then_some(
|
||||||
|
dir_entry.path()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_determinism()->Result<(),DetErr>{
|
||||||
|
let thread_limit=std::thread::available_parallelism().unwrap().get();
|
||||||
|
println!("loading map file..");
|
||||||
|
let file::LoadFormat::Map(map)=file::load("../tools/bhop_maps/5692113331.snfm")? else{
|
||||||
|
panic!("Provided map file is not a map file!");
|
||||||
|
};
|
||||||
|
let mut physics_data=PhysicsData::default();
|
||||||
|
println!("generating models..");
|
||||||
|
physics_data.generate_models(&map);
|
||||||
|
let (send,recv)=std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
let mut read_dir=std::fs::read_dir("../tools/replays")?;
|
||||||
|
|
||||||
|
// promise that &physics_data will outlive the spawned threads
|
||||||
|
let thread_results=std::thread::scope(|s|{
|
||||||
|
let mut thread_results=Vec::new();
|
||||||
|
|
||||||
|
// spawn threads
|
||||||
|
println!("spawning up to {thread_limit} threads...");
|
||||||
|
let mut active_thread_count=0;
|
||||||
|
for _ in 0..thread_limit{
|
||||||
|
if let Some(dir_entry_result)=read_dir.next(){
|
||||||
|
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
||||||
|
active_thread_count+=1;
|
||||||
|
do_thread(s,file_path,send.clone(),&physics_data);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawn another thread every time a message is received from the channel
|
||||||
|
println!("riding parallelism wave...");
|
||||||
|
while let Some(dir_entry_result)=read_dir.next(){
|
||||||
|
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
||||||
|
// wait for a thread to complete
|
||||||
|
thread_results.push(recv.recv().unwrap());
|
||||||
|
do_thread(s,file_path,send.clone(),&physics_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for remaining threads to complete
|
||||||
|
println!("waiting for all threads to complete...");
|
||||||
|
for _ in 0..active_thread_count{
|
||||||
|
thread_results.push(recv.recv().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("done.");
|
||||||
|
Ok::<_,DetErr>(thread_results)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// tally results
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Totals{
|
||||||
|
deterministic:u32,
|
||||||
|
nondeterministic:u32,
|
||||||
|
invalid:u32,
|
||||||
|
failed:u32,
|
||||||
|
}
|
||||||
|
let Totals{deterministic,nondeterministic,invalid,failed}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
|
||||||
|
match result{
|
||||||
|
Ok(Some(DeterminismResult::Deterministic))=>totals.deterministic+=1,
|
||||||
|
Ok(Some(DeterminismResult::NonDeterministic))=>totals.nondeterministic+=1,
|
||||||
|
Ok(None)=>totals.invalid+=1,
|
||||||
|
Err(_)=>totals.failed+=1,
|
||||||
|
}
|
||||||
|
totals
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("deterministic={deterministic}");
|
||||||
|
println!("nondeterministic={nondeterministic}");
|
||||||
|
println!("invalid={invalid}");
|
||||||
|
println!("failed={failed}");
|
||||||
|
|
||||||
|
assert!(nondeterministic==0);
|
||||||
|
assert!(invalid==0);
|
||||||
|
assert!(failed==0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
use crate::graphics_worker::Instruction as GraphicsInstruction;
|
use crate::graphics_worker::Instruction as GraphicsInstruction;
|
||||||
use crate::session::{SessionInputInstruction,Instruction as SessionInstruction,Session,Simulation};
|
use crate::session::{
|
||||||
|
Session,Simulation,SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction,
|
||||||
|
Instruction as SessionInstruction,
|
||||||
|
};
|
||||||
use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
|
use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
|
||||||
use strafesnet_common::physics::Time as PhysicsTime;
|
use strafesnet_common::physics::Time as PhysicsTime;
|
||||||
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||||
use strafesnet_common::timer::Timer;
|
use strafesnet_common::timer::Timer;
|
||||||
|
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Input(SessionInputInstruction),
|
SessionInput(SessionInputInstruction),
|
||||||
SetPaused(bool),
|
SessionControl(SessionControlInstruction),
|
||||||
|
SessionPlayback(SessionPlaybackInstruction),
|
||||||
Render,
|
Render,
|
||||||
Resize(winit::dpi::PhysicalSize<u32>),
|
Resize(winit::dpi::PhysicalSize<u32>),
|
||||||
ChangeMap(strafesnet_common::map::CompleteMap),
|
ChangeMap(strafesnet_common::map::CompleteMap),
|
||||||
|
LoadReplay(strafesnet_snf::bot::Segment),
|
||||||
}
|
}
|
||||||
|
|
||||||
const SESSION_INSTRUCTION_IDLE:SessionInstruction=SessionInstruction::Input(SessionInputInstruction::Other(strafesnet_common::physics::OtherOtherInstruction::Idle));
|
|
||||||
|
|
||||||
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,SessionTimeInner>>{
|
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{
|
||||||
let physics=crate::physics::PhysicsContext::default();
|
let physics=crate::physics::PhysicsState::default();
|
||||||
let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO);
|
let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO);
|
||||||
let simulation=Simulation::new(timer,physics);
|
let simulation=Simulation::new(timer,physics);
|
||||||
let mut session=Session::new(
|
let mut session=Session::new(
|
||||||
@@ -42,26 +45,34 @@ pub fn new<'a>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
match ins.instruction{
|
match ins.instruction{
|
||||||
Instruction::Input(unbuffered_instruction)=>{
|
Instruction::SessionInput(unbuffered_instruction)=>{
|
||||||
run_session_instruction!(ins.time,SessionInstruction::Input(unbuffered_instruction));
|
run_session_instruction!(ins.time,SessionInstruction::Input(unbuffered_instruction));
|
||||||
},
|
},
|
||||||
Instruction::SetPaused(paused)=>{
|
Instruction::SessionControl(unbuffered_instruction)=>{
|
||||||
run_session_instruction!(ins.time,SessionInstruction::SetPaused(paused));
|
run_session_instruction!(ins.time,SessionInstruction::Control(unbuffered_instruction));
|
||||||
|
},
|
||||||
|
Instruction::SessionPlayback(unbuffered_instruction)=>{
|
||||||
|
run_session_instruction!(ins.time,SessionInstruction::Playback(unbuffered_instruction));
|
||||||
},
|
},
|
||||||
Instruction::Render=>{
|
Instruction::Render=>{
|
||||||
run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE);
|
run_session_instruction!(ins.time,SessionInstruction::Idle);
|
||||||
let frame_state=session.get_frame_state(ins.time);
|
if let Some(frame_state)=session.get_frame_state(ins.time){
|
||||||
run_graphics_worker_instruction!(GraphicsInstruction::Render(frame_state));
|
run_graphics_worker_instruction!(GraphicsInstruction::Render(frame_state));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Instruction::Resize(physical_size)=>{
|
Instruction::Resize(physical_size)=>{
|
||||||
run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE);
|
run_session_instruction!(ins.time,SessionInstruction::Idle);
|
||||||
let user_settings=session.user_settings().clone();
|
let user_settings=session.user_settings().clone();
|
||||||
run_graphics_worker_instruction!(GraphicsInstruction::Resize(physical_size,user_settings));
|
run_graphics_worker_instruction!(GraphicsInstruction::Resize(physical_size,user_settings));
|
||||||
},
|
},
|
||||||
Instruction::ChangeMap(complete_map)=>{
|
Instruction::ChangeMap(complete_map)=>{
|
||||||
run_session_instruction!(ins.time,SessionInstruction::ChangeMap(&complete_map));
|
run_session_instruction!(ins.time,SessionInstruction::ChangeMap(&complete_map));
|
||||||
|
run_session_instruction!(ins.time,SessionInstruction::Input(SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId::MAIN,strafesnet_common::gameplay_modes::StageId::FIRST))));
|
||||||
run_graphics_worker_instruction!(GraphicsInstruction::ChangeMap(complete_map));
|
run_graphics_worker_instruction!(GraphicsInstruction::ChangeMap(complete_map));
|
||||||
},
|
},
|
||||||
|
Instruction::LoadReplay(bot)=>{
|
||||||
|
run_session_instruction!(ins.time,SessionInstruction::LoadReplay(bot));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use strafesnet_common::gameplay_modes::{ModeId,StageId};
|
use strafesnet_common::gameplay_modes::{ModeId,StageId};
|
||||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
|
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
|
||||||
// session represents the non-hardware state of the client.
|
// session represents the non-hardware state of the client.
|
||||||
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
|
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
|
||||||
use strafesnet_common::physics::{
|
use strafesnet_common::physics::{
|
||||||
ModeInstruction,OtherInstruction,OtherOtherInstruction,
|
ModeInstruction,MiscInstruction,
|
||||||
Instruction as PhysicsInputInstruction,
|
Instruction as PhysicsInputInstruction,
|
||||||
TimeInner as PhysicsTimeInner,
|
TimeInner as PhysicsTimeInner,
|
||||||
Time as PhysicsTime
|
Time as PhysicsTime
|
||||||
@@ -12,27 +14,46 @@ use strafesnet_common::timer::{Scaled,Timer};
|
|||||||
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
||||||
|
|
||||||
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
||||||
|
use crate::physics::{PhysicsContext,PhysicsData};
|
||||||
use crate::settings::UserSettings;
|
use crate::settings::UserSettings;
|
||||||
|
|
||||||
pub enum Instruction<'a>{
|
pub enum Instruction<'a>{
|
||||||
Input(SessionInputInstruction),
|
Input(SessionInputInstruction),
|
||||||
SetPaused(bool),
|
Control(SessionControlInstruction),
|
||||||
|
Playback(SessionPlaybackInstruction),
|
||||||
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
||||||
//Graphics(crate::graphics_worker::Instruction),
|
LoadReplay(strafesnet_snf::bot::Segment),
|
||||||
|
Idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SessionInputInstruction{
|
pub enum SessionInputInstruction{
|
||||||
Mouse(glam::IVec2),
|
Mouse(glam::IVec2),
|
||||||
SetControl(strafesnet_common::physics::SetControlInstruction),
|
SetControl(strafesnet_common::physics::SetControlInstruction),
|
||||||
Mode(ImplicitModeInstruction),
|
Mode(ImplicitModeInstruction),
|
||||||
Other(strafesnet_common::physics::OtherOtherInstruction),
|
Misc(strafesnet_common::physics::MiscInstruction),
|
||||||
}
|
}
|
||||||
/// Implicit mode instruction are fed separately to session.
|
/// Implicit mode instruction are fed separately to session.
|
||||||
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
|
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum ImplicitModeInstruction{
|
pub enum ImplicitModeInstruction{
|
||||||
ResetAndRestart,
|
ResetAndRestart,
|
||||||
ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId),
|
ResetAndSpawn(ModeId,StageId),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SessionControlInstruction{
|
||||||
|
SetPaused(bool),
|
||||||
|
// copy the current session simulation recording into a replay and view it
|
||||||
|
CopyRecordingIntoReplayAndSpectate,
|
||||||
|
StopSpectate,
|
||||||
|
SaveReplay,
|
||||||
|
LoadIntoReplayState,
|
||||||
|
}
|
||||||
|
pub enum SessionPlaybackInstruction{
|
||||||
|
SkipForward,
|
||||||
|
SkipBack,
|
||||||
|
TogglePaused,
|
||||||
|
DecreaseTimescale,
|
||||||
|
IncreaseTimescale,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FrameState{
|
pub struct FrameState{
|
||||||
@@ -43,12 +64,12 @@ pub struct FrameState{
|
|||||||
|
|
||||||
pub struct Simulation{
|
pub struct Simulation{
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
physics:crate::physics::PhysicsContext,
|
physics:crate::physics::PhysicsState,
|
||||||
}
|
}
|
||||||
impl Simulation{
|
impl Simulation{
|
||||||
pub const fn new(
|
pub const fn new(
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
physics:crate::physics::PhysicsContext,
|
physics:crate::physics::PhysicsState,
|
||||||
)->Self{
|
)->Self{
|
||||||
Self{
|
Self{
|
||||||
timer,
|
timer,
|
||||||
@@ -64,30 +85,80 @@ impl Simulation{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Recording{
|
||||||
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||||
|
}
|
||||||
|
impl Recording{
|
||||||
|
pub fn new(
|
||||||
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||||
|
)->Self{
|
||||||
|
Self{instructions}
|
||||||
|
}
|
||||||
|
fn clear(&mut self){
|
||||||
|
self.instructions.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
pub struct Replay{
|
pub struct Replay{
|
||||||
last_instruction_id:usize,
|
next_instruction_id:usize,
|
||||||
instructions:Vec<PhysicsInputInstruction>,
|
recording:Recording,
|
||||||
simulation:Simulation,
|
simulation:Simulation,
|
||||||
}
|
}
|
||||||
impl Replay{
|
impl Replay{
|
||||||
pub const fn new(
|
pub const fn new(
|
||||||
instructions:Vec<PhysicsInputInstruction>,
|
recording:Recording,
|
||||||
simulation:Simulation,
|
simulation:Simulation,
|
||||||
)->Self{
|
)->Self{
|
||||||
Self{
|
Self{
|
||||||
last_instruction_id:0,
|
next_instruction_id:0,
|
||||||
instructions,
|
recording,
|
||||||
simulation,
|
simulation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
|
||||||
|
let mut time=self.simulation.timer.time(time_limit);
|
||||||
|
loop{
|
||||||
|
if let Some(ins)=self.recording.instructions.get(self.next_instruction_id){
|
||||||
|
if ins.time<time{
|
||||||
|
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
|
||||||
|
self.next_instruction_id+=1;
|
||||||
|
}else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// loop playback
|
||||||
|
self.next_instruction_id=0;
|
||||||
|
// No need to reset physics because the very first instruction is 'Reset'
|
||||||
|
let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
|
self.simulation.timer.set_time(time_limit,new_time);
|
||||||
|
time=new_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
||||||
|
struct BotId(u32);
|
||||||
|
//#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
||||||
|
//struct PlayerId(u32);
|
||||||
|
|
||||||
|
enum ViewState{
|
||||||
|
Play,
|
||||||
|
//Spectate(PlayerId),
|
||||||
|
Replay(BotId),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Session{
|
pub struct Session{
|
||||||
user_settings:UserSettings,
|
user_settings:UserSettings,
|
||||||
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
||||||
|
view_state:ViewState,
|
||||||
//gui:GuiState
|
//gui:GuiState
|
||||||
|
geometry_shared:crate::physics::PhysicsData,
|
||||||
simulation:Simulation,
|
simulation:Simulation,
|
||||||
replays:Vec<Replay>,
|
// below fields not included in lite session
|
||||||
|
recording:Recording,
|
||||||
|
//players:HashMap<PlayerId,Simulation>,
|
||||||
|
replays:HashMap<BotId,Replay>,
|
||||||
}
|
}
|
||||||
impl Session{
|
impl Session{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -97,15 +168,27 @@ impl Session{
|
|||||||
Self{
|
Self{
|
||||||
user_settings,
|
user_settings,
|
||||||
mouse_interpolator:MouseInterpolator::new(),
|
mouse_interpolator:MouseInterpolator::new(),
|
||||||
|
geometry_shared:Default::default(),
|
||||||
simulation,
|
simulation,
|
||||||
replays:Vec::new(),
|
view_state:ViewState::Play,
|
||||||
|
recording:Default::default(),
|
||||||
|
replays:HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
fn clear_recording(&mut self){
|
||||||
self.simulation.physics.generate_models(map);
|
self.recording.clear();
|
||||||
}
|
}
|
||||||
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
|
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
||||||
self.simulation.get_frame_state(time)
|
self.simulation.physics.clear();
|
||||||
|
self.geometry_shared.generate_models(map);
|
||||||
|
}
|
||||||
|
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>Some(self.simulation.get_frame_state(time)),
|
||||||
|
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
|
||||||
|
replay.simulation.get_frame_state(time)
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn user_settings(&self)->&UserSettings{
|
pub fn user_settings(&self)->&UserSettings{
|
||||||
&self.user_settings
|
&self.user_settings
|
||||||
@@ -122,6 +205,7 @@ impl Session{
|
|||||||
impl InstructionConsumer<Instruction<'_>> for Session{
|
impl InstructionConsumer<Instruction<'_>> for Session{
|
||||||
type TimeInner=SessionTimeInner;
|
type TimeInner=SessionTimeInner;
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
|
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
|
||||||
|
// repetitive procedure macro
|
||||||
macro_rules! run_mouse_interpolator_instruction{
|
macro_rules! run_mouse_interpolator_instruction{
|
||||||
($instruction:expr)=>{
|
($instruction:expr)=>{
|
||||||
self.mouse_interpolator.process_instruction(TimedInstruction{
|
self.mouse_interpolator.process_instruction(TimedInstruction{
|
||||||
@@ -133,55 +217,210 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process any timeouts that occured since the last instruction
|
||||||
|
self.process_exhaustive(ins.time);
|
||||||
|
|
||||||
match ins.instruction{
|
match ins.instruction{
|
||||||
// send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime
|
// send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime
|
||||||
Instruction::Input(SessionInputInstruction::Mouse(pos))=>{
|
Instruction::Input(SessionInputInstruction::Mouse(pos))=>{
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos));
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos));
|
||||||
},
|
},
|
||||||
Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{
|
Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::SetControl(set_control_instruction)));
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::SetControl(set_control_instruction));
|
||||||
},
|
},
|
||||||
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
|
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
|
self.clear_recording();
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
|
let mode_id=self.simulation.physics.mode();
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Restart)));
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Restart(mode_id)));
|
||||||
},
|
},
|
||||||
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
|
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
|
self.clear_recording();
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id))));
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id)));
|
||||||
},
|
},
|
||||||
Instruction::Input(SessionInputInstruction::Other(other_other_instruction))=>{
|
Instruction::Input(SessionInputInstruction::Misc(misc_instruction))=>{
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(other_other_instruction)));
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(misc_instruction));
|
||||||
},
|
},
|
||||||
Instruction::SetPaused(paused)=>{
|
Instruction::Control(SessionControlInstruction::SetPaused(paused))=>{
|
||||||
// don't flush the buffered instructions in the mouse interpolator
|
// don't flush the buffered instructions in the mouse interpolator
|
||||||
// until the mouse is confirmed to be not moving at a later time
|
// until the mouse is confirmed to be not moving at a later time
|
||||||
// what if they pause for 5ms lmao
|
// what if they pause for 5ms lmao
|
||||||
_=self.simulation.timer.set_paused(ins.time,paused);
|
_=self.simulation.timer.set_paused(ins.time,paused);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
|
||||||
|
// Bind: B
|
||||||
|
|
||||||
|
// pause simulation
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,true);
|
||||||
|
|
||||||
|
// create recording
|
||||||
|
let mut recording=Recording::default();
|
||||||
|
recording.instructions.extend(self.recording.instructions.iter().cloned());
|
||||||
|
|
||||||
|
// create timer starting at first instruction (or zero if the list is empty)
|
||||||
|
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
|
let timer=Timer::unpaused(ins.time,new_time);
|
||||||
|
|
||||||
|
// create default physics state
|
||||||
|
let simulation=Simulation::new(timer,Default::default());
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
let bot_id=BotId(self.replays.len() as u32);
|
||||||
|
self.replays.insert(bot_id,Replay::new(
|
||||||
|
recording,
|
||||||
|
simulation,
|
||||||
|
));
|
||||||
|
|
||||||
|
// begin spectate
|
||||||
|
self.view_state=ViewState::Replay(bot_id);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::StopSpectate)=>{
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
// delete the bot, otherwise it's inaccessible and wastes CPU
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>{
|
||||||
|
self.replays.remove(&bot_id);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,false);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::SaveReplay)=>{
|
||||||
|
// Bind: N
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||||
|
let file_name=format!("replays/{}.snfb",ins.time);
|
||||||
|
std::thread::spawn(move ||{
|
||||||
|
std::fs::create_dir_all("replays").unwrap();
|
||||||
|
let file=std::fs::File::create(file_name).unwrap();
|
||||||
|
strafesnet_snf::bot::write_bot(std::io::BufWriter::new(file),crate::physics::VERSION.get(),replay.recording.instructions).unwrap();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,false);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::LoadIntoReplayState)=>{
|
||||||
|
// Bind: J
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||||
|
self.recording.instructions=replay.recording.instructions.into_iter().take(replay.next_instruction_id).collect();
|
||||||
|
self.simulation=replay.simulation;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// don't unpause -- use the replay timer state whether it is pasued or unpaused
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>{
|
||||||
|
// allow simulation timescale for fun
|
||||||
|
let scale=self.simulation.timer.get_scale();
|
||||||
|
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
||||||
|
},
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
let scale=replay.simulation.timer.get_scale();
|
||||||
|
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::DecreaseTimescale)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>{
|
||||||
|
// allow simulation timescale for fun
|
||||||
|
let scale=self.simulation.timer.get_scale();
|
||||||
|
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
||||||
|
},
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
let scale=replay.simulation.timer.get_scale();
|
||||||
|
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::SkipForward)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
||||||
|
replay.simulation.timer.set_time(ins.time,time);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::SkipBack)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
||||||
|
replay.simulation.timer.set_time(ins.time,time);
|
||||||
|
// resimulate the entire playback lol
|
||||||
|
replay.next_instruction_id=0;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
_=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused());
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Instruction::ChangeMap(complete_map)=>{
|
Instruction::ChangeMap(complete_map)=>{
|
||||||
|
self.clear_recording();
|
||||||
self.change_map(complete_map);
|
self.change_map(complete_map);
|
||||||
// ResetAndSpawn
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(ModeId::MAIN,StageId::FIRST))));
|
|
||||||
},
|
},
|
||||||
|
Instruction::LoadReplay(bot)=>{
|
||||||
|
// pause simulation
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,true);
|
||||||
|
|
||||||
|
// create recording
|
||||||
|
let recording=Recording::new(bot.instructions);
|
||||||
|
|
||||||
|
// create timer starting at first instruction (or zero if the list is empty)
|
||||||
|
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
|
let timer=Timer::unpaused(ins.time,new_time);
|
||||||
|
|
||||||
|
// create default physics state
|
||||||
|
let simulation=Simulation::new(timer,Default::default());
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
let bot_id=BotId(self.replays.len() as u32);
|
||||||
|
self.replays.insert(bot_id,Replay::new(
|
||||||
|
recording,
|
||||||
|
simulation,
|
||||||
|
));
|
||||||
|
|
||||||
|
// begin spectate
|
||||||
|
self.view_state=ViewState::Replay(bot_id);
|
||||||
|
},
|
||||||
|
Instruction::Idle=>{
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
||||||
|
// this just refreshes the replays
|
||||||
|
for replay in self.replays.values_mut(){
|
||||||
|
// TODO: filter idles from recording, inject new idles in real time
|
||||||
|
replay.advance(&self.geometry_shared,ins.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// run all buffered instruction produced
|
|
||||||
|
// process all emitted output instructions
|
||||||
self.process_exhaustive(ins.time);
|
self.process_exhaustive(ins.time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionConsumer<StepInstruction> for Session{
|
impl InstructionConsumer<StepInstruction> for Session{
|
||||||
type TimeInner=SessionTimeInner;
|
type TimeInner=SessionTimeInner;
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
|
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
|
||||||
// ins.time ignored???
|
let time=self.simulation.timer.time(ins.time);
|
||||||
let ins_retimed=TimedInstruction{
|
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
|
||||||
time:self.simulation.timer.time(ins.time),
|
//record
|
||||||
instruction:ins.instruction,
|
self.recording.instructions.push(instruction.clone());
|
||||||
};
|
PhysicsContext::run_input_instruction(&mut self.simulation.physics,&self.geometry_shared,instruction);
|
||||||
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins_retimed){
|
|
||||||
self.simulation.physics.run_input_instruction(instruction);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,15 +36,10 @@ fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Resul
|
|||||||
event_loop.create_window(attr)
|
event_loop.create_window(attr)
|
||||||
}
|
}
|
||||||
fn create_instance()->SetupContextPartial1{
|
fn create_instance()->SetupContextPartial1{
|
||||||
let backends=wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all);
|
let backends=wgpu::Backends::from_env().unwrap_or_default();
|
||||||
let dx12_shader_compiler=wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default();
|
|
||||||
SetupContextPartial1{
|
SetupContextPartial1{
|
||||||
backends,
|
backends,
|
||||||
instance:wgpu::Instance::new(wgpu::InstanceDescriptor{
|
instance:Default::default(),
|
||||||
backends,
|
|
||||||
dx12_shader_compiler,
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetupContextPartial1{
|
impl SetupContextPartial1{
|
||||||
@@ -220,7 +215,7 @@ pub fn setup_and_start(title:&str){
|
|||||||
setup_context,
|
setup_context,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(arg)=std::env::args().nth(1){
|
for arg in std::env::args().skip(1){
|
||||||
let path=std::path::PathBuf::from(arg);
|
let path=std::path::PathBuf::from(arg);
|
||||||
window_thread.send(TimedInstruction{
|
window_thread.send(TimedInstruction{
|
||||||
time:integer::Time::ZERO,
|
time:integer::Time::ZERO,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use strafesnet_common::instruction::TimedInstruction;
|
use strafesnet_common::instruction::TimedInstruction;
|
||||||
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||||
use strafesnet_common::physics::{OtherInstruction,OtherOtherInstruction,SetControlInstruction};
|
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
|
||||||
|
use crate::file::LoadFormat;
|
||||||
use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
|
use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
|
||||||
use crate::session::SessionInputInstruction;
|
use crate::session::{SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction};
|
||||||
|
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Resize(winit::dpi::PhysicalSize<u32>),
|
Resize(winit::dpi::PhysicalSize<u32>),
|
||||||
@@ -29,15 +30,16 @@ impl WindowContext<'_>{
|
|||||||
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()){
|
||||||
Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(),
|
Ok(LoadFormat::Map(map))=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(),
|
||||||
Err(e)=>println!("Failed to load map: {e}"),
|
Ok(LoadFormat::Bot(bot))=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::LoadReplay(bot)}).unwrap(),
|
||||||
|
Err(e)=>println!("Failed to load file: {e}"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
winit::event::WindowEvent::Focused(state)=>{
|
winit::event::WindowEvent::Focused(state)=>{
|
||||||
//pause unpause
|
//pause unpause
|
||||||
self.physics_thread.send(TimedInstruction{
|
self.physics_thread.send(TimedInstruction{
|
||||||
time,
|
time,
|
||||||
instruction:PhysicsWorkerInstruction::SetPaused(!state),
|
instruction:PhysicsWorkerInstruction::SessionControl(SessionControlInstruction::SetPaused(!state)),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
//recalculate pressed keys on focus
|
//recalculate pressed keys on focus
|
||||||
},
|
},
|
||||||
@@ -92,29 +94,76 @@ impl WindowContext<'_>{
|
|||||||
},
|
},
|
||||||
(keycode,state)=>{
|
(keycode,state)=>{
|
||||||
let s=state.is_pressed();
|
let s=state.is_pressed();
|
||||||
if let Some(session_input_instruction)=match keycode{
|
|
||||||
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(s))),
|
// internal variants for this scope
|
||||||
|
enum SessionInstructionSubset{
|
||||||
|
Input(SessionInputInstruction),
|
||||||
|
Control(SessionControlInstruction),
|
||||||
|
Playback(SessionPlaybackInstruction),
|
||||||
|
}
|
||||||
|
macro_rules! input_ctrl{
|
||||||
|
($variant:ident,$state:expr)=>{
|
||||||
|
Some(SessionInstructionSubset::Input(SessionInputInstruction::SetControl(SetControlInstruction::$variant($state))))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! input_misc{
|
||||||
|
($variant:ident,$state:expr)=>{
|
||||||
|
s.then_some(SessionInstructionSubset::Input(SessionInputInstruction::Misc(MiscInstruction::$variant)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! session_ctrl{
|
||||||
|
($variant:ident,$state:expr)=>{
|
||||||
|
s.then_some(SessionInstructionSubset::Control(SessionControlInstruction::$variant))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! session_playback{
|
||||||
|
($variant:ident,$state:expr)=>{
|
||||||
|
s.then_some(SessionInstructionSubset::Playback(SessionPlaybackInstruction::$variant))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
impl From<SessionInstructionSubset> for PhysicsWorkerInstruction{
|
||||||
|
fn from(value:SessionInstructionSubset)->Self{
|
||||||
|
match value{
|
||||||
|
SessionInstructionSubset::Input(session_input_instruction)=>PhysicsWorkerInstruction::SessionInput(session_input_instruction),
|
||||||
|
SessionInstructionSubset::Control(session_control_instruction)=>PhysicsWorkerInstruction::SessionControl(session_control_instruction),
|
||||||
|
SessionInstructionSubset::Playback(session_playback_instruction)=>PhysicsWorkerInstruction::SessionPlayback(session_playback_instruction),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(session_instruction)=match keycode{
|
||||||
|
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>input_ctrl!(SetJump,s),
|
||||||
|
// TODO: bind system so playback pausing can use spacebar
|
||||||
|
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Enter)=>session_playback!(TogglePaused,s),
|
||||||
|
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowUp)=>session_playback!(IncreaseTimescale,s),
|
||||||
|
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowDown)=>session_playback!(DecreaseTimescale,s),
|
||||||
|
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowLeft)=>session_playback!(SkipBack,s),
|
||||||
|
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowRight)=>session_playback!(SkipForward,s),
|
||||||
winit::keyboard::Key::Character(key)=>match key.as_str(){
|
winit::keyboard::Key::Character(key)=>match key.as_str(){
|
||||||
"W"|"w"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveForward(s))),
|
"W"|"w"=>input_ctrl!(SetMoveForward,s),
|
||||||
"A"|"a"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveLeft(s))),
|
"A"|"a"=>input_ctrl!(SetMoveLeft,s),
|
||||||
"S"|"s"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveBack(s))),
|
"S"|"s"=>input_ctrl!(SetMoveBack,s),
|
||||||
"D"|"d"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveRight(s))),
|
"D"|"d"=>input_ctrl!(SetMoveRight,s),
|
||||||
"E"|"e"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveUp(s))),
|
"E"|"e"=>input_ctrl!(SetMoveUp,s),
|
||||||
"Q"|"q"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveDown(s))),
|
"Q"|"q"=>input_ctrl!(SetMoveDown,s),
|
||||||
"Z"|"z"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetZoom(s))),
|
"Z"|"z"=>input_ctrl!(SetZoom,s),
|
||||||
"R"|"r"=>s.then(||{
|
"R"|"r"=>s.then(||{
|
||||||
//mouse needs to be reset since the position is absolute
|
//mouse needs to be reset since the position is absolute
|
||||||
self.mouse_pos=glam::DVec2::ZERO;
|
self.mouse_pos=glam::DVec2::ZERO;
|
||||||
SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndRestart)
|
SessionInstructionSubset::Input(SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndRestart))
|
||||||
}),
|
}),
|
||||||
"F"|"f"=>s.then_some(SessionInputInstruction::Other(OtherOtherInstruction::PracticeFly)),
|
"F"|"f"=>input_misc!(PracticeFly,s),
|
||||||
|
"B"|"b"=>session_ctrl!(CopyRecordingIntoReplayAndSpectate,s),
|
||||||
|
"X"|"x"=>session_ctrl!(StopSpectate,s),
|
||||||
|
"N"|"n"=>session_ctrl!(SaveReplay,s),
|
||||||
|
"J"|"j"=>session_ctrl!(LoadIntoReplayState,s),
|
||||||
_=>None,
|
_=>None,
|
||||||
},
|
},
|
||||||
_=>None,
|
_=>None,
|
||||||
}{
|
}{
|
||||||
self.physics_thread.send(TimedInstruction{
|
self.physics_thread.send(TimedInstruction{
|
||||||
time,
|
time,
|
||||||
instruction:PhysicsWorkerInstruction::Input(session_input_instruction),
|
instruction:session_instruction.into(),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -138,7 +187,7 @@ impl WindowContext<'_>{
|
|||||||
self.mouse_pos+=glam::dvec2(delta.0,delta.1);
|
self.mouse_pos+=glam::dvec2(delta.0,delta.1);
|
||||||
self.physics_thread.send(TimedInstruction{
|
self.physics_thread.send(TimedInstruction{
|
||||||
time,
|
time,
|
||||||
instruction:PhysicsWorkerInstruction::Input(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())),
|
instruction:PhysicsWorkerInstruction::SessionInput(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
},
|
},
|
||||||
winit::event::DeviceEvent::MouseWheel {
|
winit::event::DeviceEvent::MouseWheel {
|
||||||
@@ -148,7 +197,7 @@ impl WindowContext<'_>{
|
|||||||
if false{//self.physics.style.use_scroll{
|
if false{//self.physics.style.use_scroll{
|
||||||
self.physics_thread.send(TimedInstruction{
|
self.physics_thread.send(TimedInstruction{
|
||||||
time,
|
time,
|
||||||
instruction:PhysicsWorkerInstruction::Input(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(true))),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump
|
instruction:PhysicsWorkerInstruction::SessionInput(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(true))),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
mangohud ../target/release/strafe-client bhop_maps/5692113331.snfm
|
mangohud ../target/release/strafe-client bhop_maps/5692113331.snfm "$@"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
mangohud ../target/release/strafe-client bhop_maps/5692124338.snfm
|
mangohud ../target/release/strafe-client bhop_maps/5692124338.snfm "$@"
|
||||||
|
|||||||
1
tools/replays
Symbolic link
1
tools/replays
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/run/media/quat/Files/Documents/map-files/verify-scripts/replays
|
||||||
@@ -1 +1 @@
|
|||||||
mangohud ../target/release/strafe-client "$1"
|
mangohud ../target/release/strafe-client "$@"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
mangohud ../target/release/strafe-client bhop_maps/5692152916.snfm
|
mangohud ../target/release/strafe-client bhop_maps/5692152916.snfm "$@"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
mangohud ../target/release/strafe-client surf_maps/5692145408.snfm
|
mangohud ../target/release/strafe-client surf_maps/5692145408.snfm "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user