Skip to content

Conversation

@jviide
Copy link
Collaborator

@jviide jviide commented Dec 27, 2019

This pull request modifies the evaluate function by adding more granular tools for tracking the staticness of a vnode and its children inside a h function.

HTM already automatically tracks and caches completely static subtrees (see #132). The tracking & caching process is completely transparent for the h function. That being said, there are still some hypothetical optimization possibilities if h functions got some more (optional) information. For example, vnodes with static props but some dynamic children would not fall under the "completely static subtree" umbrella that #132 optimizes. A VDOM-based UI framework could still use that staticness information to skip diffing such nodes and jump directly into diffing their children.

This PR adds two tools for exploring such optimization possibilities:

  1. The this variable inside h function calls are now set to an object that is bound to the html function call site and position inside the tagged template string. For example in the following case the this value stays the same:

    let html = htm.bind(function() { return this; });
    
    let x = () => html`<div>${'a'}</div>`;
    let a = x();
    let b = x();
    a === b; // true

    For different call sites and different h functions this changes:

    a = html`<div>${'a'}</div>`;
    b = html`<div>${'a'}</div>`;
    a === b; // false - different call sites
    
    a = x();
    html = htm.bind(function() { return this; });
    b = x();
    a === b; // false - same call site but different h functions
  2. this[0] inside an h function call is a number, tracking the staticness of the currently created vnodes.

    If the number's lowest bit is set then the vnode being created depends on dynamic values, but its children may or may not be dynamic. If the second-lowest bit is set then some of the vnode's children depend on dynamic values, but the vnode itself may or may not be dynamic.

    By that same logic, when the two lowest bits are both zeroes then the whole subtree rooted to the current vnode is static (and HTM will cache it). When the two lowest bits are both set then both the vnode and some of its children are dynamic.

These two features can be used in conjunction to e.g. annotate created vnodes with optimization information. For example, this could be stored in the created vnode's ._callsite property. Later, when two vnodes are diffed against each other, the diffing function could check whether their ._callsite properties are set and strictly equal and _callsite[0]'s lowest bit is set, and skip diffing the vnodes and move on to their children. In iffy pseudocode:

const html = htm.bind(function(tag, props, ...children) {
  const vnode = createElement(tag, props, children);
  vnode._callsite = this;
  return vnode;
});

// Then, in a module far far away...

function diff(oldVnode, newVnode) {
  if (oldVNode._callsite === newVNode._callsite && (newVNode._callsite[0] & 1)) {
    // Short-circuit diffing, because these vnodes are static and 
    // created by the same h function from the same template.
    return diffChildren(oldNode._children, newVnode._children);
  } 
  ...
}

With these changes HTM's overall performance seems to stay the same. htm.module.js.br size grows by 7 bytes (from 570 B to 577 B).

@developit
Copy link
Owner

Having a firm indicator that the callsite matches could almost make recycling viable again. It wouldn't have the issue of recycling trees across usages, instead only ever recycling stuff that was used at the same callsite (render --> unrender --> re-render)

@jviide jviide merged commit edab3dc into developit:master Jan 21, 2020
@jviide jviide deleted the staticness-bits branch January 21, 2020 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants