|
|
|
@ -18,19 +18,19 @@ use gameplay::ModeState;
|
|
|
|
|
//
|
|
|
|
|
// When replaying a bot, use the exact physics version which it was recorded with.
|
|
|
|
|
//
|
|
|
|
|
// When validating a new bot, ignore the version and use the latest version,
|
|
|
|
|
// and overwrite the version in the file.
|
|
|
|
|
//
|
|
|
|
|
// Compatible physics versions should be determined
|
|
|
|
|
// empirically at development time via leaderboard resimulation.
|
|
|
|
|
// 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(1);
|
|
|
|
|
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
|
|
|
|
|
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
|
|
|
|
|
let compat=[0,1];
|
|
|
|
|
let compat=[0];
|
|
|
|
|
|
|
|
|
|
let mut input_version=0;
|
|
|
|
|
while input_version<compat.len(){
|
|
|
|
@ -71,7 +71,7 @@ pub enum InternalInstruction{
|
|
|
|
|
// Water,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone,Debug)]
|
|
|
|
|
#[derive(Clone,Debug,Default)]
|
|
|
|
|
pub struct InputState{
|
|
|
|
|
mouse:MouseState,
|
|
|
|
|
next_mouse:MouseState,
|
|
|
|
@ -79,15 +79,10 @@ pub struct InputState{
|
|
|
|
|
}
|
|
|
|
|
impl InputState{
|
|
|
|
|
fn set_next_mouse(&mut self,next_mouse:MouseState){
|
|
|
|
|
// would this be correct?
|
|
|
|
|
// if self.next_mouse.time==next_mouse.time{
|
|
|
|
|
// self.next_mouse=next_mouse;
|
|
|
|
|
// }else{
|
|
|
|
|
//I like your functions magic language
|
|
|
|
|
self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse);
|
|
|
|
|
//equivalently:
|
|
|
|
|
//(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone());
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){
|
|
|
|
|
(self.next_mouse,self.mouse)=(next_mouse,mouse);
|
|
|
|
@ -109,15 +104,6 @@ impl InputState{
|
|
|
|
|
((dm*t)/dt).as_ivec2()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl Default for InputState{
|
|
|
|
|
fn default()->Self{
|
|
|
|
|
Self{
|
|
|
|
|
mouse:MouseState{pos:Default::default(),time:Time::ZERO-Time::EPSILON*2},
|
|
|
|
|
next_mouse:MouseState{pos:Default::default(),time:Time::ZERO-Time::EPSILON},
|
|
|
|
|
controls:Default::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#[derive(Clone,Debug)]
|
|
|
|
|
enum JumpDirection{
|
|
|
|
|
Exactly(Planar64Vec3),
|
|
|
|
@ -200,17 +186,17 @@ fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCol
|
|
|
|
|
let normal=contact_normal(models,hitbox_mesh,contact);
|
|
|
|
|
let gravity=touching.base_acceleration(models,style,camera,input_state);
|
|
|
|
|
let control_dir=style.get_y_control_dir(camera,input_state.controls);
|
|
|
|
|
let target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal);
|
|
|
|
|
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity);
|
|
|
|
|
(gravity,target_velocity_clipped)
|
|
|
|
|
let mut target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal);
|
|
|
|
|
touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity);
|
|
|
|
|
(gravity,target_velocity)
|
|
|
|
|
}
|
|
|
|
|
fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){
|
|
|
|
|
let normal=contact_normal(models,hitbox_mesh,contact);
|
|
|
|
|
let gravity=touching.base_acceleration(models,style,camera,input_state);
|
|
|
|
|
let control_dir=style.get_y_control_dir(camera,input_state.controls);
|
|
|
|
|
let target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal);
|
|
|
|
|
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity);
|
|
|
|
|
(gravity,target_velocity_clipped)
|
|
|
|
|
let mut target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal);
|
|
|
|
|
touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity);
|
|
|
|
|
(gravity,target_velocity)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
@ -807,8 +793,8 @@ impl TouchingState{
|
|
|
|
|
//TODO: add water
|
|
|
|
|
a
|
|
|
|
|
}
|
|
|
|
|
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:Planar64Vec3)->Planar64Vec3{
|
|
|
|
|
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
|
|
|
|
|
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){
|
|
|
|
|
let contacts=self.contacts.iter().map(|contact|{
|
|
|
|
|
let n=contact_normal(models,hitbox_mesh,contact);
|
|
|
|
|
crate::push_solve::Contact{
|
|
|
|
|
position:vec3::ZERO,
|
|
|
|
@ -816,10 +802,10 @@ impl TouchingState{
|
|
|
|
|
normal:n,
|
|
|
|
|
}
|
|
|
|
|
}).collect();
|
|
|
|
|
crate::push_solve::push_solve(&contacts,velocity)
|
|
|
|
|
*velocity=crate::push_solve::push_solve(&contacts,*velocity);
|
|
|
|
|
}
|
|
|
|
|
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:Planar64Vec3)->Planar64Vec3{
|
|
|
|
|
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
|
|
|
|
|
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){
|
|
|
|
|
let contacts=self.contacts.iter().map(|contact|{
|
|
|
|
|
let n=contact_normal(models,hitbox_mesh,contact);
|
|
|
|
|
crate::push_solve::Contact{
|
|
|
|
|
position:vec3::ZERO,
|
|
|
|
@ -827,7 +813,7 @@ impl TouchingState{
|
|
|
|
|
normal:n,
|
|
|
|
|
}
|
|
|
|
|
}).collect();
|
|
|
|
|
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,start_time:Time){
|
|
|
|
|
// let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
|
|
|
|
@ -1294,17 +1280,19 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
|
|
|
|
|
let mut culled=false;
|
|
|
|
|
touching.contacts.retain(|contact|{
|
|
|
|
|
let n=contact_normal(models,hitbox_mesh,contact);
|
|
|
|
|
let r=(n.dot(v)>>52).is_positive();
|
|
|
|
|
let r=n.dot(v).is_positive();
|
|
|
|
|
if r{
|
|
|
|
|
culled=true;
|
|
|
|
|
println!("set_velocity_cull contact={:?}",contact);
|
|
|
|
|
}
|
|
|
|
|
!r
|
|
|
|
|
});
|
|
|
|
|
set_velocity(body,touching,models,hitbox_mesh,v);
|
|
|
|
|
culled
|
|
|
|
|
}
|
|
|
|
|
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){
|
|
|
|
|
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);;
|
|
|
|
|
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut v:Planar64Vec3){
|
|
|
|
|
touching.constrain_velocity(models,hitbox_mesh,&mut v);
|
|
|
|
|
body.velocity=v;
|
|
|
|
|
}
|
|
|
|
|
fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{
|
|
|
|
|
//This is not correct but is better than what I have
|
|
|
|
@ -1314,14 +1302,16 @@ fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&Phys
|
|
|
|
|
let r=n.dot(a).is_positive();
|
|
|
|
|
if r{
|
|
|
|
|
culled=true;
|
|
|
|
|
println!("set_acceleration_cull contact={:?}",contact);
|
|
|
|
|
}
|
|
|
|
|
!r
|
|
|
|
|
});
|
|
|
|
|
set_acceleration(body,touching,models,hitbox_mesh,a);
|
|
|
|
|
culled
|
|
|
|
|
}
|
|
|
|
|
fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3){
|
|
|
|
|
body.acceleration=touching.constrain_acceleration(models,hitbox_mesh,a);
|
|
|
|
|
fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut a:Planar64Vec3){
|
|
|
|
|
touching.constrain_acceleration(models,hitbox_mesh,&mut a);
|
|
|
|
|
body.acceleration=a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn teleport(
|
|
|
|
@ -1530,7 +1520,7 @@ fn collision_start_contact(
|
|
|
|
|
let model_id=contact.model_id.into();
|
|
|
|
|
let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id);
|
|
|
|
|
match &attr.contacting.contact_behaviour{
|
|
|
|
|
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(),
|
|
|
|
|
Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"),
|
|
|
|
|
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
|
|
|
|
|
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{
|
|
|
|
|
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1();
|
|
|
|
@ -1560,21 +1550,6 @@ fn collision_start_contact(
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
match &attr.general.trajectory{
|
|
|
|
|
Some(trajectory)=>{
|
|
|
|
|
match trajectory{
|
|
|
|
|
gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(),
|
|
|
|
|
gameplay_attributes::SetTrajectory::Height(_)=>todo!(),
|
|
|
|
|
gameplay_attributes::SetTrajectory::TargetPointTime{..}=>todo!(),
|
|
|
|
|
gameplay_attributes::SetTrajectory::TargetPointSpeed{..}=>todo!(),
|
|
|
|
|
&gameplay_attributes::SetTrajectory::Velocity(velocity)=>{
|
|
|
|
|
move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
|
|
|
|
|
},
|
|
|
|
|
gameplay_attributes::SetTrajectory::DotVelocity{..}=>todo!(),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
None=>(),
|
|
|
|
|
}
|
|
|
|
|
//I love making functions with 10 arguments to dodge the borrow checker
|
|
|
|
|
if allow_run_teleport_behaviour{
|
|
|
|
|
run_teleport_behaviour(model_id,attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
|
|
|
@ -1602,6 +1577,21 @@ fn collision_start_contact(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
match &attr.general.trajectory{
|
|
|
|
|
Some(trajectory)=>{
|
|
|
|
|
match trajectory{
|
|
|
|
|
gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(),
|
|
|
|
|
gameplay_attributes::SetTrajectory::Height(_)=>todo!(),
|
|
|
|
|
gameplay_attributes::SetTrajectory::TargetPointTime { target_point: _, time: _ }=>todo!(),
|
|
|
|
|
gameplay_attributes::SetTrajectory::TargetPointSpeed { target_point: _, speed: _, trajectory_choice: _ }=>todo!(),
|
|
|
|
|
&gameplay_attributes::SetTrajectory::Velocity(velocity)=>{
|
|
|
|
|
move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
|
|
|
|
|
},
|
|
|
|
|
gameplay_attributes::SetTrajectory::DotVelocity { direction: _, dot: _ }=>todo!(),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
None=>(),
|
|
|
|
|
}
|
|
|
|
|
//doing enum to set the acceleration when surfing
|
|
|
|
|
//doing input_and_body to refresh the walk state if you hit a wall while accelerating
|
|
|
|
|
move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
|
|
|
@ -1772,19 +1762,19 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
|
|
|
|
|MoveState::Fly
|
|
|
|
|
=>println!("ReachWalkTargetVelocity fired for non-walking MoveState"),
|
|
|
|
|
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{
|
|
|
|
|
//velocity is already handled by advance_time
|
|
|
|
|
//we know that the acceleration is precisely zero because the walk target is known to be reachable
|
|
|
|
|
//which means that gravity can be fully cancelled
|
|
|
|
|
//ignore moving platforms for now
|
|
|
|
|
let target=core::mem::replace(&mut walk_state.target,TransientAcceleration::Reached);
|
|
|
|
|
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
|
|
|
|
|
// check what the target was to see if it was invalid
|
|
|
|
|
match target{
|
|
|
|
|
match &walk_state.target{
|
|
|
|
|
//you are not supposed to reach a walk target which is already reached!
|
|
|
|
|
TransientAcceleration::Reached=>println!("Invalid walk target: Reached"),
|
|
|
|
|
TransientAcceleration::Reachable{..}=>(),
|
|
|
|
|
TransientAcceleration::Reached=>unreachable!(),
|
|
|
|
|
TransientAcceleration::Reachable{acceleration:_,time:_}=>{
|
|
|
|
|
//velocity is already handled by advance_time
|
|
|
|
|
//we know that the acceleration is precisely zero because the walk target is known to be reachable
|
|
|
|
|
//which means that gravity can be fully cancelled
|
|
|
|
|
//ignore moving platforms for now
|
|
|
|
|
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
|
|
|
|
|
walk_state.target=TransientAcceleration::Reached;
|
|
|
|
|
},
|
|
|
|
|
//you are not supposed to reach an unreachable walk target!
|
|
|
|
|
TransientAcceleration::Unreachable{..}=>println!("Invalid walk target: Unreachable"),
|
|
|
|
|
TransientAcceleration::Unreachable{acceleration:_}=>unreachable!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -1843,7 +1833,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|
|
|
|
let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
|
|
|
|
|
let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref();
|
|
|
|
|
let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
|
|
|
|
|
state.cull_velocity(data,jumped_velocity);
|
|
|
|
|
state.cull_velocity(&data,jumped_velocity);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
b_refresh_walk_target=false;
|
|
|
|
@ -2155,23 +2145,22 @@ mod test{
|
|
|
|
|
Deterministic,
|
|
|
|
|
NonDeterministic,
|
|
|
|
|
}
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
enum ReplayError{
|
|
|
|
|
enum DetErr{
|
|
|
|
|
Load(file::LoadError),
|
|
|
|
|
IO(std::io::Error),
|
|
|
|
|
}
|
|
|
|
|
impl From<file::LoadError> for ReplayError{
|
|
|
|
|
impl From<file::LoadError> for DetErr{
|
|
|
|
|
fn from(value:file::LoadError)->Self{
|
|
|
|
|
Self::Load(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl From<std::io::Error> for ReplayError{
|
|
|
|
|
impl From<std::io::Error> for DetErr{
|
|
|
|
|
fn from(value:std::io::Error)->Self{
|
|
|
|
|
Self::IO(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->DeterminismResult{
|
|
|
|
|
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
|
|
|
|
@ -2180,8 +2169,6 @@ mod test{
|
|
|
|
|
// invent a new bot id and insert the replay
|
|
|
|
|
println!("simulating...");
|
|
|
|
|
|
|
|
|
|
let mut non_idle_count=0;
|
|
|
|
|
|
|
|
|
|
for (i,ins) in bot.instructions.into_iter().enumerate(){
|
|
|
|
|
let state_deterministic=physics_deterministic.clone();
|
|
|
|
|
let state_filtered=physics_filtered.clone();
|
|
|
|
@ -2189,16 +2176,14 @@ mod test{
|
|
|
|
|
match ins{
|
|
|
|
|
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
|
|
|
|
|
other=>{
|
|
|
|
|
non_idle_count+=1;
|
|
|
|
|
// 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!("desync at instruction #{}",i);
|
|
|
|
|
println!("non idle instructions completed={non_idle_count}");
|
|
|
|
|
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);
|
|
|
|
@ -2218,13 +2203,13 @@ mod test{
|
|
|
|
|
}
|
|
|
|
|
DeterminismResult::Deterministic
|
|
|
|
|
}
|
|
|
|
|
type ThreadResult=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<ThreadResult>,physics_data:&'a PhysicsData){
|
|
|
|
|
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(segment_determinism(bot,physics_data)))
|
|
|
|
|
Ok(Some(run_bot_on_map(bot,physics_data)))
|
|
|
|
|
},
|
|
|
|
|
Ok(_)=>{
|
|
|
|
|
println!("Provided bot file is not a bot file!");
|
|
|
|
@ -2245,8 +2230,8 @@ mod test{
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_determinism()->Result<(),ReplayError>{
|
|
|
|
|
let thread_limit=std::thread::available_parallelism()?.get();
|
|
|
|
|
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!");
|
|
|
|
@ -2265,7 +2250,7 @@ mod test{
|
|
|
|
|
// spawn threads
|
|
|
|
|
println!("spawning up to {thread_limit} threads...");
|
|
|
|
|
let mut active_thread_count=0;
|
|
|
|
|
while active_thread_count<thread_limit{
|
|
|
|
|
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;
|
|
|
|
@ -2293,7 +2278,7 @@ mod test{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!("done.");
|
|
|
|
|
Ok::<_,ReplayError>(thread_results)
|
|
|
|
|
Ok::<_,DetErr>(thread_results)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// tally results
|
|
|
|
@ -2302,14 +2287,14 @@ mod test{
|
|
|
|
|
deterministic:u32,
|
|
|
|
|
nondeterministic:u32,
|
|
|
|
|
invalid:u32,
|
|
|
|
|
error:u32,
|
|
|
|
|
failed:u32,
|
|
|
|
|
}
|
|
|
|
|
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
|
|
|
|
|
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.error+=1,
|
|
|
|
|
Err(_)=>totals.failed+=1,
|
|
|
|
|
}
|
|
|
|
|
totals
|
|
|
|
|
});
|
|
|
|
@ -2317,11 +2302,11 @@ mod test{
|
|
|
|
|
println!("deterministic={deterministic}");
|
|
|
|
|
println!("nondeterministic={nondeterministic}");
|
|
|
|
|
println!("invalid={invalid}");
|
|
|
|
|
println!("error={error}");
|
|
|
|
|
println!("failed={failed}");
|
|
|
|
|
|
|
|
|
|
assert!(nondeterministic==0);
|
|
|
|
|
assert!(invalid==0);
|
|
|
|
|
assert!(error==0);
|
|
|
|
|
assert!(failed==0);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|