-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Description
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
RenderManagementmodule-
internal
function markDirty(block: BlockSvg) -
private
function triggerRenders()
Passed to therequestAnimationFramecallback -
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()
- public
-
internal
class Node- public
block?: BlockSvg
Optional so the root does not need tobe associated with a block - public
parent?: Node - public
children: Node[]
- public
-
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:
- Implement the
BlockTreedatastructure + unit tests. - Implement the basic
markDirtyandtriggerRerenderingfunctions. - Have blocks actually call
markDirty - Probably several rounds of fixits to clean up residual rendering-related code, unnecessary assignments to the
renderedproperty, other minor efficiency improvements, etc.