on master help sign in
Merge pull request #58 from fitzgen/refactorings

A few small refactorings

commit0a993e9758a0ed24a741fc22fff68854bef38ae1 authorNick Fitzgerald <fitzgen@gmail.com> dateThu 11 Apr 2019, 09:47 (8 years ago) parent6b0cbf497d6f40
13 files changed+623−608
Cargo.lock+2 js/change-list.js → js/change-list-interpreter.js+57−57 js/package-lock.json−5 js/package.json−11 src/cached_set.rs+13−9 src/change_list.rs−469 src/change_list/emitter.rs+204 src/change_list/js.rs+40 src/change_list/mod.rs+183 src/change_list/strings.rs+65 src/diff.rs+41−44 src/vdom.rs+17−13 tests/web/main.rs+1
Cargo.lock+2
@@ -1,3 +1,5 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
13[[package]]
24name = "aho-corasick"
35version = "0.7.3"
js/change-list.js → js/change-list-interpreter.jsrenamed+57−57
@@ -11,17 +11,17 @@ function string(mem, pointer, length) {
1111
1212const OP_TABLE = [
1313 // 0
14− function setText(changeList, mem8, mem32, i) {
14+ function setText(interpreter, mem8, mem32, i) {
1515 const pointer = mem32[i++];
1616 const length = mem32[i++];
1717 const str = string(mem8, pointer, length);
18− top(changeList.stack).textContent = str;
18+ top(interpreter.stack).textContent = str;
1919 return i;
2020 },
2121
2222 // 1
23− function removeSelfAndNextSiblings(changeList, mem8, mem32, i) {
24− const node = changeList.stack.pop();
23+ function removeSelfAndNextSiblings(interpreter, mem8, mem32, i) {
24+ const node = interpreter.stack.pop();
2525 let sibling = node.nextSibling;
2626 while (sibling) {
2727 const temp = sibling.nextSibling;
@@ -33,21 +33,21 @@ const OP_TABLE = [
3333 },
3434
3535 // 2
36− function replaceWith(changeList, mem8, mem32, i) {
37− const newNode = changeList.stack.pop();
38− const oldNode = changeList.stack.pop();
36+ function replaceWith(interpreter, mem8, mem32, i) {
37+ const newNode = interpreter.stack.pop();
38+ const oldNode = interpreter.stack.pop();
3939 oldNode.replaceWith(newNode);
40− changeList.stack.push(newNode);
40+ interpreter.stack.push(newNode);
4141 return i;
4242 },
4343
4444 // 3
45− function setAttribute(changeList, mem8, mem32, i) {
45+ function setAttribute(interpreter, mem8, mem32, i) {
4646 const nameId = mem32[i++];
4747 const valueId = mem32[i++];
48− const name = changeList.getString(nameId);
49− const value = changeList.getString(valueId);
50− const node = top(changeList.stack);
48+ const name = interpreter.getCachedString(nameId);
49+ const value = interpreter.getCachedString(valueId);
50+ const node = top(interpreter.stack);
5151 node.setAttribute(name, value);
5252
5353 // Some attributes are "volatile" and don't work through `setAttribute`.
@@ -65,10 +65,10 @@ const OP_TABLE = [
6565 },
6666
6767 // 4
68− function removeAttribute(changeList, mem8, mem32, i) {
68+ function removeAttribute(interpreter, mem8, mem32, i) {
6969 const nameId = mem32[i++];
70− const name = changeList.getString(nameId);
71− const node = top(changeList.stack);
70+ const name = interpreter.getCachedString(nameId);
71+ const node = top(interpreter.stack);
7272 node.removeAttribute(name);
7373
7474 // Some attributes are "volatile" and don't work through `removeAttribute`.
@@ -86,120 +86,120 @@ const OP_TABLE = [
8686 },
8787
8888 // 5
89− function pushFirstChild(changeList, mem8, mem32, i) {
90− changeList.stack.push(top(changeList.stack).firstChild);
89+ function pushFirstChild(interpreter, mem8, mem32, i) {
90+ interpreter.stack.push(top(interpreter.stack).firstChild);
9191 return i;
9292 },
9393
9494 // 6
95− function popPushNextSibling(changeList, mem8, mem32, i) {
96− const node = changeList.stack.pop();
97− changeList.stack.push(node.nextSibling);
95+ function popPushNextSibling(interpreter, mem8, mem32, i) {
96+ const node = interpreter.stack.pop();
97+ interpreter.stack.push(node.nextSibling);
9898 return i;
9999 },
100100
101101 // 7
102− function pop(changeList, mem8, mem32, i) {
103− changeList.stack.pop();
102+ function pop(interpreter, mem8, mem32, i) {
103+ interpreter.stack.pop();
104104 return i;
105105 },
106106
107107 // 8
108− function appendChild(changeList, mem8, mem32, i) {
109− const child = changeList.stack.pop();
110− top(changeList.stack).appendChild(child);
108+ function appendChild(interpreter, mem8, mem32, i) {
109+ const child = interpreter.stack.pop();
110+ top(interpreter.stack).appendChild(child);
111111 return i;
112112 },
113113
114114 // 9
115− function createTextNode(changeList, mem8, mem32, i) {
115+ function createTextNode(interpreter, mem8, mem32, i) {
116116 const pointer = mem32[i++];
117117 const length = mem32[i++];
118118 const text = string(mem8, pointer, length);
119− changeList.stack.push(document.createTextNode(text));
119+ interpreter.stack.push(document.createTextNode(text));
120120 return i;
121121 },
122122
123123 // 10
124− function createElement(changeList, mem8, mem32, i) {
124+ function createElement(interpreter, mem8, mem32, i) {
125125 const tagNameId = mem32[i++];
126− const tagName = changeList.getString(tagNameId);
127− changeList.stack.push(document.createElement(tagName));
126+ const tagName = interpreter.getCachedString(tagNameId);
127+ interpreter.stack.push(document.createElement(tagName));
128128 return i;
129129 },
130130
131131 // 11
132− function newEventListener(changeList, mem8, mem32, i) {
132+ function newEventListener(interpreter, mem8, mem32, i) {
133133 const eventId = mem32[i++];
134− const eventType = changeList.getString(eventId);
134+ const eventType = interpreter.getCachedString(eventId);
135135 const a = mem32[i++];
136136 const b = mem32[i++];
137− const el = top(changeList.stack);
138− el.addEventListener(eventType, changeList.eventHandler);
137+ const el = top(interpreter.stack);
138+ el.addEventListener(eventType, interpreter.eventHandler);
139139 el[`dodrio-a-${eventType}`] = a;
140140 el[`dodrio-b-${eventType}`] = b;
141141 return i;
142142 },
143143
144144 // 12
145− function updateEventListener(changeList, mem8, mem32, i) {
145+ function updateEventListener(interpreter, mem8, mem32, i) {
146146 const eventId = mem32[i++];
147− const eventType = changeList.getString(eventId);
148− const el = top(changeList.stack);
147+ const eventType = interpreter.getCachedString(eventId);
148+ const el = top(interpreter.stack);
149149 el[`dodrio-a-${eventType}`] = mem32[i++];
150150 el[`dodrio-b-${eventType}`] = mem32[i++];
151151 return i;
152152 },
153153
154154 // 13
155− function removeEventListener(changeList, mem8, mem32, i) {
155+ function removeEventListener(interpreter, mem8, mem32, i) {
156156 const eventId = mem32[i++];
157− const eventType = changeList.getString(eventId);
158− const el = top(changeList.stack);
159− el.removeEventListener(eventType, changeList.eventHandler);
157+ const eventType = interpreter.getCachedString(eventId);
158+ const el = top(interpreter.stack);
159+ el.removeEventListener(eventType, interpreter.eventHandler);
160160 return i;
161161 },
162162
163163 // 14
164− function addString(changeList, mem8, mem32, i) {
164+ function addCachedString(interpreter, mem8, mem32, i) {
165165 const pointer = mem32[i++];
166166 const length = mem32[i++];
167167 const id = mem32[i++];
168168 const str = string(mem8, pointer, length);
169− changeList.addString(str, id);
169+ interpreter.addCachedString(str, id);
170170 return i;
171171 },
172172
173173 // 15
174− function dropString(changeList, mem8, mem32, i) {
174+ function dropCachedString(interpreter, mem8, mem32, i) {
175175 const id = mem32[i++];
176− changeList.dropString(id);
176+ interpreter.dropCachedString(id);
177177 return i;
178178 },
179179
180180 // 16
181− function createElementNS(changeList, mem8, mem32, i) {
181+ function createElementNS(interpreter, mem8, mem32, i) {
182182 const tagNameId = mem32[i++];
183− const tagName = changeList.getString(tagNameId);
183+ const tagName = interpreter.getCachedString(tagNameId);
184184 const nsId = mem32[i++];
185− const ns = changeList.getString(nsId);
186− changeList.stack.push(document.createElementNS(ns, tagName));
185+ const ns = interpreter.getCachedString(nsId);
186+ interpreter.stack.push(document.createElementNS(ns, tagName));
187187 return i;
188188 },
189189
190190 // 17
191− function setAttributeNS(changeList, mem8, mem32, i) {
191+ function setAttributeNS(interpreter, mem8, mem32, i) {
192192 const nameId = mem32[i++];
193193 const valueId = mem32[i++];
194− const name = changeList.getString(nameId);
195− const value = changeList.getString(valueId);
196− const node = top(changeList.stack);
194+ const name = interpreter.getCachedString(nameId);
195+ const value = interpreter.getCachedString(valueId);
196+ const node = top(interpreter.stack);
197197 node.setAttributeNS(null, name, value);
198198 return i;
199199 }
200200];
201201
202−export class ChangeList {
202+export class ChangeListInterpreter {
203203 constructor(container) {
204204 this.trampoline = null;
205205 this.container = container;
@@ -252,15 +252,15 @@ export class ChangeList {
252252 }
253253 }
254254
255− addString(str, id) {
255+ addCachedString(str, id) {
256256 this.strings.set(id, str);
257257 }
258258
259− dropString(id) {
259+ dropCachedString(id) {
260260 this.strings.delete(id);
261261 }
262262
263− getString(id) {
263+ getCachedString(id) {
264264 return this.strings.get(id);
265265 }
266266
js/package-lock.jsondeleted−5
@@ -1,5 +0,0 @@
1−{
2− "name": "dodrio",
3− "version": "0.1.0",
4− "lockfileVersion": 1
5−}
js/package.jsondeleted−11
@@ -1,11 +0,0 @@
1−{
2− "name": "dodrio",
3− "version": "0.1.0",
4− "description": "Dodrio",
5− "main": "dodrio.js",
6− "scripts": {
7− "test": "echo \"Error: no test specified\" && exit 1"
8− },
9− "author": "Nick Fitzgerald <fitzgen@gmail.com>",
10− "license": "MPL-2.0"
11−}
src/cached_set.rs+13−9
@@ -38,27 +38,31 @@ pub(crate) struct CacheEntry {
3838}
3939
4040impl 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());
41+ pub(crate) fn new_roots_set(&self) -> FxHashSet<CacheId> {
42+ let mut roots = FxHashSet::default();
43+ roots.reserve(self.items.len());
44+ roots
45+ }
46+
47+ pub(crate) fn gc(&mut self, registry: &mut EventsRegistry, roots: FxHashSet<CacheId>) {
48+ let mut marked = FxHashSet::default();
49+ marked.reserve(self.items.len());
4650
4751 for root in roots {
48− if mark_bits.insert(*root) {
52+ if marked.insert(root) {
4953 let entry = self
5054 .items
51− .get(root)
55+ .get(&root)
5256 .expect_throw("CachedSet::gc: should have root in cached set");
5357
5458 // NB: no need to recurse here because `edges` already contains
5559 // transitive edges!
56− mark_bits.extend(entry.edges.iter().cloned());
60+ marked.extend(entry.edges.iter().cloned());
5761 }
5862 }
5963
6064 self.items.retain(|id, entry| {
61− let keep = mark_bits.contains(id);
65+ let keep = marked.contains(id);
6266 if !keep {
6367 debug!("CachedSet::gc: removing {:?}", id);
6468 let node: &Node = unsafe { &*entry.node };
src/change_list.rsdeleted−469
@@ -1,469 +0,0 @@
1−use crate::Listener;
2−use bumpalo::Bump;
3−use fxhash::FxHashMap;
4−use std::fmt;
5−
6−pub mod js {
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− }
19− } else {
20− use wasm_bindgen::prelude::*;
21−
22− #[wasm_bindgen(module = "/js/change-list.js")]
23− extern "C" {
24− #[derive(Clone, Debug)]
25− pub type ChangeList;
26−
27− #[wasm_bindgen(constructor)]
28− pub fn new(container: &web_sys::Element) -> ChangeList;
29−
30− #[wasm_bindgen(structural, method)]
31− pub fn unmount(this: &ChangeList);
32−
33− #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
34− pub fn add_change_list_range(this: &ChangeList, start: usize, len: usize);
35−
36− #[wasm_bindgen(structural, method, js_name = applyChanges)]
37− pub fn apply_changes(this: &ChangeList, memory: JsValue);
38−
39− #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
40− pub fn init_events_trampoline(
41− this: &ChangeList,
42− trampoline: &crate::EventsTrampoline,
43− );
44− }
45− }
46− }
47−}
48−
49−struct StringsCacheEntry {
50− key: u32,
51− used: bool,
52−}
53−
54−pub(crate) struct ChangeList {
55− bump: Bump,
56− strings_cache: FxHashMap<String, StringsCacheEntry>,
57− next_string_key: u32,
58− js: js::ChangeList,
59− events_trampoline: Option<crate::EventsTrampoline>,
60−}
61−
62−impl Drop for ChangeList {
63− fn drop(&mut self) {
64− debug!("Dropping ChangeList");
65− self.js.unmount();
66− }
67−}
68−
69−impl fmt::Debug for ChangeList {
70− fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71− f.debug_struct("ChangeList")
72− .field("bump", &self.bump)
73− .field("js", &self.js)
74− .field("events_trampoline", &"..")
75− .finish()
76− }
77−}
78−
79−impl ChangeList {
80− pub(crate) fn new(container: &crate::Element) -> ChangeList {
81− let bump = Bump::new();
82− let strings_cache = FxHashMap::default();
83− let js = js::ChangeList::new(container);
84− ChangeList {
85− bump,
86− strings_cache,
87− next_string_key: 0,
88− js,
89− events_trampoline: None,
90− }
91− }
92−
93− pub(crate) fn init_events_trampoline(&mut self, trampoline: crate::EventsTrampoline) {
94− debug_assert!(self.events_trampoline.is_none());
95− self.js.init_events_trampoline(&trampoline);
96− self.events_trampoline = Some(trampoline);
97− }
98−}
99−
100−cfg_if::cfg_if! {
101− if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
102− impl ChangeList {
103− pub(crate) fn apply_changes(&mut self) {
104− // Do nothing...
105− }
106− }
107− } else {
108− impl ChangeList {
109− pub(crate) fn apply_changes(&mut self) {
110− let js = &self.js;
111− unsafe {
112− self.bump.each_allocated_chunk(|ch| {
113− js.add_change_list_range(ch.as_ptr() as usize, ch.len());
114− });
115− }
116− js.apply_changes(wasm_bindgen::memory());
117− self.bump.reset();
118− }
119− }
120− }
121−}
122−
123−#[repr(u32)]
124−#[derive(Clone, Copy, Debug)]
125−enum ChangeDiscriminant {
126− /// Immediates: `(pointer, length)`
127− ///
128− /// Stack: `[... TextNode] -> [... TextNode]`
129− ///
130− /// ```text
131− /// stack.top().textContent = String(pointer, length)
132− /// ```
133− SetText = 0,
134−
135− /// Immediates: `()`
136− ///
137− /// Stack: `[... Node] -> [...]`
138− ///
139− /// ```text
140− /// node = stack.pop()
141− /// while (node.nextSibling) {
142− /// node.nextSibling.remove();
143− /// }
144− /// node.remove()
145− /// ```
146− RemoveSelfAndNextSiblings = 1,
147−
148− /// Immediates: `()`
149− ///
150− /// Stack: `[... Node Node] -> [... Node]`
151− ///
152− /// ```text
153− /// new = stack.pop()
154− /// old = stack.pop()
155− /// old.replaceWith(new)
156− /// stack.push(new)
157− /// ```
158− ReplaceWith = 2,
159−
160− /// Immediates: `(id1, id2)`
161− ///
162− /// Stack: `[... Node] -> [... Node]`
163− ///
164− /// ```text
165− /// stack.top().setAttribute(String(id1), String(id2))
166− /// ```
167− SetAttribute = 3,
168−
169− /// Immediates: (id)
170− ///
171− /// Stack: `[... Node] -> [... Node]`
172− ///
173− /// ```text
174− /// stack.top().removeAttribute(String(id))
175− /// ```
176− RemoveAttribute = 4,
177−
178− /// Immediates: `()`
179− ///
180− /// Stack: `[... Node] -> [... Node node]`
181− ///
182− /// ```text
183− /// stack.push(stack.top().firstChild)
184− /// ```
185− PushFirstChild = 5,
186−
187− /// Immediates: `()`
188− ///
189− /// Stack: `[... Node] -> [... Node]`
190− ///
191− /// ```text
192− /// stack.push(stack.pop().nextSibling)
193− /// ```
194− PopPushNextSibling = 6,
195−
196− /// Immediates: `()`
197− ///
198− /// Stack: `[... T] -> [...]`
199− ///
200− /// ```text
201− /// stack.pop()
202− /// ```
203− Pop = 7,
204−
205− /// Immediates: `()`
206− ///
207− /// Stack: `[... Node Node] -> [... Node]`
208− ///
209− /// ```text
210− /// child = stack.pop()
211− /// stack.top().appendChild(child)
212− /// ```
213− AppendChild = 8,
214−
215− /// Immediates: `(pointer, length)`
216− ///
217− /// Stack: `[...] -> [... Node]`
218− ///
219− /// ```text
220− /// stack.push(createTextNode(String(pointer, length)))
221− /// ```
222− CreateTextNode = 9,
223−
224− /// Immediates: `(id)`
225− ///
226− /// Stack: `[...] -> [... Node]`
227− ///
228− /// ```text
229− /// stack.push(createElement(String(id))
230− /// ```
231− CreateElement = 10,
232−
233− /// Immediates: `(id, A, B)`
234− ///
235− /// Stack: `[... Node] -> [... Node]`
236− ///
237− /// ```text
238− /// event = String(id)
239− /// callback = ProxyToRustCallback(A, B)
240− /// stack.top().addEventListener(event, callback)
241− /// ```
242− NewEventListener = 11,
243−
244− /// Immediates: `(id, A, B)`
245− ///
246− /// Stack: `[... Node] -> [... Node]`
247− ///
248− /// ```text
249− /// event = String(id)
250− /// new_callback = ProxyToRustCallback(A, B);
251− /// stack.top().updateEventlistener(new_callback)
252− /// ```
253− UpdateEventListener = 12,
254−
255− /// Immediates: `(id)`
256− ///
257− /// Stack: `[... Node] -> [... Node]`
258− ///
259− /// ```text
260− /// stack.top().removeEventListener(String(id));
261− /// ```
262− RemoveEventListener = 13,
263−
264− /// Immediates: `(pointer, length, id)`
265− ///
266− /// Stack: `[...] -> [...]`
267− ///
268− /// ```text
269− /// addString(String(pointer, length), id);
270− /// ```
271− AddString = 14,
272−
273− /// Immediates: `(id)`
274− ///
275− /// Stack: `[...] -> [...]`
276− ///
277− /// ```text
278− /// dropString(id);
279− /// ```
280− DropString = 15,
281−
282− /// Immediates: `(id1, id2)`
283− ///
284− /// Stack: `[...] -> [... Node]`
285− ///
286− /// ```text
287− /// stack.push(createElementNS(String(id1), String(id2))
288− /// ```
289− CreateElementNS = 16,
290−
291− /// Immediates: `(id1, id2, id3)`
292− ///
293− /// Stack: `[... Node] -> [... Node]`
294− ///
295− /// ```text
296− /// stack.top().setAttributeNS(String(id1), String(id2), String(id3))
297− /// ```
298− SetAttributeNS = 17,
299−}
300−
301−// Allocation utilities to ensure that we only allocation sequences of `u32`s
302−// into the change list's bump arena without any padding. This helps maintain
303−// the invariants required for `Bump::each_allocated_chunk`'s safety.
304−impl ChangeList {
305− // Allocate an opcode with zero immediates.
306− fn op0(&self, discriminant: ChangeDiscriminant) {
307− self.bump.alloc(discriminant as u32);
308− }
309−
310− // Allocate an opcode with one immediate.
311− fn op1(&self, discriminant: ChangeDiscriminant, a: u32) {
312− self.bump.alloc([discriminant as u32, a]);
313− }
314−
315− // Allocate an opcode with two immediates.
316− fn op2(&self, discriminant: ChangeDiscriminant, a: u32, b: u32) {
317− self.bump.alloc([discriminant as u32, a, b]);
318− }
319−
320− // Allocate an opcode with three immediates.
321− fn op3(&self, discriminant: ChangeDiscriminant, a: u32, b: u32, c: u32) {
322− self.bump.alloc([discriminant as u32, a, b, c]);
323− }
324−
325− // Note: no 4-immediate opcodes at this time.
326−}
327−
328−impl ChangeList {
329− fn ensure_string(&mut self, string: &str) -> u32 {
330− if let Some(entry) = self.strings_cache.get_mut(string) {
331− entry.used = true;
332− entry.key
333− } else {
334− let key = self.next_string_key;
335− self.next_string_key += 1;
336− let entry = StringsCacheEntry { key, used: true };
337− self.strings_cache.insert(string.to_string(), entry);
338− self.op3(
339− ChangeDiscriminant::AddString,
340− string.as_ptr() as u32,
341− string.len() as u32,
342− key,
343− );
344− key
345− }
346− }
347−
348− pub(crate) fn drop_unused_strings(&mut self) {
349− debug!("drop_unused_strings()");
350− let mut new_cache = FxHashMap::default();
351− for (key, val) in self.strings_cache.iter() {
352− if val.used {
353− new_cache.insert(
354− key.clone(),
355− StringsCacheEntry {
356− key: val.key,
357− used: false,
358− },
359− );
360− } else {
361− self.op1(ChangeDiscriminant::DropString, val.key);
362− }
363− }
364− self.strings_cache = new_cache;
365− }
366−
367− pub(crate) fn emit_set_text(&self, text: &str) {
368− debug!("emit_set_text({:?})", text);
369− self.op2(
370− ChangeDiscriminant::SetText,
371− text.as_ptr() as u32,
372− text.len() as u32,
373− );
374− }
375−
376− pub(crate) fn emit_remove_self_and_next_siblings(&self) {
377− debug!("emit_remove_self_and_next_siblings()");
378− self.op0(ChangeDiscriminant::RemoveSelfAndNextSiblings);
379− }
380−
381− pub(crate) fn emit_replace_with(&self) {
382− debug!("emit_replace_with()");
383− self.op0(ChangeDiscriminant::ReplaceWith);
384− }
385−
386− pub(crate) fn emit_set_attribute(&mut self, name: &str, value: &str) {
387− debug!("emit_set_attribute({:?}, {:?})", name, value);
388− let name_id = self.ensure_string(name);
389− let value_id = self.ensure_string(value);
390− self.op2(ChangeDiscriminant::SetAttribute, name_id, value_id);
391− }
392−
393− pub(crate) fn emit_set_attribute_ns(&mut self, name: &str, value: &str) {
394− debug!("emit_set_attribute_ns({:?}, {:?}", name, value);
395− let name_id = self.ensure_string(name);
396− let value_id = self.ensure_string(value);
397− self.op2(ChangeDiscriminant::SetAttributeNS, name_id, value_id);
398− }
399−
400− pub(crate) fn emit_remove_attribute(&mut self, name: &str) {
401− debug!("emit_remove_attribute({:?})", name);
402− let name_id = self.ensure_string(name);
403− self.op1(ChangeDiscriminant::RemoveAttribute, name_id);
404− }
405−
406− pub(crate) fn emit_push_first_child(&self) {
407− debug!("emit_push_first_child()");
408− self.op0(ChangeDiscriminant::PushFirstChild);
409− }
410−
411− pub(crate) fn emit_pop_push_next_sibling(&self) {
412− debug!("emit_pop_push_next_sibling()");
413− self.op0(ChangeDiscriminant::PopPushNextSibling);
414− }
415−
416− pub(crate) fn emit_pop(&self) {
417− debug!("emit_pop()");
418− self.op0(ChangeDiscriminant::Pop);
419− }
420−
421− pub(crate) fn emit_append_child(&self) {
422− debug!("emit_append_child()");
423− self.op0(ChangeDiscriminant::AppendChild);
424− }
425−
426− pub(crate) fn emit_create_text_node(&self, text: &str) {
427− debug!("emit_create_text_node({:?})", text);
428− self.op2(
429− ChangeDiscriminant::CreateTextNode,
430− text.as_ptr() as u32,
431− text.len() as u32,
432− );
433− }
434−
435− pub(crate) fn emit_create_element(&mut self, tag_name: &str) {
436− debug!("emit_create_element({:?})", tag_name);
437− let tag_name_id = self.ensure_string(tag_name);
438− self.op1(ChangeDiscriminant::CreateElement, tag_name_id);
439− }
440−
441− pub(crate) fn emit_create_element_ns(&mut self, tag_name: &str, ns: &str) {
442− debug!("emit_create_element_ns({:?}, {:?})", tag_name, ns);
443− let tag_name_id = self.ensure_string(tag_name);
444− let ns_id = self.ensure_string(ns);
445− self.op2(ChangeDiscriminant::CreateElementNS, tag_name_id, ns_id);
446− }
447−
448− pub(crate) fn emit_new_event_listener(&mut self, listener: &Listener) {
449− debug!("emit_new_event_listener({:?})", listener);
450− let (a, b) = listener.get_callback_parts();
451− debug_assert!(a != 0);
452− let event_id = self.ensure_string(listener.event);
453− self.op3(ChangeDiscriminant::NewEventListener, event_id, a, b);
454− }
455−
456− pub(crate) fn emit_update_event_listener(&mut self, listener: &Listener) {
457− debug!("emit_update_event_listener({:?})", listener);
458− let (a, b) = listener.get_callback_parts();
459− debug_assert!(a != 0);
460− let event_id = self.ensure_string(listener.event);
461− self.op3(ChangeDiscriminant::UpdateEventListener, event_id, a, b);
462− }
463−
464− pub(crate) fn emit_remove_event_listener(&mut self, event: &str) {
465− debug!("emit_remove_event_listener({:?})", event);
466− let event_id = self.ensure_string(event);
467− self.op1(ChangeDiscriminant::RemoveEventListener, event_id);
468− }
469−}
src/change_list/emitter.rsnew file+204
@@ -0,0 +1,204 @@
1+//! The `InstructionEmitter` is responsible for encoding change list
2+//! instructions and ensuring that each instruction has the correct number of
3+//! immediates. It ensures that the resulting change list instruction stream is
4+//! *syntactically* correct (opcodes have the correct arity of immediate
5+//! arguments, etc), while `ChangeListBuilder` wraps an emitter and additionally
6+//! ensures that the resulting change list program is *semantically* correct
7+//! (doesn't reference cached strings before they've been added to the cache,
8+//! etc).
9+//!
10+//! We encode the instructions directly into a dedicated bump arena. We
11+//! eventually pass the bump arena's chunks to the interpreter in JS, so it is
12+//! critical that nothing other than change list instructions are allocated
13+//! inside this bump, and that the instructions themselves do not contain any
14+//! padding or uninitialized memory. See the documentation for the the
15+//! `Bump::each_allocated_chunk` method for details.
16+
17+use bumpalo::Bump;
18+
19+#[derive(Debug)]
20+pub(crate) struct InstructionEmitter {
21+ bump: Bump,
22+}
23+
24+impl InstructionEmitter {
25+ /// Construct a new `InstructionEmitter` with its own bump arena.
26+ pub fn new() -> InstructionEmitter {
27+ let bump = Bump::new();
28+ InstructionEmitter { bump }
29+ }
30+
31+ /// Invoke the given function with each of the allocated instruction
32+ /// sequences that this emitter has built up.
33+ pub fn each_instruction_sequence<F>(&mut self, f: F)
34+ where
35+ F: FnMut(&[u8]),
36+ {
37+ // Note: the safety invariants required for `each_allocated_chunk` are
38+ // maintained by the fact that everything we encode as allocations in
39+ // the bump arena are in the form of `u32`s, and therefore we don't have
40+ // any uninitialized memory padding in the arena.
41+ unsafe {
42+ self.bump.each_allocated_chunk(f);
43+ }
44+ }
45+
46+ /// Reset to an empty sequence of instructions.
47+ pub fn reset(&mut self) {
48+ self.bump.reset();
49+ }
50+}
51+
52+macro_rules! define_change_list_instructions {
53+ ( $(
54+ $( #[$attr:meta] )*
55+ $name:ident (
56+ $($immediate:ident),*
57+ ) = $discriminant:expr,
58+ )* ) => {
59+ impl InstructionEmitter {
60+ $(
61+ $( #[$attr] )*
62+ #[inline]
63+ pub fn $name(&self $(, $immediate: u32)*) {
64+ self.bump.alloc_with(|| [$discriminant $(, $immediate )* ]);
65+ }
66+ )*
67+ }
68+ }
69+}
70+
71+define_change_list_instructions! {
72+ /// Stack: `[... TextNode] -> [... TextNode]`
73+ ///
74+ /// ```text
75+ /// stack.top().textContent = readString(pointer, length)
76+ /// ```
77+ set_text(pointer, length) = 0,
78+
79+ /// Stack: `[... Node] -> [...]`
80+ ///
81+ /// ```text
82+ /// node = stack.pop()
83+ /// while (node.nextSibling) {
84+ /// node.nextSibling.remove();
85+ /// }
86+ /// node.remove()
87+ /// ```
88+ remove_self_and_next_siblings() = 1,
89+
90+ /// Stack: `[... Node Node] -> [... Node]`
91+ ///
92+ /// ```text
93+ /// new = stack.pop()
94+ /// old = stack.pop()
95+ /// old.replaceWith(new)
96+ /// stack.push(new)
97+ /// ```
98+ replace_with() = 2,
99+
100+ /// Stack: `[... Node] -> [... Node]`
101+ ///
102+ /// ```text
103+ /// stack.top().setAttribute(getCachedString(attribute_key), getCachedString(value_key))
104+ /// ```
105+ set_attribute(attribute_key, value_key) = 3,
106+
107+ /// Stack: `[... Node] -> [... Node]`
108+ ///
109+ /// ```text
110+ /// stack.top().removeAttribute(getCachedString(attribute_key))
111+ /// ```
112+ remove_attribute(attribute_key) = 4,
113+
114+ /// Stack: `[... Node] -> [... Node Node]`
115+ ///
116+ /// ```text
117+ /// stack.push(stack.top().firstChild)
118+ /// ```
119+ push_first_child() = 5,
120+
121+ /// Stack: `[... Node] -> [... Node]`
122+ ///
123+ /// ```text
124+ /// stack.push(stack.pop().nextSibling)
125+ /// ```
126+ pop_push_next_sibling() = 6,
127+
128+ /// Stack: `[... T] -> [...]`
129+ ///
130+ /// ```text
131+ /// stack.pop()
132+ /// ```
133+ pop() = 7,
134+
135+ /// Stack: `[... Node Node] -> [... Node]`
136+ ///
137+ /// ```text
138+ /// child = stack.pop()
139+ /// stack.top().appendChild(child)
140+ /// ```
141+ append_child() = 8,
142+
143+ /// Stack: `[...] -> [... Node]`
144+ ///
145+ /// ```text
146+ /// stack.push(document.createTextNode(readString(pointer, length)))
147+ /// ```
148+ create_text_node(pointer, length) = 9,
149+
150+ /// Stack: `[...] -> [... Node]`
151+ ///
152+ /// ```text
153+ /// stack.push(document.createElement(getCachedString(tag_name_key))
154+ /// ```
155+ create_element(tag_name_key) = 10,
156+
157+ /// Stack: `[... Node] -> [... Node]`
158+ ///
159+ /// ```text
160+ /// event = getCachedString(event_key)
161+ /// callback = createProxyToRustCallback(a, b)
162+ /// stack.top().addEventListener(event, callback)
163+ /// ```
164+ new_event_listener(event_key, a, b) = 11,
165+
166+ /// Stack: `[... Node] -> [... Node]`
167+ ///
168+ /// ```text
169+ /// event = getCachedString(event_key)
170+ /// new_callback = createProxyToRustCallback(a, b);
171+ /// stack.top().updateEventlistener(new_callback)
172+ /// ```
173+ update_event_listener(event_key, a, b) = 12,
174+
175+ /// Stack: `[... Node] -> [... Node]`
176+ ///
177+ /// ```text
178+ /// stack.top().removeEventListener(getCachedString(event_key));
179+ /// ```
180+ remove_event_listener(event_key) = 13,
181+
182+ /// Stack: `[...] -> [...]`
183+ ///
184+ /// ```text
185+ /// addCachedString(readString(pointer, length), key);
186+ /// ```
187+ add_cached_string(pointer, length, key) = 14,
188+
189+ /// Stack: `[...] -> [...]`
190+ ///
191+ /// ```text
192+ /// dropCachedString(key);
193+ /// ```
194+ drop_cached_string(key) = 15,
195+
196+ /// Stack: `[...] -> [... Node]`
197+ ///
198+ /// ```text
199+ /// tag_name = getCachedString(tag_name_key)
200+ /// namespace = getCachedString(tag_name_key)
201+ /// stack.push(document.createElementNS(tag_name, namespace))
202+ /// ```
203+ create_element_ns(tag_name_key, namespace_key) = 16,
204+}
src/change_list/js.rsnew file+40
@@ -0,0 +1,40 @@
1+cfg_if::cfg_if! {
2+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
3+ #[derive(Clone, Debug)]
4+ pub struct ChangeListInterpreter {}
5+ impl ChangeListInterpreter {
6+ pub fn new(_container: &crate::Element) -> ChangeListInterpreter {
7+ ChangeListInterpreter {}
8+ }
9+ pub fn unmount(&self) {}
10+ pub fn add_change_list_range(&self, _start: usize, _len: usize) {}
11+ pub fn init_events_trampoline(&self, _trampoline: &crate::EventsTrampoline) {}
12+ }
13+ } else {
14+ use wasm_bindgen::prelude::*;
15+
16+ #[wasm_bindgen(module = "/js/change-list-interpreter.js")]
17+ extern "C" {
18+ #[derive(Clone, Debug)]
19+ pub type ChangeListInterpreter;
20+
21+ #[wasm_bindgen(constructor)]
22+ pub fn new(container: &web_sys::Element) -> ChangeListInterpreter;
23+
24+ #[wasm_bindgen(structural, method)]
25+ pub fn unmount(this: &ChangeListInterpreter);
26+
27+ #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
28+ pub fn add_change_list_range(this: &ChangeListInterpreter, start: usize, len: usize);
29+
30+ #[wasm_bindgen(structural, method, js_name = applyChanges)]
31+ pub fn apply_changes(this: &ChangeListInterpreter, memory: JsValue);
32+
33+ #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
34+ pub fn init_events_trampoline(
35+ this: &ChangeListInterpreter,
36+ trampoline: &crate::EventsTrampoline,
37+ );
38+ }
39+ }
40+}
src/change_list/mod.rsnew file+183
@@ -0,0 +1,183 @@
1+pub(crate) mod emitter;
2+pub(crate) mod strings;
3+
4+// Note: has to be `pub` because of `wasm-bindgen` visibility restrictions.
5+pub mod js;
6+
7+use self::emitter::InstructionEmitter;
8+use self::strings::{StringKey, StringsCache};
9+use crate::Listener;
10+
11+#[derive(Debug)]
12+pub(crate) struct ChangeListPersistentState {
13+ strings: StringsCache,
14+ emitter: InstructionEmitter,
15+ interpreter: js::ChangeListInterpreter,
16+}
17+
18+pub(crate) struct ChangeListBuilder<'a> {
19+ state: &'a mut ChangeListPersistentState,
20+}
21+
22+impl Drop for ChangeListPersistentState {
23+ fn drop(&mut self) {
24+ debug!("Dropping ChangeListPersistentState");
25+ self.interpreter.unmount();
26+ }
27+}
28+
29+impl ChangeListPersistentState {
30+ pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState {
31+ let strings = StringsCache::new();
32+ let emitter = InstructionEmitter::new();
33+ let interpreter = js::ChangeListInterpreter::new(container);
34+ ChangeListPersistentState {
35+ strings,
36+ emitter,
37+ interpreter,
38+ }
39+ }
40+
41+ pub(crate) fn init_events_trampoline(&mut self, trampoline: &crate::EventsTrampoline) {
42+ self.interpreter.init_events_trampoline(trampoline);
43+ }
44+
45+ pub(crate) fn builder<'a>(&'a mut self) -> ChangeListBuilder<'a> {
46+ ChangeListBuilder { state: self }
47+ }
48+}
49+
50+cfg_if::cfg_if! {
51+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
52+ impl ChangeListBuilder<'_> {
53+ pub(crate) fn finish(self) {
54+ self.state.strings.drop_unused_strings(&self.state.emitter);
55+
56+ // Nothing to actually apply the changes to.
57+
58+ self.state.emitter.reset();
59+ }
60+ }
61+ } else {
62+ impl ChangeListBuilder<'_> {
63+ pub(crate) fn finish(self) {
64+ self.state.strings.drop_unused_strings(&self.state.emitter);
65+
66+ // Apply the changes.
67+ let interpreter = &self.state.interpreter;
68+ self.state.emitter.each_instruction_sequence(|seq| {
69+ interpreter.add_change_list_range(seq.as_ptr() as usize, seq.len());
70+ });
71+ interpreter.apply_changes(wasm_bindgen::memory());
72+
73+ self.state.emitter.reset();
74+ }
75+ }
76+ }
77+}
78+
79+impl ChangeListBuilder<'_> {
80+ pub fn ensure_string(&mut self, string: &str) -> StringKey {
81+ self.state
82+ .strings
83+ .ensure_string(string, &self.state.emitter)
84+ }
85+
86+ pub fn set_text(&self, text: &str) {
87+ debug!("emit: set_text({:?})", text);
88+ self.state
89+ .emitter
90+ .set_text(text.as_ptr() as u32, text.len() as u32);
91+ }
92+
93+ pub fn remove_self_and_next_siblings(&self) {
94+ debug!("emit: remove_self_and_next_siblings()");
95+ self.state.emitter.remove_self_and_next_siblings();
96+ }
97+
98+ pub fn replace_with(&self) {
99+ debug!("emit: replace_with()");
100+ self.state.emitter.replace_with();
101+ }
102+
103+ pub fn set_attribute(&mut self, name: &str, value: &str) {
104+ debug!("emit: set_attribute({:?}, {:?})", name, value);
105+ let name_id = self.ensure_string(name);
106+ let value_id = self.ensure_string(value);
107+ self.state
108+ .emitter
109+ .set_attribute(name_id.into(), value_id.into());
110+ }
111+
112+ pub fn remove_attribute(&mut self, name: &str) {
113+ debug!("emit: remove_attribute({:?})", name);
114+ let name_id = self.ensure_string(name);
115+ self.state.emitter.remove_attribute(name_id.into());
116+ }
117+
118+ pub fn push_first_child(&self) {
119+ debug!("emit: push_first_child");
120+ self.state.emitter.push_first_child();
121+ }
122+
123+ pub fn pop_push_next_sibling(&self) {
124+ debug!("emit: pop_push_next_sibling");
125+ self.state.emitter.pop_push_next_sibling();
126+ }
127+
128+ pub fn pop(&self) {
129+ debug!("emit: pop");
130+ self.state.emitter.pop();
131+ }
132+
133+ pub fn append_child(&self) {
134+ debug!("emit: append_child()");
135+ self.state.emitter.append_child();
136+ }
137+
138+ pub fn create_text_node(&self, text: &str) {
139+ debug!("emit: create_text_node({:?})", text);
140+ self.state
141+ .emitter
142+ .create_text_node(text.as_ptr() as u32, text.len() as u32);
143+ }
144+
145+ pub fn create_element(&mut self, tag_name: &str) {
146+ debug!("emit: create_element({:?})", tag_name);
147+ let tag_name_id = self.ensure_string(tag_name);
148+ self.state.emitter.create_element(tag_name_id.into());
149+ }
150+
151+ pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
152+ debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
153+ let tag_name_id = self.ensure_string(tag_name);
154+ let ns_id = self.ensure_string(ns);
155+ self.state
156+ .emitter
157+ .create_element_ns(tag_name_id.into(), ns_id.into());
158+ }
159+
160+ pub fn new_event_listener(&mut self, listener: &Listener) {
161+ debug!("emit: new_event_listener({:?})", listener);
162+ let (a, b) = listener.get_callback_parts();
163+ debug_assert!(a != 0);
164+ let event_id = self.ensure_string(listener.event);
165+ self.state.emitter.new_event_listener(event_id.into(), a, b);
166+ }
167+
168+ pub fn update_event_listener(&mut self, listener: &Listener) {
169+ debug!("emit: update_event_listener({:?})", listener);
170+ let (a, b) = listener.get_callback_parts();
171+ debug_assert!(a != 0);
172+ let event_id = self.ensure_string(listener.event);
173+ self.state
174+ .emitter
175+ .update_event_listener(event_id.into(), a, b);
176+ }
177+
178+ pub fn remove_event_listener(&mut self, event: &str) {
179+ debug!("emit: remove_event_listener({:?})", event);
180+ let event_id = self.ensure_string(event);
181+ self.state.emitter.remove_event_listener(event_id.into());
182+ }
183+}
src/change_list/strings.rsnew file+65
@@ -0,0 +1,65 @@
1+use crate::change_list::emitter::InstructionEmitter;
2+use fxhash::FxHashMap;
3+
4+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5+pub struct StringKey(u32);
6+
7+impl From<StringKey> for u32 {
8+ #[inline]
9+ fn from(key: StringKey) -> u32 {
10+ key.0
11+ }
12+}
13+
14+#[derive(Debug)]
15+struct StringsCacheEntry {
16+ key: StringKey,
17+ used: bool,
18+}
19+
20+#[derive(Debug, Default)]
21+pub(crate) struct StringsCache {
22+ entries: FxHashMap<String, StringsCacheEntry>,
23+ next_string_key: u32,
24+}
25+
26+impl StringsCache {
27+ /// Create a new, empty strings cache.
28+ pub fn new() -> StringsCache {
29+ Default::default()
30+ }
31+
32+ /// Ensure that the given string is cached, and get its key.
33+ pub fn ensure_string(&mut self, string: &str, emitter: &InstructionEmitter) -> StringKey {
34+ if let Some(entry) = self.entries.get_mut(string) {
35+ entry.used = true;
36+ entry.key
37+ } else {
38+ let key = StringKey(self.next_string_key);
39+ self.next_string_key += 1;
40+ let entry = StringsCacheEntry { key, used: true };
41+ self.entries.insert(string.to_string(), entry);
42+ emitter.add_cached_string(string.as_ptr() as u32, string.len() as u32, key.into());
43+ key
44+ }
45+ }
46+
47+ pub fn drop_unused_strings(&mut self, emitter: &InstructionEmitter) {
48+ debug!("drop_unused_strings()");
49+ self.entries.retain(|_string, entry| {
50+ if entry.used {
51+ // Since this entry was used during while rendering this frame,
52+ // it is likely going to be used for the next frame as well. So
53+ // we keep it in the cache so that we don't have to repopulate
54+ // and resync the cache next frame (assuming it is reused), but
55+ // we set the `used` flag to false so that if it is not used
56+ // next frame, it will be cleaned up.
57+ entry.used = false;
58+ true
59+ } else {
60+ emitter.drop_cached_string(entry.key.into());
61+ false
62+ }
63+ });
64+ }
65+}
src/diff.rs+41−44
@@ -1,18 +1,19 @@
11use crate::{
22 cached_set::{CacheId, CachedSet},
3− change_list::ChangeList,
3+ change_list::ChangeListBuilder,
44 events::EventsRegistry,
55 node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
66};
7+use fxhash::FxHashSet;
78use std::cmp;
89
910pub(crate) fn diff(
1011 cached_set: &CachedSet,
11− change_list: &mut ChangeList,
12+ change_list: &mut ChangeListBuilder,
1213 registry: &mut EventsRegistry,
1314 old: Node,
1415 new: Node,
15− cached_roots: &mut bumpalo::collections::Vec<CacheId>,
16+ cached_roots: &mut FxHashSet<CacheId>,
1617) {
1718 match (&new.kind, &old.kind) {
1819 (
@@ -22,7 +23,7 @@ pub(crate) fn diff(
2223 debug!(" both are text nodes");
2324 if new_text != old_text {
2425 debug!(" text needs updating");
25− change_list.emit_set_text(new_text);
26+ change_list.set_text(new_text);
2627 }
2728 }
2829
@@ -30,7 +31,7 @@ pub(crate) fn diff(
3031 debug!(" replacing a text node with an element");
3132 create(cached_set, change_list, registry, new, cached_roots);
3233 registry.remove_subtree(&old);
33− change_list.emit_replace_with();
34+ change_list.replace_with();
3435 }
3536
3637 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
@@ -38,7 +39,7 @@ pub(crate) fn diff(
3839 create(cached_set, change_list, registry, new, cached_roots);
3940 // Note: text nodes cannot have event listeners, so we don't need to
4041 // remove the old node's listeners from our registry her.
41− change_list.emit_replace_with();
42+ change_list.replace_with();
4243 }
4344
4445 (
@@ -62,7 +63,7 @@ pub(crate) fn diff(
6263 debug!(" different tag names or namespaces; creating new element and replacing old element");
6364 create(cached_set, change_list, registry, new, cached_roots);
6465 registry.remove_subtree(&old);
65− change_list.emit_replace_with();
66+ change_list.replace_with();
6667 return;
6768 }
6869 diff_listeners(change_list, registry, old_listeners, new_listeners);
@@ -79,7 +80,7 @@ pub(crate) fn diff(
7980
8081 // Both the new and old nodes are cached.
8182 (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
82− cached_roots.push(new.id);
83+ cached_roots.insert(new.id);
8384
8485 if new.id == old.id {
8586 // This is the same cached node, so nothing has changed!
@@ -95,11 +96,11 @@ pub(crate) fn diff(
9596 // we assume that they are pretty different, and it isn't worth diffing
9697 // the subtrees, so we just create the new cached node afresh.
9798 (&NodeKind::Cached(ref c), _) => {
98− cached_roots.push(c.id);
99+ cached_roots.insert(c.id);
99100 let new = cached_set.get(c.id);
100101 create(cached_set, change_list, registry, new, cached_roots);
101102 registry.remove_subtree(&old);
102− change_list.emit_replace_with();
103+ change_list.replace_with();
103104 }
104105
105106 // Old cached node and new non-cached node. Again, assume that they are
@@ -107,13 +108,13 @@ pub(crate) fn diff(
107108 (_, &NodeKind::Cached(_)) => {
108109 create(cached_set, change_list, registry, new, cached_roots);
109110 registry.remove_subtree(&old);
110− change_list.emit_replace_with();
111+ change_list.replace_with();
111112 }
112113 }
113114}
114115
115116fn diff_listeners(
116− change_list: &mut ChangeList,
117+ change_list: &mut ChangeListBuilder,
117118 registry: &mut EventsRegistry,
118119 old: &[Listener],
119120 new: &[Listener],
@@ -130,12 +131,12 @@ fn diff_listeners(
130131
131132 for old_l in old {
132133 if new_l.event == old_l.event {
133− change_list.emit_update_event_listener(new_l);
134+ change_list.update_event_listener(new_l);
134135 continue 'outer1;
135136 }
136137 }
137138
138− change_list.emit_new_event_listener(new_l);
139+ change_list.new_event_listener(new_l);
139140 }
140141
141142 'outer2: for old_l in old {
@@ -146,28 +147,28 @@ fn diff_listeners(
146147 continue 'outer2;
147148 }
148149 }
149− change_list.emit_remove_event_listener(old_l.event);
150+ change_list.remove_event_listener(old_l.event);
150151 }
151152}
152153
153−fn diff_attributes(change_list: &mut ChangeList, old: &[Attribute], new: &[Attribute]) {
154+fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute]) {
154155 debug!(" updating attributes");
155156
156157 // Do O(n^2) passes to add/update and remove attributes, since
157158 // there are almost always very few attributes.
158159 'outer: for new_attr in new {
159160 if new_attr.is_volatile() {
160− change_list.emit_set_attribute(new_attr.name, new_attr.value);
161+ change_list.set_attribute(new_attr.name, new_attr.value);
161162 } else {
162163 for old_attr in old {
163164 if old_attr.name == new_attr.name {
164165 if old_attr.value != new_attr.value {
165− change_list.emit_set_attribute(new_attr.name, new_attr.value);
166+ change_list.set_attribute(new_attr.name, new_attr.value);
166167 }
167168 continue 'outer;
168169 }
169170 }
170− change_list.emit_set_attribute(new_attr.name, new_attr.value);
171+ change_list.set_attribute(new_attr.name, new_attr.value);
171172 }
172173 }
173174
@@ -177,17 +178,17 @@ fn diff_attributes(change_list: &mut ChangeList, old: &[Attribute], new: &[Attri
177178 continue 'outer2;
178179 }
179180 }
180− change_list.emit_remove_attribute(old_attr.name);
181+ change_list.remove_attribute(old_attr.name);
181182 }
182183}
183184
184185fn diff_children(
185186 cached_set: &CachedSet,
186− change_list: &mut ChangeList,
187+ change_list: &mut ChangeListBuilder,
187188 registry: &mut EventsRegistry,
188189 old: &[Node],
189190 new: &[Node],
190− cached_roots: &mut bumpalo::collections::Vec<CacheId>,
191+ cached_roots: &mut FxHashSet<CacheId>,
191192) {
192193 debug!(" updating children shared by old and new");
193194
@@ -203,11 +204,11 @@ fn diff_children(
203204 .enumerate()
204205 {
205206 if i == 0 {
206− change_list.emit_push_first_child();
207+ change_list.push_first_child();
207208 pushed = true;
208209 } else {
209210 debug_assert!(pushed);
210− change_list.emit_pop_push_next_sibling();
211+ change_list.pop_push_next_sibling();
211212 }
212213
213214 diff(
@@ -223,18 +224,18 @@ fn diff_children(
223224 if old_children.next().is_some() {
224225 debug!(" removing extra old children");
225226 debug_assert!(new_children.next().is_none());
226− if !pushed {
227− change_list.emit_push_first_child();
227+ if pushed {
228+ change_list.pop_push_next_sibling();
228229 } else {
229− change_list.emit_pop_push_next_sibling();
230+ change_list.push_first_child();
230231 }
231− change_list.emit_remove_self_and_next_siblings();
232+ change_list.remove_self_and_next_siblings();
232233 pushed = false;
233234 } else {
234235 debug!(" creating new children");
235236 for (i, new_child) in new_children.enumerate() {
236237 if i == 0 && pushed {
237− change_list.emit_pop();
238+ change_list.pop();
238239 pushed = false;
239240 }
240241 create(
@@ -244,26 +245,26 @@ fn diff_children(
244245 new_child.clone(),
245246 cached_roots,
246247 );
247− change_list.emit_append_child();
248+ change_list.append_child();
248249 }
249250 }
250251
251252 debug!(" done updating children");
252253 if pushed {
253− change_list.emit_pop();
254+ change_list.pop();
254255 }
255256}
256257
257258fn create(
258259 cached_set: &CachedSet,
259− change_list: &mut ChangeList,
260+ change_list: &mut ChangeListBuilder,
260261 registry: &mut EventsRegistry,
261262 node: Node,
262− cached_roots: &mut bumpalo::collections::Vec<CacheId>,
263+ cached_roots: &mut FxHashSet<CacheId>,
263264) {
264265 match node.kind {
265266 NodeKind::Text(TextNode { text }) => {
266− change_list.emit_create_text_node(text);
267+ change_list.create_text_node(text);
267268 }
268269 NodeKind::Element(ElementNode {
269270 tag_name,
@@ -273,22 +274,18 @@ fn create(
273274 namespace,
274275 }) => {
275276 if let Some(namespace) = namespace {
276− change_list.emit_create_element_ns(tag_name, namespace);
277+ change_list.create_element_ns(tag_name, namespace);
277278 } else {
278− change_list.emit_create_element(tag_name);
279+ change_list.create_element(tag_name);
279280 }
280281 for l in listeners {
281282 unsafe {
282283 registry.add(l);
283284 }
284− change_list.emit_new_event_listener(l);
285+ change_list.new_event_listener(l);
285286 }
286287 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− }
288+ change_list.set_attribute(&attr.name, &attr.value);
292289 }
293290 for child in children {
294291 create(
@@ -298,11 +295,11 @@ fn create(
298295 child.clone(),
299296 cached_roots,
300297 );
301− change_list.emit_append_child();
298+ change_list.append_child();
302299 }
303300 }
304301 NodeKind::Cached(c) => {
305− cached_roots.push(c.id);
302+ cached_roots.insert(c.id);
306303 let node = cached_set.get(c.id);
307304 create(cached_set, change_list, registry, node, cached_roots)
308305 }
src/vdom.rs+17−13
@@ -1,4 +1,4 @@
1−use super::change_list::ChangeList;
1+use super::change_list::ChangeListPersistentState;
22use super::RootRender;
33use crate::cached_set::CachedSet;
44use crate::events::EventsRegistry;
@@ -56,9 +56,10 @@ pub(crate) struct VdomInnerExclusive {
5656 component: Option<Box<RootRender>>,
5757
5858 dom_buffers: Option<[Bump; 2]>,
59− change_list: ManuallyDrop<ChangeList>,
59+ change_list: ManuallyDrop<ChangeListPersistentState>,
6060 container: crate::Element,
6161 events_registry: Option<Rc<RefCell<EventsRegistry>>>,
62+ events_trampoline: Option<crate::EventsTrampoline>,
6263 cached_set: crate::RefCell<CachedSet>,
6364
6465 // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
@@ -92,6 +93,7 @@ impl fmt::Debug for VdomInnerExclusive {
9293 .field("change_list", &self.change_list)
9394 .field("container", &self.container)
9495 .field("events_registry", &self.events_registry)
96+ .field("events_trampoline", &"..")
9597 .field("current_root", &self.current_root)
9698 .finish()
9799 }
@@ -160,7 +162,7 @@ impl Vdom {
160162 /// rendering component.
161163 pub fn with_boxed_root_render(container: &crate::Element, component: Box<RootRender>) -> Vdom {
162164 let dom_buffers = [Bump::new(), Bump::new()];
163− let change_list = ManuallyDrop::new(ChangeList::new(container));
165+ let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
164166
165167 // Create a dummy `<div/>` in our container.
166168 initialize_container(container);
@@ -179,6 +181,7 @@ impl Vdom {
179181 container,
180182 current_root,
181183 events_registry: None,
184+ events_trampoline: None,
182185 cached_set: crate::RefCell::new(Default::default()),
183186 }),
184187 });
@@ -188,7 +191,9 @@ impl Vdom {
188191 {
189192 let mut inner = inner.exclusive.borrow_mut();
190193 inner.events_registry = Some(events_registry);
191− inner.change_list.init_events_trampoline(events_trampoline);
194+ inner.change_list.init_events_trampoline(&events_trampoline);
195+ debug_assert!(inner.events_trampoline.is_none());
196+ inner.events_trampoline = Some(events_trampoline);
192197
193198 // Diff and apply the `contents` against our dummy `<div/>`.
194199 inner.render();
@@ -263,23 +268,28 @@ impl VdomInnerExclusive {
263268
264269 // Diff the old contents with the new contents.
265270 let old_contents = self.current_root.take().unwrap();
266− let mut cache_roots = bumpalo::collections::Vec::new_in(&dom_buffers[1]);
271+ let mut cache_roots;
267272 {
268273 let cached_set = self.cached_set.borrow();
274+ cache_roots = cached_set.new_roots_set();
275+ let mut change_list = self.change_list.builder();
269276 crate::diff::diff(
270277 &cached_set,
271− &mut self.change_list,
278+ &mut change_list,
272279 &mut registry,
273280 old_contents,
274281 new_contents.clone(),
275282 &mut cache_roots,
276283 );
284+
285+ // Tell JS to apply our diff-generated changes to the physical DOM!
286+ change_list.finish();
277287 }
278288
279289 {
280290 // Clean up unused cached renders.
281291 let mut cached_set = self.cached_set.borrow_mut();
282− cached_set.gc(&mut registry, &cache_roots);
292+ cached_set.gc(&mut registry, cache_roots);
283293 }
284294
285295 // Swap the buffers to make the bump arena with the new contents the
@@ -289,12 +299,6 @@ impl VdomInnerExclusive {
289299 }
290300
291301 self.events_registry = Some(events_registry);
292−
293− // Find and drop cached strings that aren't in use anymore.
294− self.change_list.drop_unused_strings();
295−
296− // Tell JS to apply our diff-generated changes to the physical DOM!
297− self.change_list.apply_changes();
298302 }
299303 }
300304
tests/web/main.rs+1
@@ -210,6 +210,7 @@ macro_rules! before_after {
210210 #[wasm_bindgen_test(async)]
211211 fn $name() -> impl Future<Item = (), Error = wasm_bindgen::JsValue> {
212212 use crate::{assert_before_after, RenderFn};
213+ log::debug!("############### {} ###############", stringify!($name));
213214 assert_before_after(
214215 RenderFn(|$before_bump| { $( $before )* }),
215216 RenderFn(|$after_bump| { $( $after )* })