254 lines
7.7 KiB
Rust
254 lines
7.7 KiB
Rust
use std::io::Cursor;
|
|
use std::path::Path;
|
|
use std::time::Instant;
|
|
|
|
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
|
|
|
|
fn main(){
|
|
let arg=std::env::args().skip(1).next();
|
|
match arg.as_deref(){
|
|
Some("determinism")|None=>test_determinism().unwrap(),
|
|
Some("replay")=>run_replay().unwrap(),
|
|
_=>println!("invalid argument"),
|
|
}
|
|
}
|
|
|
|
#[allow(unused)]
|
|
#[derive(Debug)]
|
|
enum ReplayError{
|
|
IO(std::io::Error),
|
|
SNF(strafesnet_snf::Error),
|
|
SNFM(strafesnet_snf::map::Error),
|
|
SNFB(strafesnet_snf::bot::Error),
|
|
}
|
|
impl From<std::io::Error> for ReplayError{
|
|
fn from(value:std::io::Error)->Self{
|
|
Self::IO(value)
|
|
}
|
|
}
|
|
impl From<strafesnet_snf::Error> for ReplayError{
|
|
fn from(value:strafesnet_snf::Error)->Self{
|
|
Self::SNF(value)
|
|
}
|
|
}
|
|
impl From<strafesnet_snf::map::Error> for ReplayError{
|
|
fn from(value:strafesnet_snf::map::Error)->Self{
|
|
Self::SNFM(value)
|
|
}
|
|
}
|
|
impl From<strafesnet_snf::bot::Error> for ReplayError{
|
|
fn from(value:strafesnet_snf::bot::Error)->Self{
|
|
Self::SNFB(value)
|
|
}
|
|
}
|
|
|
|
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
|
|
let data=std::fs::read(path)?;
|
|
Ok(Cursor::new(data))
|
|
}
|
|
|
|
fn run_replay()->Result<(),ReplayError>{
|
|
println!("loading map file..");
|
|
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
|
|
|
println!("loading bot file..");
|
|
let data=read_entire_file("../tools/replays/535s+159764769ns.snfb")?;
|
|
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
|
|
|
// 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"),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
enum DeterminismResult{
|
|
Deterministic,
|
|
NonDeterministic,
|
|
}
|
|
struct SegmentResult{
|
|
determinism:DeterminismResult,
|
|
ticks:u64,
|
|
nanoseconds:u64,
|
|
}
|
|
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->SegmentResult{
|
|
// 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...");
|
|
|
|
let mut non_idle_count=0;
|
|
let instruction_count=bot.instructions.len();
|
|
|
|
let start=Instant::now();
|
|
|
|
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=>{
|
|
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{
|
|
let nanoseconds=start.elapsed().as_nanos() as u64;
|
|
println!("desync at instruction #{}",i);
|
|
println!("non idle instructions completed={non_idle_count}");
|
|
println!("instruction #{i}={:?}",other);
|
|
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 SegmentResult{
|
|
determinism:DeterminismResult::NonDeterministic,
|
|
ticks:1+i as u64+non_idle_count,
|
|
nanoseconds,
|
|
};
|
|
}
|
|
},
|
|
}
|
|
}
|
|
let nanoseconds=start.elapsed().as_nanos() as u64;
|
|
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"),
|
|
}
|
|
SegmentResult{
|
|
determinism:DeterminismResult::Deterministic,
|
|
ticks:instruction_count as u64+non_idle_count,
|
|
nanoseconds,
|
|
}
|
|
|
|
}
|
|
type ThreadResult=Result<Option<SegmentResult>,ReplayError>;
|
|
fn read_and_run(file_path:std::path::PathBuf,physics_data:&PhysicsData)->ThreadResult{
|
|
let data=read_entire_file(file_path.as_path())?;
|
|
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
|
println!("Running {:?}",file_path.file_stem());
|
|
Ok(Some(segment_determinism(bot,physics_data)))
|
|
}
|
|
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){
|
|
s.spawn(move ||{
|
|
let result=read_and_run(file_path,physics_data);
|
|
// 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()
|
|
))
|
|
}
|
|
fn test_determinism()->Result<(),ReplayError>{
|
|
let thread_limit=std::thread::available_parallelism()?.get();
|
|
println!("loading map file..");
|
|
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
|
|
|
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;
|
|
while active_thread_count<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::<_,ReplayError>(thread_results)
|
|
})?;
|
|
|
|
// tally results
|
|
#[derive(Default)]
|
|
struct Totals{
|
|
deterministic:u32,
|
|
nondeterministic:u32,
|
|
invalid:u32,
|
|
error:u32,
|
|
}
|
|
let mut nanoseconds=0;
|
|
let mut ticks=0;
|
|
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
|
|
match result{
|
|
Ok(Some(segment_result))=>{
|
|
ticks+=segment_result.ticks;
|
|
nanoseconds+=segment_result.nanoseconds;
|
|
match segment_result.determinism{
|
|
DeterminismResult::Deterministic=>totals.deterministic+=1,
|
|
DeterminismResult::NonDeterministic=>totals.nondeterministic+=1,
|
|
}
|
|
},
|
|
Ok(None)=>totals.invalid+=1,
|
|
Err(_)=>totals.error+=1,
|
|
}
|
|
totals
|
|
});
|
|
|
|
println!("deterministic={deterministic}");
|
|
println!("nondeterministic={nondeterministic}");
|
|
println!("invalid={invalid}");
|
|
println!("error={error}");
|
|
println!("average ticks/s per core: {}",ticks*1_000_000_000/nanoseconds);
|
|
|
|
assert!(nondeterministic==0);
|
|
assert!(invalid==0);
|
|
assert!(error==0);
|
|
|
|
Ok(())
|
|
}
|