on master help sign in
Add simple string cache to the change list
d470c3dTill Schneidereit
js/change-list.js+45−21
@@ -43,12 +43,10 @@ const OP_TABLE = [
43
43
44 // 3
44 // 3
45 function setAttribute(changeList, mem8, mem32, i) {
45 function setAttribute(changeList, mem8, mem32, i) {
46− const pointer1 = mem32[i++];
46+ const nameId = mem32[i++];
47− const length1 = mem32[i++];
47+ const valueId = mem32[i++];
48− const name = string(mem8, pointer1, length1);
48+ const name = changeList.getString(nameId);
49− const pointer2 = mem32[i++];
49+ const value = changeList.getString(valueId);
50− const length2 = mem32[i++];
51− const value = string(mem8, pointer2, length2);
52 const node = top(changeList.stack);
50 const node = top(changeList.stack);
53 node.setAttribute(name, value);
51 node.setAttribute(name, value);
54
52
@@ -68,9 +66,8 @@ const OP_TABLE = [
68
66
69 // 4
67 // 4
70 function removeAttribute(changeList, mem8, mem32, i) {
68 function removeAttribute(changeList, mem8, mem32, i) {
71− const pointer = mem32[i++];
69+ const nameId = mem32[i++];
72− const length = mem32[i++];
70+ const name = changeList.getString(nameId);
73− const name = string(mem8, pointer, length);
74 const node = top(changeList.stack);
71 const node = top(changeList.stack);
75 node.removeAttribute(name);
72 node.removeAttribute(name);
76
73
@@ -125,18 +122,16 @@ const OP_TABLE = [
125
122
126 // 10
123 // 10
127 function createElement(changeList, mem8, mem32, i) {
124 function createElement(changeList, mem8, mem32, i) {
128− const pointer = mem32[i++];
125+ const tagNameId = mem32[i++];
129− const length = mem32[i++];
126+ const tagName = changeList.getString(tagNameId);
130− const tagName = string(mem8, pointer, length);
131 changeList.stack.push(document.createElement(tagName));
127 changeList.stack.push(document.createElement(tagName));
132 return i;
128 return i;
133 },
129 },
134
130
135 // 11
131 // 11
136 function newEventListener(changeList, mem8, mem32, i) {
132 function newEventListener(changeList, mem8, mem32, i) {
137− const pointer = mem32[i++];
133+ const eventId = mem32[i++];
138− const length = mem32[i++];
134+ const eventType = changeList.getString(eventId);
139− const eventType = string(mem8, pointer, length);
140 const a = mem32[i++];
135 const a = mem32[i++];
141 const b = mem32[i++];
136 const b = mem32[i++];
142 const el = top(changeList.stack);
137 const el = top(changeList.stack);
@@ -149,9 +144,8 @@ const OP_TABLE = [
149
144
150 // 12
145 // 12
151 function updateEventListener(changeList, mem8, mem32, i) {
146 function updateEventListener(changeList, mem8, mem32, i) {
152− const pointer = mem32[i++];
147+ const eventId = mem32[i++];
153− const length = mem32[i++];
148+ const eventType = changeList.getString(eventId);
154− const eventType = string(mem8, pointer, length);
155 const el = top(changeList.stack);
149 const el = top(changeList.stack);
156 const listener = el[`dodrio-${eventType}`];
150 const listener = el[`dodrio-${eventType}`];
157 listener.a = mem32[i++];
151 listener.a = mem32[i++];
@@ -161,14 +155,30 @@ const OP_TABLE = [
161
155
162 // 13
156 // 13
163 function removeEventListener(changeList, mem8, mem32, i) {
157 function removeEventListener(changeList, mem8, mem32, i) {
164− const pointer = mem32[i++];
158+ const eventId = mem32[i++];
165− const length = mem32[i++];
159+ const eventType = changeList.getString(eventId);
166− const eventType = string(mem8, pointer, length);
167 const el = top(changeList.stack);
160 const el = top(changeList.stack);
168 const listener = el[`dodrio-${eventType}`];
161 const listener = el[`dodrio-${eventType}`];
169 el.removeEventListener(eventType, listener.callback);
162 el.removeEventListener(eventType, listener.callback);
170 changeList.listeners.delete(listener);
163 changeList.listeners.delete(listener);
171 return i;
164 return i;
165+ },
166+
167+ // 14
168+ function addString(changeList, mem8, mem32, i) {
169+ const pointer = mem32[i++];
170+ const length = mem32[i++];
171+ const id = mem32[i++];
172+ const str = string(mem8, pointer, length);
173+ changeList.addString(str, id);
174+ return i;
175+ },
176+
177+ // 15
178+ function dropString(changeList, mem8, mem32, i) {
179+ const id = mem32[i++];
180+ changeList.dropString(id);
181+ return i;
172 }
182 }
173];
183];
174
184
@@ -195,6 +205,7 @@ class ChangeList {
195 this.container = container;
205 this.container = container;
196 this.ranges = [];
206 this.ranges = [];
197 this.stack = [];
207 this.stack = [];
208+ this.strings = new Map();
198 }
209 }
199
210
200 unmount() {
211 unmount() {
@@ -214,6 +225,7 @@ class ChangeList {
214 this.container = null;
225 this.container = null;
215 this.ranges = null;
226 this.ranges = null;
216 this.stack = null;
227 this.stack = null;
228+ this.strings = null;
217 }
229 }
218
230
219 addChangeListRange(start, len) {
231 addChangeListRange(start, len) {
@@ -248,6 +260,18 @@ class ChangeList {
248 }
260 }
249 }
261 }
250
262
263+ addString(str, id) {
264+ this.strings.set(id, str);
265+ }
266+
267+ dropString(id) {
268+ this.strings.delete(id);
269+ }
270+
271+ getString(id) {
272+ return this.strings.get(id);
273+ }
274+
251 initEventsTrampoline(trampoline) {
275 initEventsTrampoline(trampoline) {
252 this.eventsTrampoline = (...args) => {
276 this.eventsTrampoline = (...args) => {
253 trampoline(...args);
277 trampoline(...args);
src/change_list.rs+102−32
@@ -1,5 +1,7 @@
1use crate::Listener;
1use crate::Listener;
2use bumpalo::Bump;
2use bumpalo::Bump;
3+use std::cell::Cell;
4+use std::collections::HashMap;
3use std::fmt;
5use std::fmt;
4use std::sync::Once;
6use std::sync::Once;
5use wasm_bindgen::prelude::*;
7use wasm_bindgen::prelude::*;
@@ -32,8 +34,15 @@ pub mod js {
32 }
34 }
33}
35}
34
36
37+struct StringsCacheEntry {
38+ key: u32,
39+ used: bool,
40+}
41+
35pub(crate) struct ChangeList {
42pub(crate) struct ChangeList {
36 bump: Bump,
43 bump: Bump,
44+ strings_cache: HashMap<String, StringsCacheEntry>,
45+ next_string_key: Cell<u32>,
37 js: js::ChangeList,
46 js: js::ChangeList,
38 events_trampoline: Option<Closure<Fn(web_sys::Event, u32, u32)>>,
47 events_trampoline: Option<Closure<Fn(web_sys::Event, u32, u32)>>,
39}
48}
@@ -69,9 +78,12 @@ impl ChangeList {
69 });
78 });
70
79
71 let bump = Bump::new();
80 let bump = Bump::new();
81+ let strings_cache = HashMap::new();
72 let js = js::ChangeList::new(container);
82 let js = js::ChangeList::new(container);
73 ChangeList {
83 ChangeList {
74 bump,
84 bump,
85+ strings_cache,
86+ next_string_key: Cell::new(0),
75 js,
87 js,
76 events_trampoline: None,
88 events_trampoline: None,
77 }
89 }
@@ -238,6 +250,24 @@ enum ChangeDiscriminant {
238 /// stack.top().removeEventListener(String(pointer, length));
250 /// stack.top().removeEventListener(String(pointer, length));
239 /// ```
251 /// ```
240 RemoveEventListener = 13,
252 RemoveEventListener = 13,
253+
254+ /// Immediates: `(pointer, length, id)`
255+ ///
256+ /// Stack: `[...] -> [...]`
257+ ///
258+ /// ```text
259+ /// addString(String(pointer, length), id);
260+ /// ```
261+ AddString = 14,
262+
263+ /// Immediates: `(id)`
264+ ///
265+ /// Stack: `[...] -> [...]`
266+ ///
267+ /// ```text
268+ /// dropString(id);
269+ /// ```
270+ DropString = 15,
241}
271}
242
272
243// Allocation utilities to ensure that we only allocation sequences of `u32`s
273// Allocation utilities to ensure that we only allocation sequences of `u32`s
@@ -249,22 +279,62 @@ impl ChangeList {
249 self.bump.alloc(discriminant as u32);
279 self.bump.alloc(discriminant as u32);
250 }
280 }
251
281
252− // Note: no 1-immediate opcodes at this time.
282+ // Allocate an opcode with one immediate.
283+ fn op1(&self, discriminant: ChangeDiscriminant, a: u32) {
284+ self.bump.alloc([discriminant as u32, a]);
285+ }
253
286
254 // Allocate an opcode with two immediates.
287 // Allocate an opcode with two immediates.
255 fn op2(&self, discriminant: ChangeDiscriminant, a: u32, b: u32) {
288 fn op2(&self, discriminant: ChangeDiscriminant, a: u32, b: u32) {
256 self.bump.alloc([discriminant as u32, a, b]);
289 self.bump.alloc([discriminant as u32, a, b]);
257 }
290 }
258
291
259− // Note: no 3-immediate opcodes at this time.
260−
261− // Allocate an opcode with four immediates.
292+ // Allocate an opcode with three immediates.
262− fn op4(&self, discriminant: ChangeDiscriminant, a: u32, b: u32, c: u32, d: u32) {
293+ fn op3(&self, discriminant: ChangeDiscriminant, a: u32, b: u32, c: u32) {
263− self.bump.alloc([discriminant as u32, a, b, c, d]);
294+ self.bump.alloc([discriminant as u32, a, b, c]);
264 }
295 }
296+
297+ // Note: no 4-immediate opcodes at this time.
265}
298}
266
299
267impl ChangeList {
300impl ChangeList {
301+ fn ensure_string(&mut self, string: &str) -> u32 {
302+ let entry = self.strings_cache.get_mut(string);
303+ if entry.is_some() {
304+ let entry = entry.unwrap();
305+ entry.used = true;
306+ entry.key
307+ } else {
308+ let key = self.next_string_key.get();
309+ self.next_string_key.set(key + 1);
310+ let entry = StringsCacheEntry { key, used: true };
311+ self.strings_cache.insert(string.to_string(), entry);
312+ self.op3(
313+ ChangeDiscriminant::AddString,
314+ string.as_ptr() as u32,
315+ string.len() as u32,
316+ key
317+ );
318+ key
319+ }
320+ }
321+
322+ pub(crate) fn drop_unused_strings(&mut self) {
323+ debug!("drop_unused_strings()");
324+ let mut new_cache = HashMap::new();
325+ for (key, val) in self.strings_cache.iter() {
326+ if val.used {
327+ new_cache.insert(key.clone(), StringsCacheEntry { key: val.key, used: false });
328+ } else {
329+ self.op1(
330+ ChangeDiscriminant::DropString,
331+ val.key,
332+ );
333+ }
334+ }
335+ self.strings_cache = new_cache;
336+ }
337+
268 pub(crate) fn emit_set_text(&self, text: &str) {
338 pub(crate) fn emit_set_text(&self, text: &str) {
269 debug!("emit_set_text({:?})", text);
339 debug!("emit_set_text({:?})", text);
270 self.op2(
340 self.op2(
@@ -284,23 +354,23 @@ impl ChangeList {
284 self.op0(ChangeDiscriminant::ReplaceWith);
354 self.op0(ChangeDiscriminant::ReplaceWith);
285 }
355 }
286
356
287− pub(crate) fn emit_set_attribute(&self, name: &str, value: &str) {
357+ pub(crate) fn emit_set_attribute(&mut self, name: &str, value: &str) {
288 debug!("emit_set_attribute({:?}, {:?})", name, value);
358 debug!("emit_set_attribute({:?}, {:?})", name, value);
359+ let name_id = self.ensure_string(name);
360+ let value_id = self.ensure_string(value);
289− self.op4(
361+ self.op2(
290 ChangeDiscriminant::SetAttribute,
362 ChangeDiscriminant::SetAttribute,
291− name.as_ptr() as u32,
363+ name_id,
292− name.len() as u32,
364+ value_id,
293− value.as_ptr() as u32,
294− value.len() as u32,
295 );
365 );
296 }
366 }
297
367
298− pub(crate) fn emit_remove_attribute(&self, name: &str) {
368+ pub(crate) fn emit_remove_attribute(&mut self, name: &str) {
299 debug!("emit_remove_attribute({:?})", name);
369 debug!("emit_remove_attribute({:?})", name);
300− self.op2(
370+ let name_id = self.ensure_string(name);
371+ self.op1(
301 ChangeDiscriminant::RemoveAttribute,
372 ChangeDiscriminant::RemoveAttribute,
302− name.as_ptr() as u32,
373+ name_id,
303− name.len() as u32,
304 );
374 );
305 }
375 }
306
376
@@ -333,47 +403,47 @@ impl ChangeList {
333 );
403 );
334 }
404 }
335
405
336− pub(crate) fn emit_create_element(&self, tag_name: &str) {
406+ pub(crate) fn emit_create_element(&mut self, tag_name: &str) {
337 debug!("emit_create_element({:?})", tag_name);
407 debug!("emit_create_element({:?})", tag_name);
338− self.op2(
408+ let tag_name_id = self.ensure_string(tag_name);
409+ self.op1(
339 ChangeDiscriminant::CreateElement,
410 ChangeDiscriminant::CreateElement,
340− tag_name.as_ptr() as u32,
411+ tag_name_id,
341− tag_name.len() as u32,
342 );
412 );
343 }
413 }
344
414
345− pub(crate) fn emit_new_event_listener(&self, listener: &Listener) {
415+ pub(crate) fn emit_new_event_listener(&mut self, listener: &Listener) {
346 debug!("emit_new_event_listener({:?})", listener);
416 debug!("emit_new_event_listener({:?})", listener);
347 let (a, b) = listener.get_callback_parts();
417 let (a, b) = listener.get_callback_parts();
348 debug_assert!(a != 0);
418 debug_assert!(a != 0);
349− self.op4(
419+ let event_id = self.ensure_string(listener.event);
420+ self.op3(
350 ChangeDiscriminant::NewEventListener,
421 ChangeDiscriminant::NewEventListener,
351− listener.event.as_ptr() as u32,
422+ event_id,
352− listener.event.len() as u32,
353 a,
423 a,
354 b,
424 b,
355 );
425 );
356 }
426 }
357
427
358− pub(crate) fn emit_update_event_listener(&self, listener: &Listener) {
428+ pub(crate) fn emit_update_event_listener(&mut self, listener: &Listener) {
359 debug!("emit_update_event_listener({:?})", listener);
429 debug!("emit_update_event_listener({:?})", listener);
360 let (a, b) = listener.get_callback_parts();
430 let (a, b) = listener.get_callback_parts();
361 debug_assert!(a != 0);
431 debug_assert!(a != 0);
362− self.op4(
432+ let event_id = self.ensure_string(listener.event);
433+ self.op3(
363 ChangeDiscriminant::UpdateEventListener,
434 ChangeDiscriminant::UpdateEventListener,
364− listener.event.as_ptr() as u32,
435+ event_id,
365− listener.event.len() as u32,
366 a,
436 a,
367 b,
437 b,
368 );
438 );
369 }
439 }
370
440
371− pub(crate) fn emit_remove_event_listener(&self, event: &str) {
441+ pub(crate) fn emit_remove_event_listener(&mut self, event: &str) {
372 debug!("emit_remove_event_listener({:?})", event);
442 debug!("emit_remove_event_listener({:?})", event);
373− self.op2(
443+ let event_id = self.ensure_string(event);
444+ self.op1(
374 ChangeDiscriminant::RemoveEventListener,
445 ChangeDiscriminant::RemoveEventListener,
375− event.as_ptr() as u32,
446+ event_id,
376− event.len() as u32,
377 );
447 );
378 }
448 }
379}
449}
src/vdom.rs+8−5
@@ -256,6 +256,9 @@ impl VdomInnerExclusive {
256
256
257 self.events_registry = Some(events_registry);
257 self.events_registry = Some(events_registry);
258
258
259+ // Find and drop cached strings that aren't in use anymore.
260+ self.change_list.drop_unused_strings();
261+
259 // Tell JS to apply our diff-generated changes to the physical DOM!
262 // Tell JS to apply our diff-generated changes to the physical DOM!
260 self.change_list.apply_changes();
263 self.change_list.apply_changes();
261 }
264 }
@@ -271,7 +274,7 @@ impl VdomInnerExclusive {
271 self.current_root = Some(current);
274 self.current_root = Some(current);
272 }
275 }
273
276
274− fn diff<'a>(&self, registry: &mut EventsRegistry, old: Node<'a>, new: Node<'a>) {
277+ fn diff<'a>(&mut self, registry: &mut EventsRegistry, old: Node<'a>, new: Node<'a>) {
275 // debug!("---------------------------------------------------------");
278 // debug!("---------------------------------------------------------");
276 // debug!("dodrio::Vdom::diff");
279 // debug!("dodrio::Vdom::diff");
277 // debug!(" old = {:#?}", old);
280 // debug!(" old = {:#?}", old);
@@ -323,7 +326,7 @@ impl VdomInnerExclusive {
323 }
326 }
324
327
325 fn diff_listeners<'a>(
328 fn diff_listeners<'a>(
326− &self,
329+ &mut self,
327 registry: &mut EventsRegistry,
330 registry: &mut EventsRegistry,
328 old: &'a [Listener<'a>],
331 old: &'a [Listener<'a>],
329 new: &'a [Listener<'a>],
332 new: &'a [Listener<'a>],
@@ -357,7 +360,7 @@ impl VdomInnerExclusive {
357 }
360 }
358 }
361 }
359
362
360− fn diff_attributes(&self, old: &[Attribute], new: &[Attribute]) {
363+ fn diff_attributes(&mut self, old: &[Attribute], new: &[Attribute]) {
361 debug!(" updating attributes");
364 debug!(" updating attributes");
362
365
363 // Do O(n^2) passes to add/update and remove attributes, since
366 // Do O(n^2) passes to add/update and remove attributes, since
@@ -392,7 +395,7 @@ impl VdomInnerExclusive {
392 }
395 }
393
396
394 fn diff_children<'a>(
397 fn diff_children<'a>(
395− &self,
398+ &mut self,
396 registry: &mut EventsRegistry,
399 registry: &mut EventsRegistry,
397 old: &'a [Node<'a>],
400 old: &'a [Node<'a>],
398 new: &'a [Node<'a>],
401 new: &'a [Node<'a>],
@@ -449,7 +452,7 @@ impl VdomInnerExclusive {
449 }
452 }
450 }
453 }
451
454
452− fn create<'a>(&self, registry: &mut EventsRegistry, node: Node<'a>) {
455+ fn create<'a>(&mut self, registry: &mut EventsRegistry, node: Node<'a>) {
453 match node {
456 match node {
454 Node::Text(TextNode { text }) => {
457 Node::Text(TextNode { text }) => {
455 self.change_list.emit_create_text_node(text);
458 self.change_list.emit_create_text_node(text);