sledgehammer/lib.rs
1//!<div align="center">
2//! <!-- Crates version -->
3//! <a href="https://crates.io/crates/sledgehammer">
4//! <img src="https://img.shields.io/crates/v/sledgehammer.svg?style=flat-square"
5//! alt="Crates.io version" />
6//! </a>
7//! <!-- Downloads -->
8//! <a href="https://crates.io/crates/sledgehammer">
9//! <img src="https://img.shields.io/crates/d/sledgehammer.svg?style=flat-square"
10//! alt="Download" />
11//! </a>
12//! <!-- docs -->
13//! <a href="https://docs.rs/sledgehammer">
14//! <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
15//! alt="docs.rs docs" />
16//! </a>
17//!</div>
18//!
19//!**Breaking the WASM<->JS peformance boundry one brick at a time**
20//!### Status: There are some holes in the wall.
21//!
22//!# What is Sledgehammer?
23//!Sledgehammer provides faster rust bindings for dom manipuations by batching calls to js.
24//!
25//! # Getting started
26//! - All operations go through a [`MsgChannel`] which handles the communication with js.
27//!
28//!# Benchmarks
29//!
30//!- run this benchmark in your browser: [dom operation time only (not paint time) js-framework-benchmark](https://demonthos.github.io/wasm_bindgen_sledgehammer/)
31//!
32//!This gives more consistant results than the official js-framework-benchmark because it excludes the variation in paint time. Because sledgehammer and wasm-bindgen implementations result in the same dom calls they should have the same paint time.
33//!
34//!- A few runs of [a fork of the js-framework-benchmark:](https://github.com/demonthos/js-framework-benchmark/tree/testing)
35//!<div align="center">
36//! <img src="https://user-images.githubusercontent.com/66571940/199780394-a360581f-1496-4894-b7fe-3d5b5d627dbb.png" />
37//! <img src="https://user-images.githubusercontent.com/66571940/199780395-d7d00059-052e-40b7-9514-aba55800dc04.png" />
38//! <img src="https://user-images.githubusercontent.com/66571940/199780398-0060a62b-4d93-4a40-94a2-980835393aa2.png" />
39//!</div>
40//!
41//!# How does this compare to wasm-bindgen/web-sys:
42//!wasm-bindgen is a lot more general, and ergonomic to use than sledgehammer. It has bindings to a lot of apis that sledgehammer does not. For most users wasm-bindgen is a beter choice. Sledgehammer is specifically designed for web frameworks that want low level, fast access to the dom.
43//!
44//!# Why is it fast?
45//!
46//!## String decoding
47//!
48//!- Decoding strings are expensive to decode, but the cost doesn't change much with the size of the string. Wasm-bindgen calls TextDecoder.decode for every string. Sledehammer only calls TextEncoder.decode once per batch.
49//!
50//!- If the string is small it is faster to decode the string in javascript to avoid the constant overhead of TextDecoder.decode
51//!
52//!- See this benchmark: <https://jsbench.me/4vl97c05lb/5>
53//!
54//!## Single byte attributes and elements
55//!
56//!- In addition to making string decoding cheaper, sledehammer also uses less strings. All elements and attribute names are encoded as a single byte instead of a string and then turned back into a string in the javascipt intepreter.
57//!
58//!- To allow for custom elements and attributes, you can pass in a &str instead of a Attribute or Element enum.
59//!
60//!## Byte encoded operations
61//!
62//!- In sledehammer every operation is encoded as a sequence of bytes packed into an array. Every operation takes 1 byte plus whatever data is required for it.
63//!
64//!- Booleans are encoded as part of the operation byte to reduce the number of bytes read.
65//!
66//!- Each operation is encoded in a batch of four as a u32. Getting a number from an array buffer has a high constant cost, but getting a u32 instead of a u8 is not more expensive. Sledgehammer reads the u32 and then splits it into the 4 individual bytes.
67//!
68//!- See this benchmark: <https://jsbench.me/csl9lfauwi/2>
69//!
70//!## Minimize passing ids
71//!
72//!- A common set of operations for webframeworks to perform is traversing dom nodes after cloning them. Instead of assigning an id to every node, sledgehammer allows you to perform operations on the last node that was created or navigated to. This means traversing id takes only one byte per operation instead of 5.
73
74#![allow(non_camel_case_types)]
75
76// mod attrs;
77pub mod attribute;
78pub mod batch;
79pub mod channel;
80pub mod element;
81
82pub use attribute::{Attribute, IntoAttribue};
83pub use channel::{MsgChannel, NodeId, WritableText};
84pub use element::{Element, ElementBuilder, IntoElement, NodeBuilder, TextBuilder};
85
86use wasm_bindgen::prelude::*;
87use web_sys::Node;
88
89#[used]
90static mut MSG_PTR: usize = 0;
91#[used]
92static mut MSG_PTR_PTR: *const usize = unsafe { &MSG_PTR } as *const usize;
93#[used]
94static mut MSG_POS_UPDATED: u8 = 255;
95#[used]
96static mut MSG_METADATA_PTR: *const u8 = unsafe { &MSG_POS_UPDATED } as *const u8;
97#[used]
98static mut STR_PTR: usize = 0;
99#[used]
100static mut STR_PTR_PTR: *const usize = unsafe { &STR_PTR } as *const usize;
101#[used]
102static mut STR_LEN: usize = 0;
103#[used]
104static mut STR_LEN_PTR: *const usize = unsafe { &STR_LEN } as *const usize;
105
106#[wasm_bindgen(module = "/interpreter_opt.js")]
107// #[wasm_bindgen(module = "/interpreter.js")]
108extern "C" {
109 fn work_last_created();
110
111 fn update_last_memory(mem: JsValue);
112
113 fn last_needs_memory() -> bool;
114
115 pub(crate) type JsInterpreter;
116
117 #[wasm_bindgen(constructor)]
118 pub(crate) fn new(
119 mem: JsValue,
120 msg_pos_updated_ptr: usize,
121 msg_ptr: usize,
122 str_ptr: usize,
123 str_len_ptr: usize,
124 ) -> JsInterpreter;
125
126 #[wasm_bindgen(method)]
127 pub(crate) fn UpdateMemory(this: &JsInterpreter, mem: JsValue);
128
129 #[allow(unused)]
130 #[wasm_bindgen(method)]
131 pub(crate) fn NeedsMemory(this: &JsInterpreter) -> bool;
132
133 #[wasm_bindgen(method)]
134 pub(crate) fn SetNode(this: &JsInterpreter, id: u32, node: Node);
135
136 #[allow(unused)]
137 #[wasm_bindgen(method)]
138 pub(crate) fn GetNode(this: &JsInterpreter, id: u32) -> Node;
139}
140
141/// Something that lives in a namespace like a tag or attribute
142pub struct InNamespace<'a, T>(pub T, pub &'a str);
143
144/// Something that can live in a namespace
145pub trait WithNsExt {
146 /// Moves the item into a namespace
147 fn in_namespace(self, namespace: &str) -> InNamespace<Self>
148 where
149 Self: Sized,
150 {
151 InNamespace(self, namespace)
152 }
153}
154
155impl WithNsExt for Element {}
156impl WithNsExt for Attribute {}
157impl<'a> WithNsExt for &'a str {}