on master help sign in
Merge pull request #49 from fitzgen/benches-and-fixing-unsoundness

Fix some self-borrow unsoundness and make Cached<R> avoid re-diffing while we're at it

commit35c85d5c68002e1c5fdb92849eccf866c9ab0e09 authorNick Fitzgerald <fitzgen@gmail.com> dateTue 2 Apr 2019, 10:06 (8 years ago) parent73ddeb803dc316
31 files changed+2080−1171
.travis.yml+6−2 Cargo.lock+418−114 Cargo.toml+23 README.md+4−6 benches/benches.rs+129 crates/js-api/src/lib.rs+19−30 examples/counter/src/lib.rs+6−10 examples/game-of-life/src/lib.rs+8−11 examples/hello-world/src/lib.rs+12−14 examples/input-form/src/lib.rs+9−10 examples/js-component/src/lib.rs+4−8 examples/moire/src/lib.rs+33−58 examples/sierpinski-triangle/src/lib.rs+23−38 examples/todomvc/src/todo.rs+11−13 examples/todomvc/src/todos.rs+36−51 src/builder.rs+11−4 src/cached.rs+67−70 src/cached_set.rs+155 src/change_list.rs+76−48 src/diff.rs+310 src/events.rs+132−83 src/lib.rs+62−10 src/node.rs+77−73 src/render.rs+27−39 src/render_context.rs+63 src/vdom.rs+81−264 tests/web/cached.rs+140−7 tests/web/events.rs+5−86 tests/web/js_api.rs+12−15 tests/web/main.rs+36−22 tests/web/render.rs+85−85
.travis.yml+6−2
@@ -32,13 +32,17 @@ matrix:
3232 rust: stable
3333 addons:
3434 firefox: latest
35− script: wasm-pack test --firefox --headless
35+ script: wasm-pack test --firefox --headless -- --features xxx-unstable-internal-use-only
3636 - name: "headless browser tests (chrome)"
3737 rust: stable
3838 addons:
3939 chrome: stable
40− script: wasm-pack test --chrome --headless
40+ script: wasm-pack test --chrome --headless -- --features xxx-unstable-internal-use-only
4141 - name: "native tests"
4242 rust: stable
4343 install: echo "no install"
4444 script: cargo test --all
45+ - name: "check native benches"
46+ rust: nightly
47+ install: echo "no install"
48+ script: cargo check --benches --features xxx-unstable-internal-use-only
Cargo.lock+418−114
@@ -1,17 +1,25 @@
11[[package]]
22name = "aho-corasick"
3−version = "0.6.10"
3+version = "0.7.3"
44source = "registry+https://github.com/rust-lang/crates.io-index"
55dependencies = [
66 "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
77]
88
9+[[package]]
10+name = "arrayvec"
11+version = "0.4.10"
12+source = "registry+https://github.com/rust-lang/crates.io-index"
13+dependencies = [
14+ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
15+]
16+
917[[package]]
1018name = "atty"
1119version = "0.2.11"
1220source = "registry+https://github.com/rust-lang/crates.io-index"
1321dependencies = [
14− "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
22+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
1523 "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
1624 "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
1725]
@@ -29,7 +37,7 @@ dependencies = [
2937 "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
3038 "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
3139 "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
32− "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
40+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
3341 "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
3442 "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
3543]
@@ -39,10 +47,15 @@ name = "backtrace-sys"
3947version = "0.1.28"
4048source = "registry+https://github.com/rust-lang/crates.io-index"
4149dependencies = [
42− "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
43− "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
50+ "cc 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
51+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
4452]
4553
54+[[package]]
55+name = "bitflags"
56+version = "1.0.4"
57+source = "registry+https://github.com/rust-lang/crates.io-index"
58+
4659[[package]]
4760name = "bumpalo"
4861version = "2.3.0"
@@ -53,9 +66,14 @@ name = "byteorder"
5366version = "1.3.1"
5467source = "registry+https://github.com/rust-lang/crates.io-index"
5568
69+[[package]]
70+name = "cast"
71+version = "0.2.2"
72+source = "registry+https://github.com/rust-lang/crates.io-index"
73+
5674[[package]]
5775name = "cc"
58−version = "1.0.31"
76+version = "1.0.32"
5977source = "registry+https://github.com/rust-lang/crates.io-index"
6078
6179[[package]]
@@ -63,13 +81,31 @@ name = "cfg-if"
6381version = "0.1.7"
6482source = "registry+https://github.com/rust-lang/crates.io-index"
6583
84+[[package]]
85+name = "clap"
86+version = "2.32.0"
87+source = "registry+https://github.com/rust-lang/crates.io-index"
88+dependencies = [
89+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
90+ "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
91+ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
92+]
93+
94+[[package]]
95+name = "cloudabi"
96+version = "0.0.3"
97+source = "registry+https://github.com/rust-lang/crates.io-index"
98+dependencies = [
99+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
100+]
101+
66102[[package]]
67103name = "console_error_panic_hook"
68104version = "0.1.6"
69105source = "registry+https://github.com/rust-lang/crates.io-index"
70106dependencies = [
71107 "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
72− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
108+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
73109]
74110
75111[[package]]
@@ -78,7 +114,89 @@ version = "0.1.2"
78114source = "registry+https://github.com/rust-lang/crates.io-index"
79115dependencies = [
80116 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
81− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
117+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
118+]
119+
120+[[package]]
121+name = "criterion"
122+version = "0.2.10"
123+source = "registry+https://github.com/rust-lang/crates.io-index"
124+dependencies = [
125+ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
126+ "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
127+ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
128+ "criterion-plot 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
129+ "csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
130+ "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
131+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
132+ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
133+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
134+ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
135+ "rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
136+ "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
137+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
138+ "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
139+ "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
140+ "tinytemplate 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
141+ "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
142+]
143+
144+[[package]]
145+name = "criterion-plot"
146+version = "0.3.0"
147+source = "registry+https://github.com/rust-lang/crates.io-index"
148+dependencies = [
149+ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
150+ "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
151+ "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
152+]
153+
154+[[package]]
155+name = "crossbeam-deque"
156+version = "0.2.0"
157+source = "registry+https://github.com/rust-lang/crates.io-index"
158+dependencies = [
159+ "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
160+ "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
161+]
162+
163+[[package]]
164+name = "crossbeam-epoch"
165+version = "0.3.1"
166+source = "registry+https://github.com/rust-lang/crates.io-index"
167+dependencies = [
168+ "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
169+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
170+ "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
171+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
172+ "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
173+ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
174+ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
175+]
176+
177+[[package]]
178+name = "crossbeam-utils"
179+version = "0.2.2"
180+source = "registry+https://github.com/rust-lang/crates.io-index"
181+dependencies = [
182+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
183+]
184+
185+[[package]]
186+name = "csv"
187+version = "1.0.5"
188+source = "registry+https://github.com/rust-lang/crates.io-index"
189+dependencies = [
190+ "csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
191+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
192+]
193+
194+[[package]]
195+name = "csv-core"
196+version = "0.1.5"
197+source = "registry+https://github.com/rust-lang/crates.io-index"
198+dependencies = [
199+ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
82200]
83201
84202[[package]]
@@ -88,15 +206,16 @@ dependencies = [
88206 "bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
89207 "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
90208 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
209+ "criterion 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
91210 "dodrio-js-api 0.1.0",
92211 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
93212 "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
94− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
213+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
95214 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
96− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
97− "wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
98− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
99− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
215+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
216+ "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
217+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
218+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
100219]
101220
102221[[package]]
@@ -107,9 +226,9 @@ dependencies = [
107226 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
108227 "dodrio 0.1.0",
109228 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
110− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
111− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
112− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
229+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
230+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
231+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
113232]
114233
115234[[package]]
@@ -119,11 +238,11 @@ dependencies = [
119238 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
120239 "dodrio 0.1.0",
121240 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
122− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
123− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
124− "wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
125− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
126− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
241+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
242+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
243+ "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
244+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
245+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
127246]
128247
129248[[package]]
@@ -132,9 +251,9 @@ version = "0.1.0"
132251dependencies = [
133252 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
134253 "dodrio 0.1.0",
135− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
136− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
137− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
254+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
255+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
256+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
138257]
139258
140259[[package]]
@@ -143,9 +262,9 @@ version = "0.1.0"
143262dependencies = [
144263 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
145264 "dodrio 0.1.0",
146− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
147− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
148− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
265+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
266+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
267+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
149268]
150269
151270[[package]]
@@ -154,10 +273,10 @@ version = "0.1.0"
154273dependencies = [
155274 "dodrio 0.1.0",
156275 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
157− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
158− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
159− "wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
160− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
276+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
277+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
278+ "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
279+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
161280]
162281
163282[[package]]
@@ -167,9 +286,9 @@ dependencies = [
167286 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
168287 "dodrio 0.1.0",
169288 "dodrio-js-api 0.1.0",
170− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
171− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
172− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
289+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
290+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
291+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
173292]
174293
175294[[package]]
@@ -181,12 +300,12 @@ dependencies = [
181300 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
182301 "dodrio 0.1.0",
183302 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
184− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
303+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
185304 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
186− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
187− "wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
188− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
189− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
305+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
306+ "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
307+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
308+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
190309]
191310
192311[[package]]
@@ -196,11 +315,11 @@ dependencies = [
196315 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
197316 "dodrio 0.1.0",
198317 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
199− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
200− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
201− "wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
202− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
203− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
318+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
319+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
320+ "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
321+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
322+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
204323]
205324
206325[[package]]
@@ -212,16 +331,21 @@ dependencies = [
212331 "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
213332 "dodrio 0.1.0",
214333 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
215− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
334+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
216335 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
217336 "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
218337 "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
219− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
220− "wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
221− "wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
222− "web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
338+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
339+ "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
340+ "wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
341+ "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
223342]
224343
344+[[package]]
345+name = "either"
346+version = "1.5.1"
347+source = "registry+https://github.com/rust-lang/crates.io-index"
348+
225349[[package]]
226350name = "env_logger"
227351version = "0.6.1"
@@ -230,7 +354,7 @@ dependencies = [
230354 "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
231355 "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
232356 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
233− "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
357+ "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
234358 "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
235359]
236360
@@ -250,10 +374,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
250374dependencies = [
251375 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
252376 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
253− "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
377+ "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
254378 "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
255379]
256380
381+[[package]]
382+name = "fuchsia-cprng"
383+version = "0.1.1"
384+source = "registry+https://github.com/rust-lang/crates.io-index"
385+
257386[[package]]
258387name = "futures"
259388version = "0.1.26"
@@ -283,6 +412,14 @@ dependencies = [
283412 "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
284413]
285414
415+[[package]]
416+name = "itertools"
417+version = "0.8.0"
418+source = "registry+https://github.com/rust-lang/crates.io-index"
419+dependencies = [
420+ "either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
421+]
422+
286423[[package]]
287424name = "itoa"
288425version = "0.4.3"
@@ -290,10 +427,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
290427
291428[[package]]
292429name = "js-sys"
293−version = "0.3.16"
430+version = "0.3.17"
294431source = "registry+https://github.com/rust-lang/crates.io-index"
295432dependencies = [
296− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
433+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
297434]
298435
299436[[package]]
@@ -303,7 +440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
303440
304441[[package]]
305442name = "libc"
306−version = "0.2.50"
443+version = "0.2.51"
307444source = "registry+https://github.com/rust-lang/crates.io-index"
308445
309446[[package]]
@@ -318,16 +455,42 @@ dependencies = [
318455name = "memchr"
319456version = "2.2.0"
320457source = "registry+https://github.com/rust-lang/crates.io-index"
458+dependencies = [
459+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
460+]
461+
462+[[package]]
463+name = "memoffset"
464+version = "0.2.1"
465+source = "registry+https://github.com/rust-lang/crates.io-index"
466+
467+[[package]]
468+name = "nodrop"
469+version = "0.1.13"
470+source = "registry+https://github.com/rust-lang/crates.io-index"
321471
322472[[package]]
323473name = "nom"
324−version = "4.2.2"
474+version = "4.2.3"
325475source = "registry+https://github.com/rust-lang/crates.io-index"
326476dependencies = [
327477 "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
328478 "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
329479]
330480
481+[[package]]
482+name = "num-traits"
483+version = "0.2.6"
484+source = "registry+https://github.com/rust-lang/crates.io-index"
485+
486+[[package]]
487+name = "num_cpus"
488+version = "1.10.0"
489+source = "registry+https://github.com/rust-lang/crates.io-index"
490+dependencies = [
491+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
492+]
493+
331494[[package]]
332495name = "proc-macro2"
333496version = "0.4.27"
@@ -349,9 +512,73 @@ dependencies = [
349512 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
350513]
351514
515+[[package]]
516+name = "rand_core"
517+version = "0.3.1"
518+source = "registry+https://github.com/rust-lang/crates.io-index"
519+dependencies = [
520+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
521+]
522+
523+[[package]]
524+name = "rand_core"
525+version = "0.4.0"
526+source = "registry+https://github.com/rust-lang/crates.io-index"
527+
528+[[package]]
529+name = "rand_os"
530+version = "0.1.3"
531+source = "registry+https://github.com/rust-lang/crates.io-index"
532+dependencies = [
533+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
534+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
535+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
536+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
537+ "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
538+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
539+]
540+
541+[[package]]
542+name = "rand_xoshiro"
543+version = "0.1.0"
544+source = "registry+https://github.com/rust-lang/crates.io-index"
545+dependencies = [
546+ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
547+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
548+]
549+
550+[[package]]
551+name = "rayon"
552+version = "1.0.3"
553+source = "registry+https://github.com/rust-lang/crates.io-index"
554+dependencies = [
555+ "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
556+ "either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
557+ "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
558+]
559+
560+[[package]]
561+name = "rayon-core"
562+version = "1.4.1"
563+source = "registry+https://github.com/rust-lang/crates.io-index"
564+dependencies = [
565+ "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
566+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
567+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
568+ "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
569+]
570+
571+[[package]]
572+name = "rdrand"
573+version = "0.4.0"
574+source = "registry+https://github.com/rust-lang/crates.io-index"
575+dependencies = [
576+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
577+]
578+
352579[[package]]
353580name = "redox_syscall"
354−version = "0.1.51"
581+version = "0.1.52"
355582source = "registry+https://github.com/rust-lang/crates.io-index"
356583
357584[[package]]
@@ -359,24 +586,24 @@ name = "redox_termios"
359586version = "0.1.1"
360587source = "registry+https://github.com/rust-lang/crates.io-index"
361588dependencies = [
362− "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
589+ "redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)",
363590]
364591
365592[[package]]
366593name = "regex"
367−version = "1.1.2"
594+version = "1.1.5"
368595source = "registry+https://github.com/rust-lang/crates.io-index"
369596dependencies = [
370− "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
597+ "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
371598 "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
372− "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
599+ "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
373600 "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
374601 "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
375602]
376603
377604[[package]]
378605name = "regex-syntax"
379−version = "0.6.5"
606+version = "0.6.6"
380607source = "registry+https://github.com/rust-lang/crates.io-index"
381608dependencies = [
382609 "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -392,11 +619,24 @@ name = "ryu"
392619version = "0.2.7"
393620source = "registry+https://github.com/rust-lang/crates.io-index"
394621
622+[[package]]
623+name = "same-file"
624+version = "1.0.4"
625+source = "registry+https://github.com/rust-lang/crates.io-index"
626+dependencies = [
627+ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
628+]
629+
395630[[package]]
396631name = "scoped-tls"
397632version = "1.0.0"
398633source = "registry+https://github.com/rust-lang/crates.io-index"
399634
635+[[package]]
636+name = "scopeguard"
637+version = "0.3.3"
638+source = "registry+https://github.com/rust-lang/crates.io-index"
639+
400640[[package]]
401641name = "serde"
402642version = "1.0.89"
@@ -412,7 +652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
412652dependencies = [
413653 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
414654 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
415− "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
655+ "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
416656]
417657
418658[[package]]
@@ -432,7 +672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
432672
433673[[package]]
434674name = "syn"
435−version = "0.15.29"
675+version = "0.15.30"
436676source = "registry+https://github.com/rust-lang/crates.io-index"
437677dependencies = [
438678 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -447,7 +687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
447687dependencies = [
448688 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
449689 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
450− "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
690+ "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
451691 "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
452692]
453693
@@ -464,11 +704,19 @@ name = "termion"
464704version = "1.5.1"
465705source = "registry+https://github.com/rust-lang/crates.io-index"
466706dependencies = [
467− "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
468− "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
707+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
708+ "redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)",
469709 "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
470710]
471711
712+[[package]]
713+name = "textwrap"
714+version = "0.10.0"
715+source = "registry+https://github.com/rust-lang/crates.io-index"
716+dependencies = [
717+ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
718+]
719+
472720[[package]]
473721name = "thread_local"
474722version = "0.3.6"
@@ -477,6 +725,15 @@ dependencies = [
477725 "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
478726]
479727
728+[[package]]
729+name = "tinytemplate"
730+version = "1.0.1"
731+source = "registry+https://github.com/rust-lang/crates.io-index"
732+dependencies = [
733+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
734+ "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
735+]
736+
480737[[package]]
481738name = "ucd-util"
482739version = "0.1.3"
@@ -487,6 +744,11 @@ name = "unicode-segmentation"
487744version = "1.2.1"
488745source = "registry+https://github.com/rust-lang/crates.io-index"
489746
747+[[package]]
748+name = "unicode-width"
749+version = "0.1.5"
750+source = "registry+https://github.com/rust-lang/crates.io-index"
751+
490752[[package]]
491753name = "unicode-xid"
492754version = "0.1.0"
@@ -502,17 +764,27 @@ name = "version_check"
502764version = "0.1.5"
503765source = "registry+https://github.com/rust-lang/crates.io-index"
504766
767+[[package]]
768+name = "walkdir"
769+version = "2.2.7"
770+source = "registry+https://github.com/rust-lang/crates.io-index"
771+dependencies = [
772+ "same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
773+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
774+ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
775+]
776+
505777[[package]]
506778name = "wasm-bindgen"
507−version = "0.2.39"
779+version = "0.2.40"
508780source = "registry+https://github.com/rust-lang/crates.io-index"
509781dependencies = [
510− "wasm-bindgen-macro 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
782+ "wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
511783]
512784
513785[[package]]
514786name = "wasm-bindgen-backend"
515−version = "0.2.39"
787+version = "0.2.40"
516788source = "registry+https://github.com/rust-lang/crates.io-index"
517789dependencies = [
518790 "bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -520,63 +792,63 @@ dependencies = [
520792 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
521793 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
522794 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
523− "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
524− "wasm-bindgen-shared 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
795+ "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
796+ "wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
525797]
526798
527799[[package]]
528800name = "wasm-bindgen-futures"
529−version = "0.3.16"
801+version = "0.3.17"
530802source = "registry+https://github.com/rust-lang/crates.io-index"
531803dependencies = [
532804 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
533− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
534− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
805+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
806+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
535807]
536808
537809[[package]]
538810name = "wasm-bindgen-macro"
539−version = "0.2.39"
811+version = "0.2.40"
540812source = "registry+https://github.com/rust-lang/crates.io-index"
541813dependencies = [
542814 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
543− "wasm-bindgen-macro-support 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
815+ "wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
544816]
545817
546818[[package]]
547819name = "wasm-bindgen-macro-support"
548−version = "0.2.39"
820+version = "0.2.40"
549821source = "registry+https://github.com/rust-lang/crates.io-index"
550822dependencies = [
551823 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
552824 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
553− "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
554− "wasm-bindgen-backend 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
555− "wasm-bindgen-shared 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
825+ "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
826+ "wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
827+ "wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
556828]
557829
558830[[package]]
559831name = "wasm-bindgen-shared"
560−version = "0.2.39"
832+version = "0.2.40"
561833source = "registry+https://github.com/rust-lang/crates.io-index"
562834
563835[[package]]
564836name = "wasm-bindgen-test"
565−version = "0.2.39"
837+version = "0.2.40"
566838source = "registry+https://github.com/rust-lang/crates.io-index"
567839dependencies = [
568840 "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
569841 "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
570− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
842+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
571843 "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
572− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
573− "wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
574− "wasm-bindgen-test-macro 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
844+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
845+ "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
846+ "wasm-bindgen-test-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
575847]
576848
577849[[package]]
578850name = "wasm-bindgen-test-macro"
579−version = "0.2.39"
851+version = "0.2.40"
580852source = "registry+https://github.com/rust-lang/crates.io-index"
581853dependencies = [
582854 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -585,7 +857,7 @@ dependencies = [
585857
586858[[package]]
587859name = "wasm-bindgen-webidl"
588−version = "0.2.39"
860+version = "0.2.40"
589861source = "registry+https://github.com/rust-lang/crates.io-index"
590862dependencies = [
591863 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -593,22 +865,22 @@ dependencies = [
593865 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
594866 "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
595867 "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
596− "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
597− "wasm-bindgen-backend 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
868+ "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)",
869+ "wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
598870 "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
599871]
600872
601873[[package]]
602874name = "web-sys"
603−version = "0.3.16"
875+version = "0.3.17"
604876source = "registry+https://github.com/rust-lang/crates.io-index"
605877dependencies = [
606878 "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
607879 "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
608− "js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
880+ "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
609881 "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
610− "wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
611− "wasm-bindgen-webidl 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
882+ "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
883+ "wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
612884]
613885
614886[[package]]
@@ -616,7 +888,7 @@ name = "weedle"
616888version = "0.8.0"
617889source = "registry+https://github.com/rust-lang/crates.io-index"
618890dependencies = [
619− "nom 4.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
891+ "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
620892]
621893
622894[[package]]
@@ -656,65 +928,97 @@ dependencies = [
656928]
657929
658930[metadata]
659−"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
931+"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c"
932+"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
660933"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
661934"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
662935"checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4"
663936"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
937+"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
664938"checksum bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6a6bc2ba935b1e2f810787ceba8dfe86cbc164cee55925cae715541186673c4"
665939"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
666−"checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d"
940+"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427"
941+"checksum cc 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ad0daef304fa0b4238f5f7ed7178774b43b06f6a9b6509f6642bef4ff1f7b9b2"
667942"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
943+"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
944+"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
668945"checksum console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
669946"checksum console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e7871d2947441b0fdd8e2bd1ce2a2f75304f896582c0d572162d48290683c48"
947+"checksum criterion 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "1c6e5ee5b9652d4f851418c448af105642e1f99e9a2741a8ff45c0d2c911b1e0"
948+"checksum criterion-plot 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4107e4a5abb94267e0149922b8ff49dc70a87cc202820fdbfc0d3e1edbdc4b16"
949+"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
950+"checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
951+"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
952+"checksum csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd1c44c58078cfbeaf11fbb3eac9ae5534c23004ed770cc4bfb48e658ae4f04"
953+"checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65"
954+"checksum either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c67353c641dc847124ea1902d69bd753dee9bb3beff9aa3662ecf86c971d1fac"
670955"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a"
671956"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
672957"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
958+"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
673959"checksum futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981"
674960"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
675961"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
676962"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
963+"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
677964"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
678−"checksum js-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "3970f67ef4a716efeba501dbedcc01b4686301b661798a42b24dc8a59d2c6b01"
965+"checksum js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "d4bda84b98977341bb6ba1086ecbd9eab8bc929de0f2923c118baf76c21dd0c8"
679966"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
680−"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1"
967+"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917"
681968"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
682969"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
683−"checksum nom 4.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22293d25d3f33a8567cc8a1dc20f40c7eeb761ce83d0fcca059858580790cac3"
970+"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
971+"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
972+"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
973+"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
974+"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
684975"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
685976"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
686977"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
687−"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
978+"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
979+"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
980+"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
981+"checksum rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "03b418169fb9c46533f326efd6eed2576699c44ca92d3052a066214a8d828929"
982+"checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473"
983+"checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356"
984+"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
985+"checksum redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)" = "d32b3053e5ced86e4bc0411fec997389532bf56b000e66cb4884eeeb41413d69"
688986"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
689−"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f"
690−"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
987+"checksum regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "559008764a17de49a3146b234641644ed37d118d1ef641a0bb573d146edc6ce0"
988+"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96"
691989"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619"
692990"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
991+"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
693992"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
993+"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
694994"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560"
695995"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c"
696996"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
697997"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
698−"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2"
998+"checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2"
699999"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
7001000"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
7011001"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
1002+"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
7021003"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
1004+"checksum tinytemplate 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7655088894274afb52b807bd3c87072daa1fedd155068b8705cabfd628956115"
7031005"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
7041006"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
1007+"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
7051008"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
7061009"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
7071010"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
708−"checksum wasm-bindgen 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "06e36714cc3947a9c80ddc9715edb319e445e5ab0d0b083debec0dd52558580e"
709−"checksum wasm-bindgen-backend 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "6764493f6eeb3f8632ffd418bf7db6a7e7aca98417c115833e4663976cd60a5e"
710−"checksum wasm-bindgen-futures 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4327c859969477dbc2033bbbe3a6cc045cf155ef283ad9f3c2d804ba27d3fd9b"
711−"checksum wasm-bindgen-macro 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a473501c40a2a4da83ae7176fead61f10d003554b701071a704dd0667397027a"
712−"checksum wasm-bindgen-macro-support 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "72e5a249738c6315f65ce5527632e2a3a067cdf1765422d8ac1572dcee14f819"
713−"checksum wasm-bindgen-shared 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "70f57643dac7decc3b53f0e4ffd027e225b79281648842eb46015e58a2400870"
714−"checksum wasm-bindgen-test 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "c864512cd0031acd354ba9252c27e426d95f8e7776e877104d54b2eb482999ac"
715−"checksum wasm-bindgen-test-macro 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ec739799351a564254f1f8a7b2b5fdb4117e11c5ee68082e47301722d8973e"
716−"checksum wasm-bindgen-webidl 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "ef834a1f96cdc811875c00f20680bf15e038e0d0d458a82772afec5fe31ec7c1"
717−"checksum web-sys 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "a8286eada98bf34db48fc89ca53428c529a906bf161c6bae4e022f826323eb38"
1011+"checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1"
1012+"checksum wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "9742fc4860f47bede1090a5e4b0cfc33afcd70cfdf45dd28f2cfb02d4662b0dd"
1013+"checksum wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d7f35ecbb4180513cdb9b298543321bcb278670730415cbb3205ff2c66a477"
1014+"checksum wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "67bfbec85e48e8915b6acfa1c4d90b770ce033c96dc2067688f3bccd00d3b9e1"
1015+"checksum wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c86b06bcd28e92e87d2c2ad208889b2f69ea33f79810b91ef660cc3de65a4c"
1016+"checksum wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "81d7338dd8c67e193d8ef18e5802dc03d8710456baa792c1c2e66847e57fd389"
1017+"checksum wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "0d57c3b66f2f3e4d96b50f49b7b7e2f4cfcddc88b15744433c98c5c105b26672"
1018+"checksum wasm-bindgen-test 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "85bbc3ad78a8d59a63a02d59345e8da9a3e0c1617f79e77fd1c3da6e047db63e"
1019+"checksum wasm-bindgen-test-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "114dfdf01bd3659dd79db05480fbd2751759d1c79cd9edc22f2fe611c866e932"
1020+"checksum wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "fc7052a577eefce17a439edfce5c2eedc8432fe7457bbe949624cdf946233fa7"
1021+"checksum web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "72d02003924c77af871ad74216f828b6e32af776cbb3f5dab3dcde2f2713c012"
7181022"checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044"
7191023"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
7201024"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
Cargo.toml+23
@@ -13,6 +13,26 @@ version = "0.1.0"
1313[badges]
1414travis-ci = { repository = "fitzgen/dodrio" }
1515
16+[lib]
17+path = "src/lib.rs"
18+bench = false
19+
20+[[test]]
21+name = "web"
22+path = "tests/web/main.rs"
23+required-features = ["xxx-unstable-internal-use-only"]
24+
25+[[bench]]
26+name = "benches"
27+path = "benches/benches.rs"
28+harness = false
29+required-features = ["xxx-unstable-internal-use-only"]
30+
31+[features]
32+# Don't use this feature! It only exists for exposing otherwise-internal APIs
33+# for testing and benchmarks.
34+xxx-unstable-internal-use-only = []
35+
1636[dependencies]
1737bumpalo = "2.3.0"
1838cfg-if = "0.1.7"
@@ -50,6 +70,9 @@ features = [
5070 "NodeList",
5171]
5272
73+[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
74+criterion = "0.2.10"
75+
5376[profile.release]
5477incremental = false
5578lto = true
README.md+4−6
@@ -31,12 +31,10 @@ struct Hello {
3131}
3232
3333impl Render for Hello {
34− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
35− where
36− 'a: 'bump,
37− {
38− span(bump)
39− .children([text("Hello, "), text(&self.who), text("!")])
34+ fn render<'a>(&self, cx: &mut RenderContext<a>) -> Node<'a> {
35+ let who = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
36+ div(cx)
37+ .children([text(who.into_bump_str())])
4038 .finish()
4139 }
4240}
benches/benches.rsnew file+129
@@ -0,0 +1,129 @@
1+#![cfg(all(
2+ feature = "xxx-unstable-internal-use-only",
3+ not(target_arch = "wasm32")
4+))]
5+
6+use criterion::{
7+ black_box, criterion_group, criterion_main, Benchmark, Criterion, ParameterizedBenchmark,
8+ Throughput,
9+};
10+use dodrio::{
11+ builder::*,
12+ bumpalo::{self, Bump},
13+ CachedSet, Node, Render, RenderContext, Vdom,
14+};
15+use std::cell::RefCell;
16+
17+/// The simplest thing we can render: `<div/>`.
18+struct Empty;
19+impl Render for Empty {
20+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
21+ div(&cx).finish()
22+ }
23+}
24+
25+/// Render a list that is `self.0` items long, has attributes and listeners.
26+struct SimpleList(usize);
27+impl Render for SimpleList {
28+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
29+ let mut children = bumpalo::collections::Vec::with_capacity_in(self.0, cx.bump);
30+ children.extend((0..self.0).map(|_| {
31+ li(&cx)
32+ .attr("class", "my-list-item")
33+ .on("click", |_root, _vdom, _event| {
34+ panic!("no one should call this")
35+ })
36+ .children([text("a list item")])
37+ .finish()
38+ }));
39+ ol(&cx).attr("id", "my-list").children(children).finish()
40+ }
41+}
42+
43+fn criterion_benchmark(c: &mut Criterion) {
44+ c.bench(
45+ "render",
46+ Benchmark::new("empty", {
47+ let mut bump = Bump::new();
48+ let cached_set = RefCell::new(CachedSet::default());
49+ move |b| {
50+ bump.reset();
51+ let mut cx = RenderContext::new(&bump, &cached_set);
52+ b.iter(|| {
53+ black_box(Empty.render(&mut cx));
54+ })
55+ }
56+ }),
57+ );
58+
59+ c.bench(
60+ "render",
61+ ParameterizedBenchmark::new(
62+ "list",
63+ {
64+ let mut bump = Bump::new();
65+ let cached_set = RefCell::new(CachedSet::default());
66+ move |b, n| {
67+ bump.reset();
68+ let mut cx = RenderContext::new(&bump, &cached_set);
69+ b.iter(|| {
70+ black_box(SimpleList(*n).render(&mut cx));
71+ })
72+ }
73+ },
74+ vec![
75+ // TODO: Only test one `n` value until
76+ // https://github.com/bheisler/criterion.rs/issues/269 is fixed.
77+ //
78+ // 100,
79+ // 1_000,
80+ 10_000,
81+ ],
82+ )
83+ .throughput(|n| Throughput::Elements(*n as u32)),
84+ );
85+
86+ c.bench(
87+ "render-and-diff",
88+ ParameterizedBenchmark::new(
89+ "same-list",
90+ |b, &n| {
91+ let vdom = Vdom::new(&(), SimpleList(n));
92+ b.iter(|| {
93+ vdom.immediately_render_and_diff(SimpleList(n));
94+ black_box(&vdom);
95+ })
96+ },
97+ vec![
98+ // TODO: Only test one `n` value until
99+ // https://github.com/bheisler/criterion.rs/issues/269 is fixed.
100+ //
101+ // 100,
102+ // 1_000,
103+ 10_000,
104+ ],
105+ )
106+ .with_function("empty-to-full-list-to-empty", |b, &n| {
107+ b.iter(|| {
108+ let vdom = Vdom::new(&(), Empty);
109+ vdom.immediately_render_and_diff(SimpleList(n));
110+ black_box(&vdom);
111+ vdom.immediately_render_and_diff(Empty);
112+ black_box(&vdom);
113+ });
114+ })
115+ .with_function("append-one-and-remove-one", |b, &n| {
116+ let vdom = Vdom::new(&(), SimpleList(n));
117+ b.iter(|| {
118+ vdom.immediately_render_and_diff(SimpleList(n + 1));
119+ black_box(&vdom);
120+ vdom.immediately_render_and_diff(SimpleList(n));
121+ black_box(&vdom);
122+ })
123+ })
124+ .throughput(|n| Throughput::Elements(*n as u32)),
125+ );
126+}
127+
128+criterion_group!(benches, criterion_benchmark);
129+criterion_main!(benches);
crates/js-api/src/lib.rs+19−30
@@ -66,8 +66,7 @@ And here is a Rust rendering component that internally uses the JS rendering
6666component:
6767
6868```rust,no_run
69−use dodrio::bumpalo::Bump;
70−use dodrio::{Node, Render, Vdom};
69+use dodrio::{Node, Render, RenderContext, Vdom};
7170use dodrio_js_api::JsRender;
7271use js_sys::Object;
7372use wasm_bindgen::prelude::*;
@@ -101,15 +100,12 @@ impl GreetingViaJs {
101100/// And finally the `Render` implementation! This adds a `<p>` element and some
102101/// text around whatever the inner JS `Greeting` component renders.
103102impl Render for GreetingViaJs {
104− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
105− where
106− 'a: 'bump,
107− {
103+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
108104 use dodrio::builder::*;
109− p(bump)
105+ p(&cx)
110106 .children([
111107 text("JavaScript says: "),
112− self.js.render(bump),
108+ self.js.render(cx),
113109 ])
114110 .finish()
115111 }
@@ -119,11 +115,7 @@ impl Render for GreetingViaJs {
119115 */
120116#![deny(missing_docs, missing_debug_implementations)]
121117
122−use dodrio::{
123− builder,
124− bumpalo::{self, Bump},
125− Node, Render,
126−};
118+use dodrio::{builder, bumpalo, Node, Render, RenderContext};
127119use futures::prelude::*;
128120use js_sys::{Object, Promise, Reflect};
129121use wasm_bindgen::prelude::*;
@@ -222,11 +214,8 @@ impl JsRender {
222214}
223215
224216impl Render for JsRender {
225− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
226− where
227− 'a: 'bump,
228− {
229− create(bump, self.render())
217+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
218+ create(cx, self.render())
230219 }
231220}
232221
@@ -234,9 +223,9 @@ fn has_property(obj: &Object, property: &str) -> bool {
234223 Reflect::has(obj, &property.into()).unwrap_or_default()
235224}
236225
237−fn create(bump: &Bump, val: JsValue) -> Node {
226+fn create<'a>(cx: &mut RenderContext<'a>, val: JsValue) -> Node<'a> {
238227 if let Some(txt) = val.as_string() {
239− let text = bumpalo::collections::String::from_str_in(&txt, bump);
228+ let text = bumpalo::collections::String::from_str_in(&txt, cx.bump);
240229 return builder::text(text.into_bump_str());
241230 }
242231
@@ -251,12 +240,12 @@ fn create(bump: &Bump, val: JsValue) -> Node {
251240 );
252241
253242 let tag_name = elem.tag_name();
254− let tag_name = bumpalo::collections::String::from_str_in(&tag_name, bump);
243+ let tag_name = bumpalo::collections::String::from_str_in(&tag_name, cx.bump);
255244
256− builder::ElementBuilder::new(bump, tag_name.into_bump_str())
245+ builder::ElementBuilder::new(cx.bump, tag_name.into_bump_str())
257246 .listeners({
258247 let mut listeners =
259− bumpalo::collections::Vec::new_in(bump);
248+ bumpalo::collections::Vec::new_in(cx.bump);
260249 if has_property(&elem, "listeners") {
261250 let js_listeners = elem.listeners();
262251 listeners.reserve(js_listeners.length() as usize);
@@ -275,10 +264,10 @@ fn create(bump: &Bump, val: JsValue) -> Node {
275264 "listener objects returned by JS render methods must have an `callback` property"
276265 );
277266 let on = listener.on();
278− let on = bumpalo::collections::String::from_str_in(&on, bump);
267+ let on = bumpalo::collections::String::from_str_in(&on, cx.bump);
279268 let callback = listener.callback();
280269 let elem = elem.clone();
281− listeners.push(builder::on(bump, on.into_bump_str(), move |_root, vdom, event| {
270+ listeners.push(builder::on(cx.bump, on.into_bump_str(), move |_root, vdom, event| {
282271 let vdom = VdomWeak::new(vdom);
283272 let vdom: JsValue = vdom.into();
284273 if let Err(e) = callback.call2(&elem, &vdom, &event) {
@@ -290,7 +279,7 @@ fn create(bump: &Bump, val: JsValue) -> Node {
290279 listeners
291280 })
292281 .attributes({
293− let mut attributes = bumpalo::collections::Vec::new_in(bump);
282+ let mut attributes = bumpalo::collections::Vec::new_in(cx.bump);
294283 if has_property(&elem, "attributes") {
295284 let js_attributes = elem.attributes();
296285 attributes.reserve(js_attributes.length() as usize);
@@ -309,21 +298,21 @@ fn create(bump: &Bump, val: JsValue) -> Node {
309298 "attribute objects returned by JS render methods must have a `value` property"
310299 );
311300 let name = attribute.name();
312− let name = bumpalo::collections::String::from_str_in(&name, bump);
301+ let name = bumpalo::collections::String::from_str_in(&name, cx.bump);
313302 let value = attribute.value();
314− let value = bumpalo::collections::String::from_str_in(&value, bump);
303+ let value = bumpalo::collections::String::from_str_in(&value, cx.bump);
315304 attributes.push(builder::attr(name.into_bump_str(), value.into_bump_str()));
316305 });
317306 }
318307 attributes
319308 })
320309 .children({
321− let mut children = bumpalo::collections::Vec::new_in(bump);
310+ let mut children = bumpalo::collections::Vec::new_in(cx.bump);
322311 if has_property(&elem, "children") {
323312 let js_children = elem.children();
324313 children.reserve(js_children.length() as usize);
325314 js_children.for_each(&mut |child, _index, _array| {
326− children.push(create(bump, child));
315+ children.push(create(cx, child));
327316 });
328317 }
329318 children
examples/counter/src/lib.rs+6−10
@@ -1,5 +1,4 @@
1−use dodrio::bumpalo::{self, Bump};
2−use dodrio::Render;
1+use dodrio::{bumpalo, Render, RenderContext};
32use log::*;
43use wasm_bindgen::prelude::*;
54
@@ -28,18 +27,15 @@ impl Counter {
2827// The `Render` implementation for `Counter`s displays the current count and has
2928// buttons to increment and decrement the count.
3029impl Render for Counter {
31− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> dodrio::Node<'bump>
32− where
33− 'a: 'bump,
34− {
30+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> dodrio::Node<'a> {
3531 use dodrio::builder::*;
3632
3733 // Stringify the count as a bump-allocated string.
38− let count = bumpalo::format!(in bump, "{}", self.count);
34+ let count = bumpalo::format!(in cx.bump, "{}", self.count);
3935
40− div(bump)
36+ div(&cx)
4137 .children([
42− button(bump)
38+ button(&cx)
4339 .on("click", |root, vdom, _event| {
4440 // Cast the root render component to a `Counter`, since
4541 // we know that's what it is.
@@ -55,7 +51,7 @@ impl Render for Counter {
5551 .children([text("+")])
5652 .finish(),
5753 text(count.into_bump_str()),
58− button(bump)
54+ button(&cx)
5955 .on("click", |root, vdom, _event| {
6056 // Same as above, but decrementing instead of incrementing.
6157 root.unwrap_mut::<Counter>().decrement();
examples/game-of-life/src/lib.rs+8−11
@@ -1,5 +1,4 @@
1−use dodrio::bumpalo::{self, Bump};
2−use dodrio::{Node, Render, Vdom};
1+use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
32use futures::prelude::*;
43use std::cell::RefCell;
54use std::rc::Rc;
@@ -129,20 +128,18 @@ impl Universe {
129128
130129/// The rendering implementation for our Game of Life.
131130impl Render for Universe {
132− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
133− where
134− 'a: 'bump,
135− {
131+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
136132 use dodrio::builder::*;
137133
138− let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, bump);
134+ let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, cx.bump);
139135
140136 for row in self.cells.chunks(self.width as usize) {
141− let mut cells = bumpalo::collections::Vec::with_capacity_in(self.width as usize, bump);
137+ let mut cells =
138+ bumpalo::collections::Vec::with_capacity_in(self.width as usize, cx.bump);
142139
143140 for cell in row {
144141 cells.push(
145− span(bump)
142+ span(&cx)
146143 .attr("class", "cell")
147144 .attr(
148145 "style",
@@ -155,10 +152,10 @@ impl Render for Universe {
155152 );
156153 }
157154
158− rows.push(div(bump).attr("class", "row").children(cells).finish());
155+ rows.push(div(&cx).attr("class", "row").children(cells).finish());
159156 }
160157
161− div(bump).attr("class", "universe").children(rows).finish()
158+ div(&cx).attr("class", "universe").children(rows).finish()
162159 }
163160}
164161
examples/hello-world/src/lib.rs+12−14
@@ -1,24 +1,20 @@
1−use dodrio::bumpalo::Bump;
2−use dodrio::{Node, Render, Vdom};
1+use dodrio::{builder::*, bumpalo};
2+use dodrio::{Node, Render, RenderContext, Vdom};
33use wasm_bindgen::prelude::*;
44
55/// A rendering component that displays a greeting.
6−struct Hello<'who> {
6+struct Hello {
77 /// Who to greet.
8− who: &'who str,
8+ who: String,
99}
1010
1111// The `Render` implementation describes how to render a `Hello` component into
1212// HTML.
13−impl<'who> Render for Hello<'who> {
14− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
15− where
16− 'a: 'bump,
17− {
18− use dodrio::builder::*;
19− p(bump)
20− .children([text("Hello, "), text(self.who), text("!")])
21− .finish()
13+impl Render for Hello {
14+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
15+ let msg = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
16+ let msg = msg.into_bump_str();
17+ p(&cx).children([text(msg)]).finish()
2218 }
2319}
2420
@@ -33,7 +29,9 @@ pub fn run() {
3329 let body = document.body().unwrap_throw();
3430
3531 // Create a new `Hello` render component.
36− let component = Hello { who: "World" };
32+ let component = Hello {
33+ who: String::from("World"),
34+ };
3735
3836 // Create a virtual DOM and mount it and the `Hello` render component to the
3937 // `<body>`.
examples/input-form/src/lib.rs+9−10
@@ -1,5 +1,4 @@
1−use dodrio::bumpalo::{self, Bump};
2−use dodrio::{Node, Render};
1+use dodrio::{bumpalo, Node, Render, RenderContext};
32use wasm_bindgen::prelude::*;
43use wasm_bindgen::JsCast;
54
@@ -25,17 +24,17 @@ impl SayHelloTo {
2524// The `Render` implementation has a text `<input>` and a `<div>` that shows a
2625// greeting to the `<input>`'s value.
2726impl Render for SayHelloTo {
28− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
29− where
30− 'a: 'bump,
31− {
27+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
3228 use dodrio::builder::*;
3329
34− div(bump)
30+ div(&cx)
3531 .children([
36− input(bump)
32+ input(&cx)
3733 .attr("type", "text")
38− .attr("value", &self.who)
34+ .attr(
35+ "value",
36+ bumpalo::format!(in cx.bump, "{}", self.who).into_bump_str(),
37+ )
3938 .on("input", |root, vdom, event| {
4039 // If the event's target is our input...
4140 let input = match event
@@ -55,7 +54,7 @@ impl Render for SayHelloTo {
5554 vdom.schedule_render();
5655 })
5756 .finish(),
58− text(bumpalo::format!(in bump, "Hello, {}!", self.who).into_bump_str()),
57+ text(bumpalo::format!(in cx.bump, "Hello, {}!", self.who).into_bump_str()),
5958 ])
6059 .finish()
6160 }
examples/js-component/src/lib.rs+4−8
@@ -1,5 +1,4 @@
1−use dodrio::bumpalo::Bump;
2−use dodrio::{Node, Render, Vdom};
1+use dodrio::{Node, Render, RenderContext, Vdom};
32use dodrio_js_api::JsRender;
43use js_sys::Object;
54use wasm_bindgen::prelude::*;
@@ -33,13 +32,10 @@ impl GreetingViaJs {
3332/// Here's the `Render` implementation! This adds a `<p>` element and some text
3433/// around whatever the inner JS `Greeting` component renders.
3534impl Render for GreetingViaJs {
36− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
37− where
38− 'a: 'bump,
39− {
35+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
4036 use dodrio::builder::*;
41− p(bump)
42− .children([text("JavaScript says: "), self.js.render(bump)])
37+ p(&cx)
38+ .children([text("JavaScript says: "), self.js.render(cx)])
4339 .finish()
4440 }
4541}
examples/moire/src/lib.rs+33−58
@@ -1,8 +1,7 @@
11mod colors;
22
33use cfg_if::cfg_if;
4−use dodrio::bumpalo::{self, Bump};
5−use dodrio::{Node, Render, Vdom};
4+use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
65use std::cell::RefCell;
76use std::rc::Rc;
87use std::str;
@@ -107,10 +106,7 @@ impl Moire {
107106}
108107
109108impl Render for Moire {
110− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
111− where
112− 'a: 'bump,
113− {
109+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
114110 use dodrio::builder::*;
115111
116112 let elapsed = web_sys::window()
@@ -120,18 +116,18 @@ impl Render for Moire {
120116 .now();
121117 let elapsed = elapsed / 1600.0;
122118
123− main(bump)
124− .attr("style", self.moire_style(bump, elapsed))
119+ main(&cx)
120+ .attr("style", self.moire_style(cx, elapsed))
125121 .children([
126122 // The `<input>` that lets users control how many circles to
127123 // render.
128− input(bump)
124+ input(&cx)
129125 .attr("id", "circle-count")
130126 .attr("type", "range")
131127 .attr("min", "30")
132128 .attr("max", "500")
133129 .attr("value", {
134− let count = bumpalo::format!(in bump, "{}", self.count);
130+ let count = bumpalo::format!(in cx.bump, "{}", self.count);
135131 count.into_bump_str()
136132 })
137133 .on("change", |root, _vdom, event| {
@@ -144,8 +140,8 @@ impl Render for Moire {
144140 })
145141 .finish(),
146142 // And the actual concentric circles that form the Moire patterns.
147− self.orbiting_objects(bump, elapsed),
148− self.lemniscate_objects(bump, elapsed),
143+ self.orbiting_objects(cx, elapsed),
144+ self.lemniscate_objects(cx, elapsed),
149145 ])
150146 .finish()
151147 }
@@ -154,14 +150,11 @@ impl Render for Moire {
154150/// Rendering helper methods.
155151impl Moire {
156152 /// Generate the main Moire element's inline CSS styles.
157− fn moire_style<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> &'bump str
158− where
159− 'a: 'bump,
160− {
153+ fn moire_style<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> &'a str {
161154 let elapsed = elapsed / 3.0;
162155 let color = colors::get_interpolated_color(|(_, bg)| bg, elapsed % 1.0);
163156 let style = bumpalo::format!(
164− in bump,
157+ in cx.bump,
165158 "background-color: rgb({}, {}, {})",
166159 color.r,
167160 color.g,
@@ -171,93 +164,75 @@ impl Moire {
171164 }
172165
173166 /// Generate the orbiting circle objects.
174− fn orbiting_objects<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> Node<'bump>
175− where
176− 'a: 'bump,
177− {
167+ fn orbiting_objects<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> Node<'a> {
178168 const D: f64 = 200.0;
179169 let x = elapsed.sin() * D;
180170 let y = elapsed.cos() * D;
181− self.moving_object(bump, elapsed, x, y)
171+ self.moving_object(cx, elapsed, x, y)
182172 }
183173
184174 /// Generate the lemniscate circle objects that interact with the orbiting
185175 /// circle objects to form Moire patterns.
186− fn lemniscate_objects<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> Node<'bump>
187− where
188− 'a: 'bump,
189− {
176+ fn lemniscate_objects<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> Node<'a> {
190177 const A: f64 = 150.0;
191178 let x = elapsed.sin() * A;
192179 let y = elapsed.sin() * elapsed.cos() * A;
193− self.moving_object(bump, elapsed, x, y)
180+ self.moving_object(cx, elapsed, x, y)
194181 }
195182
196− fn moving_object<'a, 'bump>(
197− &'a self,
198− bump: &'bump Bump,
183+ fn moving_object<'a>(
184+ &self,
185+ cx: &mut RenderContext<'a>,
199186 elapsed: f64,
200187 x: f64,
201188 y: f64,
202− ) -> Node<'bump>
203− where
204− 'a: 'bump,
205− {
189+ ) -> Node<'a> {
206190 use dodrio::builder::*;
207− div(bump)
191+ div(&cx)
208192 .attr("class", "object")
209− .attr("style", self.moving_object_style(bump, x, y))
210− .children([self.circle(bump, elapsed, self.count)])
193+ .attr("style", self.moving_object_style(cx, x, y))
194+ .children([self.circle(cx, elapsed, self.count)])
211195 .finish()
212196 }
213197
214198 /// Generate inline CSS styles for a moving object.
215− fn moving_object_style<'a, 'bump>(&'a self, bump: &'bump Bump, x: f64, y: f64) -> &'bump str
216− where
217− 'a: 'bump,
218− {
219− bumpalo::format!(in bump, "left: {}px; top: {}px;", x, y).into_bump_str()
199+ fn moving_object_style<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64) -> &'a str {
200+ bumpalo::format!(in cx.bump, "left: {}px; top: {}px;", x, y).into_bump_str()
220201 }
221202
222203 /// Recursively generate `self.count` concentric circles.
223− fn circle<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64, n: u32) -> Node<'bump>
224− where
225− 'a: 'bump,
226− {
204+ fn circle<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64, n: u32) -> Node<'a> {
227205 use dodrio::builder::*;
228206
229207 let r = n * 16;
230208
231− let mut circle = div(bump)
209+ let mut circle = div(&cx)
232210 .attr("class", "circle")
233211 .attr("data-radius", {
234− let r = bumpalo::format!(in bump, "{}", r);
212+ let r = bumpalo::format!(in cx.bump, "{}", r);
235213 r.into_bump_str()
236214 })
237− .attr("style", self.circle_style(bump, elapsed, n, r));
215+ .attr("style", self.circle_style(cx, elapsed, n, r));
238216
239217 if n > 0 {
240− circle = circle.child(self.circle(bump, elapsed, n - 1));
218+ circle = circle.child(self.circle(cx, elapsed, n - 1));
241219 }
242220
243221 circle.finish()
244222 }
245223
246224 /// Generate inline CSS styles for a circle.
247− fn circle_style<'a, 'bump>(
248− &'a self,
249− bump: &'bump Bump,
225+ fn circle_style<'a>(
226+ &self,
227+ cx: &mut RenderContext<'a>,
250228 elapsed: f64,
251229 n: u32,
252230 r: u32,
253− ) -> &'bump str
254− where
255− 'a: 'bump,
256− {
231+ ) -> &'a str {
257232 let (border, alpha) = self.circle_color(elapsed, n);
258233 let margin = r / 2 + 3;
259234 let style = bumpalo::format!(
260− in bump,
235+ in cx.bump,
261236 "border-color: rgba({}, {}, {}, {});\
262237 margin-left: -{}px;\
263238 margin-top: -{}px;\
examples/sierpinski-triangle/src/lib.rs+23−38
@@ -1,5 +1,4 @@
1−use dodrio::bumpalo::{self, Bump};
2−use dodrio::{Node, Render, Vdom};
1+use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
32use std::cell::RefCell;
43use std::rc::Rc;
54use wasm_bindgen::prelude::*;
@@ -18,14 +17,11 @@ impl Container {
1817 }
1918
2019 // Generate the container style to fluctuate triangle width.
21− fn container_transform<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> &'bump str
22− where
23− 'a: 'bump,
24− {
20+ fn container_transform<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> &'a str {
2521 let t = elapsed % 10.0;
2622 let scale = 0.45 + (if t > 5.0 { 10.0 - t } else { t }) / 40.0;
2723 let transform = bumpalo::format!(
28− in bump,
24+ in cx.bump,
2925 "transform: scaleX({}) scaleY(0.7) translateZ(0.1px)",
3026 scale
3127 );
@@ -33,14 +29,11 @@ impl Container {
3329 }
3430
3531 // Generate the dot's position on the grid.
36− fn dot_style<'a, 'bump>(&'a self, bump: &'bump Bump, x: f64, y: f64) -> &'bump str
37− where
38− 'a: 'bump,
39− {
32+ fn dot_style<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64) -> &'a str {
4033 let s = self.size * 1.3;
4134 let radius = s / 2.0;
4235 let styles = bumpalo::format!(
43− in bump,
36+ in cx.bump,
4437 r#"
4538 width: {}px;
4639 height: {}px;
@@ -55,54 +48,46 @@ impl Container {
5548 }
5649
5750 // Create a dot node.
58− fn dot<'a, 'bump>(&'a self, bump: &'bump Bump, x: f64, y: f64, content: u32) -> Node<'bump>
59− where
60− 'a: 'bump,
61− {
51+ fn dot<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64, content: u32) -> Node<'a> {
6252 use dodrio::builder::{div, text};
6353
64− div(bump)
54+ div(&cx)
6555 .attr("class", "dot")
66− .attr("style", self.dot_style(bump, x, y))
56+ .attr("style", self.dot_style(cx, x, y))
6757 .child(text(
68− bumpalo::format!(in bump, "{}", content).into_bump_str(),
58+ bumpalo::format!(in cx.bump, "{}", content).into_bump_str(),
6959 ))
7060 .finish()
7161 }
7262
7363 // Create a flattened vector of dot nodes (arranged in recursive triangles)
74− fn triangle<'a, 'bump>(
75− &'a self,
76− bump: &'bump Bump,
64+ fn triangle<'a>(
65+ &self,
66+ cx: &mut RenderContext<'a>,
7767 x: f64,
7868 y: f64,
7969 s: f64,
8070 content: u32,
81− children: &mut bumpalo::collections::Vec<Node<'bump>>,
82− ) where
83− 'a: 'bump,
84− {
71+ children: &mut bumpalo::collections::Vec<Node<'a>>,
72+ ) {
8573 if s <= self.size {
86− children.push(self.dot(bump, x, y, content));
74+ children.push(self.dot(cx, x, y, content));
8775 return;
8876 }
8977
9078 let s = s / 2.0;
9179
9280 // Top of triangle
93− self.triangle(bump, x, y - (s / 2.0), s, content, children);
81+ self.triangle(cx, x, y - (s / 2.0), s, content, children);
9482 // Bottom-left of triangle
95− self.triangle(bump, x - s, y + (s / 2.0), s, content, children);
83+ self.triangle(cx, x - s, y + (s / 2.0), s, content, children);
9684 // Bottom-right of triangle
97− self.triangle(bump, x + s, y + (s / 2.0), s, content, children);
85+ self.triangle(cx, x + s, y + (s / 2.0), s, content, children);
9886 }
9987}
10088
10189impl Render for Container {
102− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
103− where
104− 'a: 'bump,
105− {
90+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
10691 use dodrio::builder::div;
10792
10893 let elapsed = web_sys::window()
@@ -114,12 +99,12 @@ impl Render for Container {
11499
115100 let modulus = elapsed as u32 % 10;
116101
117− let mut children = bumpalo::collections::Vec::new_in(bump);
118− self.triangle(bump, 0.0, 0.0, 1000.0, modulus, &mut children);
102+ let mut children = bumpalo::collections::Vec::new_in(cx.bump);
103+ self.triangle(cx, 0.0, 0.0, 1000.0, modulus, &mut children);
119104
120− div(bump)
105+ div(&cx)
121106 .attr("class", "container")
122− .attr("style", self.container_transform(bump, elapsed))
107+ .attr("style", self.container_transform(cx, elapsed))
123108 .children(children)
124109 .finish()
125110 }
examples/todomvc/src/todo.rs+11−13
@@ -1,7 +1,7 @@
11//! Type definition and `dodrio::Render` implementation for a single todo item.
22
33use crate::keys;
4−use dodrio::{bumpalo::Bump, Node, Render, RootRender, VdomWeak};
4+use dodrio::{Node, Render, RenderContext, RootRender, VdomWeak};
55use serde::{Deserialize, Serialize};
66use std::marker::PhantomData;
77use wasm_bindgen::{prelude::*, JsCast};
@@ -93,10 +93,7 @@ impl<C> Todo<C> {
9393}
9494
9595impl<C: TodoActions> Render for Todo<C> {
96− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
97− where
98− 'a: 'bump,
99− {
96+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
10097 use dodrio::{
10198 builder::*,
10299 bumpalo::{self, collections::String},
@@ -104,10 +101,11 @@ impl<C: TodoActions> Render for Todo<C> {
104101
105102 let id = self.id;
106103 let title = self.edits.as_ref().unwrap_or(&self.title);
104+ let title = bumpalo::format!(in cx.bump, "{}", title).into_bump_str();
107105
108− li(bump)
106+ li(&cx)
109107 .attr("class", {
110− let mut class = String::new_in(bump);
108+ let mut class = String::new_in(cx.bump);
111109 if self.completed {
112110 class.push_str("completed ");
113111 }
@@ -117,10 +115,10 @@ impl<C: TodoActions> Render for Todo<C> {
117115 class.into_bump_str()
118116 })
119117 .children([
120− div(bump)
118+ div(&cx)
121119 .attr("class", "view")
122120 .children([
123− input(bump)
121+ input(&cx)
124122 .attr("class", "toggle")
125123 .attr("type", "checkbox")
126124 .bool_attr("checked", self.completed)
@@ -128,13 +126,13 @@ impl<C: TodoActions> Render for Todo<C> {
128126 C::toggle_completed(root, vdom, id);
129127 })
130128 .finish(),
131− label(bump)
129+ label(&cx)
132130 .on("dblclick", move |root, vdom, _event| {
133131 C::begin_editing(root, vdom, id);
134132 })
135133 .children([text(title)])
136134 .finish(),
137− button(bump)
135+ button(&cx)
138136 .attr("class", "destroy")
139137 .on("click", move |root, vdom, _event| {
140138 C::delete(root, vdom, id);
@@ -142,13 +140,13 @@ impl<C: TodoActions> Render for Todo<C> {
142140 .finish(),
143141 ])
144142 .finish(),
145− input(bump)
143+ input(&cx)
146144 .attr("class", "edit")
147145 .attr("value", title)
148146 .attr("name", "title")
149147 .attr(
150148 "id",
151− bumpalo::format!(in bump, "todo-{}", id).into_bump_str(),
149+ bumpalo::format!(in cx.bump, "todo-{}", id).into_bump_str(),
152150 )
153151 .on("input", move |root, vdom, event| {
154152 let input = event
examples/todomvc/src/todos.rs+36−51
@@ -5,10 +5,7 @@ use crate::controller::Controller;
55use crate::todo::{Todo, TodoActions};
66use crate::visibility::Visibility;
77use crate::{keys, utils};
8−use dodrio::{
9− bumpalo::{self, Bump},
10− Node, Render, RootRender, VdomWeak,
11−};
8+use dodrio::{bumpalo, Node, Render, RenderContext, RootRender, VdomWeak};
129use serde::{Deserialize, Serialize};
1310use std::marker::PhantomData;
1411use std::mem;
@@ -136,17 +133,14 @@ impl<C> Todos<C> {
136133
137134/// Rendering helpers.
138135impl<C: TodosActions> Todos<C> {
139− fn header<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
140− where
141− 'a: 'bump,
142− {
136+ fn header<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
143137 use dodrio::builder::*;
144138
145− header(bump)
139+ header(&cx)
146140 .attr("class", "header")
147141 .children([
148− h1(bump).children([text("todos")]).finish(),
149− input(bump)
142+ h1(&cx).children([text("todos")]).finish(),
143+ input(&cx)
150144 .on("input", |root, vdom, event| {
151145 let input = event
152146 .target()
@@ -163,19 +157,19 @@ impl<C: TodosActions> Todos<C> {
163157 .attr("class", "new-todo")
164158 .attr("placeholder", "What needs to be done?")
165159 .attr("autofocus", "")
166− .attr("value", &self.draft)
160+ .attr(
161+ "value",
162+ bumpalo::format!(in cx.bump, "{}", self.draft).into_bump_str(),
163+ )
167164 .finish(),
168165 ])
169166 .finish()
170167 }
171168
172− fn todos_list<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
173− where
174− 'a: 'bump,
175− {
169+ fn todos_list<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
176170 use dodrio::{builder::*, bumpalo::collections::Vec};
177171
178− let mut todos = Vec::with_capacity_in(self.todos.len(), bump);
172+ let mut todos = Vec::with_capacity_in(self.todos.len(), cx.bump);
179173 todos.extend(
180174 self.todos
181175 .iter()
@@ -184,10 +178,10 @@ impl<C: TodosActions> Todos<C> {
184178 Visibility::Active => !t.is_complete(),
185179 Visibility::Completed => t.is_complete(),
186180 })
187− .map(|t| t.render(bump)),
181+ .map(|t| t.render(cx)),
188182 );
189183
190− section(bump)
184+ section(&cx)
191185 .attr("class", "main")
192186 .attr(
193187 "visibility",
@@ -198,7 +192,7 @@ impl<C: TodosActions> Todos<C> {
198192 },
199193 )
200194 .children([
201− input(bump)
195+ input(&cx)
202196 .attr("class", "toggle-all")
203197 .attr("id", "toggle-all")
204198 .attr("type", "checkbox")
@@ -208,19 +202,16 @@ impl<C: TodosActions> Todos<C> {
208202 C::toggle_all(root, vdom);
209203 })
210204 .finish(),
211− label(bump)
205+ label(&cx)
212206 .attr("for", "toggle-all")
213207 .children([text("Mark all as complete")])
214208 .finish(),
215− ul(bump).attr("class", "todo-list").children(todos).finish(),
209+ ul(&cx).attr("class", "todo-list").children(todos).finish(),
216210 ])
217211 .finish()
218212 }
219213
220− fn footer<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
221− where
222− 'a: 'bump,
223− {
214+ fn footer<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
224215 use dodrio::builder::*;
225216
226217 let completed_count = self.todos.iter().filter(|t| t.is_complete()).count();
@@ -230,36 +221,36 @@ impl<C: TodosActions> Todos<C> {
230221 } else {
231222 " items left"
232223 };
233− let incomplete_count = bumpalo::format!(in bump, "{}", incomplete_count);
224+ let incomplete_count = bumpalo::format!(in cx.bump, "{}", incomplete_count);
234225
235226 let clear_completed_text = bumpalo::format!(
236− in bump,
227+ in cx.bump,
237228 "Clear completed ({})",
238229 self.todos.iter().filter(|t| t.is_complete()).count()
239230 );
240231
241− footer(bump)
232+ footer(&cx)
242233 .attr("class", "footer")
243234 .bool_attr("hidden", self.todos.is_empty())
244235 .children([
245− span(bump)
236+ span(&cx)
246237 .attr("class", "todo-count")
247238 .children([
248− strong(bump)
239+ strong(&cx)
249240 .children([text(incomplete_count.into_bump_str())])
250241 .finish(),
251242 text(items_left),
252243 ])
253244 .finish(),
254− ul(bump)
245+ ul(&cx)
255246 .attr("class", "filters")
256247 .children([
257− self.visibility_swap(bump, "#/", Visibility::All),
258− self.visibility_swap(bump, "#/active", Visibility::Active),
259− self.visibility_swap(bump, "#/completed", Visibility::Completed),
248+ self.visibility_swap(cx, "#/", Visibility::All),
249+ self.visibility_swap(cx, "#/active", Visibility::Active),
250+ self.visibility_swap(cx, "#/completed", Visibility::Completed),
260251 ])
261252 .finish(),
262− button(bump)
253+ button(&cx)
263254 .on("click", |root, vdom, _event| {
264255 C::delete_completed(root, vdom);
265256 })
@@ -271,22 +262,19 @@ impl<C: TodosActions> Todos<C> {
271262 .finish()
272263 }
273264
274− fn visibility_swap<'a, 'bump>(
275− &'a self,
276− bump: &'bump Bump,
265+ fn visibility_swap<'a>(
266+ &self,
267+ cx: &mut RenderContext<'a>,
277268 url: &'static str,
278269 target_vis: Visibility,
279− ) -> Node<'bump>
280− where
281− 'a: 'bump,
282− {
270+ ) -> Node<'a> {
283271 use dodrio::builder::*;
284272
285− li(bump)
273+ li(&cx)
286274 .on("click", move |root, vdom, _event| {
287275 C::change_visibility(root, vdom, target_vis);
288276 })
289− .children([a(bump)
277+ .children([a(&cx)
290278 .attr("href", url)
291279 .attr(
292280 "class",
@@ -303,14 +291,11 @@ impl<C: TodosActions> Todos<C> {
303291}
304292
305293impl<C: TodosActions> Render for Todos<C> {
306− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
307− where
308− 'a: 'bump,
309− {
294+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
310295 use dodrio::builder::*;
311296
312− div(bump)
313− .children([self.header(bump), self.todos_list(bump), self.footer(bump)])
297+ div(&cx)
298+ .children([self.header(cx), self.todos_list(cx), self.footer(cx)])
314299 .finish()
315300 }
316301}
src/builder.rs+11−4
@@ -54,7 +54,11 @@ impl<'a>
5454 /// let my_element_builder = ElementBuilder::new(&b, tag_name);
5555 /// # fn flip_coin() -> bool { true }
5656 /// ```
57− pub fn new(bump: &'a Bump, tag_name: &'a str) -> Self {
57+ pub fn new<B>(bump: B, tag_name: &'a str) -> Self
58+ where
59+ B: Into<&'a Bump>,
60+ {
61+ let bump = bump.into();
5862 ElementBuilder {
5963 bump,
6064 tag_name,
@@ -378,14 +382,17 @@ macro_rules! builder_constructors {
378382 $(
379383 $(#[$attr])*
380384 #[inline]
381− pub fn $name<'a>(
382− bump: &'a Bump,
385+ pub fn $name<'a, B>(
386+ bump: B,
383387 ) -> ElementBuilder<
384388 'a,
385389 bumpalo::collections::Vec<'a, Listener<'a>>,
386390 bumpalo::collections::Vec<'a, Attribute<'a>>,
387391 bumpalo::collections::Vec<'a, Node<'a>>,
388− > {
392+ >
393+ where
394+ B: Into<&'a Bump>
395+ {
389396 ElementBuilder::new(bump, stringify!($name))
390397 }
391398 )*
src/cached.rs+67−70
@@ -1,26 +1,16 @@
1−use crate::{Node, Render};
2−use bumpalo::Bump;
3−use std::cell::RefCell;
4−use std::mem;
1+use crate::{
2+ cached_set::{CacheId, CachedSet},
3+ Node, Render, RenderContext,
4+};
5+use std::cell::Cell;
56use std::ops::{Deref, DerefMut};
67
7−// TODO: This implementation will only cache the rendering (generation of the
8−// virtual DOM) but not the diffing of the cached subtree. We could skip diffing
9−// for cached tree by adding `fn is_cached(&self) -> bool` to `Node` that we can
10−// check during diffing. This comes at the cost of bloating `Node`'s size (since
11−// we don't have any padding to sneak an extra `bool` field into). We should
12−// investigate whether it is worth adding this or not.
13−
148/// A renderable that supports caching for when rendering is expensive but can
159/// generate the same DOM tree.
16−#[derive(Debug)]
10+#[derive(Clone, Debug)]
1711pub struct Cached<R> {
1812 inner: R,
19− bump: bumpalo::Bump,
20− // Actually a self-reference into `self.bump`. Safe because we ensure that
21− // whenever we hand out a cached node, we use a lifetime that cannot outlive
22− // its owning `Cached<R>`.
23− cached: RefCell<Option<Node<'static>>>,
13+ cached: Cell<Option<CacheId>>,
2414}
2515
2616impl<R> Cached<R> {
@@ -29,18 +19,14 @@ impl<R> Cached<R> {
2919 /// # Example
3020 ///
3121 /// ```
32− /// use bumpalo::Bump;
33− /// use dodrio::{Cached, Node, Render};
22+ /// use dodrio::{Cached, Node, Render, RenderContext};
3423 ///
3524 /// pub struct Counter {
3625 /// count: u32,
3726 /// }
3827 ///
3928 /// impl Render for Counter {
40− /// fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
41− /// where
42− /// 'a: 'bump
43− /// {
29+ /// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
4430 /// // ...
4531 /// # unimplemented!()
4632 /// }
@@ -54,13 +40,8 @@ impl<R> Cached<R> {
5440 /// ```
5541 #[inline]
5642 pub fn new(inner: R) -> Cached<R> {
57− let bump = Bump::new();
58− let cached = RefCell::new(None);
59− Cached {
60− inner,
61− bump,
62− cached,
63− }
43+ let cached = Cell::new(None);
44+ Cached { inner, cached }
6445 }
6546
6647 /// Invalidate the cached rendering.
@@ -76,8 +57,7 @@ impl<R> Cached<R> {
7657 /// displaying greetings to old `who`s.
7758 ///
7859 /// ```
79− /// use bumpalo::Bump;
80− /// use dodrio::{Cached, Node, Render};
60+ /// use dodrio::{bumpalo, Cached, Node, Render, RenderContext};
8161 ///
8262 /// /// A component that renders to "<p>Hello, {who}!</p>"
8363 /// pub struct Hello {
@@ -85,13 +65,11 @@ impl<R> Cached<R> {
8565 /// }
8666 ///
8767 /// impl Render for Hello {
88− /// fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
89− /// where
90− /// 'a: 'bump,
91− /// {
68+ /// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
9269 /// use dodrio::builder::*;
93− /// p(bump)
94− /// .children([text("Hello, "), text(&self.who), text("!")])
70+ /// let greeting = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
71+ /// p(&cx)
72+ /// .children([text(greeting.into_bump_str())])
9573 /// .finish()
9674 /// }
9775 /// }
@@ -104,13 +82,13 @@ impl<R> Cached<R> {
10482 /// }
10583 /// ```
10684 #[inline]
107− pub fn invalidate(cached: &mut Self) {
108− *cached.cached.borrow_mut() = None;
85+ pub fn invalidate(cached: &Self) {
86+ cached.cached.set(None);
10987 }
11088
11189 /// Convert a `Cached<R>` back into a plain `R`.
11290 #[inline]
113− pub fn into_inner(cached: Cached<R>) -> R {
91+ pub fn into_inner(cached: Self) -> R {
11492 cached.inner
11593 }
11694}
@@ -129,35 +107,54 @@ impl<R> DerefMut for Cached<R> {
129107 }
130108}
131109
132−unsafe fn extend_node_lifetime<'a>(node: Node<'a>) -> Node<'static> {
133− mem::transmute(node)
134−}
135−
136−impl<R: Render> Render for Cached<R> {
137− fn render<'a, 'bump>(&'a self, _: &'bump Bump) -> Node<'bump>
138− where
139− 'a: 'bump,
140− {
141− let mut cached = self.cached.borrow_mut();
142−
143− if let Some(cached) = cached.as_ref() {
144− // The cached node is actually a self-reference, so it has the `'a`
145− // lifetime.
146− let cached: Node<'a> = cached.clone();
147−
148− // But the `'a` lifetime outlives `'bump`, so we can safely convert
149− // it to the narrower `'bump` lifetime.
150− let cached: Node<'bump> = cached;
151−
152− // Return the cached rendering!
153− return cached;
154− }
110+impl<R> Render for Cached<R>
111+where
112+ R: Render,
113+{
114+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
115+ let id = match self.cached.get() {
116+ // This does-the-cache-contain-this-id check is necessary because
117+ // the same `Cached<R>` instance can be rendered into vdom A, which
118+ // will save the results into A's cached set and yield id X. Then,
119+ // it can be rendered *again* into a second vdom B, and B's cached
120+ // set does not have the saved results for id X. If we didn't have
121+ // this check, instead of a cache miss, we would have a panic.
122+ //
123+ // This scenario should basically never happen in the real world,
124+ // however. If we ever find that it is worth sharing a cached render
125+ // component between multiple vdoms, and want to avoid these
126+ // "unnecessary" cache misses, we can do this:
127+ //
128+ // * Make each `Cached<R>` have an instance id and generation
129+ // counter.
130+ //
131+ // * On invalidation, bump the generation counter.
132+ //
133+ // * Add a generation member to the `CacheEntry`.
134+ //
135+ // * Each vdom maintains a map from `Cached<R>` instance id to
136+ // `CacheEntry`s.
137+ //
138+ // * We only re-use the cached results if the `Cached<R>`'s
139+ // generation counter matches the entry in the vdom's cached set.
140+ //
141+ // This is all do-able but is a bit more book keeping than we really
142+ // want to do unless it is well motivated.
143+ Some(id)
144+ if {
145+ let cached_set = cx.cached_set.borrow();
146+ cached_set.contains(id)
147+ } =>
148+ {
149+ id
150+ }
151+ _ => {
152+ let id = CachedSet::insert(cx, |nested_cx| self.inner.render(nested_cx));
153+ self.cached.set(Some(id));
154+ id
155+ }
156+ };
155157
156− // We don't have a cached node. Render into our own `Bump`, cache it for
157− // future renders, and return it. Same lifetimes as above.
158− let node: Node<'a> = self.inner.render(&self.bump);
159− *cached = Some(unsafe { extend_node_lifetime(node.clone()) });
160− let node: Node<'bump> = node;
161− node
158+ Node::cached(id)
162159 }
163160}
src/cached_set.rsnew file+155
@@ -0,0 +1,155 @@
1+use crate::{
2+ events::EventsRegistry,
3+ node::{Node, NodeKind},
4+ render_context::RenderContext,
5+};
6+use bumpalo::Bump;
7+use fxhash::{FxHashMap, FxHashSet};
8+use std::sync::atomic::{AtomicUsize, Ordering};
9+use std::u32;
10+use wasm_bindgen::prelude::*;
11+
12+static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
13+
14+pub_unstable_internal! {
15+ #[derive(Debug, Default)]
16+ pub(crate) struct CachedSet {
17+ items: FxHashMap<CacheId, CacheEntry>,
18+ }
19+}
20+
21+pub_unstable_internal! {
22+ #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
23+ pub(crate) struct CacheId(u32);
24+}
25+
26+#[derive(Debug)]
27+pub(crate) struct CacheEntry {
28+ bump: Bump,
29+
30+ // Actually a reference into `bump`.
31+ node: *const Node<'static>,
32+
33+ // All the other cached entries that `node` references (including transitive
34+ // edges). By eagerly following transitive edges and recording them the
35+ // once, we don't have to repeat the tracing every time we want to garbage
36+ // collect cache entries.
37+ edges: FxHashSet<CacheId>,
38+}
39+
40+impl CachedSet {
41+ pub(crate) fn gc(&mut self, registry: &mut EventsRegistry, roots: &[CacheId]) {
42+ // TODO: We need to port a hash set over in `bumpalo::collections` so
43+ // that we can use a temporary bump arena for this set.
44+ let mut mark_bits = FxHashSet::default();
45+ mark_bits.reserve(self.items.len());
46+
47+ for root in roots {
48+ if mark_bits.insert(*root) {
49+ let entry = self
50+ .items
51+ .get(root)
52+ .expect_throw("CachedSet::gc: should have root in cached set");
53+
54+ // NB: no need to recurse here because `edges` already contains
55+ // transitive edges!
56+ mark_bits.extend(entry.edges.iter().cloned());
57+ }
58+ }
59+
60+ self.items.retain(|id, entry| {
61+ let keep = mark_bits.contains(id);
62+ if !keep {
63+ debug!("CachedSet::gc: removing {:?}", id);
64+ let node: &Node = unsafe { &*entry.node };
65+ registry.remove_subtree(node);
66+ }
67+ keep
68+ });
69+ }
70+
71+ // Trace all the transitive edges to other cached entries that the given
72+ // node has.
73+ fn trace(&self, node: &Node) -> FxHashSet<CacheId> {
74+ let mut edges = FxHashSet::default();
75+
76+ let bump = Bump::new();
77+ let mut stack = bumpalo::collections::Vec::with_capacity_in(64, &bump);
78+ stack.push(node);
79+
80+ while let Some(node) = stack.pop() {
81+ match &node.kind {
82+ NodeKind::Text(_) => continue,
83+ NodeKind::Cached(c) => {
84+ debug_assert!(self.items.contains_key(&c.id));
85+ edges.insert(c.id);
86+ edges.extend(
87+ self.items
88+ .get(&c.id)
89+ .expect_throw("CachedSet::trace: should have c.id in cached set")
90+ .edges
91+ .iter()
92+ .cloned(),
93+ );
94+ }
95+ NodeKind::Element(el) => {
96+ stack.extend(el.children);
97+ }
98+ }
99+ }
100+
101+ edges
102+ }
103+
104+ fn next_id(&mut self) -> CacheId {
105+ let next = ID_COUNTER.fetch_add(1, Ordering::AcqRel) as u32;
106+ let next = if next == u32::MAX { None } else { Some(next) };
107+ CacheId(next.expect_throw("ID_COUNTER overflowed"))
108+ }
109+
110+ pub(crate) fn insert<F>(cx: &mut RenderContext, f: F) -> CacheId
111+ where
112+ F: for<'a> FnOnce(&mut RenderContext<'a>) -> Node<'a>,
113+ {
114+ let bump = Bump::new();
115+ let set = cx.cached_set;
116+ let mut nested_cx = RenderContext::new(&bump, set);
117+ let node = f(&mut nested_cx);
118+ let node = bump.alloc(node);
119+ let edges = {
120+ let set = set.borrow();
121+ set.trace(node)
122+ };
123+ let node = node as *mut Node<'_> as usize as *const Node<'static>;
124+ let entry = CacheEntry { bump, node, edges };
125+
126+ let mut set = set.borrow_mut();
127+ let id = set.next_id();
128+ debug!("CachedSet::insert: id = {:?}; entry = {:?}", id, entry);
129+ set.items.insert(id, entry);
130+ id
131+ }
132+
133+ /// Does the cached set contain a cached node with the given id?
134+ pub fn contains(&self, id: CacheId) -> bool {
135+ self.items.contains_key(&id)
136+ }
137+
138+ /// Get the node for the given cache id.
139+ pub fn get(&self, mut id: CacheId) -> Node {
140+ debug!("CachedSet::get: id = {:?}", id);
141+ loop {
142+ let entry = self
143+ .items
144+ .get(&id)
145+ .expect_throw("CachedSet::get: should have id in set");
146+ let node: &Node = unsafe { &*entry.node };
147+ if let NodeKind::Cached(ref c) = node.kind {
148+ id = c.id;
149+ continue;
150+ } else {
151+ return node.clone();
152+ }
153+ }
154+ }
155+}
src/change_list.rs+76−48
@@ -2,34 +2,63 @@ use crate::Listener;
22use bumpalo::Bump;
33use fxhash::FxHashMap;
44use std::fmt;
5−use std::sync::Once;
6−use wasm_bindgen::prelude::*;
75
86pub mod js {
9− use wasm_bindgen::prelude::*;
7+ cfg_if::cfg_if! {
8+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
9+ #[derive(Clone, Debug)]
10+ pub struct ChangeList {}
11+ impl ChangeList {
12+ pub fn new(_container: &crate::Element) -> ChangeList {
13+ ChangeList {}
14+ }
15+ pub fn unmount(&self) {}
16+ pub fn add_change_list_range(&self, _start: usize, _len: usize) {}
17+ pub fn init_events_trampoline(&self, _trampoline: &crate::EventsTrampoline) {}
18+ }
1019
11− #[wasm_bindgen]
12− extern "C" {
13− #[derive(Clone, Debug)]
14− pub type ChangeList;
20+ pub fn eval_change_list() {}
21+ } else {
22+ use wasm_bindgen::prelude::*;
1523
16− #[wasm_bindgen(constructor)]
17− pub fn new(container: &web_sys::Node) -> ChangeList;
24+ #[wasm_bindgen]
25+ extern "C" {
26+ #[derive(Clone, Debug)]
27+ pub type ChangeList;
1828
19− #[wasm_bindgen(structural, method)]
20− pub fn unmount(this: &ChangeList);
29+ #[wasm_bindgen(constructor)]
30+ pub fn new(container: &web_sys::Element) -> ChangeList;
2131
22− #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
23− pub fn add_change_list_range(this: &ChangeList, start: usize, len: usize);
32+ #[wasm_bindgen(structural, method)]
33+ pub fn unmount(this: &ChangeList);
2434
25− #[wasm_bindgen(structural, method, js_name = applyChanges)]
26− pub fn apply_changes(this: &ChangeList, memory: JsValue);
35+ #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
36+ pub fn add_change_list_range(this: &ChangeList, start: usize, len: usize);
2737
28− #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
29− pub fn init_events_trampoline(
30− this: &ChangeList,
31− trampoline: &Closure<Fn(web_sys::Event, u32, u32)>,
32− );
38+ #[wasm_bindgen(structural, method, js_name = applyChanges)]
39+ pub fn apply_changes(this: &ChangeList, memory: JsValue);
40+
41+ #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
42+ pub fn init_events_trampoline(
43+ this: &ChangeList,
44+ trampoline: &crate::EventsTrampoline,
45+ );
46+ }
47+
48+ pub fn eval_change_list() {
49+ use std::sync::Once;
50+ // XXX: Because wasm-bindgen-test doesn't support third party JS
51+ // dependencies, we can't use `wasm_bindgen(module = "...")` for our
52+ // `ChangeList` JS import. Instead, this *should* be a local JS snippet,
53+ // but that isn't implemented yet:
54+ // https://github.com/rustwasm/rfcs/pull/6
55+ static EVAL: Once = Once::new();
56+ EVAL.call_once(|| {
57+ js_sys::eval(include_str!("../js/change-list.js"))
58+ .expect_throw("should eval change-list.js OK");
59+ });
60+ }
61+ }
3362 }
3463}
3564
@@ -43,7 +72,7 @@ pub(crate) struct ChangeList {
4372 strings_cache: FxHashMap<String, StringsCacheEntry>,
4473 next_string_key: u32,
4574 js: js::ChangeList,
46− events_trampoline: Option<Closure<Fn(web_sys::Event, u32, u32)>>,
75+ events_trampoline: Option<crate::EventsTrampoline>,
4776}
4877
4978impl Drop for ChangeList {
@@ -64,18 +93,8 @@ impl fmt::Debug for ChangeList {
6493}
6594
6695impl ChangeList {
67− pub(crate) fn new(container: &web_sys::Node) -> ChangeList {
68− // XXX: Because wasm-bindgen-test doesn't support third party JS
69− // dependencies, we can't use `wasm_bindgen(module = "...")` for our
70− // `ChangeList` JS import. Instead, this *should* be a local JS snippet,
71− // but that isn't implemented yet:
72− // https://github.com/rustwasm/rfcs/pull/6
73− static EVAL: Once = Once::new();
74− EVAL.call_once(|| {
75− js_sys::eval(include_str!("../js/change-list.js"))
76− .expect("should eval change-list.js OK");
77− });
78−
96+ pub(crate) fn new(container: &crate::Element) -> ChangeList {
97+ js::eval_change_list();
7998 let bump = Bump::new();
8099 let strings_cache = FxHashMap::default();
81100 let js = js::ChangeList::new(container);
@@ -88,27 +107,36 @@ impl ChangeList {
88107 }
89108 }
90109
91− pub(crate) fn apply_changes(&mut self) {
92− let js = &self.js;
93− unsafe {
94− self.bump.each_allocated_chunk(|ch| {
95− js.add_change_list_range(ch.as_ptr() as usize, ch.len());
96− });
97− }
98− js.apply_changes(wasm_bindgen::memory());
99− self.bump.reset();
100− }
101−
102− pub(crate) fn init_events_trampoline(
103− &mut self,
104− trampoline: Closure<Fn(web_sys::Event, u32, u32)>,
105− ) {
110+ pub(crate) fn init_events_trampoline(&mut self, trampoline: crate::EventsTrampoline) {
106111 debug_assert!(self.events_trampoline.is_none());
107112 self.js.init_events_trampoline(&trampoline);
108113 self.events_trampoline = Some(trampoline);
109114 }
110115}
111116
117+cfg_if::cfg_if! {
118+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
119+ impl ChangeList {
120+ pub(crate) fn apply_changes(&mut self) {
121+ // Do nothing...
122+ }
123+ }
124+ } else {
125+ impl ChangeList {
126+ pub(crate) fn apply_changes(&mut self) {
127+ let js = &self.js;
128+ unsafe {
129+ self.bump.each_allocated_chunk(|ch| {
130+ js.add_change_list_range(ch.as_ptr() as usize, ch.len());
131+ });
132+ }
133+ js.apply_changes(wasm_bindgen::memory());
134+ self.bump.reset();
135+ }
136+ }
137+ }
138+}
139+
112140#[repr(u32)]
113141#[derive(Clone, Copy, Debug)]
114142enum ChangeDiscriminant {
src/diff.rsnew file+310
@@ -0,0 +1,310 @@
1+use crate::{
2+ cached_set::{CacheId, CachedSet},
3+ change_list::ChangeList,
4+ events::EventsRegistry,
5+ node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
6+};
7+use std::cmp;
8+
9+pub(crate) fn diff(
10+ cached_set: &CachedSet,
11+ change_list: &mut ChangeList,
12+ registry: &mut EventsRegistry,
13+ old: Node,
14+ new: Node,
15+ cached_roots: &mut bumpalo::collections::Vec<CacheId>,
16+) {
17+ match (&new.kind, &old.kind) {
18+ (
19+ &NodeKind::Text(TextNode { text: new_text }),
20+ &NodeKind::Text(TextNode { text: old_text }),
21+ ) => {
22+ debug!(" both are text nodes");
23+ if new_text != old_text {
24+ debug!(" text needs updating");
25+ change_list.emit_set_text(new_text);
26+ }
27+ }
28+
29+ (&NodeKind::Text(_), &NodeKind::Element(_)) => {
30+ debug!(" replacing a text node with an element");
31+ create(cached_set, change_list, registry, new, cached_roots);
32+ registry.remove_subtree(&old);
33+ change_list.emit_replace_with();
34+ }
35+
36+ (&NodeKind::Element(_), &NodeKind::Text(_)) => {
37+ debug!(" replacing an element with a text node");
38+ create(cached_set, change_list, registry, new, cached_roots);
39+ // Note: text nodes cannot have event listeners, so we don't need to
40+ // remove the old node's listeners from our registry her.
41+ change_list.emit_replace_with();
42+ }
43+
44+ (
45+ &NodeKind::Element(ElementNode {
46+ tag_name: new_tag_name,
47+ listeners: new_listeners,
48+ attributes: new_attributes,
49+ children: new_children,
50+ namespace: new_namespace,
51+ }),
52+ &NodeKind::Element(ElementNode {
53+ tag_name: old_tag_name,
54+ listeners: old_listeners,
55+ attributes: old_attributes,
56+ children: old_children,
57+ namespace: old_namespace,
58+ }),
59+ ) => {
60+ debug!(" updating an element");
61+ if new_tag_name != old_tag_name || new_namespace != old_namespace {
62+ debug!(" different tag names or namespaces; creating new element and replacing old element");
63+ create(cached_set, change_list, registry, new, cached_roots);
64+ registry.remove_subtree(&old);
65+ change_list.emit_replace_with();
66+ return;
67+ }
68+ diff_listeners(change_list, registry, old_listeners, new_listeners);
69+ diff_attributes(change_list, old_attributes, new_attributes);
70+ diff_children(
71+ cached_set,
72+ change_list,
73+ registry,
74+ old_children,
75+ new_children,
76+ cached_roots,
77+ );
78+ }
79+
80+ // Both the new and old nodes are cached.
81+ (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
82+ cached_roots.push(new.id);
83+
84+ if new.id == old.id {
85+ // This is the same cached node, so nothing has changed!
86+ return;
87+ }
88+
89+ let new = cached_set.get(new.id);
90+ let old = cached_set.get(old.id);
91+ diff(cached_set, change_list, registry, old, new, cached_roots);
92+ }
93+
94+ // New cached node when the old node was not cached. In this scenario,
95+ // we assume that they are pretty different, and it isn't worth diffing
96+ // the subtrees, so we just create the new cached node afresh.
97+ (&NodeKind::Cached(ref c), _) => {
98+ cached_roots.push(c.id);
99+ let new = cached_set.get(c.id);
100+ create(cached_set, change_list, registry, new, cached_roots);
101+ registry.remove_subtree(&old);
102+ change_list.emit_replace_with();
103+ }
104+
105+ // Old cached node and new non-cached node. Again, assume that they are
106+ // probably pretty different and create the new non-cached node afresh.
107+ (_, &NodeKind::Cached(_)) => {
108+ create(cached_set, change_list, registry, new, cached_roots);
109+ registry.remove_subtree(&old);
110+ change_list.emit_replace_with();
111+ }
112+ }
113+}
114+
115+fn diff_listeners(
116+ change_list: &mut ChangeList,
117+ registry: &mut EventsRegistry,
118+ old: &[Listener],
119+ new: &[Listener],
120+) {
121+ debug!(" updating event listeners");
122+
123+ 'outer1: for new_l in new {
124+ unsafe {
125+ // Safety relies on removing `new_l` from the registry manually at
126+ // the end of its lifetime. This happens below in the `'outer2`
127+ // loop, and elsewhere in diffing when removing old dom trees.
128+ registry.add(new_l);
129+ }
130+
131+ for old_l in old {
132+ if new_l.event == old_l.event {
133+ change_list.emit_update_event_listener(new_l);
134+ continue 'outer1;
135+ }
136+ }
137+
138+ change_list.emit_new_event_listener(new_l);
139+ }
140+
141+ 'outer2: for old_l in old {
142+ registry.remove(old_l);
143+
144+ for new_l in new {
145+ if new_l.event == old_l.event {
146+ continue 'outer2;
147+ }
148+ }
149+ change_list.emit_remove_event_listener(old_l.event);
150+ }
151+}
152+
153+fn diff_attributes(change_list: &mut ChangeList, old: &[Attribute], new: &[Attribute]) {
154+ debug!(" updating attributes");
155+
156+ // Do O(n^2) passes to add/update and remove attributes, since
157+ // there are almost always very few attributes.
158+ 'outer: for new_attr in new {
159+ if new_attr.is_volatile() {
160+ change_list.emit_set_attribute(new_attr.name, new_attr.value);
161+ } else {
162+ for old_attr in old {
163+ if old_attr.name == new_attr.name {
164+ if old_attr.value != new_attr.value {
165+ change_list.emit_set_attribute(new_attr.name, new_attr.value);
166+ }
167+ continue 'outer;
168+ }
169+ }
170+ change_list.emit_set_attribute(new_attr.name, new_attr.value);
171+ }
172+ }
173+
174+ 'outer2: for old_attr in old {
175+ for new_attr in new {
176+ if old_attr.name == new_attr.name {
177+ continue 'outer2;
178+ }
179+ }
180+ change_list.emit_remove_attribute(old_attr.name);
181+ }
182+}
183+
184+fn diff_children(
185+ cached_set: &CachedSet,
186+ change_list: &mut ChangeList,
187+ registry: &mut EventsRegistry,
188+ old: &[Node],
189+ new: &[Node],
190+ cached_roots: &mut bumpalo::collections::Vec<CacheId>,
191+) {
192+ debug!(" updating children shared by old and new");
193+
194+ let num_children_to_diff = cmp::min(new.len(), old.len());
195+ let mut new_children = new.iter();
196+ let mut old_children = old.iter();
197+ let mut pushed = false;
198+
199+ for (i, (new_child, old_child)) in new_children
200+ .by_ref()
201+ .zip(old_children.by_ref())
202+ .take(num_children_to_diff)
203+ .enumerate()
204+ {
205+ if i == 0 {
206+ change_list.emit_push_first_child();
207+ pushed = true;
208+ } else {
209+ debug_assert!(pushed);
210+ change_list.emit_pop_push_next_sibling();
211+ }
212+
213+ diff(
214+ cached_set,
215+ change_list,
216+ registry,
217+ old_child.clone(),
218+ new_child.clone(),
219+ cached_roots,
220+ );
221+ }
222+
223+ if old_children.next().is_some() {
224+ debug!(" removing extra old children");
225+ debug_assert!(new_children.next().is_none());
226+ if !pushed {
227+ change_list.emit_push_first_child();
228+ } else {
229+ change_list.emit_pop_push_next_sibling();
230+ }
231+ change_list.emit_remove_self_and_next_siblings();
232+ pushed = false;
233+ } else {
234+ debug!(" creating new children");
235+ for (i, new_child) in new_children.enumerate() {
236+ if i == 0 && pushed {
237+ change_list.emit_pop();
238+ pushed = false;
239+ }
240+ create(
241+ cached_set,
242+ change_list,
243+ registry,
244+ new_child.clone(),
245+ cached_roots,
246+ );
247+ change_list.emit_append_child();
248+ }
249+ }
250+
251+ debug!(" done updating children");
252+ if pushed {
253+ change_list.emit_pop();
254+ }
255+}
256+
257+fn create(
258+ cached_set: &CachedSet,
259+ change_list: &mut ChangeList,
260+ registry: &mut EventsRegistry,
261+ node: Node,
262+ cached_roots: &mut bumpalo::collections::Vec<CacheId>,
263+) {
264+ match node.kind {
265+ NodeKind::Text(TextNode { text }) => {
266+ change_list.emit_create_text_node(text);
267+ }
268+ NodeKind::Element(ElementNode {
269+ tag_name,
270+ listeners,
271+ attributes,
272+ children,
273+ namespace,
274+ }) => {
275+ if let Some(namespace) = namespace {
276+ change_list.emit_create_element_ns(tag_name, namespace);
277+ } else {
278+ change_list.emit_create_element(tag_name);
279+ }
280+ for l in listeners {
281+ unsafe {
282+ registry.add(l);
283+ }
284+ change_list.emit_new_event_listener(l);
285+ }
286+ for attr in attributes {
287+ if namespace.is_none() || attr.name.starts_with("xmlns") {
288+ change_list.emit_set_attribute(&attr.name, &attr.value);
289+ } else {
290+ change_list.emit_set_attribute_ns(&attr.name, &attr.value);
291+ }
292+ }
293+ for child in children {
294+ create(
295+ cached_set,
296+ change_list,
297+ registry,
298+ child.clone(),
299+ cached_roots,
300+ );
301+ change_list.emit_append_child();
302+ }
303+ }
304+ NodeKind::Cached(c) => {
305+ cached_roots.push(c.id);
306+ let node = cached_set.get(c.id);
307+ create(cached_set, change_list, registry, node, cached_roots)
308+ }
309+ }
310+}
src/events.rs+132−83
@@ -1,98 +1,147 @@
1−use crate::vdom::{VdomInner, VdomWeak};
2−use crate::{Listener, ListenerCallback};
1+use crate::{node::Node, vdom::VdomInner, Listener};
32use std::cell::RefCell;
4−use std::collections::HashMap;
5−use std::fmt;
6−use std::mem;
73use std::rc::{Rc, Weak};
8−use wasm_bindgen::closure::Closure;
9−use wasm_bindgen::prelude::*;
104
11−/// The events registry manages event listeners for a virtual DOM.
12−///
13−/// The events registry is persistent across virtual DOM rendering and double
14−/// buffering.
15−pub(crate) struct EventsRegistry {
16− vdom: Weak<VdomInner>,
17− active: HashMap<(u32, u32), ListenerCallback<'static>>,
18−}
5+cfg_if::cfg_if! {
6+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
7+ #[derive(Debug)]
8+ pub(crate) struct EventsRegistry {}
9+ impl EventsRegistry {
10+ pub(crate) fn new(_vdom: Weak<VdomInner>) -> (
11+ Rc<RefCell<EventsRegistry>>,
12+ crate::EventsTrampoline,
13+ ) {
14+ (Rc::new(RefCell::new(EventsRegistry {})), ())
15+ }
16+ pub(crate) fn remove(&mut self, _listener: &Listener) {}
17+ pub(crate) fn remove_subtree(&mut self, _node: &Node) {}
18+ pub(crate) unsafe fn add<'a>(&mut self, _listener: &'a Listener<'a>) {}
19+ pub(crate) fn clear_active_listeners(&mut self) {}
20+ }
21+ } else {
22+ use bumpalo::Bump;
23+ use crate::{
24+ node::{ElementNode, ListenerCallback, NodeKind},
25+ vdom::VdomWeak,
26+ };
27+ use fxhash::FxHashMap;
28+ use std::fmt;
29+ use std::mem;
30+ use wasm_bindgen::closure::Closure;
31+ use wasm_bindgen::prelude::*;
1932
20−impl fmt::Debug for EventsRegistry {
21− fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22− f.debug_struct("EventsRegistry")
23− .field("active", &self.active.keys().collect::<Vec<_>>())
24− .finish()
25− }
26−}
33+ /// The events registry manages event listeners for a virtual DOM.
34+ ///
35+ /// The events registry is persistent across virtual DOM rendering and double
36+ /// buffering.
37+ pub(crate) struct EventsRegistry {
38+ vdom: Weak<VdomInner>,
39+ active: FxHashMap<(u32, u32), ListenerCallback<'static>>,
40+ }
2741
28−impl EventsRegistry {
29− /// Construct a new events registry and JS function trampoline that weakly
30− /// holds the new registry and can be used by JS to invoke listeners on the
31− /// Rust side.
32− pub(crate) fn new(
33− vdom: Weak<VdomInner>,
34− ) -> (
35− Rc<RefCell<EventsRegistry>>,
36− Closure<Fn(web_sys::Event, u32, u32)>,
37− ) {
38− let registry = Rc::new(RefCell::new(EventsRegistry {
39− vdom,
40− active: HashMap::new(),
41− }));
42+ impl fmt::Debug for EventsRegistry {
43+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44+ f.debug_struct("EventsRegistry")
45+ .field("active", &self.active.keys().collect::<Vec<_>>())
46+ .finish()
47+ }
48+ }
4249
43− let weak_registry = Rc::downgrade(&registry);
44− let closure = Closure::wrap(Box::new(move |event, a, b| {
45− debug!("Events trampoline invoked with (0x{:x}, 0x{:x})", a, b);
46− debug_assert!(a != 0);
50+ impl EventsRegistry {
51+ /// Construct a new events registry and JS function trampoline that weakly
52+ /// holds the new registry and can be used by JS to invoke listeners on the
53+ /// Rust side.
54+ pub(crate) fn new(
55+ vdom: Weak<VdomInner>,
56+ ) -> (
57+ Rc<RefCell<EventsRegistry>>,
58+ crate::EventsTrampoline,
59+ ) {
60+ let registry = Rc::new(RefCell::new(EventsRegistry {
61+ vdom,
62+ active: FxHashMap::default(),
63+ }));
4764
48− // if the VdomInnerExclusive is keeping this closure alive, then the
49− // VdomInnerExclusive should also be keeping the registry alive
50− let registry = weak_registry.upgrade().unwrap_throw();
51− let registry = registry.borrow();
65+ let weak_registry = Rc::downgrade(&registry);
66+ let closure = Closure::wrap(Box::new(move |event, a, b| {
67+ debug!("Events trampoline invoked with (0x{:x}, 0x{:x})", a, b);
68+ debug_assert!(a != 0);
5269
53− match registry.active.get(&(a, b)) {
54− None => warn!(
55− "EventsRegistry closure invoked with unknown listener parts: \
56− (0x{:x}, 0x{:x})",
57− a, b
58− ),
59− Some(callback) => {
60− let vdom = registry.vdom.upgrade().expect_throw(
61− "if the registry is still around, then the vdom should still be around",
62− );
63− let vdom_weak = VdomWeak::new(&vdom);
64− let mut vdom = vdom.exclusive.borrow_mut();
65− let component = vdom.component_raw_mut();
66− callback(component, vdom_weak, event);
67− }
70+ // if the VdomInnerExclusive is keeping this closure alive, then the
71+ // VdomInnerExclusive should also be keeping the registry alive
72+ let registry = weak_registry.upgrade().unwrap_throw();
73+ let registry = registry.borrow();
74+
75+ match registry.active.get(&(a, b)) {
76+ None => warn!(
77+ "EventsRegistry closure invoked with unknown listener parts: \
78+ (0x{:x}, 0x{:x})",
79+ a, b
80+ ),
81+ Some(callback) => {
82+ let vdom = registry.vdom.upgrade().expect_throw(
83+ "if the registry is still around, then the vdom should still be around",
84+ );
85+ let vdom_weak = VdomWeak::new(&vdom);
86+ let mut vdom = vdom.exclusive.borrow_mut();
87+ let component = vdom.component_raw_mut();
88+ callback(component, vdom_weak, event);
89+ }
90+ }
91+ }) as Box<Fn(web_sys::Event, u32, u32)>);
92+
93+ (registry, closure)
6894 }
69− }) as Box<Fn(web_sys::Event, u32, u32)>);
7095
71− (registry, closure)
72− }
96+ pub(crate) fn remove(&mut self, listener: &Listener) {
97+ let id = listener.get_callback_parts();
98+ debug!("EventsRegistry::remove(0x{:x}, 0x{:x})", id.0, id.1);
99+ debug_assert!(id.0 != 0);
100+ self.active.remove(&id);
101+ }
73102
74− /// Add an event listener to the registry, exposing to JS.
75− ///
76− /// # Unsafety
77− ///
78− /// The listener's lifetime is extended to `'static` and it is the caller's
79− /// responsibility to ensure that the listener is not kept in the registry
80− /// after it is dropped. This is typically done via clearing the whole
81− /// registry on each render and repopulating it during diffing.
82− pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
83− let id = listener.get_callback_parts();
84− debug!("EventsRegistry::add(0x{:x}, 0x{:x})", id.0, id.1);
85− debug_assert!(id.0 != 0);
103+ pub(crate) fn remove_subtree(&mut self, node: &Node) {
104+ let bump = Bump::new();
105+ let mut stack = bumpalo::collections::Vec::with_capacity_in(64, &bump);
106+ stack.push(node);
86107
87− let callback =
88− mem::transmute::<ListenerCallback<'a>, ListenerCallback<'static>>(listener.callback);
89− let old = self.active.insert(id, callback);
90− debug_assert!(old.is_none());
91− }
108+ while let Some(node) = stack.pop() {
109+ match node.kind {
110+ NodeKind::Cached(_) | NodeKind::Text(_) => continue,
111+ NodeKind::Element(ElementNode {listeners, children, ..}) => {
112+ for l in listeners {
113+ self.remove(l);
114+ }
115+ stack.extend(children);
116+ }
117+ }
118+ }
119+ }
92120
93− /// Clear all event listeners from the registry.
94− pub(crate) fn clear_active_listeners(&mut self) {
95− debug!("EventsRegistry::clear_active_listeners()");
96− self.active.clear();
121+ /// Add an event listener to the registry, exposing to JS.
122+ ///
123+ /// # Unsafety
124+ ///
125+ /// The listener's lifetime is extended to `'static` and it is the
126+ /// caller's responsibility to ensure that the listener is not kept
127+ /// in the registry after it is dropped. This is maintained during
128+ /// diffing.
129+ pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
130+ let id = listener.get_callback_parts();
131+ debug!("EventsRegistry::add(0x{:x}, 0x{:x})", id.0, id.1);
132+ debug_assert!(id.0 != 0);
133+
134+ let callback =
135+ mem::transmute::<ListenerCallback<'a>, ListenerCallback<'static>>(listener.callback);
136+ let old = self.active.insert(id, callback);
137+ debug_assert!(old.is_none());
138+ }
139+
140+ /// Clear all event listeners from the registry.
141+ pub(crate) fn clear_active_listeners(&mut self) {
142+ debug!("EventsRegistry::clear_active_listeners()");
143+ self.active.clear();
144+ }
145+ }
97146 }
98147}
src/lib.rs+62−10
@@ -3,7 +3,7 @@
33//! ## Example
44//!
55//! ```no_run
6−//! use dodrio::{bumpalo::Bump, Attribute, Node, Render};
6+//! use dodrio::{bumpalo, Attribute, Node, Render, RenderContext};
77//! use wasm_bindgen::UnwrapThrowExt;
88//!
99//! /// A component that greets someone.
@@ -19,15 +19,13 @@
1919//! }
2020//!
2121//! impl<'who> Render for Hello<'who> {
22−//! fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
23−//! where
24−//! 'a: 'bump,
25−//! {
22+//! fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2623//! use dodrio::builder::*;
2724//!
28−//! let id = bumpalo::format!(in bump, "hello-{}", self.who);
25+//! let id = bumpalo::format!(in cx.bump, "hello-{}", self.who);
26+//! let who = bumpalo::collections::String::from_str_in(self.who, cx.bump).into_bump_str();
2927//!
30−//! div(bump)
28+//! div(&cx)
3129//! .attr("id", id.into_bump_str())
3230//! .on("click", |root, _vdom, _event| {
3331//! let hello = root.unwrap_mut::<Hello>();
@@ -37,9 +35,9 @@
3735//! })
3836//! .children([
3937//! text("Hello, "),
40−//! strong(bump)
38+//! strong(&cx)
4139//! .children([
42−//! text(self.who),
40+//! text(who),
4341//! text("!"),
4442//! ])
4543//! .finish(),
@@ -63,20 +61,74 @@ cfg_if::cfg_if! {
6361 }
6462}
6563
64+// A macro to expose items unstably, only for internal/testing usage.
65+cfg_if::cfg_if! {
66+ if #[cfg(feature = "xxx-unstable-internal-use-only")] {
67+ macro_rules! pub_unstable_internal {
68+ ( $(#[$attr:meta])* pub(crate) $( $thing:tt )* ) => {
69+ #[doc(hidden)]
70+ $( #[$attr] )*
71+ pub $( $thing )*
72+ }
73+ }
74+ } else {
75+ macro_rules! pub_unstable_internal {
76+ ( $(#[$attr:meta])* pub(crate) $( $thing:tt )* ) => {
77+ $( #[$attr] )*
78+ pub(crate) $( $thing )*
79+ }
80+ }
81+ }
82+}
83+
6684// Only `pub` so that the wasm-bindgen bindings work.
6785#[doc(hidden)]
6886pub mod change_list;
6987
7088mod cached;
89+mod cached_set;
90+mod diff;
7191mod events;
7292mod node;
7393mod render;
94+mod render_context;
7495mod vdom;
7596
7697pub mod builder;
7798
7899// Re-export items at the top level.
79100pub use self::cached::Cached;
80−pub use self::node::{Attribute, ElementNode, Listener, ListenerCallback, Node, TextNode};
101+pub use self::node::{Attribute, Listener, Node};
81102pub use self::render::{Render, RootRender};
103+pub use self::render_context::RenderContext;
82104pub use self::vdom::{Vdom, VdomWeak};
105+
106+cfg_if::cfg_if! {
107+ if #[cfg(all(target_arch = "wasm32", not(feature = "xxx-unstable-internal-use-only")))] {
108+ use wasm_bindgen::__rt::WasmRefCell as RefCell;
109+ } else {
110+ use std::cell::RefCell;
111+ }
112+}
113+
114+// Polyfill some Web stuff for benchmarking...
115+cfg_if::cfg_if! {
116+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
117+ /// An element node in the physical DOM.
118+ pub type Element = ();
119+
120+ pub(crate) type EventsTrampoline = ();
121+ } else {
122+ /// An element node in the physical DOM.
123+ pub type Element = web_sys::Element;
124+
125+ pub(crate) type EventsTrampoline = wasm_bindgen::closure::Closure<Fn(web_sys::Event, u32, u32)>;
126+ }
127+}
128+
129+cfg_if::cfg_if! {
130+ if #[cfg(feature = "xxx-unstable-internal-use-only")] {
131+ pub use self::cached_set::{CachedSet};
132+ pub use self::node::{ElementNode, NodeKind, TextNode};
133+ }
134+}
src/node.rs+77−73
@@ -1,35 +1,69 @@
1−use crate::{RootRender, VdomWeak};
1+use crate::{cached_set::CacheId, RootRender, VdomWeak};
22use bumpalo::Bump;
33use std::fmt;
44use std::iter;
5+use std::marker::PhantomData;
56use std::mem;
67
7−/// A node is either a text node or an element.
8+/// A virtual DOM node.
89#[derive(Debug, Clone)]
9−pub enum Node<'a> {
10− /// A text node.
11− Text(TextNode<'a>),
10+pub struct Node<'a> {
11+ #[cfg(feature = "xxx-unstable-internal-use-only")]
12+ #[doc(hidden)]
13+ pub kind: NodeKind<'a>,
1214
13− /// An element potentially with attributes and children.
14− Element(ElementNode<'a>),
15+ #[cfg(not(feature = "xxx-unstable-internal-use-only"))]
16+ pub(crate) kind: NodeKind<'a>,
1517}
1618
17−/// Text nodes are just a string of text. They cannot have attributes or
18−/// children.
19−#[derive(Debug, Clone)]
20−pub struct TextNode<'a> {
21− pub(crate) text: &'a str,
19+pub_unstable_internal! {
20+ /// A node is either a text node or an element.
21+ #[derive(Debug, Clone)]
22+ pub(crate) enum NodeKind<'a> {
23+ /// A text node.
24+ Text(TextNode<'a>),
25+
26+ /// An element potentially with attributes and children.
27+ Element(ElementNode<'a>),
28+
29+ /// A node in the vdom's `CachedSet`. This allows us to avoid
30+ /// re-rendering and re-diffing subtrees.
31+ Cached(CachedNode<'a>),
32+ }
2233}
2334
24−/// Elements have a tag name, zero or more attributes, and zero or more
25−/// children.
26−#[derive(Debug, Clone)]
27−pub struct ElementNode<'a> {
28− pub(crate) tag_name: &'a str,
29− pub(crate) listeners: &'a [Listener<'a>],
30− pub(crate) attributes: &'a [Attribute<'a>],
31− pub(crate) children: &'a [Node<'a>],
32− pub(crate) namespace: Option<&'a str>,
35+pub_unstable_internal! {
36+ /// Text nodes are just a string of text. They cannot have attributes or
37+ /// children.
38+ #[derive(Debug, Clone)]
39+ pub(crate) struct TextNode<'a> {
40+ pub text: &'a str,
41+ }
42+}
43+
44+pub_unstable_internal! {
45+ /// Elements have a tag name, zero or more attributes, and zero or more
46+ /// children.
47+ #[derive(Debug, Clone)]
48+ pub(crate) struct ElementNode<'a> {
49+ pub tag_name: &'a str,
50+ pub listeners: &'a [Listener<'a>],
51+ pub attributes: &'a [Attribute<'a>],
52+ pub children: &'a [Node<'a>],
53+ pub namespace: Option<&'a str>,
54+ }
55+}
56+
57+pub_unstable_internal! {
58+ /// A cached node exists in an arena that is internal to the `Vdom`. It
59+ /// allows us to avoid both re-rendering a sub-tree and re-diffing
60+ /// it. `CachedNode`s cannot contain cycles, since they can only contain
61+ /// other `CachedNode`s that already exist when they are being constructed.
62+ #[derive(Debug, Clone)]
63+ pub(crate) struct CachedNode<'a> {
64+ pub id: CacheId,
65+ pub _phantom: PhantomData<&'a Node<'a>>,
66+ }
3367}
3468
3569/// An event listener callback function.
@@ -39,7 +73,7 @@ pub struct ElementNode<'a> {
3973/// 1. The virtual DOM's root rendering component.
4074/// 2. A capability to scheduler virtual DOM re-rendering.
4175/// 3. The event that occurred.
42−pub type ListenerCallback<'a> =
76+pub(crate) type ListenerCallback<'a> =
4377 &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static);
4478
4579/// An event listener.
@@ -121,35 +155,34 @@ impl<'a> Node<'a> {
121155 let attributes: &'a Attributes = bump.alloc(attributes);
122156 let attributes: &'a [Attribute<'a>] = attributes.as_ref();
123157
124− Node::Element(ElementNode {
125− tag_name,
126− listeners,
127− attributes,
128− children,
129− namespace,
130− })
131− }
132−
133− /// Is this node a text node?
134− pub fn is_text(&self) -> bool {
135− match self {
136− Node::Text(_) => true,
137− _ => false,
158+ Node {
159+ kind: NodeKind::Element(ElementNode {
160+ tag_name,
161+ listeners,
162+ attributes,
163+ children,
164+ namespace,
165+ }),
138166 }
139167 }
140168
141− /// Is this node an element?
142− pub fn is_element(&self) -> bool {
143− match self {
144− Node::Element { .. } => true,
145− _ => false,
169+ /// Construct a new text node with the given text.
170+ #[inline]
171+ pub(crate) fn text(text: &'a str) -> Node<'a> {
172+ Node {
173+ kind: NodeKind::Text(TextNode { text }),
146174 }
147175 }
148176
149− /// Construct a new text node with the given text.
177+ /// Construct a new cached node with the given id.
150178 #[inline]
151− pub(crate) fn text(text: &'a str) -> Node<'a> {
152− Node::Text(TextNode { text })
179+ pub(crate) fn cached(id: CacheId) -> Node<'a> {
180+ Node {
181+ kind: NodeKind::Cached(CachedNode {
182+ id,
183+ _phantom: PhantomData,
184+ }),
185+ }
153186 }
154187}
155188
@@ -169,35 +202,6 @@ impl<'a> IntoIterator for Node<'a> {
169202 }
170203}
171204
172−impl<'a> TextNode<'a> {
173− /// Get this text node's text content.
174− pub fn text(&self) -> &'a str {
175− self.text
176− }
177−}
178−
179−impl<'a> ElementNode<'a> {
180− /// Get this element's tag name.
181− pub fn tag_name(&self) -> &'a str {
182− self.tag_name
183− }
184−
185− /// Get this element's attributes.
186− pub fn attributes(&self) -> &'a [Attribute<'a>] {
187− self.attributes
188− }
189−
190− /// Get this element's attributes.
191− pub fn children(&self) -> &'a [Node<'a>] {
192− self.children
193− }
194−
195− /// Get this element's namespace.
196− pub fn namespace(&self) -> Option<&'a str> {
197− self.namespace
198− }
199−}
200−
201205union CallbackFatPtr<'a> {
202206 callback: ListenerCallback<'a>,
203207 parts: (u32, u32),
src/render.rs+27−39
@@ -1,5 +1,4 @@
1−use crate::Node;
2−use bumpalo::Bump;
1+use crate::{Node, RenderContext};
32use std::any::Any;
43use std::rc::Rc;
54use wasm_bindgen::UnwrapThrowExt;
@@ -11,37 +10,25 @@ use wasm_bindgen::UnwrapThrowExt;
1110///
1211/// ## `Bump` Allocation
1312///
14−/// `Render` implementations can use the provided `Bump` for very fast
15−/// allocation for anything that needs to be allocated during rendering.
16−///
17−/// ## The `'a: 'bump` Lifetime Bound
18−///
19−/// The `'a: 'bump` bounds enforce that `self` outlives the given bump
20−/// allocator. This means that if `self` contains a string, the string does not
21−/// need to be copied into the output `Node` and can be used by reference
22−/// instead (i.e. it prevents accidentally using the string after its been
23−/// freed). The `'a: 'bump` bound also enables abstractions like
24−/// `dodrio::Cached` that can re-use cached `Node`s across `render`s without
25−/// copying them.
13+/// `Render` implementations can use the `Bump` inside the provided
14+/// `RenderContext` for very fast allocation for anything that needs to be
15+/// temporarily allocated during rendering.
2616///
2717/// ## Example
2818///
2919/// ```no_run
30−/// use dodrio::{bumpalo::Bump, Node, Render};
20+/// use dodrio::{Node, Render, RenderContext};
3121///
3222/// pub struct MyComponent;
3323///
3424/// impl Render for MyComponent {
35−/// fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
36−/// where
37−/// 'a: 'bump
38−/// {
25+/// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
3926/// use dodrio::builder::*;
4027///
41−/// p(bump)
28+/// p(&cx)
4229/// .children([
4330/// text("This is "),
44−/// strong(bump).children([text("my component")]).finish(),
31+/// strong(&cx).children([text("my component")]).finish(),
4532/// text(" rendered!"),
4633/// ])
4734/// .finish()
@@ -49,22 +36,17 @@ use wasm_bindgen::UnwrapThrowExt;
4936/// }
5037/// ```
5138pub trait Render {
52− /// Render `self` as a virtual DOM. Use the given `Bump` for temporary
53− /// allocations.
54− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
55− where
56− 'a: 'bump;
39+ /// Render `self` as a virtual DOM. Use the given context's `Bump` for
40+ /// temporary allocations.
41+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a>;
5742}
5843
5944impl<'r, R> Render for &'r R
6045where
6146 R: Render,
6247{
63− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
64− where
65− 'a: 'bump,
66− {
67− (**self).render(bump)
48+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
49+ (**self).render(cx)
6850 }
6951}
7052
@@ -72,11 +54,8 @@ impl<R> Render for Rc<R>
7254where
7355 R: Render,
7456{
75− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
76− where
77− 'a: 'bump,
78− {
79− (**self).render(bump)
57+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
58+ (**self).render(cx)
8059 }
8160}
8261
@@ -99,7 +78,10 @@ pub trait RootRender: Any + Render {
9978 fn as_any_mut(&mut self) -> &mut Any;
10079}
10180
102−impl<T: Any + Render> RootRender for T {
81+impl<T> RootRender for T
82+where
83+ T: Any + Render,
84+{
10385 fn as_any(&self) -> &Any {
10486 self
10587 }
@@ -117,7 +99,10 @@ impl dyn RootRender {
11799 ///
118100 /// Panics if this virtual DOM's root rendering component is not an `R`
119101 /// instance.
120− pub fn unwrap_ref<R: RootRender>(&self) -> &R {
102+ pub fn unwrap_ref<R>(&self) -> &R
103+ where
104+ R: RootRender,
105+ {
121106 self.as_any()
122107 .downcast_ref::<R>()
123108 .expect_throw("bad `RootRender::unwrap_ref` call")
@@ -130,7 +115,10 @@ impl dyn RootRender {
130115 ///
131116 /// Panics if this virtual DOM's root rendering component is not an `R`
132117 /// instance.
133− pub fn unwrap_mut<R: RootRender>(&mut self) -> &mut R {
118+ pub fn unwrap_mut<R>(&mut self) -> &mut R
119+ where
120+ R: RootRender,
121+ {
134122 self.as_any_mut()
135123 .downcast_mut::<R>()
136124 .expect_throw("bad `RootRender::unwrap_ref` call")
src/render_context.rsnew file+63
@@ -0,0 +1,63 @@
1+use crate::cached_set::CachedSet;
2+use bumpalo::Bump;
3+use std::fmt;
4+
5+/// Common context available to all `Render` implementations.
6+///
7+/// Notably, the `RenderContext` gives access to the bump arena that the virtual
8+/// DOM should be allocated within. This is available via the `bump` field.
9+pub struct RenderContext<'a> {
10+ /// The underlying bump arena that virtual DOMs are rendered into.
11+ ///
12+ /// ## Example
13+ ///
14+ /// ```
15+ /// use dodrio::RenderContext;
16+ ///
17+ /// // Given a rendering context, allocate an i32 inside its bump arena.
18+ /// fn foo<'a>(cx: &mut RenderContext<'a>) -> &'a mut i32 {
19+ /// cx.bump.alloc(42)
20+ /// }
21+ /// ```
22+ pub bump: &'a Bump,
23+
24+ pub(crate) cached_set: &'a crate::RefCell<CachedSet>,
25+
26+ // Prevent exhaustive matching on the rendering context, so we can always
27+ // add more members in a semver-compatible way.
28+ _non_exhaustive: (),
29+}
30+
31+impl fmt::Debug for RenderContext<'_> {
32+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33+ f.debug_struct("RenderContext")
34+ .field("bump", &self.bump)
35+ .finish()
36+ }
37+}
38+
39+impl<'a> RenderContext<'a> {
40+ pub_unstable_internal! {
41+ pub(crate) fn new(bump: &'a Bump, cached_set: &'a crate::RefCell<CachedSet>) -> Self {
42+ RenderContext {
43+ bump,
44+ cached_set,
45+ _non_exhaustive: (),
46+ }
47+ }
48+ }
49+}
50+
51+impl<'a, 'b> From<&'b RenderContext<'a>> for &'a Bump {
52+ #[inline]
53+ fn from(cx: &'b RenderContext<'a>) -> &'a Bump {
54+ cx.bump
55+ }
56+}
57+
58+impl<'a, 'b, 'c> From<&'c &'b mut RenderContext<'a>> for &'a Bump {
59+ #[inline]
60+ fn from(cx: &'c &'b mut RenderContext<'a>) -> &'a Bump {
61+ cx.bump
62+ }
63+}
src/vdom.rs+81−264
@@ -1,12 +1,13 @@
11use super::change_list::ChangeList;
2−use super::node::{Attribute, ElementNode, Listener, Node, TextNode};
32use super::RootRender;
3+use crate::cached_set::CachedSet;
44use crate::events::EventsRegistry;
5+use crate::node::Node;
6+use crate::RenderContext;
57use bumpalo::Bump;
68use futures::future::Future;
79use std::cell::Cell;
810use std::cell::RefCell;
9−use std::cmp;
1011use std::fmt;
1112use std::mem;
1213use std::mem::ManuallyDrop;
@@ -54,10 +55,11 @@ pub(crate) struct VdomInnerExclusive {
5455 // implementation.
5556 component: Option<Box<RootRender>>,
5657
57− dom_buffers: [Bump; 2],
58+ dom_buffers: Option<[Bump; 2]>,
5859 change_list: ManuallyDrop<ChangeList>,
59− container: web_sys::Element,
60+ container: crate::Element,
6061 events_registry: Option<Rc<RefCell<EventsRegistry>>>,
62+ cached_set: crate::RefCell<CachedSet>,
6163
6264 // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
6365 // caching renders, into `self.component`'s bump.
@@ -109,7 +111,36 @@ impl Drop for VdomInnerExclusive {
109111 let mut registry = registry.borrow_mut();
110112 registry.clear_active_listeners();
111113
112− self.container.set_inner_html("");
114+ empty_container(&self.container);
115+ }
116+}
117+
118+cfg_if::cfg_if! {
119+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
120+ fn empty_container(_container: &crate::Element) {}
121+ fn initialize_container(_container: &crate::Element) {}
122+ } else {
123+ fn empty_container(container: &crate::Element) {
124+ container.set_inner_html("");
125+ }
126+
127+ fn initialize_container(container: &crate::Element) {
128+ empty_container(container);
129+
130+ // Create the dummy `<div/>` child in the container.
131+ let window = web_sys::window().expect_throw("should have access to the Window");
132+ let document = window
133+ .document()
134+ .expect("should have access to the Document");
135+ container
136+ .append_child(
137+ document
138+ .create_element("div")
139+ .expect("should create element OK")
140+ .as_ref(),
141+ )
142+ .expect("should append child OK");
143+ }
113144 }
114145}
115146
@@ -118,7 +149,7 @@ impl Vdom {
118149 /// rendering component.
119150 ///
120151 /// This will box the given component into trait object.
121− pub fn new<R>(container: &web_sys::Element, component: R) -> Vdom
152+ pub fn new<R>(container: &crate::Element, component: R) -> Vdom
122153 where
123154 R: RootRender,
124155 {
@@ -127,31 +158,14 @@ impl Vdom {
127158
128159 /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
129160 /// rendering component.
130− pub fn with_boxed_root_render(
131− container: &web_sys::Element,
132− component: Box<RootRender>,
133− ) -> Vdom {
161+ pub fn with_boxed_root_render(container: &crate::Element, component: Box<RootRender>) -> Vdom {
134162 let dom_buffers = [Bump::new(), Bump::new()];
135163 let change_list = ManuallyDrop::new(ChangeList::new(container));
136164
137− // Ensure that the container is empty.
138− container.set_inner_html("");
139−
140165 // Create a dummy `<div/>` in our container.
166+ initialize_container(container);
141167 let current_root = Node::element(&dom_buffers[0], "div", [], [], [], None);
142168 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
143− let window = web_sys::window().expect("should have access to the Window");
144− let document = window
145− .document()
146− .expect("should have access to the Document");
147− container
148− .append_child(
149− document
150− .create_element("div")
151− .expect("should create element OK")
152− .as_ref(),
153− )
154− .expect("should append child OK");
155169
156170 let container = container.clone();
157171 let inner = Rc::new(VdomInner {
@@ -160,11 +174,12 @@ impl Vdom {
160174 },
161175 exclusive: RefCell::new(VdomInnerExclusive {
162176 component: Some(component),
163− dom_buffers,
177+ dom_buffers: Some(dom_buffers),
164178 change_list,
165179 container,
166180 current_root,
167181 events_registry: None,
182+ cached_set: crate::RefCell::new(Default::default()),
168183 }),
169184 });
170185
@@ -182,6 +197,19 @@ impl Vdom {
182197 Vdom { inner }
183198 }
184199
200+ /// Immediately re-render and diff. Only for internal testing and
201+ /// benchmarking purposes.
202+ #[cfg(feature = "xxx-unstable-internal-use-only")]
203+ pub fn immediately_render_and_diff<R>(&self, component: R)
204+ where
205+ R: RootRender,
206+ {
207+ let mut exclusive = self.inner.exclusive.borrow_mut();
208+ let component = Box::new(component) as Box<RootRender>;
209+ exclusive.component = Some(component);
210+ exclusive.render();
211+ }
212+
185213 /// Run this virtual DOM and its listeners forever and never unmount it.
186214 #[inline]
187215 pub fn forget(self) {
@@ -221,36 +249,42 @@ impl VdomInnerExclusive {
221249 pub(crate) fn render(&mut self) {
222250 unsafe {
223251 let events_registry = self.events_registry.take().unwrap();
224−
225252 {
226− // All the old listeners are no longer active. We will build a new
227− // set of active listeners when diffing.
228− //
229− // NB: if we end up avoiding diffing cached renders (instead of just
230− // avoiding re-rendering them) then we will need to maintain cached
231− // active listeners, and can't just clear all active listeners and
232− // rebuild them here.
233253 let mut registry = events_registry.borrow_mut();
234− registry.clear_active_listeners();
235254
236255 // Reset the inactive bump arena's pointer.
237− self.dom_buffers[1].reset();
256+ let mut dom_buffers = self.dom_buffers.take().unwrap_throw();
257+ dom_buffers[1].reset();
238258
239259 // Render the new current contents into the inactive bump arena.
240− let new_contents = self
241− .component
242− .as_ref()
243− .unwrap_throw()
244− .render(&self.dom_buffers[1]);
260+ let mut cx = RenderContext::new(&dom_buffers[1], &self.cached_set);
261+ let new_contents = self.component.as_ref().unwrap_throw().render(&mut cx);
245262 let new_contents = extend_node_lifetime(new_contents);
246263
247264 // Diff the old contents with the new contents.
248265 let old_contents = self.current_root.take().unwrap();
249− self.diff(&mut registry, old_contents, new_contents.clone());
266+ let mut cache_roots = bumpalo::collections::Vec::new_in(&dom_buffers[1]);
267+ {
268+ let cached_set = self.cached_set.borrow();
269+ crate::diff::diff(
270+ &cached_set,
271+ &mut self.change_list,
272+ &mut registry,
273+ old_contents,
274+ new_contents.clone(),
275+ &mut cache_roots,
276+ );
277+ }
278+
279+ {
280+ // Clean up unused cached renders.
281+ let mut cached_set = self.cached_set.borrow_mut();
282+ cached_set.gc(&mut registry, &cache_roots);
283+ }
250284
251285 // Swap the buffers to make the bump arena with the new contents the
252286 // active arena, and the old one into the inactive arena.
253− self.swap_buffers();
287+ self.swap_buffers(dom_buffers);
254288 self.set_current_root(new_contents);
255289 }
256290
@@ -264,234 +298,17 @@ impl VdomInnerExclusive {
264298 }
265299 }
266300
267− fn swap_buffers(&mut self) {
268− let (first, second) = self.dom_buffers.as_mut().split_at_mut(1);
301+ fn swap_buffers(&mut self, mut dom_buffers: [Bump; 2]) {
302+ debug_assert!(self.dom_buffers.is_none());
303+ let (first, second) = dom_buffers.as_mut().split_at_mut(1);
269304 mem::swap(&mut first[0], &mut second[0]);
305+ self.dom_buffers = Some(dom_buffers);
270306 }
271307
272308 unsafe fn set_current_root(&mut self, current: Node<'static>) {
273309 debug_assert!(self.current_root.is_none());
274310 self.current_root = Some(current);
275311 }
276−
277− fn diff<'a>(&mut self, registry: &mut EventsRegistry, old: Node<'a>, new: Node<'a>) {
278− // debug!("---------------------------------------------------------");
279− // debug!("dodrio::Vdom::diff");
280− // debug!(" old = {:#?}", old);
281− // debug!(" new = {:#?}", new);
282− match (&new, old) {
283− (&Node::Text(TextNode { text: new_text }), Node::Text(TextNode { text: old_text })) => {
284− debug!(" both are text nodes");
285− if new_text != old_text {
286− debug!(" text needs updating");
287− self.change_list.emit_set_text(new_text);
288− }
289− }
290− (&Node::Text(_), Node::Element(_)) => {
291− debug!(" replacing a text node with an element");
292− self.create(registry, new);
293− self.change_list.emit_replace_with();
294− }
295− (&Node::Element(_), Node::Text(_)) => {
296− debug!(" replacing an element with a text node");
297− self.create(registry, new);
298− self.change_list.emit_replace_with();
299− }
300− (
301− &Node::Element(ElementNode {
302− tag_name: new_tag_name,
303− listeners: new_listeners,
304− attributes: new_attributes,
305− children: new_children,
306− namespace: new_namespace,
307− }),
308− Node::Element(ElementNode {
309− tag_name: old_tag_name,
310− listeners: old_listeners,
311− attributes: old_attributes,
312− children: old_children,
313− namespace: old_namespace,
314− }),
315− ) => {
316− debug!(" updating an element");
317− if new_tag_name != old_tag_name || new_namespace != old_namespace {
318− debug!(" different tag names or namespaces; creating new element and replacing old element");
319− self.create(registry, new);
320− self.change_list.emit_replace_with();
321− return;
322− }
323− self.diff_listeners(registry, old_listeners, new_listeners);
324− self.diff_attributes(old_attributes, new_attributes);
325− self.diff_children(registry, old_children, new_children);
326− }
327− }
328− }
329−
330− fn diff_listeners<'a>(
331− &mut self,
332− registry: &mut EventsRegistry,
333− old: &'a [Listener<'a>],
334− new: &'a [Listener<'a>],
335− ) {
336− debug!(" updating event listeners");
337−
338− 'outer1: for new_l in new {
339− unsafe {
340− // Safety relies on removing `new_l` from the registry manually
341− // at the end of its lifetime. This happens when we invoke
342− // `clear_active_listeners` at the start of a new rendering
343− // phase.
344− registry.add(new_l);
345− }
346− for old_l in old {
347− if new_l.event == old_l.event {
348− self.change_list.emit_update_event_listener(new_l);
349− continue 'outer1;
350− }
351− }
352− self.change_list.emit_new_event_listener(new_l);
353− }
354−
355− 'outer2: for old_l in old {
356− for new_l in new {
357− if new_l.event == old_l.event {
358− continue 'outer2;
359− }
360− }
361− self.change_list.emit_remove_event_listener(old_l.event);
362− }
363− }
364−
365− fn diff_attributes(&mut self, old: &[Attribute], new: &[Attribute]) {
366− debug!(" updating attributes");
367−
368− // Do O(n^2) passes to add/update and remove attributes, since
369− // there are almost always very few attributes.
370− 'outer: for new_attr in new {
371− if new_attr.is_volatile() {
372− self.change_list
373− .emit_set_attribute(new_attr.name, new_attr.value);
374− } else {
375− for old_attr in old {
376− if old_attr.name == new_attr.name {
377− if old_attr.value != new_attr.value {
378− self.change_list
379− .emit_set_attribute(new_attr.name, new_attr.value);
380− }
381− continue 'outer;
382− }
383− }
384− self.change_list
385− .emit_set_attribute(new_attr.name, new_attr.value);
386− }
387− }
388−
389− 'outer2: for old_attr in old {
390− for new_attr in new {
391− if old_attr.name == new_attr.name {
392− continue 'outer2;
393− }
394− }
395− self.change_list.emit_remove_attribute(old_attr.name);
396− }
397− }
398−
399− fn diff_children<'a>(
400− &mut self,
401− registry: &mut EventsRegistry,
402− old: &'a [Node<'a>],
403− new: &'a [Node<'a>],
404− ) {
405− debug!(" updating children shared by old and new");
406−
407− let num_children_to_diff = cmp::min(new.len(), old.len());
408− let mut new_children = new.iter();
409− let mut old_children = old.iter();
410− let mut pushed = false;
411−
412− for (i, (new_child, old_child)) in new_children
413− .by_ref()
414− .zip(old_children.by_ref())
415− .take(num_children_to_diff)
416− .enumerate()
417− {
418− if i == 0 {
419− self.change_list.emit_push_first_child();
420− pushed = true;
421− } else {
422− debug_assert!(pushed);
423− self.change_list.emit_pop_push_next_sibling();
424− }
425−
426− self.diff(registry, old_child.clone(), new_child.clone());
427− }
428−
429− if old_children.next().is_some() {
430− debug!(" removing extra old children");
431− debug_assert!(new_children.next().is_none());
432− if !pushed {
433− self.change_list.emit_push_first_child();
434− } else {
435− self.change_list.emit_pop_push_next_sibling();
436− }
437− self.change_list.emit_remove_self_and_next_siblings();
438− pushed = false;
439− } else {
440− debug!(" creating new children");
441− for (i, new_child) in new_children.enumerate() {
442− if i == 0 && pushed {
443− self.change_list.emit_pop();
444− pushed = false;
445− }
446− self.create(registry, new_child.clone());
447− self.change_list.emit_append_child();
448− }
449− }
450−
451− debug!(" done updating children");
452− if pushed {
453− self.change_list.emit_pop();
454− }
455− }
456−
457− fn create<'a>(&mut self, registry: &mut EventsRegistry, node: Node<'a>) {
458− match node {
459− Node::Text(TextNode { text }) => {
460− self.change_list.emit_create_text_node(text);
461− }
462− Node::Element(ElementNode {
463− tag_name,
464− listeners,
465− attributes,
466− children,
467− namespace,
468− }) => {
469− if let Some(namespace) = namespace {
470− self.change_list.emit_create_element_ns(tag_name, namespace);
471− } else {
472− self.change_list.emit_create_element(tag_name);
473− }
474− for l in listeners {
475− unsafe {
476− registry.add(l);
477− }
478− self.change_list.emit_new_event_listener(l);
479− }
480− for attr in attributes {
481− if namespace.is_none() || attr.name.starts_with("xmlns") {
482− self.change_list.emit_set_attribute(&attr.name, &attr.value);
483− } else {
484− self.change_list
485− .emit_set_attribute_ns(&attr.name, &attr.value);
486− }
487− }
488− for child in children {
489− self.create(registry, child.clone());
490− self.change_list.emit_append_child();
491− }
492− }
493− }
494− }
495312}
496313
497314fn request_animation_frame(f: &Closure<FnMut()>) {
tests/web/cached.rs+140−7
@@ -1,5 +1,5 @@
1−use super::{assert_rendered, create_element, RenderFn};
2−use dodrio::{builder::*, bumpalo::Bump, Cached, Node, Render, Vdom};
1+use super::{assert_rendered, before_after, create_element, RenderFn};
2+use dodrio::{builder::*, bumpalo, Cached, Node, Render, RenderContext, Vdom};
33use futures::prelude::*;
44use std::cell::Cell;
55use std::rc::Rc;
@@ -19,14 +19,11 @@ impl CountRenders {
1919}
2020
2121impl Render for CountRenders {
22− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
23− where
24− 'a: 'bump,
25− {
22+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
2623 let count = self.render_count.get() + 1;
2724 self.render_count.set(count);
2825
29− let s = bumpalo::format!(in bump, "{}", count);
26+ let s = bumpalo::format!(in cx.bump, "{}", count);
3027 text(s.into_bump_str())
3128 }
3229}
@@ -99,3 +96,139 @@ fn uses_cached_render() -> impl Future<Item = (), Error = JsValue> {
9996 })
10097 .map_err(|e| JsValue::from(e.to_string()))
10198}
99+
100+struct Id(&'static str);
101+impl Render for Id {
102+ fn render<'a>(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
103+ text(self.0)
104+ }
105+}
106+
107+thread_local! {
108+ static WARM_CHEESE: Rc<Cached<Id>> = Rc::new(Cached::new(Id("cheese")));
109+ static WARM_CHEESIER: Rc<Cached<Id>> = Rc::new(Cached::new(Id("cheesier")));
110+}
111+
112+fn warm_cheese<'a>(cx: &mut RenderContext<'a>) -> Node<'a> {
113+ WARM_CHEESE.with(|c| {
114+ let _ = c.render(cx);
115+ c.render(cx)
116+ })
117+}
118+
119+fn warm_cheesier<'a>(cx: &mut RenderContext<'a>) -> Node<'a> {
120+ WARM_CHEESIER.with(|c| {
121+ let _ = c.render(cx);
122+ c.render(cx)
123+ })
124+}
125+
126+before_after! {
127+ cold_cache_and_not_cached {
128+ before(cx) {
129+ Cached::new(Id("cheese")).render(cx)
130+ }
131+ after(cx) {
132+ Id("ravioli").render(cx)
133+ }
134+ }
135+
136+ not_cached_and_cold_cache {
137+ before(cx) {
138+ Id("ravioli").render(cx)
139+ }
140+ after(cx) {
141+ Cached::new(Id("cheese")).render(cx)
142+ }
143+ }
144+
145+ cold_cache_and_cold_cache_same {
146+ before(cx) {
147+ Cached::new(Id("cheese")).render(cx)
148+ }
149+ after(cx) {
150+ Cached::new(Id("cheese")).render(cx)
151+ }
152+ }
153+
154+ cold_cache_and_cold_cache_different {
155+ before(cx) {
156+ Cached::new(Id("cheese")).render(cx)
157+ }
158+ after(cx) {
159+ Cached::new(Id("ravioli")).render(cx)
160+ }
161+ }
162+
163+ warm_cache_and_not_cached {
164+ before(cx) {
165+ warm_cheese(cx)
166+ }
167+ after(cx) {
168+ Id("ravioli").render(cx)
169+ }
170+ }
171+
172+ not_cached_and_warm_cache {
173+ before(cx) {
174+ Id("ravioli").render(cx)
175+ }
176+ after(cx) {
177+ warm_cheese(cx)
178+ }
179+ }
180+
181+ warm_cache_and_warm_cache_same {
182+ before(cx) {
183+ warm_cheese(cx)
184+ }
185+ after(cx) {
186+ warm_cheese(cx)
187+ }
188+ }
189+
190+ warm_cache_and_warm_cache_different {
191+ before(cx) {
192+ warm_cheese(cx)
193+ }
194+ after(cx) {
195+ warm_cheesier(cx)
196+ }
197+ }
198+
199+ cold_cache_and_warm_cache_same {
200+ before(cx) {
201+ Cached::new(Id("cheese")).render(cx)
202+ }
203+ after(cx) {
204+ warm_cheese(cx)
205+ }
206+ }
207+
208+ cold_cache_and_warm_cache_different {
209+ before(cx) {
210+ Cached::new(Id("cheese")).render(cx)
211+ }
212+ after(cx) {
213+ warm_cheesier(cx)
214+ }
215+ }
216+
217+ warm_cache_and_cold_cache_same {
218+ before(cx) {
219+ warm_cheese(cx)
220+ }
221+ after(cx) {
222+ Cached::new(Id("cheese")).render(cx)
223+ }
224+ }
225+
226+ warm_cache_and_cold_cache_different {
227+ before(cx) {
228+ warm_cheesier(cx)
229+ }
230+ after(cx) {
231+ Cached::new(Id("cheese")).render(cx)
232+ }
233+ }
234+}
tests/web/events.rs+5−86
@@ -1,6 +1,5 @@
11use super::create_element;
2−use bumpalo::Bump;
3−use dodrio::{Node, Render, Vdom};
2+use dodrio::{Node, Render, RenderContext, Vdom};
43use futures::Future;
54use std::cell::{Cell, RefCell};
65use std::rc::Rc;
@@ -26,12 +25,9 @@ impl EventContainer {
2625}
2726
2827impl Render for EventContainer {
29− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
30− where
31− 'a: 'bump,
32− {
28+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
3329 use dodrio::builder::*;
34− div(bump)
30+ div(&cx)
3531 .attr("id", "target")
3632 .on(self.event, |root, _scheduler, _event| {
3733 (root.unwrap_mut::<EventContainer>().on_event)();
@@ -138,16 +134,13 @@ impl ListensOnlyOnFirstRender {
138134}
139135
140136impl Render for ListensOnlyOnFirstRender {
141− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
142− where
143− 'a: 'bump,
144− {
137+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
145138 use dodrio::builder::*;
146139
147140 let count = self.count.get();
148141 self.count.set(count + 1);
149142
150− let mut elem = div(bump).attr("id", "target");
143+ let mut elem = div(&cx).attr("id", "target");
151144 if count == 0 {
152145 elem = elem.on("click", |root, _scheduler, _event| {
153146 (root.unwrap_mut::<ListensOnlyOnFirstRender>().callback)();
@@ -272,77 +265,3 @@ fn event_does_not_fire_after_unmounting() -> impl Future<Item = (), Error = JsVa
272265 })
273266 .map_err(|e| JsValue::from(e.to_string()))
274267}
275−
276−struct CountRendersAndListen {
277− count: Cell<usize>,
278− callback: Box<FnMut(usize)>,
279−}
280−
281−impl CountRendersAndListen {
282− fn new<F>(callback: F) -> CountRendersAndListen
283− where
284− F: 'static + FnMut(usize),
285− {
286− CountRendersAndListen {
287− count: Cell::new(0),
288− callback: Box::new(callback),
289− }
290− }
291−}
292−
293−impl Render for CountRendersAndListen {
294− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
295− where
296− 'a: 'bump,
297− {
298− use dodrio::builder::*;
299−
300− let count = self.count.get() + 1;
301− self.count.set(count);
302−
303− button(bump)
304− .attr("id", "target")
305− .on("click", |comp, _vdom, _event| {
306− let comp: &mut _ = comp.unwrap_mut::<dodrio::Cached<CountRendersAndListen>>();
307− let comp: &mut CountRendersAndListen = comp;
308− let n = comp.count.get();
309− (comp.callback)(n);
310− })
311− .finish()
312− }
313−}
314−
315−#[wasm_bindgen_test(async)]
316−fn cached_listeners_work_correctly() -> impl Future<Item = (), Error = JsValue> {
317− let container = create_element("div");
318−
319− let (sender, receiver) = futures::sync::oneshot::channel();
320− let mut sender = Some(sender);
321− let vdom = Vdom::new(
322− &container,
323− dodrio::Cached::new(CountRendersAndListen::new(move |n| {
324− sender.take().unwrap().send(n).unwrap();
325− })),
326− );
327−
328− let weak0 = vdom.weak();
329− let weak1 = vdom.weak();
330− let weak2 = vdom.weak();
331− vdom.weak()
332− // Re-render a few times, so we are using the cached version.
333− .render()
334− .and_then(move |_| weak0.render())
335− .and_then(move |_| weak1.render())
336− .and_then(move |_| weak2.render())
337− .map_err(|e| JsValue::from(e.to_string()))
338− .and_then(move |_| {
339− target(&container).click();
340−
341− receiver
342− .map(|n| {
343− assert_eq!(n, 1);
344− drop(vdom);
345− })
346− .map_err(|e| JsValue::from(e.to_string()))
347− })
348−}
tests/web/js_api.rs+12−15
@@ -1,5 +1,5 @@
11use super::{assert_rendered, create_element, RenderFn};
2−use dodrio::{builder::*, bumpalo::Bump, Node, Render, Vdom};
2+use dodrio::{builder::*, Node, Render, RenderContext, Vdom};
33use dodrio_js_api::JsRender;
44use futures::prelude::*;
55use futures::sync::oneshot;
@@ -37,13 +37,10 @@ impl WrapJs {
3737}
3838
3939impl Render for WrapJs {
40− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
41− where
42− 'a: 'bump,
43− {
44− div(bump)
40+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
41+ div(&cx)
4542 .attr("class", "wrap-js")
46− .children([self.inner.render(bump)])
43+ .children([self.inner.render(cx)])
4744 .finish()
4845 }
4946}
@@ -109,14 +106,14 @@ fn can_use_js_rendering_components() -> impl Future<Item = (), Error = JsValue>
109106 let vdom = Rc::new(Vdom::new(&container, component));
110107 assert_rendered(
111108 &container,
112− &RenderFn(|bump| {
113− div(bump)
109+ &RenderFn(|cx| {
110+ div(&cx)
114111 .attr("class", "wrap-js")
115− .children([span(bump)
112+ .children([span(&cx)
116113 .attr("class", "js-component")
117114 .children([
118115 text("Here is some plain text"),
119− b(bump)
116+ b(&cx)
120117 .children([text("...and here is some bold text")])
121118 .finish(),
122119 text("0"),
@@ -138,14 +135,14 @@ fn can_use_js_rendering_components() -> impl Future<Item = (), Error = JsValue>
138135 .map(move |_| {
139136 assert_rendered(
140137 &container,
141− &RenderFn(|bump| {
142− div(bump)
138+ &RenderFn(|cx| {
139+ div(&cx)
143140 .attr("class", "wrap-js")
144− .children([span(bump)
141+ .children([span(&cx)
145142 .attr("class", "js-component")
146143 .children([
147144 text("Here is some plain text"),
148− b(bump)
145+ b(&cx)
149146 .children([text("...and here is some bold text")])
150147 .finish(),
151148 text("1"),
tests/web/main.rs+36−22
@@ -1,11 +1,14 @@
11//! Test suite for the Web and headless browsers.
22
3−#![cfg(target_arch = "wasm32")]
3+#![cfg(all(feature = "xxx-unstable-internal-use-only", target_arch = "wasm32"))]
44
55use bumpalo::Bump;
6−use dodrio::{Attribute, Node, Render, Vdom};
6+use dodrio::{
7+ Attribute, CachedSet, ElementNode, Node, NodeKind, Render, RenderContext, TextNode, Vdom,
8+};
79use futures::prelude::*;
810use log::*;
11+use std::cell::RefCell;
912use std::rc::Rc;
1013use wasm_bindgen::prelude::*;
1114use wasm_bindgen::JsCast;
@@ -49,12 +52,16 @@ pub fn init_logging() {
4952pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
5053 init_logging();
5154
55+ let cached_set = &RefCell::new(CachedSet::default());
5256 let bump = &Bump::new();
53− let node = r.render(bump);
57+ let cx = &mut RenderContext::new(bump, cached_set);
58+ let node = r.render(cx);
5459 let child = container
5560 .first_child()
5661 .expect("container does not have anything rendered into it?");
57− check_node(&child, &node);
62+
63+ let cached_set = cached_set.borrow();
64+ check_node(&cached_set, &child, &node);
5865
5966 fn stringify_actual_node(n: &web_sys::Node) -> String {
6067 if let Some(el) = n.dyn_ref::<web_sys::Element>() {
@@ -64,12 +71,12 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
6471 }
6572 }
6673
67− fn check_node(actual: &web_sys::Node, expected: &Node) {
74+ fn check_node(cached_set: &CachedSet, actual: &web_sys::Node, expected: &Node) {
6875 debug!("check_render:");
6976 debug!(" actual = {}", stringify_actual_node(&actual));
7077 debug!(" expected = {:#?}", expected);
71− match expected {
72− Node::Text(text_node) => {
78+ match expected.kind {
79+ NodeKind::Text(TextNode { text }) => {
7380 assert_eq!(
7481 actual.node_name().to_uppercase(),
7582 "#TEXT",
@@ -77,25 +84,35 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
7784 );
7885 assert_eq!(
7986 actual.text_content().unwrap_or_default(),
80− text_node.text(),
87+ text,
8188 "actual.text_content() == expected.text()"
8289 );
8390 }
84− Node::Element(elem) => {
91+ NodeKind::Element(ElementNode {
92+ tag_name,
93+ attributes,
94+ children,
95+ namespace,
96+ ..
97+ }) => {
8598 assert_eq!(
8699 actual.node_name().to_uppercase(),
87− elem.tag_name().to_uppercase(),
100+ tag_name.to_uppercase(),
88101 "actual.node_name() == expected.tag_name()"
89102 );
90103 let actual = actual
91104 .dyn_ref::<web_sys::Element>()
92105 .expect("`actual` should be an `Element`");
93− check_attributes(actual.attributes(), elem.attributes());
94− check_children(actual.child_nodes(), elem.children());
95− if let Some(namespace) = elem.namespace() {
106+ check_attributes(actual.attributes(), attributes);
107+ check_children(cached_set, actual.child_nodes(), children);
108+ if let Some(namespace) = namespace {
96109 assert_eq!(actual.namespace_uri(), Some(namespace.into()))
97110 }
98111 }
112+ NodeKind::Cached(ref c) => {
113+ let expected = cached_set.get(c.id);
114+ check_node(cached_set, actual, &expected);
115+ }
99116 }
100117 }
101118
@@ -118,7 +135,7 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
118135 }
119136 }
120137
121− fn check_children(actual: web_sys::NodeList, expected: &[Node]) {
138+ fn check_children(cached_set: &CachedSet, actual: web_sys::NodeList, expected: &[Node]) {
122139 assert_eq!(
123140 actual.length(),
124141 expected.len() as u32,
@@ -126,7 +143,7 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
126143 );
127144 for (i, child) in expected.iter().enumerate() {
128145 let actual_child = actual.item(i as u32).unwrap();
129− check_node(&actual_child, child);
146+ check_node(cached_set, &actual_child, child);
130147 }
131148 }
132149}
@@ -134,17 +151,14 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
134151/// Use the function `F` to render.
135152pub struct RenderFn<F>(F)
136153where
137− F: for<'bump> Fn(&'bump Bump) -> Node<'bump>;
154+ F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>;
138155
139156impl<F> Render for RenderFn<F>
140157where
141− F: for<'bump> Fn(&'bump Bump) -> Node<'bump>,
158+ F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>,
142159{
143− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
144− where
145− 'a: 'bump,
146− {
147− (self.0)(bump)
160+ fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
161+ (self.0)(cx)
148162 }
149163}
150164
tests/web/render.rs+85−85
@@ -6,7 +6,7 @@ use wasm_bindgen_test::*;
66
77#[wasm_bindgen_test]
88fn render_initial_text() {
9− let hello = Rc::new(RenderFn(|_bump| text("hello")));
9+ let hello = Rc::new(RenderFn(|_cx| text("hello")));
1010
1111 let container = create_element("div");
1212 let _vdom = Vdom::new(&container, hello.clone());
@@ -15,10 +15,10 @@ fn render_initial_text() {
1515
1616#[wasm_bindgen_test]
1717fn render_initial_node() {
18− let hello = Rc::new(RenderFn(|bump| {
19− div(bump)
18+ let hello = Rc::new(RenderFn(|cx| {
19+ div(&cx)
2020 .attr("id", "hello-world")
21− .children([text("Hello "), span(bump).child(text("World!")).finish()])
21+ .children([text("Hello "), span(&cx).child(text("World!")).finish()])
2222 .finish()
2323 }));
2424
@@ -30,96 +30,96 @@ fn render_initial_node() {
3030#[wasm_bindgen_test]
3131fn container_is_emptied_upon_drop() {
3232 let container = create_element("div");
33− let vdom = Vdom::new(&container, RenderFn(|_bump| text("blah")));
33+ let vdom = Vdom::new(&container, RenderFn(|_cx| text("blah")));
3434 drop(vdom);
3535 assert!(container.first_child().is_none());
3636}
3737
3838before_after! {
3939 same_text {
40− before(_bump) {
40+ before(_cx) {
4141 text("hello")
4242 }
43− after(_bump) {
43+ after(_cx) {
4444 text("hello")
4545 }
4646 }
4747
4848 update_text {
49− before(_bump) {
49+ before(_cx) {
5050 text("before")
5151 }
52− after(_bump) {
52+ after(_cx) {
5353 text("after")
5454 }
5555 }
5656
5757 replace_text_with_elem {
58− before(_bump) {
58+ before(_cx) {
5959 text("before")
6060 }
61− after(bump) {
62− div(bump).finish()
61+ after(cx) {
62+ div(&cx).finish()
6363 }
6464 }
6565
6666 replace_elem_with_text {
67− before(bump) {
68− div(bump).finish()
67+ before(cx) {
68+ div(&cx).finish()
6969 }
70− after(_bump) {
70+ after(_cx) {
7171 text("before")
7272 }
7373 }
7474
7575 same_elem {
76− before(bump) {
77− div(bump).finish()
76+ before(cx) {
77+ div(&cx).finish()
7878 }
79− after(bump) {
80− div(bump).finish()
79+ after(cx) {
80+ div(&cx).finish()
8181 }
8282 }
8383
8484 elems_with_different_tag_names {
85− before(bump) {
86− span(bump).finish()
85+ before(cx) {
86+ span(&cx).finish()
8787 }
88− after(bump) {
89− div(bump).finish()
88+ after(cx) {
89+ div(&cx).finish()
9090 }
9191 }
9292
9393 same_tag_name_update_attribute {
94− before(bump) {
95− div(bump).attr("value", "1").finish()
94+ before(cx) {
95+ div(&cx).attr("value", "1").finish()
9696 }
97− after(bump) {
98− div(bump).attr("value", "2").finish()
97+ after(cx) {
98+ div(&cx).attr("value", "2").finish()
9999 }
100100 }
101101
102102 same_tag_name_remove_attribute {
103− before(bump) {
104− div(bump).attr("value", "1").finish()
103+ before(cx) {
104+ div(&cx).attr("value", "1").finish()
105105 }
106− after(bump) {
107− div(bump).finish()
106+ after(cx) {
107+ div(&cx).finish()
108108 }
109109 }
110110
111111 same_tag_name_add_attribute {
112− before(bump) {
113− div(bump).finish()
112+ before(cx) {
113+ div(&cx).finish()
114114 }
115− after(bump) {
116− div(bump).attr("value", "2").finish()
115+ after(cx) {
116+ div(&cx).attr("value", "2").finish()
117117 }
118118 }
119119
120120 same_tag_name_many_attributes {
121− before(bump) {
122− div(bump)
121+ before(cx) {
122+ div(&cx)
123123 .attr("before-1", "1")
124124 .attr("shared-1", "1")
125125 .attr("modified-1", "1")
@@ -131,8 +131,8 @@ before_after! {
131131 .attr("modified-3", "3")
132132 .finish()
133133 }
134− after(bump) {
135− div(bump)
134+ after(cx) {
135+ div(&cx)
136136 .attr("after-1", "1")
137137 .attr("shared-1", "1")
138138 .attr("modified-1", "100")
@@ -147,108 +147,108 @@ before_after! {
147147 }
148148
149149 same_tag_same_children {
150− before(bump) {
151− div(bump).child(text("child")).finish()
150+ before(cx) {
151+ div(&cx).child(text("child")).finish()
152152 }
153− after(bump) {
154− div(bump).child(text("child")).finish()
153+ after(cx) {
154+ div(&cx).child(text("child")).finish()
155155 }
156156 }
157157
158158 same_tag_update_child {
159− before(bump) {
160− div(bump).child(text("before")).finish()
159+ before(cx) {
160+ div(&cx).child(text("before")).finish()
161161 }
162− after(bump) {
163− div(bump).child(text("after")).finish()
162+ after(cx) {
163+ div(&cx).child(text("after")).finish()
164164 }
165165 }
166166
167167 same_tag_add_child {
168− before(bump) {
169− div(bump).finish()
168+ before(cx) {
169+ div(&cx).finish()
170170 }
171− after(bump) {
172− div(bump).child(text("child")).finish()
171+ after(cx) {
172+ div(&cx).child(text("child")).finish()
173173 }
174174 }
175175
176176 same_tag_remove_child {
177− before(bump) {
178− div(bump).child(text("child")).finish()
177+ before(cx) {
178+ div(&cx).child(text("child")).finish()
179179 }
180− after(bump) {
181− div(bump).finish()
180+ after(cx) {
181+ div(&cx).finish()
182182 }
183183 }
184184
185185 same_tag_update_many_children {
186− before(bump) {
187− div(bump)
186+ before(cx) {
187+ div(&cx)
188188 .children([
189− div(bump).finish(),
190− span(bump).finish(),
191− p(bump).finish(),
189+ div(&cx).finish(),
190+ span(&cx).finish(),
191+ p(&cx).finish(),
192192 ])
193193 .finish()
194194 }
195− after(bump) {
196− div(bump)
195+ after(cx) {
196+ div(&cx)
197197 .children([
198− span(bump).finish(),
199− p(bump).finish(),
200− div(bump).finish(),
198+ span(&cx).finish(),
199+ p(&cx).finish(),
200+ div(&cx).finish(),
201201 ])
202202 .finish()
203203 }
204204 }
205205
206206 same_tag_remove_many_children {
207− before(bump) {
208− div(bump)
207+ before(cx) {
208+ div(&cx)
209209 .children([
210− div(bump).finish(),
211− span(bump).finish(),
212− p(bump).finish(),
210+ div(&cx).finish(),
211+ span(&cx).finish(),
212+ p(&cx).finish(),
213213 ])
214214 .finish()
215215 }
216− after(bump) {
217− div(bump)
216+ after(cx) {
217+ div(&cx)
218218 .children([
219− div(bump).finish(),
219+ div(&cx).finish(),
220220 ])
221221 .finish()
222222 }
223223 }
224224
225225 same_tag_add_many_children {
226− before(bump) {
227− div(bump)
226+ before(cx) {
227+ div(&cx)
228228 .children([
229− div(bump).finish(),
229+ div(&cx).finish(),
230230 ])
231231 .finish()
232232 }
233− after(bump) {
234− div(bump)
233+ after(cx) {
234+ div(&cx)
235235 .children([
236− div(bump).finish(),
237− span(bump).finish(),
238− p(bump).finish(),
236+ div(&cx).finish(),
237+ span(&cx).finish(),
238+ p(&cx).finish(),
239239 ])
240240 .finish()
241241 }
242242 }
243243
244244 same_tag_different_namespace {
245− before(bump) {
246− div(bump)
245+ before(cx) {
246+ div(&cx)
247247 .namespace(Some("http://example.com"))
248248 .finish()
249249 }
250− after(bump) {
251− div(bump)
250+ after(cx) {
251+ div(&cx)
252252 .namespace(Some("http://example.net"))
253253 .finish()
254254 }