Skip to content

Refactor rendering to use a queue #6786

@BeksOmega

Description

@BeksOmega

Is your feature request related to a problem? Please describe.

Currently we are doing a lot of unnecessary rendering that doesn't actually get painted to the screen. Experimentally I've found an ~5x performance improvement from eliminating these extra renders.

The reason we're getting extra renders is that currently every time a something on a block is changed (e.g. it is connected, disconnected, an input is added, a field value is changed, etc) It gets immediately rerendered, and all of its parent blocks are also rerendered to accomodate that.

Related, we sometimes also set the rendered property on blocks to false to try to disable extra renders, but this leads to lots of bugs. This proposal would also fix that problem by eliminating the need to set rendered to false.

Describe the solution you'd like

Create a "queue" of blocks to be rendered, and then trigger that rendering from a requestAnimationFrame callback. The queue will actually be a tree of blocks that need to be rendered. And the rendering will be triggered depth-first (from leaf nodes to parent nodes) so that for each "batch of block changes" will only require each affected block to be rerendered once.

More specifically I think the API should look like the following.

  • internal RenderManagement module
    • internal function markDirty(block: BlockSvg)

    • private function triggerRenders()
      Passed to the requestAnimationFrame callback

    • internal class BlockTree
      This is the tree of blocks that will be walked when rendering.

      • public add(block:BlockSvg)
      • public remove(block: BlockSvg)
      • public has(block: BlockSvg): boolean
      • public getNodeFor(block:BlockSvg): Node
      • public getRootNode(): Node
      • pulbic clear()
    • internal class Node

      • public block?: BlockSvg
        Optional so the root does not need tobe associated with a block
      • public parent?: Node
      • public children: Node[]

Describe alternatives you've considered

I considered making the BlockTree and Node class much more generic. E.g. so they would be programmed against some interface like:

interface INode {
  id: string;
  getParent(): INode;
}

But decided that since this logic is very specific to how we want to handle rendering, there wasn't much use in the additional abstraction. If we ever need a BlockTree for something else, we can always pull it out of the RenderManagement module and generalize it :P

Implementation Plan

My initial plan for getting this in is:

  1. Implement the BlockTree datastructure + unit tests.
  2. Implement the basic markDirty and triggerRerendering functions.
  3. Have blocks actually call markDirty
  4. Probably several rounds of fixits to clean up residual rendering-related code, unnecessary assignments to the rendered property, other minor efficiency improvements, etc.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions