Skip to content

Bitmask-based change tracking #1922

@Rich-Harris

Description

@Rich-Harris

I've brought this up in chat previously, figured it'd be worth opening an issue.

One of the ways in which Svelte avoids unnecessary work is by tracking which properties are 'dirty' — thus <h1>Hello {name}!</h1> yields the following update code:

if (changed.name) {
  setData(text1, ctx.name);
}

This is all well and good, but it can result in unwieldy code if the expression (or, in the case of e.g. an each block, any of the expressions within) depends on multiple values:

if (changed.foo || changed.bar || changed.baz) {
  setData(text, ctx.foo * ctx.bar * ctx.baz);
}

We could instead use a bitmask. Take all the values that are referenced in the template (or in reactive declarations), sort them by frequency, and assign them a value that is a power of 2. In the case above, foo might be 1, bar might be 2, baz might be 4 and qux might be 8.

function handle_click() {
-  foo += 1; $$invalidate('foo', foo);
-  bar += 1; $$invalidate('bar', bar);
+  foo += 1; $$invalidate('foo', foo, 1);
+  bar += 1; $$invalidate('bar', bar, 2);
}

Then, instead of changed being an object that gets recreated after each update, it's just a number:

if (changed & (/*foo, bar or baz*/ 7)) {
  setData(text, ctx.foo * ctx.bar * ctx.baz);
}

If changed was equal to 8 (i.e. qux changed) nothing would happen. This minifies much better:

-if(a.foo||a.bar||a.baz)b(c,d.foo*d.bar*d.baz)
+if(a&7)b(c,d.foo*d.bar*d.baz)

Drawbacks

It's slightly harder to understand what's going on by looking at the code. Also, without adding more complexity (e.g. making changed an array of numbers, or only doing the bitmask thing where possible) it places a constraint on components: you can only reference 53 separate top-level variables (beyond that, you're in unsafe integer territory). For the vast majority of components that wouldn't be an issue, but it's not impossible to imagine someone hitting that wall. For that reason, we might want to consider doing this for v3, since it would be a breaking change.

Mangled context

While we're here, perhaps we should consider mangling context properties:

function handle_click() {
-  foo += 1; $$invalidate('foo', foo, 1);
-  bar += 1; $$invalidate('bar', bar, 2);
+  foo += 1; $$invalidate(1, foo);
+  bar += 1; $$invalidate(2, bar);
}
if (changed & (/*foo, bar or baz*/ 7)) {
-  setData(text, ctx.foo * ctx.bar * ctx.baz);
+  setData(text, ctx[1] * ctx[2] * ctx[3]);
}
// minified
-if(a&7)b(c,d.foo*d.bar*d.baz)
+if(a&7)b(c,d[1]*d[2]*d[3])

Nested properties

Another thing to consider: we might want to take better advantage of information at our disposal when mutating objects and arrays:

function toggleTodo(i) {
  todos[i].done = !todos[i].done;
}

In this situation, we could theoretically output code that didn't loop over todos (as currently happens) but instead went straight to the affected iteration. (I'm glossing over some stuff here.) I don't know if that's something we need to consider if we go down this alternative path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions