Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
2ffce9d9ca
|
|||
|
8a55fbffd9
|
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
hard_tabs=true
|
||||||
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -1407,6 +1407,13 @@ version = "0.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde"
|
checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ratio_from_float"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"strafesnet_common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratio_ops"
|
name = "ratio_ops"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -1687,6 +1694,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"glam",
|
"glam",
|
||||||
"pollster",
|
"pollster",
|
||||||
|
"ratio_from_float",
|
||||||
"strafesnet_common",
|
"strafesnet_common",
|
||||||
"strafesnet_graphics",
|
"strafesnet_graphics",
|
||||||
"strafesnet_roblox_bot_file",
|
"strafesnet_roblox_bot_file",
|
||||||
@@ -1700,6 +1708,7 @@ dependencies = [
|
|||||||
name = "strafesnet_roblox_bot_player_wasm_module"
|
name = "strafesnet_roblox_bot_player_wasm_module"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ratio_from_float",
|
||||||
"strafesnet_common",
|
"strafesnet_common",
|
||||||
"strafesnet_graphics",
|
"strafesnet_graphics",
|
||||||
"strafesnet_roblox_bot_file",
|
"strafesnet_roblox_bot_file",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
members = [
|
members = [
|
||||||
"lib",
|
"lib",
|
||||||
"native-player",
|
"native-player",
|
||||||
|
"ratio_from_float",
|
||||||
"wasm-module"
|
"wasm-module"
|
||||||
]
|
]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ impl PlaybackHead{
|
|||||||
state.set_offset(offset);
|
state.set_offset(offset);
|
||||||
self.timer=Timer::from_state(state,paused);
|
self.timer=Timer::from_state(state,paused);
|
||||||
}
|
}
|
||||||
|
pub fn set_scale(&mut self,time:SessionTime,new_scale:strafesnet_common::integer::Ratio64){
|
||||||
|
self.timer.set_scale(time,new_scale);
|
||||||
|
}
|
||||||
pub fn advance_time(&mut self,bot:&CompleteBot,time:SessionTime){
|
pub fn advance_time(&mut self,bot:&CompleteBot,time:SessionTime){
|
||||||
let mut simulation_time=self.time(bot,time);
|
let mut simulation_time=self.time(bot,time);
|
||||||
let mut time_float=simulation_time.get() as f64/PhysicsTime::ONE_SECOND.get() as f64;
|
let mut time_float=simulation_time.get() as f64/PhysicsTime::ONE_SECOND.get() as f64;
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pollster = "0.4.0"
|
ratio_from_float = { version = "0.1.0", path = "../ratio_from_float" }
|
||||||
strafesnet_roblox_bot_player = { version = "0.1.0", path = "../lib" }
|
strafesnet_roblox_bot_player = { version = "0.1.0", path = "../lib" }
|
||||||
|
pollster = "0.4.0"
|
||||||
wgpu = "28.0.0"
|
wgpu = "28.0.0"
|
||||||
winit = "0.30.12"
|
winit = "0.30.12"
|
||||||
strafesnet_common.workspace = true
|
strafesnet_common.workspace = true
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub struct PlayerWorker<'a>{
|
|||||||
graphics_thread:Graphics,
|
graphics_thread:Graphics,
|
||||||
bot:Option<CompleteBot>,
|
bot:Option<CompleteBot>,
|
||||||
playback_head:PlaybackHead,
|
playback_head:PlaybackHead,
|
||||||
|
playback_speed:i8,
|
||||||
}
|
}
|
||||||
impl<'a> PlayerWorker<'a>{
|
impl<'a> PlayerWorker<'a>{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -35,6 +36,7 @@ impl<'a> PlayerWorker<'a>{
|
|||||||
graphics_thread,
|
graphics_thread,
|
||||||
bot:None,
|
bot:None,
|
||||||
playback_head,
|
playback_head,
|
||||||
|
playback_speed:0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn send(&mut self,ins:TimedInstruction<Instruction,SessionTime>){
|
pub fn send(&mut self,ins:TimedInstruction<Instruction,SessionTime>){
|
||||||
@@ -48,7 +50,16 @@ impl<'a> PlayerWorker<'a>{
|
|||||||
Instruction::SessionControl(SessionControlInstruction::SkipBack)=>{
|
Instruction::SessionControl(SessionControlInstruction::SkipBack)=>{
|
||||||
self.playback_head.seek_backward(SessionTime::from_secs(5));
|
self.playback_head.seek_backward(SessionTime::from_secs(5));
|
||||||
},
|
},
|
||||||
Instruction::SessionControl(session_control_instruction)=>{},
|
Instruction::SessionControl(SessionControlInstruction::DecreaseTimescale)=>{
|
||||||
|
self.playback_speed=self.playback_speed.saturating_sub(1).max(-48);
|
||||||
|
let speed=2.0f64.powf(self.playback_speed as f64/3.0);
|
||||||
|
self.playback_head.set_scale(ins.time,ratio_from_float::ratio_from_f64(speed).unwrap());
|
||||||
|
},
|
||||||
|
Instruction::SessionControl(SessionControlInstruction::IncreaseTimescale)=>{
|
||||||
|
self.playback_speed=self.playback_speed.saturating_add(1).min(48);
|
||||||
|
let speed=2.0f64.powf(self.playback_speed as f64/3.0);
|
||||||
|
self.playback_head.set_scale(ins.time,ratio_from_float::ratio_from_f64(speed).unwrap());
|
||||||
|
},
|
||||||
Instruction::Render=>if let Some(bot)=&self.bot{
|
Instruction::Render=>if let Some(bot)=&self.bot{
|
||||||
self.playback_head.advance_time(bot,ins.time);
|
self.playback_head.advance_time(bot,ins.time);
|
||||||
let (pos,angles)=self.playback_head.get_position_angles(bot,ins.time);
|
let (pos,angles)=self.playback_head.get_position_angles(bot,ins.time);
|
||||||
|
|||||||
7
ratio_from_float/Cargo.toml
Normal file
7
ratio_from_float/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "ratio_from_float"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
strafesnet_common.workspace = true
|
||||||
128
ratio_from_float/src/lib.rs
Normal file
128
ratio_from_float/src/lib.rs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
use strafesnet_common::integer::Ratio64;
|
||||||
|
|
||||||
|
/// Convert an `f64` to a `Ratio64`.
|
||||||
|
///
|
||||||
|
/// Returns `None` for NaN, infinities, or when the exact fraction would overflow `i64`/`u64`.
|
||||||
|
/// The result is always reduced to lowest terms.
|
||||||
|
pub fn ratio_from_f64(x: f64) -> Option<Ratio64> {
|
||||||
|
// Handle special values first
|
||||||
|
match x.classify() {
|
||||||
|
core::num::FpCategory::Nan | core::num::FpCategory::Infinite => return None,
|
||||||
|
core::num::FpCategory::Zero => return Ratio64::new(0, 1),
|
||||||
|
core::num::FpCategory::Subnormal | core::num::FpCategory::Normal => {
|
||||||
|
if x < i64::MIN as f64 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if (i64::MAX as f64) < x {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Pull out the raw bits
|
||||||
|
let bits: u64 = x.to_bits();
|
||||||
|
let sign: i64 = if (bits >> 63) != 0 { -1 } else { 1 };
|
||||||
|
let exp_raw: u32 = ((bits >> 52) & 0x7FF) as u32;
|
||||||
|
let mant: u64 = bits & 0xFFFFFFFFFFFFF; // 52 bits
|
||||||
|
|
||||||
|
// 3️⃣ Normalise exponent and mantissa
|
||||||
|
let (exp, mant) = if exp_raw == 0 {
|
||||||
|
// subnormal
|
||||||
|
(1 - 1023, mant) // unbiased exponent = -1022
|
||||||
|
} else {
|
||||||
|
// normal
|
||||||
|
((exp_raw as i32) - 1023, mant | (1 << 52)) // implicit leading 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// value = sign * mant * 2^(exp-52)
|
||||||
|
let shift = exp - 52; // may be negative
|
||||||
|
|
||||||
|
// 4️⃣ Build numerator / denominator as 64‑bit values
|
||||||
|
// ────────────────────────────────────────
|
||||||
|
// If shift is positive → numerator = mant << shift
|
||||||
|
// If shift is negative → denominator = 1 << (-shift)
|
||||||
|
// We use the checked arithmetic helpers to catch overflow.
|
||||||
|
let (mut num, den) = if shift >= 0 {
|
||||||
|
// shift <= 63 because 53‑bit mantissa * 2^shift must fit in i64
|
||||||
|
let s = shift as u32;
|
||||||
|
let n = (mant as i64).checked_shl(s)?;
|
||||||
|
(n, 1)
|
||||||
|
} else {
|
||||||
|
// shift is negative
|
||||||
|
let s = (-shift) as u32;
|
||||||
|
if s > 63 {
|
||||||
|
// 2^s would not fit in a u64 → underflow
|
||||||
|
return Ratio64::new(0, 1);
|
||||||
|
}
|
||||||
|
(mant as i64, 1u64 << s)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5️⃣ Apply the sign
|
||||||
|
num *= sign;
|
||||||
|
|
||||||
|
Ratio64::new(num, den)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic() {
|
||||||
|
let r = ratio_from_f64(1.5).unwrap();
|
||||||
|
assert_eq!(r.num(), 3);
|
||||||
|
assert_eq!(r.den(), 2);
|
||||||
|
|
||||||
|
let r = ratio_from_f64(0.1).unwrap();
|
||||||
|
// 0.1 = 3602879701896397 / 36028797018963968
|
||||||
|
assert_eq!(r.num(), 3602879701896397);
|
||||||
|
assert_eq!(r.den(), 36028797018963968);
|
||||||
|
|
||||||
|
let r = ratio_from_f64(-3.141592653589793).unwrap();
|
||||||
|
assert_eq!(r.num(), -884279719003555);
|
||||||
|
assert_eq!(r.den(), 281474976710656);
|
||||||
|
|
||||||
|
// NaN / Infinity → None
|
||||||
|
assert!(ratio_from_f64(f64::NAN).is_none());
|
||||||
|
assert!(ratio_from_f64(f64::INFINITY).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overflow() {
|
||||||
|
// value that would need > 64‑bit numerator
|
||||||
|
let f = (i64::MAX as f64) * 2.0; // just above i64::MAX
|
||||||
|
assert!(ratio_from_f64(f).is_none());
|
||||||
|
|
||||||
|
// subnormal: denominator would need 2^1074 > u64::MAX
|
||||||
|
let sub = f64::MIN_POSITIVE / 2.0; // 2.22507e‑308 / 2 = 1.1125e‑308
|
||||||
|
assert_eq!(ratio_from_f64(sub).unwrap().num(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let numbers = [
|
||||||
|
0.0,
|
||||||
|
-0.0,
|
||||||
|
1.0,
|
||||||
|
-1.0,
|
||||||
|
3.141592653589793,
|
||||||
|
1.5,
|
||||||
|
0.1,
|
||||||
|
2.225073858507201e-308, // subnormal
|
||||||
|
1.7976931348623157e308, // max normal
|
||||||
|
];
|
||||||
|
|
||||||
|
for f in numbers {
|
||||||
|
match ratio_from_f64(f) {
|
||||||
|
Some(r) => println!(
|
||||||
|
"{:>15} → {:>15} / {:>15} (≈ {:.20})",
|
||||||
|
f,
|
||||||
|
r.num(),
|
||||||
|
r.den(),
|
||||||
|
f
|
||||||
|
),
|
||||||
|
None => println!("{:>15} → overflow / NaN / infinite", f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ edition = "2024"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ratio_from_float = { version = "0.1.0", path = "../ratio_from_float" }
|
||||||
strafesnet_roblox_bot_player = { version = "0.1.0", path = "../lib" }
|
strafesnet_roblox_bot_player = { version = "0.1.0", path = "../lib" }
|
||||||
strafesnet_common.workspace = true
|
strafesnet_common.workspace = true
|
||||||
strafesnet_graphics.workspace = true
|
strafesnet_graphics.workspace = true
|
||||||
|
|||||||
Reference in New Issue
Block a user