on master help sign in
Refactor the change list compiler
56f9f86Nick Fitzgerald
Cargo.lock+2
@@ -1,3 +1,5 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
1[[package]]
3[[package]]
2name = "aho-corasick"
4name = "aho-corasick"
3version = "0.7.3"
5version = "0.7.3"
js/change-list-interpreter.js+18−18
@@ -45,8 +45,8 @@ const OP_TABLE = [
45 function setAttribute(interpreter, mem8, mem32, i) {
45 function setAttribute(interpreter, mem8, mem32, i) {
46 const nameId = mem32[i++];
46 const nameId = mem32[i++];
47 const valueId = mem32[i++];
47 const valueId = mem32[i++];
48− const name = interpreter.getString(nameId);
48+ const name = interpreter.getCachedString(nameId);
49− const value = interpreter.getString(valueId);
49+ const value = interpreter.getCachedString(valueId);
50 const node = top(interpreter.stack);
50 const node = top(interpreter.stack);
51 node.setAttribute(name, value);
51 node.setAttribute(name, value);
52
52
@@ -67,7 +67,7 @@ const OP_TABLE = [
67 // 4
67 // 4
68 function removeAttribute(interpreter, mem8, mem32, i) {
68 function removeAttribute(interpreter, mem8, mem32, i) {
69 const nameId = mem32[i++];
69 const nameId = mem32[i++];
70− const name = interpreter.getString(nameId);
70+ const name = interpreter.getCachedString(nameId);
71 const node = top(interpreter.stack);
71 const node = top(interpreter.stack);
72 node.removeAttribute(name);
72 node.removeAttribute(name);
73
73
@@ -123,7 +123,7 @@ const OP_TABLE = [
123 // 10
123 // 10
124 function createElement(interpreter, mem8, mem32, i) {
124 function createElement(interpreter, mem8, mem32, i) {
125 const tagNameId = mem32[i++];
125 const tagNameId = mem32[i++];
126− const tagName = interpreter.getString(tagNameId);
126+ const tagName = interpreter.getCachedString(tagNameId);
127 interpreter.stack.push(document.createElement(tagName));
127 interpreter.stack.push(document.createElement(tagName));
128 return i;
128 return i;
129 },
129 },
@@ -131,7 +131,7 @@ const OP_TABLE = [
131 // 11
131 // 11
132 function newEventListener(interpreter, mem8, mem32, i) {
132 function newEventListener(interpreter, mem8, mem32, i) {
133 const eventId = mem32[i++];
133 const eventId = mem32[i++];
134− const eventType = interpreter.getString(eventId);
134+ const eventType = interpreter.getCachedString(eventId);
135 const a = mem32[i++];
135 const a = mem32[i++];
136 const b = mem32[i++];
136 const b = mem32[i++];
137 const el = top(interpreter.stack);
137 const el = top(interpreter.stack);
@@ -144,7 +144,7 @@ const OP_TABLE = [
144 // 12
144 // 12
145 function updateEventListener(interpreter, mem8, mem32, i) {
145 function updateEventListener(interpreter, mem8, mem32, i) {
146 const eventId = mem32[i++];
146 const eventId = mem32[i++];
147− const eventType = interpreter.getString(eventId);
147+ const eventType = interpreter.getCachedString(eventId);
148 const el = top(interpreter.stack);
148 const el = top(interpreter.stack);
149 el[`dodrio-a-${eventType}`] = mem32[i++];
149 el[`dodrio-a-${eventType}`] = mem32[i++];
150 el[`dodrio-b-${eventType}`] = mem32[i++];
150 el[`dodrio-b-${eventType}`] = mem32[i++];
@@ -154,35 +154,35 @@ const OP_TABLE = [
154 // 13
154 // 13
155 function removeEventListener(interpreter, mem8, mem32, i) {
155 function removeEventListener(interpreter, mem8, mem32, i) {
156 const eventId = mem32[i++];
156 const eventId = mem32[i++];
157− const eventType = interpreter.getString(eventId);
157+ const eventType = interpreter.getCachedString(eventId);
158 const el = top(interpreter.stack);
158 const el = top(interpreter.stack);
159 el.removeEventListener(eventType, interpreter.eventHandler);
159 el.removeEventListener(eventType, interpreter.eventHandler);
160 return i;
160 return i;
161 },
161 },
162
162
163 // 14
163 // 14
164− function addString(interpreter, mem8, mem32, i) {
164+ function addCachedString(interpreter, mem8, mem32, i) {
165 const pointer = mem32[i++];
165 const pointer = mem32[i++];
166 const length = mem32[i++];
166 const length = mem32[i++];
167 const id = mem32[i++];
167 const id = mem32[i++];
168 const str = string(mem8, pointer, length);
168 const str = string(mem8, pointer, length);
169− interpreter.addString(str, id);
169+ interpreter.addCachedString(str, id);
170 return i;
170 return i;
171 },
171 },
172
172
173 // 15
173 // 15
174− function dropString(interpreter, mem8, mem32, i) {
174+ function dropCachedString(interpreter, mem8, mem32, i) {
175 const id = mem32[i++];
175 const id = mem32[i++];
176− interpreter.dropString(id);
176+ interpreter.dropCachedString(id);
177 return i;
177 return i;
178 },
178 },
179
179
180 // 16
180 // 16
181 function createElementNS(interpreter, mem8, mem32, i) {
181 function createElementNS(interpreter, mem8, mem32, i) {
182 const tagNameId = mem32[i++];
182 const tagNameId = mem32[i++];
183− const tagName = interpreter.getString(tagNameId);
183+ const tagName = interpreter.getCachedString(tagNameId);
184 const nsId = mem32[i++];
184 const nsId = mem32[i++];
185− const ns = interpreter.getString(nsId);
185+ const ns = interpreter.getCachedString(nsId);
186 interpreter.stack.push(document.createElementNS(ns, tagName));
186 interpreter.stack.push(document.createElementNS(ns, tagName));
187 return i;
187 return i;
188 },
188 },
@@ -191,8 +191,8 @@ const OP_TABLE = [
191 function setAttributeNS(interpreter, mem8, mem32, i) {
191 function setAttributeNS(interpreter, mem8, mem32, i) {
192 const nameId = mem32[i++];
192 const nameId = mem32[i++];
193 const valueId = mem32[i++];
193 const valueId = mem32[i++];
194− const name = interpreter.getString(nameId);
194+ const name = interpreter.getCachedString(nameId);
195− const value = interpreter.getString(valueId);
195+ const value = interpreter.getCachedString(valueId);
196 const node = top(interpreter.stack);
196 const node = top(interpreter.stack);
197 node.setAttributeNS(null, name, value);
197 node.setAttributeNS(null, name, value);
198 return i;
198 return i;
@@ -252,15 +252,15 @@ export class ChangeListInterpreter {
252 }
252 }
253 }
253 }
254
254
255− addString(str, id) {
255+ addCachedString(str, id) {
256 this.strings.set(id, str);
256 this.strings.set(id, str);
257 }
257 }
258
258
259− dropString(id) {
259+ dropCachedString(id) {
260 this.strings.delete(id);
260 this.strings.delete(id);
261 }
261 }
262
262
263− getString(id) {
263+ getCachedString(id) {
264 return this.strings.get(id);
264 return this.strings.get(id);
265 }
265 }
266
266
src/change_list.rsdeleted−464
@@ -1,464 +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 ChangeListInterpreter {}
11− impl ChangeListInterpreter {
12− pub fn new(_container: &crate::Element) -> ChangeListInterpreter {
13− ChangeListInterpreter {}
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-interpreter.js")]
23− extern "C" {
24− #[derive(Clone, Debug)]
25− pub type ChangeListInterpreter;
26−
27− #[wasm_bindgen(constructor)]
28− pub fn new(container: &web_sys::Element) -> ChangeListInterpreter;
29−
30− #[wasm_bindgen(structural, method)]
31− pub fn unmount(this: &ChangeListInterpreter);
32−
33− #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
34− pub fn add_change_list_range(this: &ChangeListInterpreter, start: usize, len: usize);
35−
36− #[wasm_bindgen(structural, method, js_name = applyChanges)]
37− pub fn apply_changes(this: &ChangeListInterpreter, memory: JsValue);
38−
39− #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
40− pub fn init_events_trampoline(
41− this: &ChangeListInterpreter,
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− interpreter: js::ChangeListInterpreter,
59−}
60−
61−impl Drop for ChangeList {
62− fn drop(&mut self) {
63− debug!("Dropping ChangeList");
64− self.interpreter.unmount();
65− }
66−}
67−
68−impl fmt::Debug for ChangeList {
69− fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70− f.debug_struct("ChangeList")
71− .field("bump", &self.bump)
72− .field("interpreter", &self.interpreter)
73− .finish()
74− }
75−}
76−
77−impl ChangeList {
78− pub(crate) fn new(container: &crate::Element) -> ChangeList {
79− let bump = Bump::new();
80− let strings_cache = FxHashMap::default();
81− let interpreter = js::ChangeListInterpreter::new(container);
82− ChangeList {
83− bump,
84− strings_cache,
85− next_string_key: 0,
86− interpreter,
87− }
88− }
89−
90− pub(crate) fn init_events_trampoline(&mut self, trampoline: &crate::EventsTrampoline) {
91− self.interpreter.init_events_trampoline(trampoline);
92− }
93−}
94−
95−cfg_if::cfg_if! {
96− if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
97− impl ChangeList {
98− pub(crate) fn apply_changes(&mut self) {
99− // Do nothing...
100− }
101− }
102− } else {
103− impl ChangeList {
104− pub(crate) fn apply_changes(&mut self) {
105− let interpreter = &self.interpreter;
106− unsafe {
107− self.bump.each_allocated_chunk(|ch| {
108− interpreter.add_change_list_range(ch.as_ptr() as usize, ch.len());
109− });
110− }
111− interpreter.apply_changes(wasm_bindgen::memory());
112− self.bump.reset();
113− }
114− }
115− }
116−}
117−
118−#[repr(u32)]
119−#[derive(Clone, Copy, Debug)]
120−enum ChangeDiscriminant {
121− /// Immediates: `(pointer, length)`
122− ///
123− /// Stack: `[... TextNode] -> [... TextNode]`
124− ///
125− /// ```text
126− /// stack.top().textContent = String(pointer, length)
127− /// ```
128− SetText = 0,
129−
130− /// Immediates: `()`
131− ///
132− /// Stack: `[... Node] -> [...]`
133− ///
134− /// ```text
135− /// node = stack.pop()
136− /// while (node.nextSibling) {
137− /// node.nextSibling.remove();
138− /// }
139− /// node.remove()
140− /// ```
141− RemoveSelfAndNextSiblings = 1,
142−
143− /// Immediates: `()`
144− ///
145− /// Stack: `[... Node Node] -> [... Node]`
146− ///
147− /// ```text
148− /// new = stack.pop()
149− /// old = stack.pop()
150− /// old.replaceWith(new)
151− /// stack.push(new)
152− /// ```
153− ReplaceWith = 2,
154−
155− /// Immediates: `(id1, id2)`
156− ///
157− /// Stack: `[... Node] -> [... Node]`
158− ///
159− /// ```text
160− /// stack.top().setAttribute(String(id1), String(id2))
161− /// ```
162− SetAttribute = 3,
163−
164− /// Immediates: (id)
165− ///
166− /// Stack: `[... Node] -> [... Node]`
167− ///
168− /// ```text
169− /// stack.top().removeAttribute(String(id))
170− /// ```
171− RemoveAttribute = 4,
172−
173− /// Immediates: `()`
174− ///
175− /// Stack: `[... Node] -> [... Node node]`
176− ///
177− /// ```text
178− /// stack.push(stack.top().firstChild)
179− /// ```
180− PushFirstChild = 5,
181−
182− /// Immediates: `()`
183− ///
184− /// Stack: `[... Node] -> [... Node]`
185− ///
186− /// ```text
187− /// stack.push(stack.pop().nextSibling)
188− /// ```
189− PopPushNextSibling = 6,
190−
191− /// Immediates: `()`
192− ///
193− /// Stack: `[... T] -> [...]`
194− ///
195− /// ```text
196− /// stack.pop()
197− /// ```
198− Pop = 7,
199−
200− /// Immediates: `()`
201− ///
202− /// Stack: `[... Node Node] -> [... Node]`
203− ///
204− /// ```text
205− /// child = stack.pop()
206− /// stack.top().appendChild(child)
207− /// ```
208− AppendChild = 8,
209−
210− /// Immediates: `(pointer, length)`
211− ///
212− /// Stack: `[...] -> [... Node]`
213− ///
214− /// ```text
215− /// stack.push(createTextNode(String(pointer, length)))
216− /// ```
217− CreateTextNode = 9,
218−
219− /// Immediates: `(id)`
220− ///
221− /// Stack: `[...] -> [... Node]`
222− ///
223− /// ```text
224− /// stack.push(createElement(String(id))
225− /// ```
226− CreateElement = 10,
227−
228− /// Immediates: `(id, A, B)`
229− ///
230− /// Stack: `[... Node] -> [... Node]`
231− ///
232− /// ```text
233− /// event = String(id)
234− /// callback = ProxyToRustCallback(A, B)
235− /// stack.top().addEventListener(event, callback)
236− /// ```
237− NewEventListener = 11,
238−
239− /// Immediates: `(id, A, B)`
240− ///
241− /// Stack: `[... Node] -> [... Node]`
242− ///
243− /// ```text
244− /// event = String(id)
245− /// new_callback = ProxyToRustCallback(A, B);
246− /// stack.top().updateEventlistener(new_callback)
247− /// ```
248− UpdateEventListener = 12,
249−
250− /// Immediates: `(id)`
251− ///
252− /// Stack: `[... Node] -> [... Node]`
253− ///
254− /// ```text
255− /// stack.top().removeEventListener(String(id));
256− /// ```
257− RemoveEventListener = 13,
258−
259− /// Immediates: `(pointer, length, id)`
260− ///
261− /// Stack: `[...] -> [...]`
262− ///
263− /// ```text
264− /// addString(String(pointer, length), id);
265− /// ```
266− AddString = 14,
267−
268− /// Immediates: `(id)`
269− ///
270− /// Stack: `[...] -> [...]`
271− ///
272− /// ```text
273− /// dropString(id);
274− /// ```
275− DropString = 15,
276−
277− /// Immediates: `(id1, id2)`
278− ///
279− /// Stack: `[...] -> [... Node]`
280− ///
281− /// ```text
282− /// stack.push(createElementNS(String(id1), String(id2))
283− /// ```
284− CreateElementNS = 16,
285−
286− /// Immediates: `(id1, id2, id3)`
287− ///
288− /// Stack: `[... Node] -> [... Node]`
289− ///
290− /// ```text
291− /// stack.top().setAttributeNS(String(id1), String(id2), String(id3))
292− /// ```
293− SetAttributeNS = 17,
294−}
295−
296−// Allocation utilities to ensure that we only allocation sequences of `u32`s
297−// into the change list's bump arena without any padding. This helps maintain
298−// the invariants required for `Bump::each_allocated_chunk`'s safety.
299−impl ChangeList {
300− // Allocate an opcode with zero immediates.
301− fn op0(&self, discriminant: ChangeDiscriminant) {
302− self.bump.alloc(discriminant as u32);
303− }
304−
305− // Allocate an opcode with one immediate.
306− fn op1(&self, discriminant: ChangeDiscriminant, a: u32) {
307− self.bump.alloc([discriminant as u32, a]);
308− }
309−
310− // Allocate an opcode with two immediates.
311− fn op2(&self, discriminant: ChangeDiscriminant, a: u32, b: u32) {
312− self.bump.alloc([discriminant as u32, a, b]);
313− }
314−
315− // Allocate an opcode with three immediates.
316− fn op3(&self, discriminant: ChangeDiscriminant, a: u32, b: u32, c: u32) {
317− self.bump.alloc([discriminant as u32, a, b, c]);
318− }
319−
320− // Note: no 4-immediate opcodes at this time.
321−}
322−
323−impl ChangeList {
324− fn ensure_string(&mut self, string: &str) -> u32 {
325− if let Some(entry) = self.strings_cache.get_mut(string) {
326− entry.used = true;
327− entry.key
328− } else {
329− let key = self.next_string_key;
330− self.next_string_key += 1;
331− let entry = StringsCacheEntry { key, used: true };
332− self.strings_cache.insert(string.to_string(), entry);
333− self.op3(
334− ChangeDiscriminant::AddString,
335− string.as_ptr() as u32,
336− string.len() as u32,
337− key,
338− );
339− key
340− }
341− }
342−
343− pub(crate) fn drop_unused_strings(&mut self) {
344− debug!("drop_unused_strings()");
345− let mut new_cache = FxHashMap::default();
346− for (key, val) in self.strings_cache.iter() {
347− if val.used {
348− new_cache.insert(
349− key.clone(),
350− StringsCacheEntry {
351− key: val.key,
352− used: false,
353− },
354− );
355− } else {
356− self.op1(ChangeDiscriminant::DropString, val.key);
357− }
358− }
359− self.strings_cache = new_cache;
360− }
361−
362− pub(crate) fn emit_set_text(&self, text: &str) {
363− debug!("emit_set_text({:?})", text);
364− self.op2(
365− ChangeDiscriminant::SetText,
366− text.as_ptr() as u32,
367− text.len() as u32,
368− );
369− }
370−
371− pub(crate) fn emit_remove_self_and_next_siblings(&self) {
372− debug!("emit_remove_self_and_next_siblings()");
373− self.op0(ChangeDiscriminant::RemoveSelfAndNextSiblings);
374− }
375−
376− pub(crate) fn emit_replace_with(&self) {
377− debug!("emit_replace_with()");
378− self.op0(ChangeDiscriminant::ReplaceWith);
379− }
380−
381− pub(crate) fn emit_set_attribute(&mut self, name: &str, value: &str) {
382− debug!("emit_set_attribute({:?}, {:?})", name, value);
383− let name_id = self.ensure_string(name);
384− let value_id = self.ensure_string(value);
385− self.op2(ChangeDiscriminant::SetAttribute, name_id, value_id);
386− }
387−
388− pub(crate) fn emit_set_attribute_ns(&mut self, name: &str, value: &str) {
389− debug!("emit_set_attribute_ns({:?}, {:?}", name, value);
390− let name_id = self.ensure_string(name);
391− let value_id = self.ensure_string(value);
392− self.op2(ChangeDiscriminant::SetAttributeNS, name_id, value_id);
393− }
394−
395− pub(crate) fn emit_remove_attribute(&mut self, name: &str) {
396− debug!("emit_remove_attribute({:?})", name);
397− let name_id = self.ensure_string(name);
398− self.op1(ChangeDiscriminant::RemoveAttribute, name_id);
399− }
400−
401− pub(crate) fn emit_push_first_child(&self) {
402− debug!("emit_push_first_child()");
403− self.op0(ChangeDiscriminant::PushFirstChild);
404− }
405−
406− pub(crate) fn emit_pop_push_next_sibling(&self) {
407− debug!("emit_pop_push_next_sibling()");
408− self.op0(ChangeDiscriminant::PopPushNextSibling);
409− }
410−
411− pub(crate) fn emit_pop(&self) {
412− debug!("emit_pop()");
413− self.op0(ChangeDiscriminant::Pop);
414− }
415−
416− pub(crate) fn emit_append_child(&self) {
417− debug!("emit_append_child()");
418− self.op0(ChangeDiscriminant::AppendChild);
419− }
420−
421− pub(crate) fn emit_create_text_node(&self, text: &str) {
422− debug!("emit_create_text_node({:?})", text);
423− self.op2(
424− ChangeDiscriminant::CreateTextNode,
425− text.as_ptr() as u32,
426− text.len() as u32,
427− );
428− }
429−
430− pub(crate) fn emit_create_element(&mut self, tag_name: &str) {
431− debug!("emit_create_element({:?})", tag_name);
432− let tag_name_id = self.ensure_string(tag_name);
433− self.op1(ChangeDiscriminant::CreateElement, tag_name_id);
434− }
435−
436− pub(crate) fn emit_create_element_ns(&mut self, tag_name: &str, ns: &str) {
437− debug!("emit_create_element_ns({:?}, {:?})", tag_name, ns);
438− let tag_name_id = self.ensure_string(tag_name);
439− let ns_id = self.ensure_string(ns);
440− self.op2(ChangeDiscriminant::CreateElementNS, tag_name_id, ns_id);
441− }
442−
443− pub(crate) fn emit_new_event_listener(&mut self, listener: &Listener) {
444− debug!("emit_new_event_listener({:?})", listener);
445− let (a, b) = listener.get_callback_parts();
446− debug_assert!(a != 0);
447− let event_id = self.ensure_string(listener.event);
448− self.op3(ChangeDiscriminant::NewEventListener, event_id, a, b);
449− }
450−
451− pub(crate) fn emit_update_event_listener(&mut self, listener: &Listener) {
452− debug!("emit_update_event_listener({:?})", listener);
453− let (a, b) = listener.get_callback_parts();
454− debug_assert!(a != 0);
455− let event_id = self.ensure_string(listener.event);
456− self.op3(ChangeDiscriminant::UpdateEventListener, event_id, a, b);
457− }
458−
459− pub(crate) fn emit_remove_event_listener(&mut self, event: &str) {
460− debug!("emit_remove_event_listener({:?})", event);
461− let event_id = self.ensure_string(event);
462− self.op1(ChangeDiscriminant::RemoveEventListener, event_id);
463− }
464−}
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+185
@@ -0,0 +1,185 @@
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 {
47+ state: self,
48+ }
49+ }
50+}
51+
52+cfg_if::cfg_if! {
53+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
54+ impl ChangeListBuilder<'_> {
55+ pub(crate) fn finish(self) {
56+ self.state.strings.drop_unused_strings(&self.state.emitter);
57+
58+ // Nothing to actually apply the changes to.
59+
60+ self.state.emitter.reset();
61+ }
62+ }
63+ } else {
64+ impl ChangeListBuilder<'_> {
65+ pub(crate) fn finish(self) {
66+ self.state.strings.drop_unused_strings(&self.state.emitter);
67+
68+ // Apply the changes.
69+ let interpreter = &self.state.interpreter;
70+ self.state.emitter.each_instruction_sequence(|seq| {
71+ interpreter.add_change_list_range(seq.as_ptr() as usize, seq.len());
72+ });
73+ interpreter.apply_changes(wasm_bindgen::memory());
74+
75+ self.state.emitter.reset();
76+ }
77+ }
78+ }
79+}
80+
81+impl ChangeListBuilder<'_> {
82+ pub fn ensure_string(&mut self, string: &str) -> StringKey {
83+ self.state
84+ .strings
85+ .ensure_string(string, &self.state.emitter)
86+ }
87+
88+ pub fn set_text(&self, text: &str) {
89+ debug!("emit: set_text({:?})", text);
90+ self.state
91+ .emitter
92+ .set_text(text.as_ptr() as u32, text.len() as u32);
93+ }
94+
95+ pub fn remove_self_and_next_siblings(&self) {
96+ debug!("emit: remove_self_and_next_siblings()");
97+ self.state.emitter.remove_self_and_next_siblings();
98+ }
99+
100+ pub fn replace_with(&self) {
101+ debug!("emit: replace_with()");
102+ self.state.emitter.replace_with();
103+ }
104+
105+ pub fn set_attribute(&mut self, name: &str, value: &str) {
106+ debug!("emit: set_attribute({:?}, {:?})", name, value);
107+ let name_id = self.ensure_string(name);
108+ let value_id = self.ensure_string(value);
109+ self.state
110+ .emitter
111+ .set_attribute(name_id.into(), value_id.into());
112+ }
113+
114+ pub fn remove_attribute(&mut self, name: &str) {
115+ debug!("emit: remove_attribute({:?})", name);
116+ let name_id = self.ensure_string(name);
117+ self.state.emitter.remove_attribute(name_id.into());
118+ }
119+
120+ pub fn push_first_child(&self) {
121+ debug!("emit: push_first_child");
122+ self.state.emitter.push_first_child();
123+ }
124+
125+ pub fn pop_push_next_sibling(&self) {
126+ debug!("emit: pop_push_next_sibling");
127+ self.state.emitter.pop_push_next_sibling();
128+ }
129+
130+ pub fn pop(&self) {
131+ debug!("emit: pop");
132+ self.state.emitter.pop();
133+ }
134+
135+ pub fn append_child(&self) {
136+ debug!("emit: append_child()");
137+ self.state.emitter.append_child();
138+ }
139+
140+ pub fn create_text_node(&self, text: &str) {
141+ debug!("emit: create_text_node({:?})", text);
142+ self.state
143+ .emitter
144+ .create_text_node(text.as_ptr() as u32, text.len() as u32);
145+ }
146+
147+ pub fn create_element(&mut self, tag_name: &str) {
148+ debug!("emit: create_element({:?})", tag_name);
149+ let tag_name_id = self.ensure_string(tag_name);
150+ self.state.emitter.create_element(tag_name_id.into());
151+ }
152+
153+ pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
154+ debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
155+ let tag_name_id = self.ensure_string(tag_name);
156+ let ns_id = self.ensure_string(ns);
157+ self.state
158+ .emitter
159+ .create_element_ns(tag_name_id.into(), ns_id.into());
160+ }
161+
162+ pub fn new_event_listener(&mut self, listener: &Listener) {
163+ debug!("emit: new_event_listener({:?})", listener);
164+ let (a, b) = listener.get_callback_parts();
165+ debug_assert!(a != 0);
166+ let event_id = self.ensure_string(listener.event);
167+ self.state.emitter.new_event_listener(event_id.into(), a, b);
168+ }
169+
170+ pub fn update_event_listener(&mut self, listener: &Listener) {
171+ debug!("emit: update_event_listener({:?})", listener);
172+ let (a, b) = listener.get_callback_parts();
173+ debug_assert!(a != 0);
174+ let event_id = self.ensure_string(listener.event);
175+ self.state
176+ .emitter
177+ .update_event_listener(event_id.into(), a, b);
178+ }
179+
180+ pub fn remove_event_listener(&mut self, event: &str) {
181+ debug!("emit: remove_event_listener({:?})", event);
182+ let event_id = self.ensure_string(event);
183+ self.state.emitter.remove_event_listener(event_id.into());
184+ }
185+}
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+34−38
@@ -1,6 +1,6 @@
1use crate::{
1use crate::{
2 cached_set::{CacheId, CachedSet},
2 cached_set::{CacheId, CachedSet},
3− change_list::ChangeList,
3+ change_list::ChangeListBuilder,
4 events::EventsRegistry,
4 events::EventsRegistry,
5 node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
5 node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
6};
6};
@@ -8,7 +8,7 @@ use std::cmp;
8
8
9pub(crate) fn diff(
9pub(crate) fn diff(
10 cached_set: &CachedSet,
10 cached_set: &CachedSet,
11− change_list: &mut ChangeList,
11+ change_list: &mut ChangeListBuilder,
12 registry: &mut EventsRegistry,
12 registry: &mut EventsRegistry,
13 old: Node,
13 old: Node,
14 new: Node,
14 new: Node,
@@ -22,7 +22,7 @@ pub(crate) fn diff(
22 debug!(" both are text nodes");
22 debug!(" both are text nodes");
23 if new_text != old_text {
23 if new_text != old_text {
24 debug!(" text needs updating");
24 debug!(" text needs updating");
25− change_list.emit_set_text(new_text);
25+ change_list.set_text(new_text);
26 }
26 }
27 }
27 }
28
28
@@ -30,7 +30,7 @@ pub(crate) fn diff(
30 debug!(" replacing a text node with an element");
30 debug!(" replacing a text node with an element");
31 create(cached_set, change_list, registry, new, cached_roots);
31 create(cached_set, change_list, registry, new, cached_roots);
32 registry.remove_subtree(&old);
32 registry.remove_subtree(&old);
33− change_list.emit_replace_with();
33+ change_list.replace_with();
34 }
34 }
35
35
36 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
36 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
@@ -38,7 +38,7 @@ pub(crate) fn diff(
38 create(cached_set, change_list, registry, new, cached_roots);
38 create(cached_set, change_list, registry, new, cached_roots);
39 // Note: text nodes cannot have event listeners, so we don't need to
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.
40 // remove the old node's listeners from our registry her.
41− change_list.emit_replace_with();
41+ change_list.replace_with();
42 }
42 }
43
43
44 (
44 (
@@ -62,7 +62,7 @@ pub(crate) fn diff(
62 debug!(" different tag names or namespaces; creating new element and replacing old element");
62 debug!(" different tag names or namespaces; creating new element and replacing old element");
63 create(cached_set, change_list, registry, new, cached_roots);
63 create(cached_set, change_list, registry, new, cached_roots);
64 registry.remove_subtree(&old);
64 registry.remove_subtree(&old);
65− change_list.emit_replace_with();
65+ change_list.replace_with();
66 return;
66 return;
67 }
67 }
68 diff_listeners(change_list, registry, old_listeners, new_listeners);
68 diff_listeners(change_list, registry, old_listeners, new_listeners);
@@ -99,7 +99,7 @@ pub(crate) fn diff(
99 let new = cached_set.get(c.id);
99 let new = cached_set.get(c.id);
100 create(cached_set, change_list, registry, new, cached_roots);
100 create(cached_set, change_list, registry, new, cached_roots);
101 registry.remove_subtree(&old);
101 registry.remove_subtree(&old);
102− change_list.emit_replace_with();
102+ change_list.replace_with();
103 }
103 }
104
104
105 // Old cached node and new non-cached node. Again, assume that they are
105 // Old cached node and new non-cached node. Again, assume that they are
@@ -107,13 +107,13 @@ pub(crate) fn diff(
107 (_, &NodeKind::Cached(_)) => {
107 (_, &NodeKind::Cached(_)) => {
108 create(cached_set, change_list, registry, new, cached_roots);
108 create(cached_set, change_list, registry, new, cached_roots);
109 registry.remove_subtree(&old);
109 registry.remove_subtree(&old);
110− change_list.emit_replace_with();
110+ change_list.replace_with();
111 }
111 }
112 }
112 }
113}
113}
114
114
115fn diff_listeners(
115fn diff_listeners(
116− change_list: &mut ChangeList,
116+ change_list: &mut ChangeListBuilder,
117 registry: &mut EventsRegistry,
117 registry: &mut EventsRegistry,
118 old: &[Listener],
118 old: &[Listener],
119 new: &[Listener],
119 new: &[Listener],
@@ -130,12 +130,12 @@ fn diff_listeners(
130
130
131 for old_l in old {
131 for old_l in old {
132 if new_l.event == old_l.event {
132 if new_l.event == old_l.event {
133− change_list.emit_update_event_listener(new_l);
133+ change_list.update_event_listener(new_l);
134 continue 'outer1;
134 continue 'outer1;
135 }
135 }
136 }
136 }
137
137
138− change_list.emit_new_event_listener(new_l);
138+ change_list.new_event_listener(new_l);
139 }
139 }
140
140
141 'outer2: for old_l in old {
141 'outer2: for old_l in old {
@@ -146,28 +146,28 @@ fn diff_listeners(
146 continue 'outer2;
146 continue 'outer2;
147 }
147 }
148 }
148 }
149− change_list.emit_remove_event_listener(old_l.event);
149+ change_list.remove_event_listener(old_l.event);
150 }
150 }
151}
151}
152
152
153−fn diff_attributes(change_list: &mut ChangeList, old: &[Attribute], new: &[Attribute]) {
153+fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute]) {
154 debug!(" updating attributes");
154 debug!(" updating attributes");
155
155
156 // Do O(n^2) passes to add/update and remove attributes, since
156 // Do O(n^2) passes to add/update and remove attributes, since
157 // there are almost always very few attributes.
157 // there are almost always very few attributes.
158 'outer: for new_attr in new {
158 'outer: for new_attr in new {
159 if new_attr.is_volatile() {
159 if new_attr.is_volatile() {
160− change_list.emit_set_attribute(new_attr.name, new_attr.value);
160+ change_list.set_attribute(new_attr.name, new_attr.value);
161 } else {
161 } else {
162 for old_attr in old {
162 for old_attr in old {
163 if old_attr.name == new_attr.name {
163 if old_attr.name == new_attr.name {
164 if old_attr.value != new_attr.value {
164 if old_attr.value != new_attr.value {
165− change_list.emit_set_attribute(new_attr.name, new_attr.value);
165+ change_list.set_attribute(new_attr.name, new_attr.value);
166 }
166 }
167 continue 'outer;
167 continue 'outer;
168 }
168 }
169 }
169 }
170− change_list.emit_set_attribute(new_attr.name, new_attr.value);
170+ change_list.set_attribute(new_attr.name, new_attr.value);
171 }
171 }
172 }
172 }
173
173
@@ -177,13 +177,13 @@ fn diff_attributes(change_list: &mut ChangeList, old: &[Attribute], new: &[Attri
177 continue 'outer2;
177 continue 'outer2;
178 }
178 }
179 }
179 }
180− change_list.emit_remove_attribute(old_attr.name);
180+ change_list.remove_attribute(old_attr.name);
181 }
181 }
182}
182}
183
183
184fn diff_children(
184fn diff_children(
185 cached_set: &CachedSet,
185 cached_set: &CachedSet,
186− change_list: &mut ChangeList,
186+ change_list: &mut ChangeListBuilder,
187 registry: &mut EventsRegistry,
187 registry: &mut EventsRegistry,
188 old: &[Node],
188 old: &[Node],
189 new: &[Node],
189 new: &[Node],
@@ -203,11 +203,11 @@ fn diff_children(
203 .enumerate()
203 .enumerate()
204 {
204 {
205 if i == 0 {
205 if i == 0 {
206− change_list.emit_push_first_child();
206+ change_list.push_first_child();
207 pushed = true;
207 pushed = true;
208 } else {
208 } else {
209 debug_assert!(pushed);
209 debug_assert!(pushed);
210− change_list.emit_pop_push_next_sibling();
210+ change_list.pop_push_next_sibling();
211 }
211 }
212
212
213 diff(
213 diff(
@@ -223,18 +223,18 @@ fn diff_children(
223 if old_children.next().is_some() {
223 if old_children.next().is_some() {
224 debug!(" removing extra old children");
224 debug!(" removing extra old children");
225 debug_assert!(new_children.next().is_none());
225 debug_assert!(new_children.next().is_none());
226− if !pushed {
226+ if pushed {
227− change_list.emit_push_first_child();
227+ change_list.pop_push_next_sibling();
228 } else {
228 } else {
229− change_list.emit_pop_push_next_sibling();
229+ change_list.push_first_child();
230 }
230 }
231− change_list.emit_remove_self_and_next_siblings();
231+ change_list.remove_self_and_next_siblings();
232 pushed = false;
232 pushed = false;
233 } else {
233 } else {
234 debug!(" creating new children");
234 debug!(" creating new children");
235 for (i, new_child) in new_children.enumerate() {
235 for (i, new_child) in new_children.enumerate() {
236 if i == 0 && pushed {
236 if i == 0 && pushed {
237− change_list.emit_pop();
237+ change_list.pop();
238 pushed = false;
238 pushed = false;
239 }
239 }
240 create(
240 create(
@@ -244,26 +244,26 @@ fn diff_children(
244 new_child.clone(),
244 new_child.clone(),
245 cached_roots,
245 cached_roots,
246 );
246 );
247− change_list.emit_append_child();
247+ change_list.append_child();
248 }
248 }
249 }
249 }
250
250
251 debug!(" done updating children");
251 debug!(" done updating children");
252 if pushed {
252 if pushed {
253− change_list.emit_pop();
253+ change_list.pop();
254 }
254 }
255}
255}
256
256
257fn create(
257fn create(
258 cached_set: &CachedSet,
258 cached_set: &CachedSet,
259− change_list: &mut ChangeList,
259+ change_list: &mut ChangeListBuilder,
260 registry: &mut EventsRegistry,
260 registry: &mut EventsRegistry,
261 node: Node,
261 node: Node,
262 cached_roots: &mut bumpalo::collections::Vec<CacheId>,
262 cached_roots: &mut bumpalo::collections::Vec<CacheId>,
263) {
263) {
264 match node.kind {
264 match node.kind {
265 NodeKind::Text(TextNode { text }) => {
265 NodeKind::Text(TextNode { text }) => {
266− change_list.emit_create_text_node(text);
266+ change_list.create_text_node(text);
267 }
267 }
268 NodeKind::Element(ElementNode {
268 NodeKind::Element(ElementNode {
269 tag_name,
269 tag_name,
@@ -273,22 +273,18 @@ fn create(
273 namespace,
273 namespace,
274 }) => {
274 }) => {
275 if let Some(namespace) = namespace {
275 if let Some(namespace) = namespace {
276− change_list.emit_create_element_ns(tag_name, namespace);
276+ change_list.create_element_ns(tag_name, namespace);
277 } else {
277 } else {
278− change_list.emit_create_element(tag_name);
278+ change_list.create_element(tag_name);
279 }
279 }
280 for l in listeners {
280 for l in listeners {
281 unsafe {
281 unsafe {
282 registry.add(l);
282 registry.add(l);
283 }
283 }
284− change_list.emit_new_event_listener(l);
284+ change_list.new_event_listener(l);
285 }
285 }
286 for attr in attributes {
286 for attr in attributes {
287− if namespace.is_none() || attr.name.starts_with("xmlns") {
287+ change_list.set_attribute(&attr.name, &attr.value);
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 }
288 }
293 for child in children {
289 for child in children {
294 create(
290 create(
@@ -298,7 +294,7 @@ fn create(
298 child.clone(),
294 child.clone(),
299 cached_roots,
295 cached_roots,
300 );
296 );
301− change_list.emit_append_child();
297+ change_list.append_child();
302 }
298 }
303 }
299 }
304 NodeKind::Cached(c) => {
300 NodeKind::Cached(c) => {
src/vdom.rs+8−10
@@ -1,4 +1,4 @@
1−use super::change_list::ChangeList;
1+use super::change_list::ChangeListPersistentState;
2use super::RootRender;
2use super::RootRender;
3use crate::cached_set::CachedSet;
3use crate::cached_set::CachedSet;
4use crate::events::EventsRegistry;
4use crate::events::EventsRegistry;
@@ -56,7 +56,7 @@ pub(crate) struct VdomInnerExclusive {
56 component: Option<Box<RootRender>>,
56 component: Option<Box<RootRender>>,
57
57
58 dom_buffers: Option<[Bump; 2]>,
58 dom_buffers: Option<[Bump; 2]>,
59− change_list: ManuallyDrop<ChangeList>,
59+ change_list: ManuallyDrop<ChangeListPersistentState>,
60 container: crate::Element,
60 container: crate::Element,
61 events_registry: Option<Rc<RefCell<EventsRegistry>>>,
61 events_registry: Option<Rc<RefCell<EventsRegistry>>>,
62 events_trampoline: Option<crate::EventsTrampoline>,
62 events_trampoline: Option<crate::EventsTrampoline>,
@@ -162,7 +162,7 @@ impl Vdom {
162 /// rendering component.
162 /// rendering component.
163 pub fn with_boxed_root_render(container: &crate::Element, component: Box<RootRender>) -> Vdom {
163 pub fn with_boxed_root_render(container: &crate::Element, component: Box<RootRender>) -> Vdom {
164 let dom_buffers = [Bump::new(), Bump::new()];
164 let dom_buffers = [Bump::new(), Bump::new()];
165− let change_list = ManuallyDrop::new(ChangeList::new(container));
165+ let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
166
166
167 // Create a dummy `<div/>` in our container.
167 // Create a dummy `<div/>` in our container.
168 initialize_container(container);
168 initialize_container(container);
@@ -271,14 +271,18 @@ impl VdomInnerExclusive {
271 let mut cache_roots = bumpalo::collections::Vec::new_in(&dom_buffers[1]);
271 let mut cache_roots = bumpalo::collections::Vec::new_in(&dom_buffers[1]);
272 {
272 {
273 let cached_set = self.cached_set.borrow();
273 let cached_set = self.cached_set.borrow();
274+ let mut change_list = self.change_list.builder();
274 crate::diff::diff(
275 crate::diff::diff(
275 &cached_set,
276 &cached_set,
276− &mut self.change_list,
277+ &mut change_list,
277 &mut registry,
278 &mut registry,
278 old_contents,
279 old_contents,
279 new_contents.clone(),
280 new_contents.clone(),
280 &mut cache_roots,
281 &mut cache_roots,
281 );
282 );
283+
284+ // Tell JS to apply our diff-generated changes to the physical DOM!
285+ change_list.finish();
282 }
286 }
283
287
284 {
288 {
@@ -294,12 +298,6 @@ impl VdomInnerExclusive {
294 }
298 }
295
299
296 self.events_registry = Some(events_registry);
300 self.events_registry = Some(events_registry);
297−
298− // Find and drop cached strings that aren't in use anymore.
299− self.change_list.drop_unused_strings();
300−
301− // Tell JS to apply our diff-generated changes to the physical DOM!
302− self.change_list.apply_changes();
303 }
301 }
304 }
302 }
305
303
tests/web/main.rs+1
@@ -210,6 +210,7 @@ macro_rules! before_after {
210 #[wasm_bindgen_test(async)]
210 #[wasm_bindgen_test(async)]
211 fn $name() -> impl Future<Item = (), Error = wasm_bindgen::JsValue> {
211 fn $name() -> impl Future<Item = (), Error = wasm_bindgen::JsValue> {
212 use crate::{assert_before_after, RenderFn};
212 use crate::{assert_before_after, RenderFn};
213+ log::debug!("############### {} ###############", stringify!($name));
213 assert_before_after(
214 assert_before_after(
214 RenderFn(|$before_bump| { $( $before )* }),
215 RenderFn(|$before_bump| { $( $before )* }),
215 RenderFn(|$after_bump| { $( $after )* })
216 RenderFn(|$after_bump| { $( $after )* })