Files
roblox-bot-player/wasm-module/src/lib.rs
2026-03-13 10:56:04 -07:00

236 lines
7.0 KiB
Rust

use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsError;
use strafesnet_roblox_bot_file::v0;
use strafesnet_roblox_bot_player::{bot,bvh,head,time,graphics};
use strafesnet_graphics::{setup,surface};
// Hack to keep the code compiling,
// SurfaceTarget::Canvas is not available in IDE for whatever reason.
struct ToSurfaceTarget(web_sys::HtmlCanvasElement);
impl From<ToSurfaceTarget> for wgpu::SurfaceTarget<'static>{
fn from(ToSurfaceTarget(canvas):ToSurfaceTarget)->Self{
#[cfg(target_arch = "wasm32")]
let target=wgpu::SurfaceTarget::Canvas(canvas);
#[expect(unused)]
#[cfg(not(target_arch = "wasm32"))]
let target=panic!("{canvas:?}");
#[allow(unused)]
target
}
}
#[wasm_bindgen]
pub struct Graphics{
graphics:graphics::Graphics,
surface:surface::Surface<'static>,
device:wgpu::Device,
queue:wgpu::Queue,
}
#[wasm_bindgen]
pub async fn setup_graphics(canvas:web_sys::HtmlCanvasElement)->Result<Graphics,JsError>{
let size=glam::uvec2(canvas.width(),canvas.height());
let instance_desc=wgpu::InstanceDescriptor::from_env_or_default();
let instance=wgpu::util::new_instance_with_webgpu_detection(&instance_desc).await;
let surface=setup::step2::create_surface(&instance,ToSurfaceTarget(canvas)).map_err(|e|JsError::new(&e.to_string()))?;
let adapter=instance.request_adapter(&wgpu::RequestAdapterOptions{
power_preference:wgpu::PowerPreference::HighPerformance,
force_fallback_adapter:false,
compatible_surface:Some(&surface),
}).await.map_err(|e|JsError::new(&e.to_string()))?;
let (device,queue)=setup::step4::request_device(&adapter).await.map_err(|e|JsError::new(&e.to_string()))?;
let surface=setup::step5::configure_surface(&adapter,&device,surface,(size.x,size.y)).map_err(|e|JsError::new(&e.to_string()))?;
Ok(Graphics{
graphics:graphics::Graphics::new(&device,&queue,size,surface.view_format()),
surface,
device,
queue,
})
}
#[wasm_bindgen]
impl Graphics{
#[wasm_bindgen]
pub fn render(&mut self,bot:&CompleteBot,head:&PlaybackHead,time:f64){
let time=time::from_float(time).unwrap();
let (pos,angles)=head.head.get_position_angles(&bot.bot,time);
let frame=self.surface.new_frame(&self.device);
let mut encoder=self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None});
self.graphics.encode_commands(&mut encoder,frame.view(),pos,angles);
self.queue.submit([encoder.finish()]);
frame.present();
}
#[wasm_bindgen]
pub fn resize(&mut self,width:u32,height:u32,fov_slope_x:f32,fov_slope_y:f32){
let size=[width,height].into();
self.surface.configure(&self.device,size);
self.graphics.resize(&self.device,size,[fov_slope_x as f32,fov_slope_y as f32].into());
}
#[wasm_bindgen]
pub fn change_map(&mut self,map:&CompleteMap){
self.graphics.change_map(&self.device,&self.queue,&map.map);
}
}
#[wasm_bindgen]
pub struct CompleteBot{
bot:bot::CompleteBot,
}
#[wasm_bindgen]
impl CompleteBot{
#[wasm_bindgen(constructor)]
pub fn new(data:&[u8])->Result<Self,JsError>{
let timelines=v0::read_all_to_block(std::io::Cursor::new(data)).map_err(|e|JsError::new(&e.to_string()))?;
Ok(Self{
bot:bot::CompleteBot::new(timelines),
})
}
#[wasm_bindgen]
pub fn duration(&self)->f64{
self.bot.duration().into()
}
#[wasm_bindgen]
pub fn run_duration(&self,mode_id:u32)->Option<f64>{
let mode=v0::ModeID(mode_id);
Some(self.bot.run_duration(mode)?.into())
}
}
#[wasm_bindgen]
pub struct CompleteMap{
map:strafesnet_common::map::CompleteMap,
}
#[wasm_bindgen]
impl CompleteMap{
#[wasm_bindgen(constructor)]
pub fn new(data:&[u8])->Result<Self,JsError>{
let map=strafesnet_snf::read_map(std::io::Cursor::new(data))
.map_err(|e|JsError::new(&e.to_string()))?
.into_complete_map()
.map_err(|e|JsError::new(&e.to_string()))?;
Ok(Self{
map,
})
}
}
#[wasm_bindgen]
pub struct PlaybackHead{
head:head::PlaybackHead,
}
#[wasm_bindgen]
impl PlaybackHead{
#[wasm_bindgen(constructor)]
pub fn new(bot:&CompleteBot,time:f64)->Self{
let time=time::from_float(time).unwrap();
Self{
head:head::PlaybackHead::new(&bot.bot,time),
}
}
#[wasm_bindgen]
pub fn advance_time(&mut self,bot:&CompleteBot,time:f64){
let time=time::from_float(time).unwrap();
self.head.advance_time(&bot.bot,time);
}
#[wasm_bindgen]
pub fn set_paused(&mut self,time:f64,paused:bool){
let time=time::from_float(time).unwrap();
self.head.set_paused(time,paused);
}
#[wasm_bindgen]
pub fn get_scale(&mut self)->f64{
let scale=self.head.get_scale();
scale.num() as f64/scale.den() as f64
}
#[wasm_bindgen]
pub fn set_scale(&mut self,time:f64,scale:f64){
let time=time::from_float(time).unwrap();
self.head.set_scale(time,scale.try_into().unwrap());
}
#[wasm_bindgen]
pub fn get_head_time(&self,time:f64)->f64{
let time=time::from_float(time).unwrap();
let time=self.head.time(time);
time.into()
}
/// Set the playback head position to new_time.
#[wasm_bindgen]
pub fn set_head_time(&mut self,bot:&CompleteBot,time:f64,new_time:f64){
let time=time::from_float(time).unwrap();
let new_time=time::from_float(new_time).unwrap();
self.head.set_time(&bot.bot,time,new_time);
}
#[wasm_bindgen]
pub fn get_run_time(&self,bot:&CompleteBot,time:f64,mode_id:u32)->Option<f64>{
let time=time::from_float(time).unwrap();
let time=bot.bot.time(self.head.time(time));
let mode=v0::ModeID(mode_id);
let run_time=self.head.state().get_run(mode)?.time(time);
Some(run_time.into())
}
#[wasm_bindgen]
pub fn is_run_in_progress(&self,mode_id:u32)->Option<bool>{
let mode=v0::ModeID(mode_id);
Some(self.head.state().get_run(mode)?.is_in_progress())
}
#[wasm_bindgen]
pub fn is_run_finished(&self,mode_id:u32)->Option<bool>{
let mode=v0::ModeID(mode_id);
Some(self.head.state().get_run(mode)?.is_finished())
}
#[wasm_bindgen]
pub fn get_fov_slope_y(&self)->f64{
self.head.state().get_fov_y()
}
#[wasm_bindgen]
pub fn get_speed(&self,bot:&CompleteBot,time:f64)->f32{
let time=time::from_float(time).unwrap();
let velocity=self.head.get_velocity(&bot.bot,time);
use glam::Vec3Swizzles;
velocity.xz().length()
}
#[wasm_bindgen]
pub fn get_game_controls(&self)->u32{
self.head.state().get_controls().bits()
}
#[wasm_bindgen]
pub fn get_position(&self,bot:&CompleteBot,time:f64)->Vector3{
let time=time::from_float(time).unwrap();
let position=self.head.get_position(&bot.bot,time);
Vector3(position)
}
/// Returns the camera angles yaw delta between the last game tick and the most recent game tick.
#[wasm_bindgen]
pub fn get_angles_yaw_delta(&self)->f32{
self.head.state().get_angles_delta().y
}
}
#[wasm_bindgen]
pub struct Vector3(glam::Vec3);
#[wasm_bindgen]
impl Vector3{
#[wasm_bindgen]
pub fn to_array(&self)->Vec<f32>{
self.0.to_array().to_vec()
}
}
#[wasm_bindgen]
pub struct Bvh{
bvh:bvh::Bvh,
}
#[wasm_bindgen]
impl Bvh{
#[wasm_bindgen(constructor)]
pub fn new(bot:&CompleteBot)->Self{
Self{
bvh:bvh::Bvh::new(&bot.bot),
}
}
#[wasm_bindgen]
pub fn closest_time_to_point(&self,bot:&CompleteBot,point:&Vector3)->Option<f64>{
Some(bot.bot.playback_time(self.bvh.closest_time_to_point(&bot.bot,point.0)?).into())
}
}