on master help sign in
Merge pull request #61 from fitzgen/traversal-optimization-take-2
d278678Nick Fitzgerald
Cargo.toml+8
@@ -29,6 +29,14 @@ harness = false
29required-features = ["xxx-unstable-internal-use-only"]
29required-features = ["xxx-unstable-internal-use-only"]
30
30
31[features]
31[features]
32+# Don't rely on this feature! Its behavior will change and it could go away at
33+# any time. This enables monkey-patching DOM methods so that we wrap them and
34+# trace what DOM methods are used and how many times they are invoked when
35+# applying our emitted change lists to the physical DOM. This is useful for
36+# evaluating the differences between how Dodrio applies changes from virtual
37+# nodes to the physical DOM versus how other virtual DOM libraries do the same.
38+xxx-unstable-strace = []
39+
32# Don't use this feature! It only exists for exposing otherwise-internal APIs
40# Don't use this feature! It only exists for exposing otherwise-internal APIs
33# for testing and benchmarks.
41# for testing and benchmarks.
34xxx-unstable-internal-use-only = []
42xxx-unstable-internal-use-only = []
js/change-list-interpreter.js+18−7
@@ -86,15 +86,23 @@ const OP_TABLE = [
86 },
86 },
87
87
88 // 5
88 // 5
89− function pushFirstChild(interpreter, mem8, mem32, i) {
89+ function pushReverseChild(interpreter, mem8, mem32, i) {
90+ const n = mem32[i++];
91+ const parent = top(interpreter.stack);
92+ const children = parent.childNodes;
93+ const child = children[children.length - n - 1];
90− interpreter.stack.push(top(interpreter.stack).firstChild);
94+ interpreter.stack.push(child);
91 return i;
95 return i;
92 },
96 },
93
97
94 // 6
98 // 6
95− function popPushNextSibling(interpreter, mem8, mem32, i) {
99+ function popPushChild(interpreter, mem8, mem32, i) {
100+ const n = mem32[i++];
96− const node = interpreter.stack.pop();
101+ interpreter.stack.pop();
102+ const parent = top(interpreter.stack);
103+ const children = parent.childNodes;
104+ const child = children[n];
97− interpreter.stack.push(node.nextSibling);
105+ interpreter.stack.push(child);
98 return i;
106 return i;
99 },
107 },
100
108
@@ -226,9 +234,12 @@ const OP_TABLE = [
226 },
234 },
227
235
228 // 21
236 // 21
229− function pushLastChild(interpreter, mem8, mem32, i) {
237+ function popPushReverseChild(interpreter, mem8, mem32, i) {
238+ const n = mem32[i++];
239+ interpreter.stack.pop();
230 const parent = top(interpreter.stack);
240 const parent = top(interpreter.stack);
231− const child = parent.lastChild;
241+ const children = parent.childNodes;
242+ const child = children[children.length - n - 1];
232 interpreter.stack.push(child);
243 interpreter.stack.push(child);
233 return i;
244 return i;
234 },
245 },
js/strace.jsnew file+71
@@ -0,0 +1,71 @@
1+export function initStrace() {
2+ instrumentAll(Element.prototype);
3+ instrumentAll(Document.prototype);
4+ instrumentAll(Node.prototype);
5+ instrumentAll(EventTarget.prototype);
6+}
7+
8+const counts = new Map;
9+let timer = null;
10+
11+function log(ctorName, kind, key) {
12+ const entry = `${kind} ${ctorName}#${key}`
13+ const count = counts.get(entry);
14+ if (count === undefined) {
15+ counts.set(entry, 1);
16+ } else {
17+ counts.set(entry, count + 1);
18+ }
19+
20+ if (timer !== null) {
21+ clearTimeout(timer);
22+ }
23+ timer = setTimeout(dumpAndReset, 100);
24+}
25+
26+function dumpAndReset() {
27+ const data = [...counts]
28+ .sort((a, b) => b[1] - a[1])
29+ .map(a => ({ "DOM Method": a[0], "Count": a[1] }));
30+ console.table(data, ["DOM Method", "Count"]);
31+ counts.clear();
32+}
33+
34+function instrument(proto, key, desc) {
35+ const ctorName = proto.constructor.name;
36+
37+ if (typeof desc.value == "function") {
38+ const f = desc.value;
39+ desc.value = function (...args) {
40+ log(ctorName, "call", key);
41+ return f.apply(this, args);
42+ };
43+ }
44+
45+ if (typeof desc.get == "function") {
46+ const get = desc.get;
47+ desc.get = function (...args) {
48+ log(ctorName, "get", key);
49+ return get.apply(this, args);
50+ };
51+ }
52+
53+ if (typeof desc.set == "function") {
54+ const set = desc.set;
55+ desc.set = function (...args) {
56+ log(ctorName, "set", key);
57+ return set.apply(this, args);
58+ };
59+ }
60+
61+ Object.defineProperty(proto, key, desc);
62+}
63+
64+function instrumentAll(proto) {
65+ for (const key of Object.keys(proto)) {
66+ const desc = Object.getOwnPropertyDescriptor(proto, key);
67+ if (desc.configurable) {
68+ instrument(proto, key, desc);
69+ }
70+ }
71+}
src/builder.rs+12−3
@@ -282,13 +282,22 @@ where
282 /// ```
282 /// ```
283 #[inline]
283 #[inline]
284 pub fn finish(self) -> Node<'a> {
284 pub fn finish(self) -> Node<'a> {
285+ let children: &'a Children = self.bump.alloc(self.children);
286+ let children: &'a [Node<'a>] = children.as_ref();
287+
288+ let listeners: &'a Listeners = self.bump.alloc(self.listeners);
289+ let listeners: &'a [Listener<'a>] = listeners.as_ref();
290+
291+ let attributes: &'a Attributes = self.bump.alloc(self.attributes);
292+ let attributes: &'a [Attribute<'a>] = attributes.as_ref();
293+
285 Node::element(
294 Node::element(
286 self.bump,
295 self.bump,
287 self.key,
296 self.key,
288 self.tag_name,
297 self.tag_name,
289− self.listeners,
298+ listeners,
290− self.attributes,
299+ attributes,
291− self.children,
300+ children,
292 self.namespace,
301 self.namespace,
293 )
302 )
294 }
303 }
src/change_list/emitter.rs+14−8
@@ -114,16 +114,21 @@ define_change_list_instructions! {
114 /// Stack: `[... Node] -> [... Node Node]`
114 /// Stack: `[... Node] -> [... Node Node]`
115 ///
115 ///
116 /// ```text
116 /// ```text
117− /// stack.push(stack.top().firstChild)
117+ /// parent = stack.top()
118+ /// child = parent.childNodes[parent.childNodes.length - n - 1]
119+ /// stack.push(child)
118 /// ```
120 /// ```
119− push_first_child() = 5,
121+ push_reverse_child(n) = 5,
120
122
121− /// Stack: `[... Node] -> [... Node]`
123+ /// Stack: `[... Node Node] -> [... Node Node]`
122 ///
124 ///
123 /// ```text
125 /// ```text
124− /// stack.push(stack.pop().nextSibling)
126+ /// stack.pop();
127+ /// parent = stack.top();
128+ /// child = parent.childNodes[n]
129+ /// stack.push(child)
125 /// ```
130 /// ```
126− pop_push_next_sibling() = 6,
131+ pop_push_child(n) = 6,
127
132
128 /// Stack: `[... T] -> [...]`
133 /// Stack: `[... T] -> [...]`
129 ///
134 ///
@@ -240,14 +245,15 @@ define_change_list_instructions! {
240 /// ```
245 /// ```
241 insert_before() = 20,
246 insert_before() = 20,
242
247
243− /// Stack: `[... Node] -> [... Node Node]`
248+ /// Stack: `[... Node Node] -> [... Node Node]`
244 ///
249 ///
245 /// ```text
250 /// ```text
251+ /// stack.pop()
246 /// parent = stack.top()
252 /// parent = stack.top()
247− /// child = parent.lastChild
253+ /// child = parent.childNodes[parent.childNodes.length - n - 1]
248 /// stack.push(child)
254 /// stack.push(child)
249 /// ```
255 /// ```
250− push_last_child() = 21,
256+ pop_push_reverse_child(n) = 21,
251
257
252 /// Stack: `[... Node] -> [... Node]`
258 /// Stack: `[... Node] -> [... Node]`
253 ///
259 ///
src/change_list/mod.rs+96−25
@@ -1,17 +1,20 @@
1pub(crate) mod emitter;
1pub(crate) mod emitter;
2pub(crate) mod strings;
2pub(crate) mod strings;
3+pub(crate) mod traversal;
3
4
4// Note: has to be `pub` because of `wasm-bindgen` visibility restrictions.
5// Note: has to be `pub` because of `wasm-bindgen` visibility restrictions.
5pub mod js;
6pub mod js;
6
7
7use self::emitter::InstructionEmitter;
8use self::emitter::InstructionEmitter;
8use self::strings::{StringKey, StringsCache};
9use self::strings::{StringKey, StringsCache};
10+use self::traversal::{MoveTo, Traversal};
9use crate::Listener;
11use crate::Listener;
10
12
11#[derive(Debug)]
13#[derive(Debug)]
12pub(crate) struct ChangeListPersistentState {
14pub(crate) struct ChangeListPersistentState {
13 strings: StringsCache,
15 strings: StringsCache,
14 emitter: InstructionEmitter,
16 emitter: InstructionEmitter,
17+ traversal: Traversal,
15 interpreter: js::ChangeListInterpreter,
18 interpreter: js::ChangeListInterpreter,
16}
19}
17
20
@@ -30,10 +33,12 @@ impl ChangeListPersistentState {
30 pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState {
33 pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState {
31 let strings = StringsCache::new();
34 let strings = StringsCache::new();
32 let emitter = InstructionEmitter::new();
35 let emitter = InstructionEmitter::new();
36+ let traversal = Traversal::new();
33 let interpreter = js::ChangeListInterpreter::new(container);
37 let interpreter = js::ChangeListInterpreter::new(container);
34 ChangeListPersistentState {
38 ChangeListPersistentState {
35 strings,
39 strings,
36 emitter,
40 emitter,
41+ traversal,
37 interpreter,
42 interpreter,
38 }
43 }
39 }
44 }
@@ -59,6 +64,7 @@ cfg_if::cfg_if! {
59 // Nothing to actually apply the changes to.
64 // Nothing to actually apply the changes to.
60
65
61 self.state.emitter.reset();
66 self.state.emitter.reset();
67+ self.state.traversal.reset();
62 }
68 }
63 }
69 }
64 } else {
70 } else {
@@ -74,11 +80,85 @@ cfg_if::cfg_if! {
74 interpreter.apply_changes(wasm_bindgen::memory());
80 interpreter.apply_changes(wasm_bindgen::memory());
75
81
76 self.state.emitter.reset();
82 self.state.emitter.reset();
83+ self.state.traversal.reset();
77 }
84 }
78 }
85 }
79 }
86 }
80}
87}
81
88
89+/// Traversal methods.
90+impl ChangeListBuilder<'_> {
91+ pub fn go_down(&mut self) {
92+ self.state.traversal.down();
93+ }
94+
95+ pub fn go_down_to_child(&mut self, index: usize) {
96+ self.state.traversal.down();
97+ self.state.traversal.sibling(index);
98+ }
99+
100+ pub fn go_down_to_reverse_child(&mut self, index: usize) {
101+ self.state.traversal.down();
102+ self.state.traversal.reverse_sibling(index);
103+ }
104+
105+ pub fn go_up(&mut self) {
106+ self.state.traversal.up();
107+ }
108+
109+ pub fn go_to_sibling(&mut self, index: usize) {
110+ self.state.traversal.sibling(index);
111+ }
112+
113+ pub fn go_to_temp_sibling(&mut self, temp: u32) {
114+ self.state.traversal.up();
115+ self.state.traversal.down_to_temp(temp);
116+ }
117+
118+ pub fn go_down_to_temp_child(&mut self, temp: u32) {
119+ self.state.traversal.down_to_temp(temp);
120+ }
121+
122+ pub fn commit_traversal(&mut self) {
123+ if self.state.traversal.is_committed() {
124+ return;
125+ }
126+
127+ for mv in self.state.traversal.commit() {
128+ match mv {
129+ MoveTo::Parent => {
130+ debug!("emit: pop");
131+ self.state.emitter.pop();
132+ }
133+ MoveTo::Child(n) => {
134+ debug!("emit: push_child({})", n);
135+ self.state.emitter.push_child(n);
136+ }
137+ MoveTo::ReverseChild(n) => {
138+ debug!("emit: push_reverse_child({})", n);
139+ self.state.emitter.push_reverse_child(n);
140+ }
141+ MoveTo::Sibling(n) => {
142+ debug!("emit: pop_push_child({})", n);
143+ self.state.emitter.pop_push_child(n);
144+ }
145+ MoveTo::ReverseSibling(n) => {
146+ debug!("emit: pop_push_reverse_child({})", n);
147+ self.state.emitter.pop_push_reverse_child(n);
148+ }
149+ MoveTo::TempChild(temp) => {
150+ debug!("emit: push_temporary({})", temp);
151+ self.state.emitter.push_temporary(temp);
152+ }
153+ }
154+ }
155+ }
156+
157+ pub fn traversal_is_committed(&self) -> bool {
158+ self.state.traversal.is_committed()
159+ }
160+}
161+
82impl ChangeListBuilder<'_> {
162impl ChangeListBuilder<'_> {
83 pub fn next_temporary(&self) -> u32 {
163 pub fn next_temporary(&self) -> u32 {
84 self.next_temporary
164 self.next_temporary
@@ -89,6 +169,7 @@ impl ChangeListBuilder<'_> {
89 }
169 }
90
170
91 pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
171 pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
172+ debug_assert!(self.traversal_is_committed());
92 debug_assert!(start < end);
173 debug_assert!(start < end);
93 let temp_base = self.next_temporary;
174 let temp_base = self.next_temporary;
94 debug!(
175 debug!(
@@ -103,26 +184,19 @@ impl ChangeListBuilder<'_> {
103 }
184 }
104
185
105 pub fn push_temporary(&self, temp: u32) {
186 pub fn push_temporary(&self, temp: u32) {
187+ debug_assert!(self.traversal_is_committed());
106 debug!("emit: push_temporary({})", temp);
188 debug!("emit: push_temporary({})", temp);
107 self.state.emitter.push_temporary(temp);
189 self.state.emitter.push_temporary(temp);
108 }
190 }
109
191
110− pub fn push_child(&self, child: usize) {
111− debug!("emit: push_child({})", child);
112− self.state.emitter.push_child(child as u32);
113− }
114−
115 pub fn remove_child(&self, child: usize) {
192 pub fn remove_child(&self, child: usize) {
193+ debug_assert!(self.traversal_is_committed());
116 debug!("emit: remove_child({})", child);
194 debug!("emit: remove_child({})", child);
117 self.state.emitter.remove_child(child as u32);
195 self.state.emitter.remove_child(child as u32);
118 }
196 }
119
197
120− pub fn push_last_child(&self) {
121− debug!("emit: push_last_child()");
122− self.state.emitter.push_last_child();
123− }
124−
125 pub fn insert_before(&self) {
198 pub fn insert_before(&self) {
199+ debug_assert!(self.traversal_is_committed());
126 debug!("emit: insert_before()");
200 debug!("emit: insert_before()");
127 self.state.emitter.insert_before();
201 self.state.emitter.insert_before();
128 }
202 }
@@ -134,6 +208,7 @@ impl ChangeListBuilder<'_> {
134 }
208 }
135
209
136 pub fn set_text(&self, text: &str) {
210 pub fn set_text(&self, text: &str) {
211+ debug_assert!(self.traversal_is_committed());
137 debug!("emit: set_text({:?})", text);
212 debug!("emit: set_text({:?})", text);
138 self.state
213 self.state
139 .emitter
214 .emitter
@@ -141,16 +216,19 @@ impl ChangeListBuilder<'_> {
141 }
216 }
142
217
143 pub fn remove_self_and_next_siblings(&self) {
218 pub fn remove_self_and_next_siblings(&self) {
219+ debug_assert!(self.traversal_is_committed());
144 debug!("emit: remove_self_and_next_siblings()");
220 debug!("emit: remove_self_and_next_siblings()");
145 self.state.emitter.remove_self_and_next_siblings();
221 self.state.emitter.remove_self_and_next_siblings();
146 }
222 }
147
223
148 pub fn replace_with(&self) {
224 pub fn replace_with(&self) {
225+ debug_assert!(self.traversal_is_committed());
149 debug!("emit: replace_with()");
226 debug!("emit: replace_with()");
150 self.state.emitter.replace_with();
227 self.state.emitter.replace_with();
151 }
228 }
152
229
153 pub fn set_attribute(&mut self, name: &str, value: &str) {
230 pub fn set_attribute(&mut self, name: &str, value: &str) {
231+ debug_assert!(self.traversal_is_committed());
154 debug!("emit: set_attribute({:?}, {:?})", name, value);
232 debug!("emit: set_attribute({:?}, {:?})", name, value);
155 let name_id = self.ensure_string(name);
233 let name_id = self.ensure_string(name);
156 let value_id = self.ensure_string(value);
234 let value_id = self.ensure_string(value);
@@ -160,32 +238,20 @@ impl ChangeListBuilder<'_> {
160 }
238 }
161
239
162 pub fn remove_attribute(&mut self, name: &str) {
240 pub fn remove_attribute(&mut self, name: &str) {
241+ debug_assert!(self.traversal_is_committed());
163 debug!("emit: remove_attribute({:?})", name);
242 debug!("emit: remove_attribute({:?})", name);
164 let name_id = self.ensure_string(name);
243 let name_id = self.ensure_string(name);
165 self.state.emitter.remove_attribute(name_id.into());
244 self.state.emitter.remove_attribute(name_id.into());
166 }
245 }
167
246
168− pub fn push_first_child(&self) {
169− debug!("emit: push_first_child");
170− self.state.emitter.push_first_child();
171− }
172−
173− pub fn pop_push_next_sibling(&self) {
174− debug!("emit: pop_push_next_sibling");
175− self.state.emitter.pop_push_next_sibling();
176− }
177−
178− pub fn pop(&self) {
179− debug!("emit: pop");
180− self.state.emitter.pop();
181− }
182−
183 pub fn append_child(&self) {
247 pub fn append_child(&self) {
248+ debug_assert!(self.traversal_is_committed());
184 debug!("emit: append_child()");
249 debug!("emit: append_child()");
185 self.state.emitter.append_child();
250 self.state.emitter.append_child();
186 }
251 }
187
252
188 pub fn create_text_node(&self, text: &str) {
253 pub fn create_text_node(&self, text: &str) {
254+ debug_assert!(self.traversal_is_committed());
189 debug!("emit: create_text_node({:?})", text);
255 debug!("emit: create_text_node({:?})", text);
190 self.state
256 self.state
191 .emitter
257 .emitter
@@ -193,12 +259,14 @@ impl ChangeListBuilder<'_> {
193 }
259 }
194
260
195 pub fn create_element(&mut self, tag_name: &str) {
261 pub fn create_element(&mut self, tag_name: &str) {
262+ debug_assert!(self.traversal_is_committed());
196 debug!("emit: create_element({:?})", tag_name);
263 debug!("emit: create_element({:?})", tag_name);
197 let tag_name_id = self.ensure_string(tag_name);
264 let tag_name_id = self.ensure_string(tag_name);
198 self.state.emitter.create_element(tag_name_id.into());
265 self.state.emitter.create_element(tag_name_id.into());
199 }
266 }
200
267
201 pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
268 pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
269+ debug_assert!(self.traversal_is_committed());
202 debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
270 debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
203 let tag_name_id = self.ensure_string(tag_name);
271 let tag_name_id = self.ensure_string(tag_name);
204 let ns_id = self.ensure_string(ns);
272 let ns_id = self.ensure_string(ns);
@@ -208,6 +276,7 @@ impl ChangeListBuilder<'_> {
208 }
276 }
209
277
210 pub fn new_event_listener(&mut self, listener: &Listener) {
278 pub fn new_event_listener(&mut self, listener: &Listener) {
279+ debug_assert!(self.traversal_is_committed());
211 debug!("emit: new_event_listener({:?})", listener);
280 debug!("emit: new_event_listener({:?})", listener);
212 let (a, b) = listener.get_callback_parts();
281 let (a, b) = listener.get_callback_parts();
213 debug_assert!(a != 0);
282 debug_assert!(a != 0);
@@ -216,6 +285,7 @@ impl ChangeListBuilder<'_> {
216 }
285 }
217
286
218 pub fn update_event_listener(&mut self, listener: &Listener) {
287 pub fn update_event_listener(&mut self, listener: &Listener) {
288+ debug_assert!(self.traversal_is_committed());
219 debug!("emit: update_event_listener({:?})", listener);
289 debug!("emit: update_event_listener({:?})", listener);
220 let (a, b) = listener.get_callback_parts();
290 let (a, b) = listener.get_callback_parts();
221 debug_assert!(a != 0);
291 debug_assert!(a != 0);
@@ -226,6 +296,7 @@ impl ChangeListBuilder<'_> {
226 }
296 }
227
297
228 pub fn remove_event_listener(&mut self, event: &str) {
298 pub fn remove_event_listener(&mut self, event: &str) {
299+ debug_assert!(self.traversal_is_committed());
229 debug!("emit: remove_event_listener({:?})", event);
300 debug!("emit: remove_event_listener({:?})", event);
230 let event_id = self.ensure_string(event);
301 let event_id = self.ensure_string(event);
231 self.state.emitter.remove_event_listener(event_id.into());
302 self.state.emitter.remove_event_listener(event_id.into());
src/change_list/traversal.rsnew file+344
@@ -0,0 +1,344 @@
1+//! Keeps track of where we are moving in a DOM tree, and shortens traversal
2+//! paths between mutations to their minimal number of operations.
3+
4+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5+pub enum MoveTo {
6+ /// Move from the current node up to its parent.
7+ Parent,
8+
9+ /// Move to the current node's n^th child.
10+ Child(u32),
11+
12+ /// Move to the current node's n^th from last child.
13+ ReverseChild(u32),
14+
15+ /// Move to the n^th sibling. Not relative from the current
16+ /// location. Absolute indexed within all of the current siblings.
17+ Sibling(u32),
18+
19+ /// Move to the n^th from last sibling. Not relative from the current
20+ /// location. Absolute indexed within all of the current siblings.
21+ ReverseSibling(u32),
22+
23+ /// Move down to the given saved temporary child.
24+ TempChild(u32),
25+}
26+
27+#[derive(Debug)]
28+pub struct Traversal {
29+ uncommitted: Vec<MoveTo>,
30+}
31+
32+impl Traversal {
33+ /// Construct a new `Traversal` with its internal storage backed by the
34+ /// given bump arena.
35+ pub fn new() -> Traversal {
36+ Traversal {
37+ uncommitted: Vec::with_capacity(32),
38+ }
39+ }
40+
41+ /// Move the traversal up in the tree.
42+ pub fn up(&mut self) {
43+ match self.uncommitted.last() {
44+ Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
45+ self.uncommitted.pop();
46+ self.uncommitted.push(MoveTo::Parent);
47+ }
48+ Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
49+ self.uncommitted.pop();
50+ // And we're back at the parent.
51+ }
52+ _ => {
53+ self.uncommitted.push(MoveTo::Parent);
54+ }
55+ }
56+ }
57+
58+ /// Move the traversal down in the tree to the first child of the current
59+ /// node.
60+ pub fn down(&mut self) {
61+ if let Some(&MoveTo::Parent) = self.uncommitted.last() {
62+ self.uncommitted.pop();
63+ self.sibling(0);
64+ } else {
65+ self.uncommitted.push(MoveTo::Child(0));
66+ }
67+ }
68+
69+ /// Move the traversal to the n^th sibling.
70+ pub fn sibling(&mut self, index: usize) {
71+ let index = index as u32;
72+ match self.uncommitted.last_mut() {
73+ Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
74+ *n = index;
75+ }
76+ Some(MoveTo::ReverseSibling(_)) => {
77+ self.uncommitted.pop();
78+ self.uncommitted.push(MoveTo::Sibling(index));
79+ }
80+ Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
81+ self.uncommitted.pop();
82+ self.uncommitted.push(MoveTo::Child(index))
83+ }
84+ _ => {
85+ self.uncommitted.push(MoveTo::Sibling(index));
86+ }
87+ }
88+ }
89+
90+ /// Move the the n^th from last sibling.
91+ pub fn reverse_sibling(&mut self, index: usize) {
92+ let index = index as u32;
93+ match self.uncommitted.last_mut() {
94+ Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
95+ *n = index;
96+ }
97+ Some(MoveTo::Sibling(_)) => {
98+ self.uncommitted.pop();
99+ self.uncommitted.push(MoveTo::ReverseSibling(index));
100+ }
101+ Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
102+ self.uncommitted.pop();
103+ self.uncommitted.push(MoveTo::ReverseChild(index))
104+ }
105+ _ => {
106+ self.uncommitted.push(MoveTo::ReverseSibling(index));
107+ }
108+ }
109+ }
110+
111+ /// Go to the given saved temporary.
112+ pub fn down_to_temp(&mut self, temp: u32) {
113+ match self.uncommitted.last() {
114+ Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
115+ self.uncommitted.pop();
116+ }
117+ Some(MoveTo::Parent)
118+ | Some(MoveTo::TempChild(_))
119+ | Some(MoveTo::Child(_))
120+ | Some(MoveTo::ReverseChild(_))
121+ | None => {
122+ // Can't remove moves to parents since we rely on their stack
123+ // pops.
124+ }
125+ }
126+ self.uncommitted.push(MoveTo::TempChild(temp));
127+ }
128+
129+ /// Are all the traversal's moves committed? That is, are there no moves
130+ /// that have *not* been committed yet?
131+ #[inline]
132+ pub fn is_committed(&self) -> bool {
133+ self.uncommitted.is_empty()
134+ }
135+
136+ /// Commit this traversals moves and return the optimized path from the last
137+ /// commit.
138+ #[inline]
139+ pub fn commit(&mut self) -> Moves {
140+ Moves {
141+ inner: self.uncommitted.drain(..),
142+ }
143+ }
144+
145+ #[inline]
146+ pub fn reset(&mut self) {
147+ self.uncommitted.clear();
148+ }
149+}
150+
151+pub struct Moves<'a> {
152+ inner: std::vec::Drain<'a, MoveTo>,
153+}
154+
155+impl Iterator for Moves<'_> {
156+ type Item = MoveTo;
157+
158+ #[inline]
159+ fn next(&mut self) -> Option<MoveTo> {
160+ self.inner.next()
161+ }
162+}
163+
164+#[cfg(test)]
165+mod tests {
166+ use super::*;
167+
168+ #[test]
169+ fn test_traversal() {
170+ fn t<F>(f: F) -> Box<FnMut(&mut Traversal)>
171+ where
172+ F: 'static + FnMut(&mut Traversal),
173+ {
174+ Box::new(f) as _
175+ }
176+
177+ for (mut traverse, expected_moves) in vec![
178+ (
179+ t(|t| {
180+ t.down();
181+ }),
182+ vec![MoveTo::Child(0)],
183+ ),
184+ (
185+ t(|t| {
186+ t.up();
187+ }),
188+ vec![MoveTo::Parent],
189+ ),
190+ (
191+ t(|t| {
192+ t.sibling(42);
193+ }),
194+ vec![MoveTo::Sibling(42)],
195+ ),
196+ (
197+ t(|t| {
198+ t.down();
199+ t.up();
200+ }),
201+ vec![],
202+ ),
203+ (
204+ t(|t| {
205+ t.down();
206+ t.sibling(2);
207+ t.up();
208+ }),
209+ vec![],
210+ ),
211+ (
212+ t(|t| {
213+ t.down();
214+ t.sibling(3);
215+ }),
216+ vec![MoveTo::Child(3)],
217+ ),
218+ (
219+ t(|t| {
220+ t.down();
221+ t.sibling(4);
222+ t.sibling(8);
223+ }),
224+ vec![MoveTo::Child(8)],
225+ ),
226+ (
227+ t(|t| {
228+ t.sibling(1);
229+ t.sibling(1);
230+ }),
231+ vec![MoveTo::Sibling(1)],
232+ ),
233+ (
234+ t(|t| {
235+ t.reverse_sibling(3);
236+ }),
237+ vec![MoveTo::ReverseSibling(3)],
238+ ),
239+ (
240+ t(|t| {
241+ t.down();
242+ t.reverse_sibling(3);
243+ }),
244+ vec![MoveTo::ReverseChild(3)],
245+ ),
246+ (
247+ t(|t| {
248+ t.down();
249+ t.reverse_sibling(3);
250+ t.up();
251+ }),
252+ vec![],
253+ ),
254+ (
255+ t(|t| {
256+ t.down();
257+ t.reverse_sibling(3);
258+ t.reverse_sibling(6);
259+ }),
260+ vec![MoveTo::ReverseChild(6)],
261+ ),
262+ (
263+ t(|t| {
264+ t.up();
265+ t.reverse_sibling(3);
266+ t.reverse_sibling(6);
267+ }),
268+ vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
269+ ),
270+ (
271+ t(|t| {
272+ t.up();
273+ t.sibling(3);
274+ t.sibling(6);
275+ }),
276+ vec![MoveTo::Parent, MoveTo::Sibling(6)],
277+ ),
278+ (
279+ t(|t| {
280+ t.sibling(3);
281+ t.sibling(6);
282+ t.up();
283+ }),
284+ vec![MoveTo::Parent],
285+ ),
286+ (
287+ t(|t| {
288+ t.reverse_sibling(3);
289+ t.reverse_sibling(6);
290+ t.up();
291+ }),
292+ vec![MoveTo::Parent],
293+ ),
294+ (
295+ t(|t| {
296+ t.down();
297+ t.down_to_temp(3);
298+ }),
299+ vec![MoveTo::Child(0), MoveTo::TempChild(3)],
300+ ),
301+ (
302+ t(|t| {
303+ t.down_to_temp(3);
304+ t.sibling(5);
305+ }),
306+ vec![MoveTo::Child(5)],
307+ ),
308+ (
309+ t(|t| {
310+ t.down_to_temp(3);
311+ t.reverse_sibling(5);
312+ }),
313+ vec![MoveTo::ReverseChild(5)],
314+ ),
315+ (
316+ t(|t| {
317+ t.down_to_temp(3);
318+ t.up();
319+ }),
320+ vec![],
321+ ),
322+ (
323+ t(|t| {
324+ t.sibling(2);
325+ t.up();
326+ t.down_to_temp(3);
327+ }),
328+ vec![MoveTo::Parent, MoveTo::TempChild(3)],
329+ ),
330+ (
331+ t(|t| {
332+ t.up();
333+ t.down_to_temp(3);
334+ }),
335+ vec![MoveTo::Parent, MoveTo::TempChild(3)],
336+ ),
337+ ] {
338+ let mut traversal = Traversal::new();
339+ traverse(&mut traversal);
340+ let actual_moves: Vec<_> = traversal.commit().collect();
341+ assert_eq!(actual_moves, expected_moves);
342+ }
343+ }
344+}
src/diff.rs+67−67
@@ -32,17 +32,20 @@ pub(crate) fn diff(
32 &NodeKind::Text(TextNode { text: old_text }),
32 &NodeKind::Text(TextNode { text: old_text }),
33 ) => {
33 ) => {
34 if new_text != old_text {
34 if new_text != old_text {
35+ change_list.commit_traversal();
35 change_list.set_text(new_text);
36 change_list.set_text(new_text);
36 }
37 }
37 }
38 }
38
39
39 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
40 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
41+ change_list.commit_traversal();
40 create(cached_set, change_list, registry, new, cached_roots);
42 create(cached_set, change_list, registry, new, cached_roots);
41 registry.remove_subtree(&old);
43 registry.remove_subtree(&old);
42 change_list.replace_with();
44 change_list.replace_with();
43 }
45 }
44
46
45 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
47 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
48+ change_list.commit_traversal();
46 create(cached_set, change_list, registry, new, cached_roots);
49 create(cached_set, change_list, registry, new, cached_roots);
47 // Note: text nodes cannot have event listeners, so we don't need to
50 // Note: text nodes cannot have event listeners, so we don't need to
48 // remove the old node's listeners from our registry her.
51 // remove the old node's listeners from our registry her.
@@ -68,6 +71,7 @@ pub(crate) fn diff(
68 }),
71 }),
69 ) => {
72 ) => {
70 if new_tag_name != old_tag_name || new_namespace != old_namespace {
73 if new_tag_name != old_tag_name || new_namespace != old_namespace {
74+ change_list.commit_traversal();
71 create(cached_set, change_list, registry, new, cached_roots);
75 create(cached_set, change_list, registry, new, cached_roots);
72 registry.remove_subtree(&old);
76 registry.remove_subtree(&old);
73 change_list.replace_with();
77 change_list.replace_with();
@@ -103,6 +107,7 @@ pub(crate) fn diff(
103 // we assume that they are pretty different, and it isn't worth diffing
107 // we assume that they are pretty different, and it isn't worth diffing
104 // the subtrees, so we just create the new cached node afresh.
108 // the subtrees, so we just create the new cached node afresh.
105 (&NodeKind::Cached(ref c), _) => {
109 (&NodeKind::Cached(ref c), _) => {
110+ change_list.commit_traversal();
106 cached_roots.insert(c.id);
111 cached_roots.insert(c.id);
107 let new = cached_set.get(c.id);
112 let new = cached_set.get(c.id);
108 create(cached_set, change_list, registry, new, cached_roots);
113 create(cached_set, change_list, registry, new, cached_roots);
@@ -113,6 +118,7 @@ pub(crate) fn diff(
113 // Old cached node and new non-cached node. Again, assume that they are
118 // Old cached node and new non-cached node. Again, assume that they are
114 // probably pretty different and create the new non-cached node afresh.
119 // probably pretty different and create the new non-cached node afresh.
115 (_, &NodeKind::Cached(_)) => {
120 (_, &NodeKind::Cached(_)) => {
121+ change_list.commit_traversal();
116 create(cached_set, change_list, registry, new, cached_roots);
122 create(cached_set, change_list, registry, new, cached_roots);
117 registry.remove_subtree(&old);
123 registry.remove_subtree(&old);
118 change_list.replace_with();
124 change_list.replace_with();
@@ -133,6 +139,10 @@ fn diff_listeners(
133 old: &[Listener],
139 old: &[Listener],
134 new: &[Listener],
140 new: &[Listener],
135) {
141) {
142+ if !old.is_empty() || !new.is_empty() {
143+ change_list.commit_traversal();
144+ }
145+
136 'outer1: for new_l in new {
146 'outer1: for new_l in new {
137 unsafe {
147 unsafe {
138 // Safety relies on removing `new_l` from the registry manually at
148 // Safety relies on removing `new_l` from the registry manually at
@@ -175,16 +185,20 @@ fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new:
175 // there are almost always very few attributes.
185 // there are almost always very few attributes.
176 'outer: for new_attr in new {
186 'outer: for new_attr in new {
177 if new_attr.is_volatile() {
187 if new_attr.is_volatile() {
188+ change_list.commit_traversal();
178 change_list.set_attribute(new_attr.name, new_attr.value);
189 change_list.set_attribute(new_attr.name, new_attr.value);
179 } else {
190 } else {
180 for old_attr in old {
191 for old_attr in old {
181 if old_attr.name == new_attr.name {
192 if old_attr.name == new_attr.name {
182 if old_attr.value != new_attr.value {
193 if old_attr.value != new_attr.value {
194+ change_list.commit_traversal();
183 change_list.set_attribute(new_attr.name, new_attr.value);
195 change_list.set_attribute(new_attr.name, new_attr.value);
184 }
196 }
185 continue 'outer;
197 continue 'outer;
186 }
198 }
187 }
199 }
200+
201+ change_list.commit_traversal();
188 change_list.set_attribute(new_attr.name, new_attr.value);
202 change_list.set_attribute(new_attr.name, new_attr.value);
189 }
203 }
190 }
204 }
@@ -195,6 +209,8 @@ fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new:
195 continue 'outer2;
209 continue 'outer2;
196 }
210 }
197 }
211 }
212+
213+ change_list.commit_traversal();
198 change_list.remove_attribute(old_attr.name);
214 change_list.remove_attribute(old_attr.name);
199 }
215 }
200}
216}
@@ -217,13 +233,17 @@ fn diff_children(
217) {
233) {
218 if new.is_empty() {
234 if new.is_empty() {
219 if !old.is_empty() {
235 if !old.is_empty() {
236+ change_list.commit_traversal();
220 remove_all_children(change_list, registry, old);
237 remove_all_children(change_list, registry, old);
221 }
238 }
222 return;
239 return;
223 }
240 }
224
241
225 if old.is_empty() {
242 if old.is_empty() {
243+ if !new.is_empty() {
244+ change_list.commit_traversal();
226− create_and_append_children(cached_set, change_list, registry, new, cached_roots);
245+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
246+ }
227 return;
247 return;
228 }
248 }
229
249
@@ -384,42 +404,24 @@ fn diff_keyed_prefix(
384 new: &[Node],
404 new: &[Node],
385 cached_roots: &mut FxHashSet<CacheId>,
405 cached_roots: &mut FxHashSet<CacheId>,
386) -> KeyedPrefixResult {
406) -> KeyedPrefixResult {
387− let mut pushed = false;
407+ change_list.go_down();
388 let mut shared_prefix_count = 0;
408 let mut shared_prefix_count = 0;
389
409
390− for (old, new) in old.iter().zip(new.iter()) {
410+ for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
391 if old.key() != new.key() {
411 if old.key() != new.key() {
392 break;
412 break;
393 }
413 }
394
414
395− if pushed {
415+ change_list.go_to_sibling(i);
396− debug_assert!(shared_prefix_count > 0);
397− change_list.pop_push_next_sibling();
398− } else {
399− debug_assert_eq!(shared_prefix_count, 0);
400− change_list.push_first_child();
401− pushed = true;
402− }
403−
404 diff(cached_set, change_list, registry, old, new, cached_roots);
416 diff(cached_set, change_list, registry, old, new, cached_roots);
405 shared_prefix_count += 1;
417 shared_prefix_count += 1;
406−
407− // At the end of the loop, the change list stack looks like
408− //
409− // [... parent child_we_just_diffed]
410− debug_assert!(pushed);
411 }
418 }
412
419
413 // If that was all of the old children, then create and append the remaining
420 // If that was all of the old children, then create and append the remaining
414 // new children and we're finished.
421 // new children and we're finished.
415 if shared_prefix_count == old.len() {
422 if shared_prefix_count == old.len() {
416− debug_assert!(
423+ change_list.go_up();
417− pushed,
418− "we handle the case of empty children before calling this method, so
419− `shared_prefix_count` must be greater than zero, and therefore we
420− must have pushed in the above loop"
421− );
422− change_list.pop();
424+ change_list.commit_traversal();
423 create_and_append_children(
425 create_and_append_children(
424 cached_set,
426 cached_set,
425 change_list,
427 change_list,
@@ -433,17 +435,13 @@ fn diff_keyed_prefix(
433 // And if that was all of the new children, then remove all of the remaining
435 // And if that was all of the new children, then remove all of the remaining
434 // old children and we're finished.
436 // old children and we're finished.
435 if shared_prefix_count == new.len() {
437 if shared_prefix_count == new.len() {
436− // Same as above.
438+ change_list.go_to_sibling(shared_prefix_count);
437− debug_assert!(pushed);
439+ change_list.commit_traversal();
438− change_list.pop_push_next_sibling();
439− change_list.remove_self_and_next_siblings();
440+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
440 return KeyedPrefixResult::Finished;
441 return KeyedPrefixResult::Finished;
441 }
442 }
442
443
443− if pushed {
444− debug_assert!(shared_prefix_count > 0);
445− change_list.pop();
444+ change_list.go_up();
446− }
447 KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
445 KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
448}
446}
449
447
@@ -507,9 +505,11 @@ fn diff_keyed_middle(
507 // afresh.
505 // afresh.
508 if shared_suffix_count == 0 && shared_keys.is_empty() {
506 if shared_suffix_count == 0 && shared_keys.is_empty() {
509 if shared_prefix_count == 0 {
507 if shared_prefix_count == 0 {
508+ change_list.commit_traversal();
510 remove_all_children(change_list, registry, old);
509 remove_all_children(change_list, registry, old);
511 } else {
510 } else {
512− change_list.pop_push_next_sibling();
511+ change_list.go_down_to_child(shared_prefix_count);
512+ change_list.commit_traversal();
513 remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
513 remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
514 }
514 }
515 create_and_append_children(cached_set, change_list, registry, new, cached_roots);
515 create_and_append_children(cached_set, change_list, registry, new, cached_roots);
@@ -529,6 +529,7 @@ fn diff_keyed_middle(
529 .unwrap_or(old.len());
529 .unwrap_or(old.len());
530
530
531 if end - start > 0 {
531 if end - start > 0 {
532+ change_list.commit_traversal();
532 let mut t = change_list.save_children_to_temporaries(
533 let mut t = change_list.save_children_to_temporaries(
533 shared_prefix_count + start,
534 shared_prefix_count + start,
534 shared_prefix_count + end,
535 shared_prefix_count + end,
@@ -553,6 +554,7 @@ fn diff_keyed_middle(
553 for (i, old_child) in old.iter().enumerate().rev() {
554 for (i, old_child) in old.iter().enumerate().rev() {
554 if !shared_keys.contains(&old_child.key()) {
555 if !shared_keys.contains(&old_child.key()) {
555 registry.remove_subtree(old_child);
556 registry.remove_subtree(old_child);
557+ change_list.commit_traversal();
556 change_list.remove_child(i + shared_prefix_count);
558 change_list.remove_child(i + shared_prefix_count);
557 removed_count += 1;
559 removed_count += 1;
558 }
560 }
@@ -596,7 +598,7 @@ fn diff_keyed_middle(
596 // shared suffix to the change list stack.
598 // shared suffix to the change list stack.
597 //
599 //
598 // [... parent]
600 // [... parent]
599− change_list.push_child(old_shared_suffix_start - removed_count);
601+ change_list.go_down_to_child(old_shared_suffix_start - removed_count);
600 // [... parent first_child_of_shared_suffix]
602 // [... parent first_child_of_shared_suffix]
601 } else {
603 } else {
602 // There is no shared suffix coming after these middle children.
604 // There is no shared suffix coming after these middle children.
@@ -609,7 +611,7 @@ fn diff_keyed_middle(
609 let old_index = new_index_to_old_index[last_index];
611 let old_index = new_index_to_old_index[last_index];
610 let temp = old_index_to_temp[old_index];
612 let temp = old_index_to_temp[old_index];
611 // [... parent]
613 // [... parent]
612− change_list.push_temporary(temp);
614+ change_list.go_down_to_temp_child(temp);
613 // [... parent last]
615 // [... parent last]
614 diff(
616 diff(
615 cached_set,
617 cached_set,
@@ -622,18 +624,21 @@ fn diff_keyed_middle(
622 if new_index_is_in_lis.contains(&last_index) {
624 if new_index_is_in_lis.contains(&last_index) {
623 // Don't move it, since it is already where it needs to be.
625 // Don't move it, since it is already where it needs to be.
624 } else {
626 } else {
627+ change_list.commit_traversal();
628+ // [... parent last]
625 change_list.append_child();
629 change_list.append_child();
626 // [... parent]
630 // [... parent]
627− change_list.push_temporary(temp);
631+ change_list.go_down_to_temp_child(temp);
628 // [... parent last]
632 // [... parent last]
629 }
633 }
630 } else {
634 } else {
635+ change_list.commit_traversal();
631 // [... parent]
636 // [... parent]
632 create(cached_set, change_list, registry, last, cached_roots);
637 create(cached_set, change_list, registry, last, cached_roots);
633 // [... parent last]
638 // [... parent last]
634 change_list.append_child();
639 change_list.append_child();
635 // [... parent]
640 // [... parent]
636− change_list.push_last_child();
641+ change_list.go_down_to_reverse_child(0);
637 // [... parent last]
642 // [... parent last]
638 }
643 }
639 }
644 }
@@ -642,6 +647,7 @@ fn diff_keyed_middle(
642 let old_index = new_index_to_old_index[new_index];
647 let old_index = new_index_to_old_index[new_index];
643 if old_index == u32::MAX as usize {
648 if old_index == u32::MAX as usize {
644 debug_assert!(!shared_keys.contains(&new_child.key()));
649 debug_assert!(!shared_keys.contains(&new_child.key()));
650+ change_list.commit_traversal();
645 // [... parent successor]
651 // [... parent successor]
646 create(cached_set, change_list, registry, new_child, cached_roots);
652 create(cached_set, change_list, registry, new_child, cached_roots);
647 // [... parent successor new_child]
653 // [... parent successor new_child]
@@ -654,11 +660,10 @@ fn diff_keyed_middle(
654
660
655 if new_index_is_in_lis.contains(&new_index) {
661 if new_index_is_in_lis.contains(&new_index) {
656 // [... parent successor]
662 // [... parent successor]
657− change_list.pop();
663+ change_list.go_to_temp_sibling(temp);
658− // [... parent]
659− change_list.push_temporary(temp);
660 // [... parent new_child]
664 // [... parent new_child]
661 } else {
665 } else {
666+ change_list.commit_traversal();
662 // [... parent successor]
667 // [... parent successor]
663 change_list.push_temporary(temp);
668 change_list.push_temporary(temp);
664 // [... parent successor new_child]
669 // [... parent successor new_child]
@@ -678,7 +683,7 @@ fn diff_keyed_middle(
678 }
683 }
679
684
680 // [... parent child]
685 // [... parent child]
681− change_list.pop();
686+ change_list.go_up();
682 // [... parent]
687 // [... parent]
683}
688}
684
689
@@ -702,10 +707,11 @@ fn diff_keyed_suffix(
702 debug_assert!(!old.is_empty());
707 debug_assert!(!old.is_empty());
703
708
704 // [... parent]
709 // [... parent]
705− change_list.push_child(new_shared_suffix_start);
710+ change_list.go_down();
706 // [... parent new_child]
711 // [... parent new_child]
707
712
708− for (old_child, new_child) in old.iter().zip(new.iter()) {
713+ for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
714+ change_list.go_to_sibling(new_shared_suffix_start + i);
709 diff(
715 diff(
710 cached_set,
716 cached_set,
711 change_list,
717 change_list,
@@ -714,14 +720,10 @@ fn diff_keyed_suffix(
714 new_child,
720 new_child,
715 cached_roots,
721 cached_roots,
716 );
722 );
717−
718− // [... parent this_new_child]
719− change_list.pop_push_next_sibling();
720− // [... parent next_new_child]
721 }
723 }
722
724
723 // [... parent]
725 // [... parent]
724− change_list.pop();
726+ change_list.go_up();
725}
727}
726
728
727// Diff children that are not keyed.
729// Diff children that are not keyed.
@@ -744,17 +746,14 @@ fn diff_non_keyed_children(
744 debug_assert!(!new.is_empty());
746 debug_assert!(!new.is_empty());
745 debug_assert!(!old.is_empty());
747 debug_assert!(!old.is_empty());
746
748
747− for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
748− if i == 0 {
749− // [... parent]
750− change_list.push_first_child();
751− // [... parent first_child]
749+ // [... parent]
752− } else {
753− // [... parent prev_sibling]
754− change_list.pop_push_next_sibling();
750+ change_list.go_down();
755− // [... parent next_sibling]
751+ // [... parent child]
756− }
757
752
753+ for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
754+ // [... parent prev_child]
755+ change_list.go_to_sibling(i);
756+ // [... parent this_child]
758 diff(
757 diff(
759 cached_set,
758 cached_set,
760 change_list,
759 change_list,
@@ -765,23 +764,20 @@ fn diff_non_keyed_children(
765 );
764 );
766 }
765 }
767
766
768− // Note that because `new` and `old` are not empty, the previous loop always
769− // executes at least once, so the change list stack is now:
770− //
771− // [... parent child]
772−
773 match old.len().cmp(&new.len()) {
767 match old.len().cmp(&new.len()) {
774 Ordering::Greater => {
768 Ordering::Greater => {
775− // [... parent last_shared_child]
769+ // [... parent prev_child]
776− change_list.pop_push_next_sibling();
770+ change_list.go_to_sibling(new.len());
777 // [... parent first_child_to_remove]
771 // [... parent first_child_to_remove]
772+ change_list.commit_traversal();
778 remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
773 remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
779 // [... parent]
774 // [... parent]
780 }
775 }
781 Ordering::Less => {
776 Ordering::Less => {
782 // [... parent last_child]
777 // [... parent last_child]
783− change_list.pop();
778+ change_list.go_up();
784 // [... parent]
779 // [... parent]
780+ change_list.commit_traversal();
785 create_and_append_children(
781 create_and_append_children(
786 cached_set,
782 cached_set,
787 change_list,
783 change_list,
@@ -792,7 +788,7 @@ fn diff_non_keyed_children(
792 }
788 }
793 Ordering::Equal => {
789 Ordering::Equal => {
794 // [... parent child]
790 // [... parent child]
795− change_list.pop();
791+ change_list.go_up();
796 // [... parent]
792 // [... parent]
797 }
793 }
798 }
794 }
@@ -812,6 +808,7 @@ fn create_and_append_children(
812 new: &[Node],
808 new: &[Node],
813 cached_roots: &mut FxHashSet<CacheId>,
809 cached_roots: &mut FxHashSet<CacheId>,
814) {
810) {
811+ debug_assert!(change_list.traversal_is_committed());
815 for child in new {
812 for child in new {
816 create(cached_set, change_list, registry, child, cached_roots);
813 create(cached_set, change_list, registry, child, cached_roots);
817 change_list.append_child();
814 change_list.append_child();
@@ -830,6 +827,7 @@ fn remove_all_children(
830 registry: &mut EventsRegistry,
827 registry: &mut EventsRegistry,
831 old: &[Node],
828 old: &[Node],
832) {
829) {
830+ debug_assert!(change_list.traversal_is_committed());
833 for child in old {
831 for child in old {
834 registry.remove_subtree(child);
832 registry.remove_subtree(child);
835 }
833 }
@@ -852,6 +850,7 @@ fn remove_self_and_next_siblings(
852 registry: &mut EventsRegistry,
850 registry: &mut EventsRegistry,
853 old: &[Node],
851 old: &[Node],
854) {
852) {
853+ debug_assert!(change_list.traversal_is_committed());
855 for child in old {
854 for child in old {
856 registry.remove_subtree(child);
855 registry.remove_subtree(child);
857 }
856 }
@@ -874,6 +873,7 @@ fn create(
874 node: &Node,
873 node: &Node,
875 cached_roots: &mut FxHashSet<CacheId>,
874 cached_roots: &mut FxHashSet<CacheId>,
876) {
875) {
876+ debug_assert!(change_list.traversal_is_committed());
877 match node.kind {
877 match node.kind {
878 NodeKind::Text(TextNode { text }) => {
878 NodeKind::Text(TextNode { text }) => {
879 change_list.create_text_node(text);
879 change_list.create_text_node(text);
src/lib.rs+2−1
@@ -92,13 +92,14 @@ mod events;
92mod node;
92mod node;
93mod render;
93mod render;
94mod render_context;
94mod render_context;
95+mod strace;
95mod vdom;
96mod vdom;
96
97
97pub mod builder;
98pub mod builder;
98
99
99// Re-export items at the top level.
100// Re-export items at the top level.
100pub use self::cached::Cached;
101pub use self::cached::Cached;
101−pub use self::node::{Attribute, Listener, Node};
102+pub use self::node::{Attribute, Listener, Node, NodeKey};
102pub use self::render::{Render, RootRender};
103pub use self::render::{Render, RootRender};
103pub use self::render_context::RenderContext;
104pub use self::render_context::RenderContext;
104pub use self::vdom::{Vdom, VdomWeak};
105pub use self::vdom::{Vdom, VdomWeak};
src/node.rs+35−24
@@ -68,24 +68,44 @@ pub_unstable_internal! {
68 }
68 }
69}
69}
70
70
71−pub_unstable_internal! {
72− /// The key for keyed children.
71+/// The key for keyed children.
72+///
73+/// Keys must be unique among siblings.
74+///
75+/// If any sibling is keyed, then they all must be keyed.
73− #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
76+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
74− pub(crate) struct NodeKey(pub(crate) u32);
77+pub struct NodeKey(pub(crate) u32);
78+
79+impl Default for NodeKey {
80+ fn default() -> NodeKey {
81+ NodeKey::NONE
82+ }
75}
83}
76
84
77impl NodeKey {
85impl NodeKey {
86+ /// The default, lack of a key.
78 pub const NONE: NodeKey = NodeKey(u32::MAX);
87 pub const NONE: NodeKey = NodeKey(u32::MAX);
79
88
89+ /// Is this key `NodeKey::NONE`?
80 #[inline]
90 #[inline]
81 pub fn is_none(&self) -> bool {
91 pub fn is_none(&self) -> bool {
82 *self == Self::NONE
92 *self == Self::NONE
83 }
93 }
84
94
95+ /// Is this key not `NodeKey::NONE`?
85 #[inline]
96 #[inline]
86 pub fn is_some(&self) -> bool {
97 pub fn is_some(&self) -> bool {
87 !self.is_none()
98 !self.is_none()
88 }
99 }
100+
101+ /// Create a new `NodeKey`.
102+ ///
103+ /// `key` must not be `u32::MAX`.
104+ #[inline]
105+ pub fn new(key: u32) -> Self {
106+ debug_assert_ne!(key, u32::MAX);
107+ NodeKey(key)
108+ }
89}
109}
90
110
91/// An event listener callback function.
111/// An event listener callback function.
@@ -162,31 +182,22 @@ impl<'a> Attribute<'a> {
162}
182}
163
183
164impl<'a> Node<'a> {
184impl<'a> Node<'a> {
165− /// Construct a new Node of type element with given tag name and children
185+ /// Low-level constructor for making a new `Node` of type element with given
186+ /// parts.
187+ ///
188+ /// This is primarily intended for JSX and templating proc-macros to compile
189+ /// down into. If you are building nodes by-hand, prefer using the
190+ /// `dodrio::builder::*` APIs.
166 #[inline]
191 #[inline]
167− pub(crate) fn element<Listeners, Attributes, Children>(
192+ pub fn element(
168 bump: &'a Bump,
193 bump: &'a Bump,
169 key: NodeKey,
194 key: NodeKey,
170 tag_name: &'a str,
195 tag_name: &'a str,
171− listeners: Listeners,
196+ listeners: &'a [Listener<'a>],
172− attributes: Attributes,
197+ attributes: &'a [Attribute<'a>],
173− children: Children,
198+ children: &'a [Node<'a>],
174 namespace: Option<&'a str>,
199 namespace: Option<&'a str>,
175− ) -> Node<'a>
200+ ) -> Node<'a> {
176− where
177− Listeners: 'a + AsRef<[Listener<'a>]>,
178− Attributes: 'a + AsRef<[Attribute<'a>]>,
179− Children: 'a + AsRef<[Node<'a>]>,
180− {
181− let children: &'a Children = bump.alloc(children);
182− let children: &'a [Node<'a>] = children.as_ref();
183−
184− let listeners: &'a Listeners = bump.alloc(listeners);
185− let listeners: &'a [Listener<'a>] = listeners.as_ref();
186−
187− let attributes: &'a Attributes = bump.alloc(attributes);
188− let attributes: &'a [Attribute<'a>] = attributes.as_ref();
189−
190 let element = bump.alloc_with(|| ElementNode {
201 let element = bump.alloc_with(|| ElementNode {
191 key,
202 key,
192 tag_name,
203 tag_name,
src/strace.rsnew file+22
@@ -0,0 +1,22 @@
1+cfg_if::cfg_if! {
2+ if #[cfg(feature = "xxx-unstable-strace")] {
3+ use wasm_bindgen::prelude::*;
4+
5+ #[wasm_bindgen(module = "/js/strace.js")]
6+ extern "C" {
7+ #[wasm_bindgen(js_name = initStrace)]
8+ fn really_init_strace();
9+ }
10+
11+ pub fn init_strace() {
12+ use std::sync::Once;
13+ static STRACE: Once = Once::new();
14+ STRACE.call_once(|| {
15+ really_init_strace();
16+ });
17+ }
18+ } else {
19+ #[inline]
20+ pub fn init_strace() {}
21+ }
22+}
src/vdom.rs+4−1
@@ -161,12 +161,15 @@ impl Vdom {
161 /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
161 /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
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+ crate::strace::init_strace();
165+
164 let dom_buffers = [Bump::new(), Bump::new()];
166 let dom_buffers = [Bump::new(), Bump::new()];
165 let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
167 let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
166
168
167 // Create a dummy `<div/>` in our container.
169 // Create a dummy `<div/>` in our container.
168 initialize_container(container);
170 initialize_container(container);
171+ let current_root =
169− let current_root = Node::element(&dom_buffers[0], NodeKey::NONE, "div", [], [], [], None);
172+ Node::element(&dom_buffers[0], NodeKey::NONE, "div", &[], &[], &[], None);
170 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
173 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
171
174
172 let container = container.clone();
175 let container = container.clone();