hand.rs

19.4 kB · rust · 672 lines

1#![allow(dead_code)]23use mrlyrs::core::cell::Cell;4use mrlyrs::core::colors::Color;5use mrlyrs::core::error::Error;6use mrlyrs::core::rng::Rng as Stream;7use mrlyrs::core::tensor::{Dtype, Tensor};8use mrlyrs::core::Json;9use mrlyrs::math::bang::Code;10use mrlyrs::math::cell::models::{Cell2d, Cell3d, CellNd};11use mrlyrs::math::six::{Cell6d, Orientation, Projection};12use serde::de::DeserializeOwned;13use serde::Serialize;14use std::collections::HashMap;15use std::fmt::Display;16use std::hash::Hash;17use std::str::FromStr;18use wasm_bindgen::prelude::*;19use wasm_bindgen::JsCast;2021// ERRORS2223pub fn throw(error: Error) -> JsValue {24    js_sys::Error::new(&error.to_string()).into()25}2627pub fn refuse(message: &str) -> JsValue {28    js_sys::Error::new(message).into()29}3031// SERDE3233fn serializer() -> serde_wasm_bindgen::Serializer {34    serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true)35}3637pub fn to_js<T: Serialize + ?Sized>(value: &T) -> Result<JsValue, JsValue> {38    value39        .serialize(&serializer())40        .map_err(|error| refuse(&error.to_string()))41}4243pub fn from_js<T: DeserializeOwned>(value: &JsValue) -> Result<T, JsValue> {44    serde_wasm_bindgen::from_value(value.clone()).map_err(|error| refuse(&error.to_string()))45}4647pub fn json_to_js(value: &Json) -> Result<JsValue, JsValue> {48    to_js(value)49}5051pub fn json_from_js(value: &JsValue) -> Result<Json, JsValue> {52    from_js(value)53}5455pub fn plain(value: &JsValue) -> Result<JsValue, JsValue> {56    if !value.is_object() {57        return Ok(value.clone());58    }59    match field(value, "toJSON")?.dyn_ref::<js_sys::Function>() {60        Some(to_json) => to_json.call0(value),61        None => Ok(value.clone()),62    }63}6465// FIELDS6667fn field(value: &JsValue, name: &str) -> Result<JsValue, JsValue> {68    js_sys::Reflect::get(value, &JsValue::from_str(name))69}7071fn put(object: &js_sys::Object, name: &str, value: JsValue) -> Result<(), JsValue> {72    js_sys::Reflect::set(object, &JsValue::from_str(name), &value)?;73    Ok(())74}7576fn drop_field(object: &js_sys::Object, name: &str) -> Result<(), JsValue> {77    js_sys::Reflect::delete_property(object, &JsValue::from_str(name))?;78    Ok(())79}8081fn object(value: &JsValue) -> Result<&js_sys::Object, JsValue> {82    value83        .dyn_ref::<js_sys::Object>()84        .ok_or_else(|| refuse("an object was wanted here."))85}8687fn missing(value: &JsValue) -> bool {88    value.is_undefined() || value.is_null()89}9091// SCALARS9293pub fn number_from_js(value: &JsValue) -> Result<f64, JsValue> {94    value95        .as_f64()96        .ok_or_else(|| refuse("a number was wanted here."))97}9899pub fn bool_from_js(value: &JsValue) -> Result<bool, JsValue> {100    value101        .as_bool()102        .ok_or_else(|| refuse("a boolean was wanted here."))103}104105pub fn string_from_js(value: &JsValue) -> Result<String, JsValue> {106    value107        .as_string()108        .ok_or_else(|| refuse("a string was wanted here."))109}110111pub fn char_from_js(value: &JsValue) -> Result<char, JsValue> {112    let text = string_from_js(value)?;113    let mut chars = text.chars();114    match (chars.next(), chars.next()) {115        (Some(c), None) => Ok(c),116        _ => Err(refuse("a one-character string was wanted here.")),117    }118}119120fn decimal(value: &JsValue) -> Option<String> {121    if let Some(text) = value.as_string() {122        return Some(text.trim().to_string());123    }124    if value.is_bigint() {125        return value126            .unchecked_ref::<js_sys::BigInt>()127            .to_string(10)128            .ok()129            .map(String::from);130    }131    let number = value.as_f64()?;132    if number.is_finite() && number.fract() == 0.0 {133        Some(format!("{number:.0}"))134    } else {135        None136    }137}138139fn wide_from_js<T: FromStr>(value: &JsValue, name: &str) -> Result<T, JsValue> {140    decimal(value)141        .and_then(|text| text.parse().ok())142        .ok_or_else(|| refuse(&format!("{name} wants a decimal string, a whole number or a bigint.")))143}144145pub fn u128_to_js(value: u128) -> String {146    value.to_string()147}148149pub fn u128_from_js(value: &JsValue) -> Result<u128, JsValue> {150    wide_from_js(value, "a u128")151}152153pub fn i128_from_js(value: &JsValue) -> Result<i128, JsValue> {154    wide_from_js(value, "an i128")155}156157pub fn u64_from_js(value: &JsValue) -> Result<u64, JsValue> {158    wide_from_js(value, "a u64")159}160161pub fn i64_from_js(value: &JsValue) -> Result<i64, JsValue> {162    wide_from_js(value, "an i64")163}164165pub fn code_to_js(code: Code) -> String {166    code.get().to_string()167}168169pub fn code_from_js(value: &JsValue) -> Result<Code, JsValue> {170    Ok(Code::from(u128_from_js(value)?))171}172173// LISTS174175pub fn list_from_js<T>(176    value: &JsValue,177    item: impl Fn(&JsValue) -> Result<T, JsValue>,178) -> Result<Vec<T>, JsValue> {179    if let Some(array) = value.dyn_ref::<js_sys::Array>() {180        return array.iter().map(|x| item(&x)).collect();181    }182    match js_sys::try_iter(value)? {183        Some(iter) => iter.map(|x| item(&x?)).collect(),184        None => Err(refuse("a list was wanted here.")),185    }186}187188pub fn list_to_js<T>(189    items: impl IntoIterator<Item = T>,190    item: impl Fn(T) -> Result<JsValue, JsValue>,191) -> Result<JsValue, JsValue> {192    let out = js_sys::Array::new();193    for x in items {194        out.push(&item(x)?);195    }196    Ok(out.into())197}198199pub fn option_from_js<T>(200    value: &JsValue,201    item: impl Fn(&JsValue) -> Result<T, JsValue>,202) -> Result<Option<T>, JsValue> {203    if missing(value) {204        Ok(None)205    } else {206        item(value).map(Some)207    }208}209210pub fn option_to_js<T>(211    value: Option<T>,212    item: impl Fn(T) -> Result<JsValue, JsValue>,213) -> Result<JsValue, JsValue> {214    match value {215        Some(x) => item(x),216        None => Ok(JsValue::UNDEFINED),217    }218}219220pub fn item(value: &JsValue, index: u32) -> Result<JsValue, JsValue> {221    let array = value222        .dyn_ref::<js_sys::Array>()223        .ok_or_else(|| refuse("an array was wanted here."))?;224    if index >= array.length() {225        return Err(refuse(&format!("an array of at least {} was wanted here.", index + 1)));226    }227    Ok(array.get(index))228}229230pub fn first(value: &JsValue) -> Result<JsValue, JsValue> {231    item(value, 0)232}233234pub fn array_from_js<T, const N: usize>(235    value: &JsValue,236    item: impl Fn(&JsValue) -> Result<T, JsValue>,237) -> Result<[T; N], JsValue> {238    list_from_js(value, item)?239        .try_into()240        .map_err(|_| refuse(&format!("a list of {N} was wanted here.")))241}242243pub fn tuple_to_js(items: &[JsValue]) -> JsValue {244    items.iter().collect::<js_sys::Array>().into()245}246247pub fn map_from_js<K: FromStr + Eq + Hash, V>(248    value: &JsValue,249    each: impl Fn(&JsValue) -> Result<V, JsValue>,250) -> Result<HashMap<K, V>, JsValue> {251    let mut out = HashMap::new();252    for entry in js_sys::Object::entries(object(value)?).iter() {253        let key = string_from_js(&item(&entry, 0)?)?;254        let key = key255            .parse()256            .map_err(|_| refuse(&format!("the key {key:?} does not read.")))?;257        out.insert(key, each(&item(&entry, 1)?)?);258    }259    Ok(out)260}261262pub fn map_to_js<'a, K: Display + 'a, V: 'a>(263    entries: impl IntoIterator<Item = (&'a K, &'a V)>,264    item: impl Fn(&V) -> Result<JsValue, JsValue>,265) -> Result<JsValue, JsValue> {266    let out = js_sys::Object::new();267    for (key, value) in entries {268        put(&out, &key.to_string(), item(value)?)?;269    }270    Ok(out.into())271}272273pub trait Typed {274    fn typed(&self) -> JsValue;275}276277macro_rules! typed_arrays {278    ($($scalar:ty => $array:ident),* $(,)?) => {279        $(280            impl Typed for [$scalar] {281                fn typed(&self) -> JsValue {282                    js_sys::$array::from(self).into()283                }284            }285        )*286    };287}288289typed_arrays! {290    u8 => Uint8Array,291    u16 => Uint16Array,292    u32 => Uint32Array,293    i8 => Int8Array,294    i16 => Int16Array,295    i32 => Int32Array,296    f32 => Float32Array,297    f64 => Float64Array,298    u64 => BigUint64Array,299    i64 => BigInt64Array,300}301302impl Typed for [usize] {303    fn typed(&self) -> JsValue {304        let narrow: Vec<u32> = self.iter().map(|&n| n as u32).collect();305        js_sys::Uint32Array::from(&narrow[..]).into()306    }307}308309pub fn typed<T: Typed + ?Sized>(items: &T) -> JsValue {310    items.typed()311}312313// SHAPES314315pub fn shape_to_js(shape: &[usize]) -> JsValue {316    shape317        .iter()318        .map(|&length| JsValue::from_f64(length as f64))319        .collect::<js_sys::Array>()320        .into()321}322323pub fn shape_from_js(value: &JsValue) -> Result<Vec<usize>, JsValue> {324    let array = value325        .dyn_ref::<js_sys::Array>()326        .ok_or_else(|| refuse("a shape wants an array of lengths."))?;327    array328        .iter()329        .map(|length| {330            length331                .as_f64()332                .filter(|n| n.is_finite() && *n >= 0.0)333                .map(|n| n as usize)334                .ok_or_else(|| refuse("a shape wants whole lengths at or above zero."))335        })336        .collect()337}338339// BUFFERS340341pub fn bytes_to_js(bytes: &[u8]) -> JsValue {342    js_sys::Uint8Array::from(bytes).into()343}344345pub fn bytes_from_js(value: &JsValue) -> Result<Vec<u8>, JsValue> {346    if let Some(array) = value.dyn_ref::<js_sys::Uint8Array>() {347        return Ok(array.to_vec());348    }349    if let Some(array) = value.dyn_ref::<js_sys::Uint8ClampedArray>() {350        return Ok(array.to_vec());351    }352    if let Some(array) = value.dyn_ref::<js_sys::Array>() {353        return array354            .iter()355            .map(|item| {356                item.as_f64()357                    .filter(|n| (0.0..=255.0).contains(n))358                    .map(|n| n as u8)359                    .ok_or_else(|| refuse("a byte array wants numbers from zero to 255."))360            })361            .collect();362    }363    Err(refuse("a Uint8Array or an array of bytes was wanted here."))364}365366fn data_to_js(tensor: &Tensor) -> Result<JsValue, JsValue> {367    let data = match tensor.dtype() {368        Dtype::U8 => js_sys::Uint8Array::from(tensor.bytes().map_err(throw)?).into(),369        Dtype::U16 => js_sys::Uint16Array::from(tensor.u16s().map_err(throw)?).into(),370        Dtype::U32 => js_sys::Uint32Array::from(tensor.u32s().map_err(throw)?).into(),371        Dtype::I32 => js_sys::Int32Array::from(tensor.i32s().map_err(throw)?).into(),372    };373    Ok(data)374}375376fn data_from_js(value: &JsValue, shape: Vec<usize>) -> Result<Tensor, JsValue> {377    if let Some(array) = value.dyn_ref::<js_sys::Uint16Array>() {378        return Tensor::u16(array.to_vec(), shape).map_err(throw);379    }380    if let Some(array) = value.dyn_ref::<js_sys::Uint32Array>() {381        return Tensor::u32(array.to_vec(), shape).map_err(throw);382    }383    if let Some(array) = value.dyn_ref::<js_sys::Int32Array>() {384        return Tensor::i32(array.to_vec(), shape).map_err(throw);385    }386    Tensor::u8(bytes_from_js(value)?, shape).map_err(throw)387}388389// TENSORS390391fn fill_tensor(out: &js_sys::Object, tensor: &Tensor) -> Result<(), JsValue> {392    put(out, "shape", shape_to_js(&tensor.shape))?;393    put(out, "data", data_to_js(tensor)?)394}395396pub fn tensor_to_js(tensor: &Tensor) -> Result<JsValue, JsValue> {397    let out = js_sys::Object::new();398    fill_tensor(&out, tensor)?;399    Ok(out.into())400}401402pub fn tensor_into_js(value: &JsValue, tensor: &Tensor) -> Result<(), JsValue> {403    fill_tensor(object(value)?, tensor)404}405406pub fn tensor_from_js(value: &JsValue) -> Result<Tensor, JsValue> {407    let shape = shape_from_js(&field(value, "shape")?)?;408    data_from_js(&field(value, "data")?, shape)409}410411// CELLS412413fn fill_cell(out: &js_sys::Object, cell: &Cell) -> Result<(), JsValue> {414    put(out, "shape", shape_to_js(&cell.types.shape))?;415    put(out, "types", data_to_js(&cell.types)?)?;416    match &cell.colors {417        Some(colors) => {418            let flat: Vec<u8> = colors.iter().flatten().copied().collect();419            put(out, "colors", bytes_to_js(&flat))?;420        }421        None => drop_field(out, "colors")?,422    }423    match &cell.tags {424        Some(tags) => put(out, "tags", data_to_js(tags)?),425        None => drop_field(out, "tags"),426    }427}428429pub fn cell_to_js(cell: &Cell) -> Result<JsValue, JsValue> {430    let out = js_sys::Object::new();431    fill_cell(&out, cell)?;432    Ok(out.into())433}434435pub fn cell_into_js(value: &JsValue, cell: &Cell) -> Result<(), JsValue> {436    fill_cell(object(value)?, cell)437}438439pub fn cell_from_js(value: &JsValue) -> Result<Cell, JsValue> {440    let shape = shape_from_js(&field(value, "shape")?)?;441    let types = data_from_js(&field(value, "types")?, shape.clone())?;442    let painted = field(value, "colors")?;443    let colors = if missing(&painted) {444        None445    } else {446        let flat = bytes_from_js(&painted)?;447        if flat.len() != types.size() * 4 {448            return Err(refuse("cell colors want four bytes a cell."));449        }450        Some(451            flat.chunks_exact(4)452                .map(|rgba| [rgba[0], rgba[1], rgba[2], rgba[3]])453                .collect(),454        )455    };456    let tagged = field(value, "tags")?;457    let tags = if missing(&tagged) {458        None459    } else {460        Some(data_from_js(&tagged, shape)?)461    };462    Ok(Cell {463        types,464        colors,465        tags,466    })467}468469pub fn cell_rank(value: &JsValue) -> Result<usize, JsValue> {470    Ok(shape_from_js(&field(value, "shape")?)?.len())471}472473fn cell_nd_from_js<const N: usize>(value: &JsValue) -> Result<CellNd<N>, JsValue> {474    let cell = cell_from_js(value)?;475    if cell.types.shape.len() != N {476        return Err(refuse(&format!(477            "a {N}d cell wants a shape of {N} lengths."478        )));479    }480    Ok(CellNd { cell })481}482483pub fn cell2d_to_js(cell: &Cell2d) -> Result<JsValue, JsValue> {484    cell_to_js(&cell.cell)485}486487pub fn cell2d_from_js(value: &JsValue) -> Result<Cell2d, JsValue> {488    cell_nd_from_js(value)489}490491pub fn cell3d_to_js(cell: &Cell3d) -> Result<JsValue, JsValue> {492    cell_to_js(&cell.cell)493}494495pub fn cell3d_from_js(value: &JsValue) -> Result<Cell3d, JsValue> {496    cell_nd_from_js(value)497}498499pub fn cell6d_to_js(hex: &Cell6d) -> Result<JsValue, JsValue> {500    let out = js_sys::Object::new();501    put(&out, "cell", cell2d_to_js(&hex.cell)?)?;502    put(&out, "projection", to_js(&hex.projection)?)?;503    put(&out, "orientation", to_js(&hex.orientation)?)?;504    put(&out, "start", JsValue::from_f64(hex.start as f64))?;505    Ok(out.into())506}507508pub fn cell6d_from_js(value: &JsValue) -> Result<Cell6d, JsValue> {509    let cell = cell2d_from_js(&field(value, "cell")?)?;510    let projection: Projection = from_js(&field(value, "projection")?)?;511    let orientation: Orientation = from_js(&field(value, "orientation")?)?;512    let start = number_from_js(&field(value, "start")?)? as u8;513    Ok(Cell6d::new(cell, projection, orientation, start))514}515516// COLORS517518pub fn color_to_js(color: Color) -> JsValue {519    [color.r, color.g, color.b, color.a]520        .iter()521        .map(|&channel| JsValue::from_f64(channel as f64))522        .collect::<js_sys::Array>()523        .into()524}525526pub fn color_from_js(value: &JsValue) -> Result<Color, JsValue> {527    let channels = bytes_from_js(value)?;528    match channels.len() {529        3 => Ok(Color::rgb(channels[0], channels[1], channels[2])),530        4 => Ok(Color::rgba(531            channels[0],532            channels[1],533            channels[2],534            channels[3],535        )),536        _ => Err(refuse("a color wants three or four bytes.")),537    }538}539540pub fn colors_to_js(colors: &[Color]) -> JsValue {541    colors542        .iter()543        .map(|&color| color_to_js(color))544        .collect::<js_sys::Array>()545        .into()546}547548pub fn colors_from_js(value: &JsValue) -> Result<Vec<Color>, JsValue> {549    let array = value550        .dyn_ref::<js_sys::Array>()551        .ok_or_else(|| refuse("a palette wants an array of colors."))?;552    array.iter().map(|color| color_from_js(&color)).collect()553}554555// CHANCE556557/// A seeded random stream, opened from a number or a bigint seed.558#[wasm_bindgen]559pub struct Rng {560    stream: Stream,561}562563#[wasm_bindgen]564impl Rng {565    /// Opens the stream on the seed.566    #[wasm_bindgen(constructor)]567    pub fn new(seed: JsValue) -> Result<Rng, JsValue> {568        Ok(Rng {569            stream: Stream::new(u64_from_js(&seed)?),570        })571    }572    /// Draws a float at or above zero and below one.573    pub fn unit(&mut self) -> f64 {574        self.stream.unit()575    }576    /// Draws an integer below n, or zero when n is zero.577    pub fn below(&mut self, n: usize) -> usize {578        self.stream.below(n)579    }580    /// Draws an integer between lo and hi inclusive, or lo when hi is not above lo.581    pub fn range(&mut self, lo: f64, hi: f64) -> f64 {582        self.stream.range(lo as i64, hi as i64) as f64583    }584    /// Draws a fair coin flip.585    pub fn boolean(&mut self) -> bool {586        self.stream.boolean()587    }588    /// Returns true with probability p.589    pub fn chance(&mut self, p: f64) -> bool {590        self.stream.chance(p)591    }592    /// Draws amount distinct indices below length, or every index when amount is larger.593    pub fn sample_indices(&mut self, length: usize, amount: usize) -> Vec<u32> {594        self.stream595            .sample_indices(length, amount)596            .into_iter()597            .map(|index| index as u32)598            .collect()599    }600    /// Draws one item of the array, the same draw as Rust's choice.601    pub fn choice(&mut self, items: JsValue) -> Result<JsValue, JsValue> {602        let indices: Vec<u32> = (0..length(&items)?).collect();603        let index = *self.stream.choice(&indices).map_err(throw)?;604        js_sys::Reflect::get_u32(&items, index)605    }606    /// Shuffles the array in place, the same permutation as Rust's shuffle.607    pub fn shuffle(&mut self, items: JsValue) -> Result<(), JsValue> {608        let mut order: Vec<u32> = (0..length(&items)?).collect();609        self.stream.shuffle(&mut order);610        let moved = order611            .into_iter()612            .map(|index| js_sys::Reflect::get_u32(&items, index))613            .collect::<Result<Vec<JsValue>, JsValue>>()?;614        for (at, item) in (0..).zip(moved) {615            js_sys::Reflect::set_u32(&items, at, &item)?;616        }617        Ok(())618    }619    /// Prints the stream state, the way an optional stream crosses in.620    #[wasm_bindgen(js_name = "__state")]621    pub fn state(&self) -> Result<String, JsValue> {622        serde_json::to_string(&self.stream).map_err(|error| refuse(&error.to_string()))623    }624    /// Reads a stream state back, the way an optional stream crosses out.625    #[wasm_bindgen(js_name = "__restore")]626    pub fn restore(&mut self, state: &str) -> Result<(), JsValue> {627        self.stream = serde_json::from_str(state).map_err(|error| refuse(&error.to_string()))?;628        Ok(())629    }630}631632impl Rng {633    pub fn wrap(stream: Stream) -> Rng {634        Rng { stream }635    }636    pub fn stream(&mut self) -> &mut Stream {637        &mut self.stream638    }639}640641fn length(items: &JsValue) -> Result<u32, JsValue> {642    if !items.is_object() {643        return Err(refuse("an array was wanted here."));644    }645    field(items, "length")?646        .as_f64()647        .filter(|n| n.is_finite() && *n >= 0.0)648        .map(|n| n as u32)649        .ok_or_else(|| refuse("an array was wanted here."))650}651652fn method(value: &JsValue, name: &str) -> Result<js_sys::Function, JsValue> {653    field(value, name)?654        .dyn_into::<js_sys::Function>()655        .map_err(|_| refuse("an Rng was wanted here."))656}657658pub fn stream_from_js(value: &JsValue) -> Result<Option<Stream>, JsValue> {659    if missing(value) {660        return Ok(None);661    }662    let state = method(value, "__state")?.call0(value)?;663    serde_json::from_str(&string_from_js(&state)?)664        .map(Some)665        .map_err(|error| refuse(&error.to_string()))666}667668pub fn stream_to_js(value: &JsValue, stream: &Stream) -> Result<(), JsValue> {669    let state = serde_json::to_string(stream).map_err(|error| refuse(&error.to_string()))?;670    method(value, "__restore")?.call1(value, &JsValue::from_str(&state))?;671    Ok(())672}