sledgehammer 0.2.0

Fast bindings for dom manipulations
Documentation
//! This module contains the [`MsgChannel`] type which is used to send batched dom manipulation operations.
//! The main way to add operations to a [`MsgChannel`] is by dirrectly calling the methods on it, and then calling [`MsgChannel::flush`] to send the operations to the browser.
//!
//!

use std::{fmt::Arguments, io::Write};

use web_sys::Node;

use crate::{
    batch::{Batch, Op, PreparedBatch},
    last_needs_memory, update_last_memory, work_last_created, ElementBuilder, IntoAttribue,
    IntoElement, JsInterpreter, MSG_METADATA_PTR, MSG_PTR_PTR, STR_LEN_PTR, STR_PTR_PTR,
};

/// Tracks if a interpreter has been created. Used to prevent multiple interpreters from being created.
static mut INTERPRETER_EXISTS: bool = false;

/// An id that may be either the last node or a node with an assigned id.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MaybeId {
    /// The last node that was created or navigated to.
    LastNode,
    /// A node that was created and stored with an id
    Node(NodeId),
}

impl MaybeId {
    #[inline(always)]
    pub(crate) const fn encoded_size(&self) -> u8 {
        match self {
            MaybeId::LastNode => 0,
            MaybeId::Node(_) => 4,
        }
    }
}

/// A node that was created and stored with an id
/// It is recommended to create and store ids with a slab allocator with an exposed slab index for example the excellent [slab](https://docs.rs/slab) crate.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(pub u32);

/// The [`MsgChannel`] handles communication with the dom. It allows you to send batched operations to the dom.
/// All of the functions that are not marked otherwise are qued and not exicuted imidately. When you want to exicute the que you have to call [`MsgChannel::flush`].
/// There should only be one [`MsgChannel`] per application.
pub struct MsgChannel {
    pub(crate) js_interpreter: JsInterpreter,
    batch: Batch,
}

impl Default for MsgChannel {
    fn default() -> Self {
        unsafe {
            assert!(
                !INTERPRETER_EXISTS,
                "Found another MsgChannel. Only one MsgChannel can be created"
            );
            INTERPRETER_EXISTS = true;
        }
        debug_assert!(0x1F > Op::NoOp as u8);
        format!(
            "init: {:?}, {:?}, {:?}",
            unsafe { MSG_PTR_PTR as usize },
            unsafe { STR_PTR_PTR as usize },
            unsafe { STR_LEN_PTR as usize }
        );
        let js_interpreter = unsafe {
            JsInterpreter::new(
                wasm_bindgen::memory(),
                MSG_METADATA_PTR as usize,
                MSG_PTR_PTR as usize,
                STR_PTR_PTR as usize,
                STR_LEN_PTR as usize,
            )
        };

        Self {
            js_interpreter,
            batch: Batch::default(),
        }
    }
}

impl MsgChannel {
    /// IMPORTANT: This method is exicuted immediatly and does not wait for the next flush
    ///
    /// Example:
    /// ```no_run
    /// let window = web_sys::window().unwrap();
    /// let document = window.document().unwrap();
    /// let body = document.body().unwrap();
    /// let mut channel = MsgChannel::default();
    /// // assign the NodeId(0) to the body element from web-sys
    /// channel.set_node(NodeId(0), JsCast::dyn_into(body).unwrap());
    /// // no need to call flush here because set_node is exicuted immediatly
    /// ```
    pub fn set_node(&mut self, id: NodeId, node: Node) {
        self.js_interpreter.SetNode(id.0, node);
    }

    /// IMPORTANT: This method is exicuted immediatly and does not wait for the next flush
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// channel.create_element("div", Some(NodeId(0)));
    /// channel.flush();
    /// let element = channel.get_node(NodeId(0));
    /// let text = element.text_content().map(|t| t + " + web-sys");
    /// element.set_text_content(text.as_deref());
    /// // no need to call flush here because get_node is exicuted immediatly
    /// ```
    pub fn get_node(&mut self, id: NodeId) -> Node {
        self.js_interpreter.GetNode(id.0)
    }

    /// Exicutes any queued operations in the order they were added
    ///
    /// Example:
    ///
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // this does not immediatly create a <div> or <p>
    /// channel.create_element("div", None);
    /// channel.create_element("p", None);
    /// // this creates the <div> and <p> elements
    /// channel.flush();
    /// ```
    pub fn flush(&mut self) {
        self.batch.encode_op(Op::Stop);
        run_batch(&self.batch.msg, &self.batch.str_buf);
        self.batch.msg.clear();
        self.batch.current_op_batch_idx = 0;
        self.batch.current_op_byte_idx = 3;
        self.batch.str_buf.clear();
    }

    /// Appends a number of nodes as children of the given node.
    ///
    /// Example:
    ///
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// channel.create_element("div", Some(NodeId(0)));
    /// channel.create_element("p", None);
    /// // append the <p> element to the <div> element
    /// channel.append_child(MaybeId::Node(NodeId(0)), MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn append_child(&mut self, root: MaybeId, child: MaybeId) {
        self.batch.append_child(root, child)
    }

    /// Replace a node with another node
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// channel.create_element("div", Some(NodeId(0)));
    /// channel.create_element("p", None);
    /// // replace the <p> element with the <div> element
    /// channel.replace_with(MaybeId::Node(NodeId(0)), MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn replace_with(&mut self, root: MaybeId, node: MaybeId) {
        self.batch.replace_with(root, node)
    }

    /// Insert a single node after a given node.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// channel.create_element("div", Some(NodeId(0)));
    /// channel.create_element("p", None);
    /// // insert the <p> element after the <div> element
    /// channel.insert_after(MaybeId::Node(NodeId(0)), MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn insert_after(&mut self, root: MaybeId, node: MaybeId) {
        self.batch.insert_after(root, node)
    }

    /// Insert a single node before a given node.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// channel.create_element("div", Some(NodeId(0)));
    /// channel.create_element("p", None);
    /// // insert the <p> element before the <div> element
    /// channel.insert_before(MaybeId::Node(NodeId(0)), MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn insert_before(&mut self, root: MaybeId, node: MaybeId) {
        self.batch.insert_before(root, node)
    }

    /// Remove a node from the DOM.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// channel.create_element("p", None);
    /// // remove the <p> element
    /// channel.remove(MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn remove(&mut self, id: MaybeId) {
        self.batch.remove(id)
    }

    /// Create a new text node
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a text node with the text "Hello World"
    /// channel.create_text_node("Hello World", None);
    /// channel.flush();
    pub fn create_text_node(&mut self, text: impl WritableText, id: MaybeId) {
        self.batch.create_text_node(text, id)
    }

    /// Create a new element node
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a <div> element
    /// channel.create_element("div", None);
    /// channel.flush();
    /// ```
    pub fn create_element<'a, 'b>(&mut self, tag: impl IntoElement<'a, 'b>, id: Option<NodeId>) {
        self.batch.create_element(tag, id)
    }

    /// Set the textcontent of a node.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a text node with the text "Hello World"
    /// channel.create_text_node("Hello ", None);
    /// // set the text content of the text node to "Hello World!!!"
    /// channel.set_text_content("World!!!", MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn set_text(&mut self, text: impl WritableText, root: MaybeId) {
        self.batch.set_text(text, root)
    }

    /// Set the value of a node's attribute.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a <div> element
    /// channel.create_element("div", None);
    /// // set the attribute "id" to "my-div" on the <div> element
    /// channel.set_attribute(Attribute::id, "my-div", MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn set_attribute<'a, 'b>(
        &mut self,
        attr: impl IntoAttribue<'a, 'b>,
        value: impl WritableText,
        root: MaybeId,
    ) {
        self.batch.set_attribute(attr, value, root)
    }

    /// Remove an attribute from a node.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a <div> element
    /// channel.create_element("div", None);
    /// channel.set_attribute(Attribute::id, "my-div", MaybeId::LastNode);
    /// // remove the attribute "id" from the <div> element
    /// channel.remove_attribute(Attribute::id, MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn remove_attribute<'a, 'b>(&mut self, attr: impl IntoAttribue<'a, 'b>, root: MaybeId) {
        self.batch.remove_attribute(attr, root)
    }

    /// Clone a node and store it with a new id.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a <div> element
    /// channel.create_element("div", None);
    /// // clone the <div> element and store it with the id 1
    /// channel.clone_node(MaybeId::LastNode, Some(NodeId(1)));
    /// channel.flush();
    /// ```
    pub fn clone_node(&mut self, id: MaybeId, new_id: MaybeId) {
        self.batch.clone_node(id, new_id)
    }

    /// Move the last node to the first child
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a element: <div><p></p></div>
    /// channel.build_full_element(
    ///     ElementBuilder::new("div".into())
    ///         .children(&[
    ///             ElementBuilder::new(Element::p.into())
    ///                 .into(),
    ///         ]),
    /// );
    /// // move from the <div> to the <p>
    /// channel.first_child();
    /// // operatons modifing the <p> element...
    /// channel.flush();
    /// ```
    pub fn first_child(&mut self) {
        self.batch.first_child()
    }

    /// Move the last node to the next sibling
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a element: <div><h1></h1><p></p></div>
    /// channel.build_full_element(
    ///     ElementBuilder::new("div".into())
    ///         .children(&[
    ///             ElementBuilder::new(Element::h1.into())
    ///                 .into(),
    ///             ElementBuilder::new(Element::p.into())
    ///         ]),
    /// );
    /// // move from the <div> to the <h1>
    /// channel.first_child();
    /// // move from the <h1> to the <p>
    /// channel.next_sibling();
    /// // operatons modifing the <p> element...
    /// channel.flush();
    /// ```
    pub fn next_sibling(&mut self) {
        self.batch.next_sibling()
    }

    /// Move the last node to the parent node
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a element: <div><p></p></div>
    /// channel.build_full_element(
    ///     ElementBuilder::new("div".into())
    ///         .children(&[
    ///             ElementBuilder::new(Element::p.into())
    ///                 .id(NodeId(0))
    ///                 .into(),
    ///         ]),
    /// );
    /// // move to the <p> element
    /// channel.set_last_node(NodeId(0));
    /// // move from the <p> to the <div>
    /// channel.parent_node();
    /// // operatons modifing the <p> element...
    /// channel.flush();
    /// ```
    pub fn parent_node(&mut self) {
        self.batch.parent_node()
    }

    /// Store the last node with the given id. This is useful when traversing the document tree.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a element without an id
    /// channel.create_element("div", None);
    /// // store the <div> element with the id 0
    /// channel.set_last_node(NodeId(0));
    /// channel.flush();
    /// ```
    pub fn store_with_id(&mut self, id: NodeId) {
        self.batch.store_with_id(id)
    }

    /// Set the last node to the given id. The last node can be used to traverse the document tree without passing objects between wasm and js every time.
    ///
    /// Example:
    /// ```no_run
    /// let mut channel = MsgChannel::default();
    /// // create a element: <div><h1><h2></h2></h1><p></p></div>
    /// channel.build_full_element(
    ///     ElementBuilder::new("div".into())
    ///         .children(&[
    ///             ElementBuilder::new(Element::h1.into())
    ///                 .children(&[
    ///                     ElementBuilder::new(Element::h2.into())
    ///                         .into(),
    ///                 ]).into(),
    ///             ElementBuilder::new(Element::p.into())
    ///                 .into(),
    ///         ]),
    /// );
    /// // move from the <div> to the <h1>
    /// channel.first_child();
    /// // store the <h1> element with the id 0
    /// channel.store_with_id(NodeId(0));
    /// // move from the <h1> to the <h2>
    /// channel.first_child();
    /// // update something in the <h2> element...
    /// // restore the <h1> element
    /// channel.set_last_node(NodeId(0));
    /// // move from the <h1> to the <p>
    /// channel.next_sibling();
    /// // operatons modifing the <p> element...
    /// channel.flush();
    /// ```
    pub fn set_last_node(&mut self, id: NodeId) {
        self.batch.set_last_node(id)
    }

    /// Build a full element, slightly more efficent than creating the element creating the element with `create_element` and then setting the attributes.
    ///
    /// Example:
    /// ```rust
    /// let mut channel = MsgChannel::default();
    /// // create an element using sledgehammer
    /// channel.build_full_element(
    ///     ElementBuilder::new("div".into())
    ///         .id(NodeId(0))
    ///         .attrs(&[(Attribute::style.into(), "color: blue")])
    ///         .children(&[
    ///             ElementBuilder::new(Element::p.into())
    ///                 .into(),
    ///             TextBuilder::new("Hello from sledgehammer!").into(),
    ///         ]),
    /// );
    /// channel.flush();
    /// ```
    pub fn build_full_element(&mut self, el: ElementBuilder) {
        self.batch.build_full_element(el)
    }

    /// Set a style property on a node.
    ///
    /// Example:
    /// ```rust
    /// let mut channel = MsgChannel::default();
    /// channel.create_element("div", None);
    /// // set the style property "color" to "blue"
    /// channel.set_style("color", "blue", MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn set_style(&mut self, style: &str, value: &str, id: MaybeId) {
        self.batch.set_style(style, value, id)
    }

    /// Remove a style property from a node.
    ///
    /// Example:
    /// ```rust
    /// let mut channel = MsgChannel::default();
    /// channel.create_element("div", None);
    /// channel.set_style("color", "blue", MaybeId::LastNode);
    /// // remove the color style
    /// channel.remove_style("color", MaybeId::LastNode);
    /// channel.flush();
    /// ```
    pub fn remove_style(&mut self, style: &str, id: MaybeId) {
        self.batch.remove_style(style, id)
    }

    /// Adds a batch of operations to the current batch.
    ///
    /// Example:
    /// ```rust
    /// let mut channel = MsgChannel::default();
    /// let mut batch = Batch::default();
    /// batch.create_element("div", None);
    /// // add the batch to the channel
    /// channel.append(batch);
    /// channel.flush();
    /// ```
    pub fn append(&mut self, batch: Batch) {
        self.batch.append(batch);
    }

    /// IMPORTANT: This method is exicuted immediatly and does not wait for the next flush
    ///
    /// Run a batch of operations on the DOM immediately. This only runs the operations that are in the batch, not the operations that are queued in the [`MsgChannel`].
    ///
    /// Example:
    /// ```rust
    /// let mut channel = MsgChannel::default();
    /// let mut batch = Batch::default();
    /// batch.create_element("div", None);
    /// // add the batch to the channel
    /// channel.run_batch(&batch.finalize());
    /// ```
    pub fn run_batch(&mut self, batch: impl PreparedBatch) {
        run_batch(batch.msg(), batch.str());
    }
}

fn run_batch(msg: &[u8], str_buf: &[u8]) {
    debug_assert_eq!(0usize.to_le_bytes().len(), 32 / 8);
    let msg_ptr = msg.as_ptr() as usize;
    let str_ptr = str_buf.as_ptr() as usize;
    // the pointer will only be updated when the message vec is resized, so we have a flag to check if the pointer has changed to avoid unnecessary decoding
    if unsafe { *MSG_METADATA_PTR } == 255 {
        // this is the first message, so we need to encode all the metadata
        unsafe {
            let mut_ptr_ptr: *mut usize = std::mem::transmute(MSG_PTR_PTR);
            *mut_ptr_ptr = msg_ptr;
            let mut_metadata_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR);
            // the first bit encodes if the msg pointer has changed
            *mut_metadata_ptr = 1;
            let mut_str_ptr_ptr: *mut usize = std::mem::transmute(STR_PTR_PTR);
            *mut_str_ptr_ptr = str_ptr as usize;
            // the second bit encodes if the str pointer has changed
            *mut_metadata_ptr |= 2;
        }
    } else {
        if unsafe { *MSG_PTR_PTR } != msg_ptr {
            unsafe {
                let mut_ptr_ptr: *mut usize = std::mem::transmute(MSG_PTR_PTR);
                *mut_ptr_ptr = msg_ptr;
                let mut_ptr_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR);
                // the first bit encodes if the msg pointer has changed
                *mut_ptr_ptr = 1;
            }
        } else {
            unsafe {
                let mut_ptr_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR);
                // the first bit encodes if the msg pointer has changed
                *mut_ptr_ptr = 0;
            }
        }
        if unsafe { *STR_PTR_PTR } != str_ptr {
            unsafe {
                let mut_str_ptr_ptr: *mut usize = std::mem::transmute(STR_PTR_PTR);
                *mut_str_ptr_ptr = str_ptr as usize;
                let mut_metadata_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR);
                // the second bit encodes if the str pointer has changed
                *mut_metadata_ptr |= 1 << 1;
            }
        }
    }
    unsafe {
        let mut_metadata_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR);
        if !str_buf.is_empty() {
            // the third bit encodes if there is any strings
            *mut_metadata_ptr |= 1 << 2;
            let mut_str_len_ptr: *mut usize = std::mem::transmute(STR_LEN_PTR);
            *mut_str_len_ptr = str_buf.len() as usize;
            if *mut_str_len_ptr < 100 {
                // the fourth bit encodes if the strings are entirely ascii and small
                *mut_metadata_ptr |= (str_buf.is_ascii() as u8) << 3;
            }
        }
    }
    if last_needs_memory() {
        update_last_memory(wasm_bindgen::memory());
    }
    work_last_created();
}

/// Something that can be written as a utf-8 string to a buffer
pub trait WritableText {
    fn write_as_text(self, to: &mut Vec<u8>);
}

impl WritableText for char {
    fn write_as_text(self, to: &mut Vec<u8>) {
        to.push(self as u8);
    }
}

impl<'a> WritableText for &'a str {
    #[inline(always)]
    fn write_as_text(self, to: &mut Vec<u8>) {
        let len = self.len();
        to.reserve(len);
        let old_len = to.len();
        #[allow(clippy::uninit_vec)]
        unsafe {
            let ptr = to.as_mut_ptr();
            let bytes = self.as_bytes();
            let str_ptr = bytes.as_ptr();
            for o in 0..len {
                *ptr.add(old_len + o) = *str_ptr.add(o);
            }
            to.set_len(old_len + len);
        }
        // let _ = to.write(self.as_bytes());
    }
}

impl WritableText for Arguments<'_> {
    fn write_as_text(self, to: &mut Vec<u8>) {
        let _ = to.write_fmt(self);
    }
}

impl<F> WritableText for F
where
    F: FnOnce(&mut Vec<u8>),
{
    fn write_as_text(self, to: &mut Vec<u8>) {
        self(to);
    }
}

macro_rules! write_unsized {
    ($t: ty) => {
        impl WritableText for $t {
            fn write_as_text(self, to: &mut Vec<u8>) {
                let mut n = self;
                let mut n2 = n;
                let mut num_digits = 0;
                while n2 > 0 {
                    n2 /= 10;
                    num_digits += 1;
                }
                let len = num_digits;
                to.reserve(len);
                let ptr = to.as_mut_ptr().cast::<u8>();
                let old_len = to.len();
                let mut i = len - 1;
                loop {
                    unsafe { ptr.add(old_len + i).write((n % 10) as u8 + b'0') }
                    n /= 10;

                    if n == 0 {
                        break;
                    } else {
                        i -= 1;
                    }
                }

                #[allow(clippy::uninit_vec)]
                unsafe {
                    to.set_len(old_len + (len - i));
                }
            }
        }
    };
}

macro_rules! write_sized {
    ($t: ty) => {
        impl WritableText for $t {
            fn write_as_text(self, to: &mut Vec<u8>) {
                let neg = self < 0;
                let mut n = if neg {
                    match self.checked_abs() {
                        Some(n) => n,
                        None => <$t>::MAX / 2 + 1,
                    }
                } else {
                    self
                };
                let mut n2 = n;
                let mut num_digits = 0;
                while n2 > 0 {
                    n2 /= 10;
                    num_digits += 1;
                }
                let len = if neg { num_digits + 1 } else { num_digits };
                to.reserve(len);
                let ptr = to.as_mut_ptr().cast::<u8>();
                let old_len = to.len();
                let mut i = len - 1;
                loop {
                    unsafe { ptr.add(old_len + i).write((n % 10) as u8 + b'0') }
                    n /= 10;

                    if n == 0 {
                        break;
                    } else {
                        i -= 1;
                    }
                }

                if neg {
                    i -= 1;
                    unsafe { ptr.add(old_len + i).write(b'-') }
                }

                #[allow(clippy::uninit_vec)]
                unsafe {
                    to.set_len(old_len + (len - i));
                }
            }
        }
    };
}

write_unsized!(u8);
write_unsized!(u16);
write_unsized!(u32);
write_unsized!(u64);
write_unsized!(u128);
write_unsized!(usize);

write_sized!(i8);
write_sized!(i16);
write_sized!(i32);
write_sized!(i64);
write_sized!(i128);
write_sized!(isize);