Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3c31de9f9f
|
|||
|
513602e551
|
|||
|
b147c96240
|
|||
|
8f6b7bb8da
|
|||
|
2304f19239
|
|||
|
a2155f0864
|
|||
|
181f39ff37
|
|||
|
5f99a47cf2
|
|||
|
e44fd7cbb9
|
|||
|
60874fb769
|
|||
|
a2f1572281
|
|||
|
baf6f7e99b
|
|||
|
934be7a28a
|
|||
|
cdd2f5cd89
|
|||
|
2f76e5e63d
|
|||
|
5fa13de6cf
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/bots
|
||||
/replays
|
||||
/target
|
||||
|
||||
111
Cargo.lock
generated
111
Cargo.lock
generated
@@ -8,6 +8,12 @@ version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "binrw"
|
||||
version = "0.15.0"
|
||||
@@ -38,12 +44,21 @@ version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "bnum"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "119771309b95163ec7aaf79810da82f7cd0599c19722d48b9c03894dca833966"
|
||||
|
||||
[[package]]
|
||||
name = "bot-cruncher"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"strafesnet_common",
|
||||
"strafesnet_physics",
|
||||
"strafesnet_roblox_bot_file",
|
||||
"strafesnet_snf",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
@@ -60,6 +75,18 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "fixed_wide"
|
||||
version = "0.2.2"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "1397e01522f708e80dcf6c17db69139abb57c43212226b60b50fc09d91c607b5"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bnum",
|
||||
"paste",
|
||||
"ratio_ops",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
@@ -149,6 +176,23 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74a4d85559e2637d3d839438b5b3d75c31e655276f9544d72475c36b92fabbed"
|
||||
|
||||
[[package]]
|
||||
name = "id"
|
||||
version = "0.1.0"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "2337e7a6c273082b672e377e159d7a168fb51438461b7c4033c79a515dd7a25a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
@@ -158,6 +202,17 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linear_ops"
|
||||
version = "0.1.1"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "d6ea2e52a83eab4afe56536e6d27f8e815bd994111ccdc3e2c0aafce77014286"
|
||||
dependencies = [
|
||||
"fixed_wide",
|
||||
"paste",
|
||||
"ratio_ops",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
@@ -170,6 +225,12 @@ version = "4.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
@@ -200,6 +261,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratio_ops"
|
||||
version = "0.1.1"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "93f2dc5bfc9d878028a699e77c6f88ac59d23404218af9fcfbfc190610f49c80"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
@@ -207,16 +274,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_roblox_bot_file"
|
||||
version = "0.9.3"
|
||||
name = "strafesnet_common"
|
||||
version = "0.8.0"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "423d931e4f4f97406a86519a22172d1fc0d5b9d8c3b2d4553ae89b641bbd555c"
|
||||
checksum = "171f2b754a8c59b335578824d5465d9637fb41ec4906c6a8c1fd39206891b09c"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"fixed_wide",
|
||||
"glam",
|
||||
"id",
|
||||
"linear_ops",
|
||||
"ratio_ops",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_physics"
|
||||
version = "0.0.1"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "8bf75c5ba62556c83da56c70cc1e0f31b842fdd993646dc533ac7a844a05b926"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"glam",
|
||||
"id",
|
||||
"strafesnet_common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_roblox_bot_file"
|
||||
version = "0.8.1"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "33d0fa524476d8b6cf23269b0c9cff6334b70585546b807cb8ec193858defecd"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"bitflags",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_snf"
|
||||
version = "0.3.2"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "aab96a189e3f5c4e5eca1feae704c0d6ceaa0de37a41e29ef3a89816e354292f"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"id",
|
||||
"strafesnet_common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
|
||||
@@ -5,6 +5,9 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.31"
|
||||
strafesnet_roblox_bot_file = { version = "0.9.3", registry = "strafesnet" }
|
||||
strafesnet_common = { version = "0.8.0", registry = "strafesnet" }
|
||||
strafesnet_physics = { version = "0.0.1", registry = "strafesnet" }
|
||||
strafesnet_roblox_bot_file = { version = "0.8.1", registry = "strafesnet" }
|
||||
strafesnet_snf = { version = "0.3.2", registry = "strafesnet" }
|
||||
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "fs"] }
|
||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||
|
||||
302
src/main.rs
302
src/main.rs
@@ -1,6 +1,8 @@
|
||||
use strafesnet_roblox_bot_file::v0;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::path::{Path,PathBuf};
|
||||
use strafesnet_common::integer::{Planar64,Planar64Vec3};
|
||||
use strafesnet_physics::physics::{PhysicsData,Time};
|
||||
use strafesnet_roblox_bot_file::v0;
|
||||
use futures::{StreamExt,TryStreamExt};
|
||||
|
||||
#[expect(dead_code)]
|
||||
@@ -8,9 +10,11 @@ use futures::{StreamExt,TryStreamExt};
|
||||
enum Error{
|
||||
InvalidArgs,
|
||||
Io(std::io::Error),
|
||||
DecodeMap(strafesnet_snf::Error),
|
||||
DecodeMap2(strafesnet_snf::map::Error),
|
||||
BotFile{
|
||||
path:PathBuf,
|
||||
err:RoundTripError,
|
||||
err:SimError,
|
||||
},
|
||||
Join(tokio::task::JoinError),
|
||||
}
|
||||
@@ -24,105 +28,243 @@ impl From<tokio::task::JoinError> for Error{
|
||||
Self::Join(value)
|
||||
}
|
||||
}
|
||||
#[expect(dead_code)]
|
||||
#[derive(Debug)]
|
||||
enum RoundTripError{
|
||||
Decode(v0::Error),
|
||||
Encode(strafesnet_roblox_bot_file::BinrwError),
|
||||
RoundTripDecode(v0::Error),
|
||||
NotEqual,
|
||||
|
||||
const fn fly_time(distance:Planar64)->Time{
|
||||
Time::raw(distance.to_raw()*Time::ONE_SECOND.get()/(80*Planar64::ONE.to_raw()))
|
||||
}
|
||||
|
||||
// #[expect(dead_code)]
|
||||
#[derive(Debug)]
|
||||
enum SimError{
|
||||
Decode(v0::Error),
|
||||
}
|
||||
fn run_roblox_bot_in_strafe_client_physics(physics_data:&PhysicsData,block:&v0::Block,file_stem:std::ffi::OsString,offset:Planar64Vec3){
|
||||
use strafesnet_common::instruction::TimedInstruction;
|
||||
use strafesnet_common::mouse::MouseState;
|
||||
use strafesnet_common::physics::{Instruction,MouseInstruction,SetControlInstruction,ModeInstruction,MiscInstruction};
|
||||
use strafesnet_physics::physics::{PhysicsState,PhysicsContext};
|
||||
|
||||
fn vector3(v:&v0::Vector3)->Planar64Vec3{
|
||||
strafesnet_common::integer::vec3::try_from_f32_array([v.x,v.y,v.z]).unwrap()
|
||||
}
|
||||
|
||||
let mut input_it=block.input_events.iter();
|
||||
|
||||
let Some(input)=input_it.next()else{
|
||||
return;
|
||||
};
|
||||
let Some(output)=block.output_events.first()else{
|
||||
return;
|
||||
};
|
||||
let Some(setting)=block.setting_events.iter().find(|event|matches!(event.event.setting_type,v0::SettingType::Sensitivity))else{
|
||||
return;
|
||||
};
|
||||
|
||||
let mut recording=Vec::new();
|
||||
let mut physics=PhysicsState::default();
|
||||
let mut time=Time::ZERO;
|
||||
macro_rules! run{
|
||||
($time:expr,$ins:expr)=>{
|
||||
let ins=TimedInstruction{
|
||||
time:$time,
|
||||
instruction:$ins,
|
||||
};
|
||||
recording.push(ins.clone());
|
||||
PhysicsContext::run_input_instruction(&mut physics,physics_data,ins);
|
||||
};
|
||||
}
|
||||
|
||||
// === State Initialization ===
|
||||
|
||||
// Reset
|
||||
run!(time,Instruction::Mode(ModeInstruction::Reset));
|
||||
// generate an instruction to set the sensitivity (sens is also based on fov in rbhop, but pure in strafe client)
|
||||
let fov=1.0;//tan(90)
|
||||
let zoom=1.0;
|
||||
let pixels_to_radians=setting.event.value*fov*zoom/128.0;
|
||||
// convert radians to Angle32
|
||||
let sensitivity=(pixels_to_radians*strafesnet_common::integer::Angle32::FRAC_PI_2.get() as f64/core::f64::consts::FRAC_PI_2) as i64;
|
||||
let sensitivity_ratio=strafesnet_common::integer::Ratio64::new(sensitivity,1).unwrap();
|
||||
run!(time,Instruction::Misc(MiscInstruction::SetSensitivity(strafesnet_common::integer::Ratio64Vec2{
|
||||
x:sensitivity_ratio,
|
||||
y:sensitivity_ratio,
|
||||
})));
|
||||
run!(time,Instruction::Mode(ModeInstruction::Restart(strafesnet_common::gameplay_modes::ModeId::MAIN)));
|
||||
|
||||
// Fly!
|
||||
run!(time,Instruction::Misc(MiscInstruction::PracticeFly));
|
||||
|
||||
// NoClip!
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetSprint(true)));
|
||||
|
||||
// generate instructions to fly to the starting position
|
||||
let fly_offset=offset+vector3(&output.event.position)-physics.body().position;
|
||||
if fly_offset.x.is_negative(){
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveLeft(true)));
|
||||
time+=fly_time(-fly_offset.x);
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveLeft(false)));
|
||||
}else{
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveRight(true)));
|
||||
time+=fly_time(fly_offset.x);
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveRight(false)));
|
||||
}
|
||||
if fly_offset.y.is_negative(){
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveDown(true)));
|
||||
time+=fly_time(-fly_offset.y);
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveDown(false)));
|
||||
}else{
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveUp(true)));
|
||||
time+=fly_time(fly_offset.y);
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveUp(false)));
|
||||
}
|
||||
if fly_offset.z.is_negative(){
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveForward(true)));
|
||||
time+=fly_time(-fly_offset.z);
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveForward(false)));
|
||||
}else{
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveBack(true)));
|
||||
time+=fly_time(fly_offset.z);
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetMoveBack(false)));
|
||||
}
|
||||
|
||||
// Fly off
|
||||
run!(time,Instruction::Misc(MiscInstruction::PracticeFly));
|
||||
|
||||
// NoClip off
|
||||
run!(time,Instruction::SetControl(SetControlInstruction::SetSprint(false)));
|
||||
|
||||
// TODO: generate instructions to set up the velocity
|
||||
|
||||
let t0=Time::from_nanos((output.time*Time::ONE_SECOND.get() as f64) as i64);
|
||||
// generate an instruction that will rotate the camera to match output.angles
|
||||
let mut last_mouse=&input.event.mouse_pos;
|
||||
// generate an instruction for each pressed key
|
||||
let mut last_controls=v0::GameControls::empty();
|
||||
|
||||
// construct an iterator to detect key changes and mouse movement
|
||||
// run key changes first, then mouse movement
|
||||
let it=input_it.map(|event|{
|
||||
let time=Time::from_nanos((event.time*Time::ONE_SECOND.get() as f64) as i64)-t0+time;
|
||||
// detect controls changes
|
||||
let controls={
|
||||
let new_controls=event.event.game_controls;
|
||||
let mask=new_controls^last_controls;
|
||||
last_controls=new_controls;
|
||||
(mask,new_controls)
|
||||
};
|
||||
// detect mouse changes
|
||||
let mouse=(event.event.mouse_pos.x!=last_mouse.x||event.event.mouse_pos.y!=last_mouse.y).then(||{
|
||||
last_mouse=&event.event.mouse_pos;
|
||||
Instruction::Mouse(MouseInstruction::SetNextMouse(MouseState{
|
||||
pos:[event.event.mouse_pos.x as i32,event.event.mouse_pos.y as i32].into(),
|
||||
time,
|
||||
}))
|
||||
});
|
||||
(time,controls,mouse)
|
||||
});
|
||||
|
||||
// detect deviation from space radius and time range.
|
||||
// if the current position is not near any timeline position within +-1s, abort
|
||||
|
||||
for (time,(mask,controls),mouse) in it{
|
||||
macro_rules! run_control{
|
||||
($control:ident,$ins:ident)=>{
|
||||
if mask.contains(v0::GameControls::$control){
|
||||
let state=controls.contains(v0::GameControls::$control);
|
||||
let instruction=Instruction::SetControl(SetControlInstruction::$ins(state));
|
||||
run!(time,instruction);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
run_control!(MoveForward,SetMoveForward);
|
||||
run_control!(MoveLeft,SetMoveLeft);
|
||||
run_control!(MoveBack,SetMoveBack);
|
||||
run_control!(MoveRight,SetMoveRight);
|
||||
run_control!(MoveUp,SetMoveUp);
|
||||
run_control!(MoveDown,SetMoveDown);
|
||||
// run_control!(LookUp);
|
||||
// run_control!(LookLeft);
|
||||
// run_control!(LookDown);
|
||||
// run_control!(LookRight);
|
||||
run_control!(Jump,SetJump);
|
||||
// run_control!(Crouch);
|
||||
run_control!(Sprint,SetSprint);
|
||||
run_control!(Zoom,SetZoom);
|
||||
// run_control!(Use);
|
||||
// run_control!(Action1);
|
||||
// run_control!(Action2);
|
||||
|
||||
if let Some(instruction)=mouse{
|
||||
run!(time,instruction);
|
||||
}
|
||||
}
|
||||
// idle for 1s just in case we need to glide into the finish zone
|
||||
|
||||
// if the bot completed the map, write a .snfb file
|
||||
match physics.get_finish_time(){
|
||||
Some(time)=>println!("finish time:{}",time),
|
||||
None=>println!("simulation did not end in finished state"),
|
||||
}
|
||||
|
||||
let mut replays_path=std::env::current_dir().unwrap();
|
||||
let mut file_name=PathBuf::from(file_stem);
|
||||
file_name.set_extension("snfb");
|
||||
std::fs::create_dir_all(replays_path.as_path()).unwrap();
|
||||
replays_path.push(file_name);
|
||||
let file=std::fs::File::create(replays_path).unwrap();
|
||||
strafesnet_snf::bot::write_bot(
|
||||
std::io::BufWriter::new(file),
|
||||
strafesnet_physics::VERSION.get(),
|
||||
recording,
|
||||
).unwrap();
|
||||
println!("Finished writing bot file!");
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main()->Result<(),Error>{
|
||||
const PREFETCH_QUEUE:usize=64;
|
||||
|
||||
let Some(dir)=std::env::args().skip(1).next()else{
|
||||
let mut args=std::env::args().skip(1);
|
||||
let (Some(map),Some(dir))=(args.next(),args.next())else{
|
||||
return Err(Error::InvalidArgs);
|
||||
};
|
||||
|
||||
const ONE_SECOND:u64=1<<32;
|
||||
#[derive(Default)]
|
||||
struct FoldState{
|
||||
count:usize,
|
||||
jumps:u64,
|
||||
duration:u64,
|
||||
settings:u64,
|
||||
outliers:Vec<PathBuf>,
|
||||
}
|
||||
impl std::fmt::Display for FoldState{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"count={} jumps={} duration={:.3} settings={}",self.count,self.jumps,self.duration as f64/(ONE_SECOND as f64),self.settings)
|
||||
}
|
||||
}
|
||||
impl FoldState{
|
||||
fn accumulate(&mut self,path:&Path,block:&v0::Block){
|
||||
if let (Some(first),Some(last))=(block.output_events.first(),block.output_events.last()){
|
||||
let last=last.time*ONE_SECOND as f64;
|
||||
let first=first.time*ONE_SECOND as f64;
|
||||
let duration=last as u64-first as u64;
|
||||
if 30000*ONE_SECOND<duration{
|
||||
self.outliers.push(path.to_owned());
|
||||
return;
|
||||
}
|
||||
self.duration+=duration;
|
||||
}
|
||||
self.count+=1;
|
||||
self.jumps+=block.sound_events.iter()
|
||||
.filter(|event|event.event.sound_type==v0::SoundType::JumpGround)
|
||||
.count() as u64;
|
||||
// find settings events after run has started
|
||||
if let Some(run_start)=block.run_events.iter().find(|event|matches!(event.event,v0::RunEvent::Start(_))){
|
||||
self.settings+=block.setting_events.iter()
|
||||
.filter(|event|run_start.time<event.time)
|
||||
.count() as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("loading map file..");
|
||||
let data=tokio::fs::read(map).await?;
|
||||
let map=strafesnet_snf::read_map(std::io::Cursor::new(data))
|
||||
.map_err(Error::DecodeMap)?
|
||||
.into_complete_map()
|
||||
.map_err(Error::DecodeMap2)?;
|
||||
|
||||
// grab mapstart position
|
||||
let start_zone_model_id=map.modes.clone().denormalize().get_mode(strafesnet_common::gameplay_modes::ModeId::MAIN).unwrap().get_start();
|
||||
let offset=map.models[start_zone_model_id.get() as usize].transform.translation;
|
||||
|
||||
// create recording
|
||||
println!("generating models..");
|
||||
let physics_data:&_=Box::leak(Box::new(PhysicsData::new(&map)));
|
||||
|
||||
let available_parallelism=std::thread::available_parallelism()?.get();
|
||||
let read_dir=tokio::fs::read_dir(dir).await?;
|
||||
let tally=tokio_stream::wrappers::ReadDirStream::new(read_dir).map(|dir_entry|async move{
|
||||
tokio_stream::wrappers::ReadDirStream::new(read_dir).map(|dir_entry|async move{
|
||||
let entry=dir_entry?;
|
||||
let path=entry.path();
|
||||
let file=tokio::fs::read(path.as_path()).await?;
|
||||
Ok((path,file))
|
||||
})
|
||||
.buffer_unordered(PREFETCH_QUEUE)
|
||||
.map(|result:Result<_,Error>|async move{
|
||||
let (path,file)=result?;
|
||||
.try_for_each_concurrent(available_parallelism,|(path,file)|async move{
|
||||
let file_stem=path.file_stem().unwrap().to_owned();
|
||||
let result=tokio::task::spawn_blocking(move||{
|
||||
let block=v0::read_all_to_block(std::io::Cursor::new(file.as_slice())).map_err(RoundTripError::Decode)?;
|
||||
let mut data=Vec::with_capacity(file.len()+1024);
|
||||
v0::serialize(&block,&mut std::io::Cursor::new(&mut data)).map_err(RoundTripError::Encode)?;
|
||||
let block_rt=v0::read_all_to_block(std::io::Cursor::new(data)).map_err(RoundTripError::RoundTripDecode)?;
|
||||
if !(
|
||||
block_rt.input_events.len()==block.input_events.len()&&
|
||||
block_rt.output_events.len()==block.output_events.len()&&
|
||||
block_rt.sound_events.len()==block.sound_events.len()&&
|
||||
block_rt.world_events.len()==block.world_events.len()&&
|
||||
block_rt.gravity_events.len()==block.gravity_events.len()&&
|
||||
block_rt.run_events.len()==block.run_events.len()&&
|
||||
block_rt.camera_events.len()==block.camera_events.len()&&
|
||||
block_rt.setting_events.len()==block.setting_events.len()
|
||||
){
|
||||
return Err(RoundTripError::NotEqual);
|
||||
}
|
||||
Ok(block)
|
||||
let block=v0::read_all_to_block(std::io::Cursor::new(file.as_slice())).map_err(SimError::Decode)?;
|
||||
run_roblox_bot_in_strafe_client_physics(physics_data,&block,file_stem,offset);
|
||||
Ok(())
|
||||
}).await?;
|
||||
match result{
|
||||
Err(err)=>Err(Error::BotFile{path,err}),
|
||||
Ok(block)=>Ok((path,block)),
|
||||
Ok(())=>Ok(()),
|
||||
}
|
||||
})
|
||||
.buffer_unordered(available_parallelism)
|
||||
.try_fold(FoldState::default(),async|mut state,(path,block)|{
|
||||
state.accumulate(&path,&block);
|
||||
println!("{:?} {}",path.file_name(),state);
|
||||
Ok(state)
|
||||
}).await?;
|
||||
for path in &tally.outliers{
|
||||
println!("outlier: {:?}",path.file_name());
|
||||
}
|
||||
println!("{}",tally);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user