use alpha2_engine::{PlaybackClock, PlaybackPolicy};
use alpha2_time::FrameRate;
use wasm_bindgen::prelude::*;
use crate::web::{invalid_input, typed_error};
#[wasm_bindgen(js_name = PlaybackClock)]
pub struct WebPlaybackClock {
clock: PlaybackClock,
}
#[wasm_bindgen(js_name = ClockStep)]
pub struct WebClockStep {
tick: f64,
audible: bool,
wait_for_frame: bool,
}
#[wasm_bindgen(js_class = ClockStep)]
impl WebClockStep {
#[wasm_bindgen(getter)]
#[must_use]
pub fn tick(&self) -> f64 {
self.tick
}
#[wasm_bindgen(getter)]
#[must_use]
pub fn audible(&self) -> bool {
self.audible
}
#[wasm_bindgen(getter, js_name = waitForFrame)]
#[must_use]
pub fn wait_for_frame(&self) -> bool {
self.wait_for_frame
}
}
#[wasm_bindgen(js_class = PlaybackClock)]
impl WebPlaybackClock {
#[wasm_bindgen(constructor)]
pub fn new(
policy: &str,
frame_rate_num: u32,
frame_rate_den: u32,
rate: f64,
tick: f64,
now_ms: f64,
) -> Result<Self, JsValue> {
let policy = parse_policy(policy)?;
let frame_rate =
FrameRate::new(frame_rate_num, frame_rate_den).map_err(|error| typed_error(&error))?;
if !rate.is_finite() || rate == 0.0 {
return Err(invalid_input("playback rate must be finite and non-zero"));
}
Ok(Self {
clock: PlaybackClock::new(policy, frame_rate, rate, ticks(tick)?, now_ms),
})
}
pub fn audible(policy: &str, rate: f64) -> Result<bool, JsValue> {
Ok(parse_policy(policy)?.audible(rate))
}
pub fn step(
&mut self,
now_ms: f64,
heard: Option<f64>,
presented: bool,
) -> Result<WebClockStep, JsValue> {
let heard = heard.map(ticks).transpose()?;
let step = self.clock.step(now_ms, heard, presented);
#[allow(clippy::cast_precision_loss)]
let tick = step.tick as f64;
Ok(WebClockStep {
tick,
audible: step.audible,
wait_for_frame: step.wait_for_frame,
})
}
}
fn parse_policy(policy: &str) -> Result<PlaybackPolicy, JsValue> {
serde_json::from_value(serde_json::Value::String(policy.to_owned()))
.map_err(|_| invalid_input(format!("unknown playback policy {policy:?}")))
}
fn ticks(value: f64) -> Result<i64, JsValue> {
const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991.0;
if !value.is_finite() || value.fract() != 0.0 || value.abs() > MAX_SAFE_INTEGER {
return Err(invalid_input("ticks must be a safe integer"));
}
#[allow(clippy::cast_possible_truncation)]
Ok(value as i64)
}