Skip to content

Commit

Permalink
address Text perf rendering issues
Browse files Browse the repository at this point in the history
  • Loading branch information
mggower committed Feb 12, 2024
1 parent cd1067f commit ad0ba22
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 130 deletions.
3 changes: 1 addition & 2 deletions src/renderers/webgl/LifecycleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { LineSegment } from './objects/lineSegment'
import { NodeFill } from './objects/nodeFill'
import { Arrow } from './objects/arrow'
import ObjectManager from './objects/ObjectManager'
import TextHighlight from './objects/text/TextHighlight'
import Icon from './objects/Icon'
import Text from './objects/text/Text'

Expand All @@ -12,7 +11,7 @@ export default class LifecycleManager {
icons = new ObjectManager<Icon>(1000)
edges = new ObjectManager<LineSegment>(2000)
arrows = new ObjectManager<Arrow>(1000)
labels = new ObjectManager<Text | TextHighlight>(2000)
labels = new ObjectManager<Text>(2000)
interactions = new ObjectManager(2000)
// interactions = new ObjectManager<HitArea>(2000) // TODO

Expand Down
196 changes: 118 additions & 78 deletions src/renderers/webgl/edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export class EdgeRenderer {
targetRadius?: number

private hitArea: EdgeHitArea
private arrow?: { forward: Arrow; reverse?: undefined } | { forward?: undefined; reverse: Arrow } | { forward: Arrow; reverse: Arrow }
private forwardArrow?: Arrow
private reverseArrow?: Arrow
private doubleClickTimeout: NodeJS.Timeout | undefined
private doubleClick = false

Expand All @@ -52,35 +53,34 @@ export class EdgeRenderer {
this.target = target

const arrow = edge.style?.arrow ?? DEFAULT_ARROW
if (arrow !== this.arrowStyle) {
this.arrow?.forward?.delete()
this.arrow?.reverse?.delete()
this.arrow = undefined

if (arrow !== this.arrow) {
switch (arrow) {
case 'forward':
this.arrow = { forward: new Arrow(this.renderer.edgesContainer, this.renderer.arrow) }
this.createArrow('forward').deleteArrow('reverse')
break

case 'reverse':
this.arrow = { reverse: new Arrow(this.renderer.edgesContainer, this.renderer.arrow) }
this.deleteArrow('forward').createArrow('reverse')
break

case 'both':
this.arrow = {
forward: new Arrow(this.renderer.edgesContainer, this.renderer.arrow),
reverse: new Arrow(this.renderer.edgesContainer, this.renderer.arrow)
}
this.createArrow('forward').createArrow('reverse')
break

case 'none':
this.deleteArrow('forward').deleteArrow('reverse')
break
}
}

if (edge.label === undefined || edge.label.trim() === '') {
if (this.label) {
if (this.label) {
if (edge.label === undefined || edge.label.trim() === '') {
this.managers.labels.delete(this.label)
this.label = undefined
} else {
this.label.update(edge.label, edge.style?.label)
}
} else if (this.label === undefined) {
this.label = new Text(this.renderer.assets, this.renderer.labelsContainer, edge.label, edge.style?.label, DEFAULT_LABEL_STYLE)
} else {
this.label.update(edge.label, edge.style?.label)
}

return this
Expand All @@ -95,51 +95,6 @@ export class EdgeRenderer {
const targetRadius = this.target.strokes.radius
const isVisible = this.visible(Math.min(x0, x1), Math.min(y0, y1), Math.max(x0, x1), Math.max(y0, y1))

// TODO - disable events if edge has no event handlers
// TODO - disable events when dragging/scrolling
const shouldHitAreaMount = isVisible && this.renderer.zoom > MIN_INTERACTION_ZOOM
const hitAreaMounted = this.managers.interactions.isMounted(this.hitArea)
if (shouldHitAreaMount && !hitAreaMounted) {
this.managers.interactions.mount(this.hitArea)
} else if (!shouldHitAreaMount && hitAreaMounted) {
this.managers.interactions.unmount(this.hitArea)
}

const lineMounted = this.managers.edges.isMounted(this.lineSegment)
if (isVisible && !lineMounted) {
this.managers.edges.mount(this.lineSegment)
} else if (!isVisible && lineMounted) {
this.managers.edges.unmount(this.lineSegment)
}

if (this.arrow?.forward) {
const forwardArrowMounted = this.managers.arrows.isMounted(this.arrow.forward)
if (isVisible && !forwardArrowMounted) {
this.managers.arrows.mount(this.arrow.forward)
} else if (!isVisible && forwardArrowMounted) {
this.managers.arrows.unmount(this.arrow.forward)
}
}

if (this.arrow?.reverse) {
const reverseArrowMounted = this.managers.arrows.isMounted(this.arrow.reverse)
if (isVisible && !reverseArrowMounted) {
this.managers.arrows.mount(this.arrow.reverse)
} else if (!isVisible && reverseArrowMounted) {
this.managers.arrows.unmount(this.arrow.reverse)
}
}

if (this.label) {
const shouldLabelMount = isVisible && this.renderer.zoom > MIN_LABEL_ZOOM
const labelMounted = this.managers.labels.isMounted(this.label)
if (shouldLabelMount && !labelMounted) {
this.managers.labels.mount(this.label)
} else if (!shouldLabelMount && labelMounted) {
this.managers.labels.unmount(this.label)
}
}

if (isVisible) {
const width = this.edge?.style?.width ?? DEFAULT_EDGE_WIDTH
const stroke = this.edge?.style?.stroke ?? DEFAULT_EDGE_COLOR
Expand Down Expand Up @@ -171,31 +126,32 @@ export class EdgeRenderer {
let edgeX1 = this.x1
let edgeY1 = this.y1

if (this.arrow?.forward) {
const edgePoint = movePoint(x1, y1, this.theta, this.targetRadius + this.arrow.forward.height)
if (this.forwardArrow) {
const edgePoint = movePoint(x1, y1, this.theta, this.targetRadius + this.forwardArrow.height)
edgeX1 = edgePoint[0]
edgeY1 = edgePoint[1]
const [arrowX1, arrowY1] = movePoint(x1, y1, this.theta, this.targetRadius)
this.arrow.forward.update(arrowX1, arrowY1, this.theta, this.stroke, this.strokeOpacity)
this.forwardArrow.update(arrowX1, arrowY1, this.theta, this.stroke, this.strokeOpacity)
} else {
const edgePoint = movePoint(x1, y1, this.theta, this.targetRadius)
edgeX1 = edgePoint[0]
edgeY1 = edgePoint[1]
}

if (this.arrow?.reverse) {
const edgePoint = movePoint(x0, y0, this.theta, -this.sourceRadius - this.arrow.reverse.height)
if (this.reverseArrow) {
const edgePoint = movePoint(x0, y0, this.theta, -this.sourceRadius - this.reverseArrow.height)
edgeX0 = edgePoint[0]
edgeY0 = edgePoint[1]
const [arrowX0, arrowY0] = movePoint(x0, y0, this.theta, -this.sourceRadius)
this.arrow.reverse.update(arrowX0, arrowY0, this.theta + Math.PI, this.stroke, this.strokeOpacity)
this.reverseArrow.update(arrowX0, arrowY0, this.theta + Math.PI, this.stroke, this.strokeOpacity)
} else {
const edgePoint = movePoint(x0, y0, this.theta, -this.sourceRadius)
edgeX0 = edgePoint[0]
edgeY0 = edgePoint[1]
}

this.center = midPoint(edgeX0, edgeY0, edgeX1, edgeY1)

if (this.label) {
this.label.rotation = this.theta
this.label.moveTo(...this.center)
Expand All @@ -206,18 +162,68 @@ export class EdgeRenderer {
this.hitArea.update(edgeX0, edgeY0, edgeX1, edgeY1, this.width, this.theta)
}
}

// TODO - disable events if edge has no event handlers
// TODO - disable events when dragging/scrolling
const shouldHitAreaMount = isVisible && this.renderer.zoom > MIN_INTERACTION_ZOOM
const hitAreaMounted = this.managers.interactions.isMounted(this.hitArea)
if (shouldHitAreaMount && !hitAreaMounted) {
this.managers.interactions.mount(this.hitArea)
} else if (!shouldHitAreaMount && hitAreaMounted) {
this.managers.interactions.unmount(this.hitArea)
}

const lineMounted = this.managers.edges.isMounted(this.lineSegment)
if (isVisible && !lineMounted) {
this.managers.edges.mount(this.lineSegment)
} else if (!isVisible && lineMounted) {
this.managers.edges.unmount(this.lineSegment)
}

if (this.forwardArrow) {
const forwardArrowMounted = this.managers.arrows.isMounted(this.forwardArrow)
if (isVisible && !forwardArrowMounted) {
this.managers.arrows.mount(this.forwardArrow)
} else if (!isVisible && forwardArrowMounted) {
this.managers.arrows.unmount(this.forwardArrow)
}
}

if (this.reverseArrow) {
const reverseArrowMounted = this.managers.arrows.isMounted(this.reverseArrow)
if (isVisible && !reverseArrowMounted) {
this.managers.arrows.mount(this.reverseArrow)
} else if (!isVisible && reverseArrowMounted) {
this.managers.arrows.unmount(this.reverseArrow)
}
}

const shouldLabelMount = isVisible && this.renderer.zoom > MIN_LABEL_ZOOM

if (shouldLabelMount) {
this.applyLabel()
}

if (this.label) {
const labelMounted = this.managers.labels.isMounted(this.label)
if (shouldLabelMount && !labelMounted) {
this.managers.labels.mount(this.label)
} else if (!shouldLabelMount && labelMounted) {
this.managers.labels.unmount(this.label)
}
}
}

delete() {
clearTimeout(this.doubleClickTimeout)

this.managers.edges.delete(this.lineSegment)
this.managers.interactions.delete(this.hitArea)
if (this.arrow?.forward) {
this.managers.arrows.delete(this.arrow.forward)
if (this.forwardArrow) {
this.managers.arrows.delete(this.forwardArrow)
}
if (this.arrow?.reverse) {
this.managers.arrows.delete(this.arrow.reverse)
if (this.reverseArrow) {
this.managers.arrows.delete(this.reverseArrow)
}
}

Expand Down Expand Up @@ -385,15 +391,49 @@ export class EdgeRenderer {
return this.renderer.managers
}

private get arrowStyle(): ArrowStyle {
if (this.arrow === undefined) {
private get arrow(): ArrowStyle {
if (this.forwardArrow === undefined && this.reverseArrow === undefined) {
return 'none'
} else if (this.arrow.forward !== undefined && this.arrow.reverse !== undefined) {
return 'both'
} else if (this.arrow.forward !== undefined) {
} else if (this.reverseArrow === undefined) {
return 'forward'
} else {
} else if (this.forwardArrow === undefined) {
return 'reverse'
} else {
return 'both'
}
}

private deleteArrow(arrow: 'forward' | 'reverse') {
if (arrow === 'forward' && this.forwardArrow) {
this.managers.arrows.delete(this.forwardArrow)
this.forwardArrow = undefined
} else if (arrow === 'reverse' && this.reverseArrow) {
this.managers.arrows.delete(this.reverseArrow)
this.reverseArrow = undefined
}

return this
}

private createArrow(arrow: 'forward' | 'reverse') {
if (arrow === 'forward' && this.forwardArrow === undefined) {
this.forwardArrow = new Arrow(this.renderer.edgesContainer, this.renderer.arrow)
} else if (arrow === 'reverse' && this.reverseArrow === undefined) {
this.reverseArrow = new Arrow(this.renderer.edgesContainer, this.renderer.arrow)
}

return this
}

private applyLabel() {
const label = this.edge.label
const style = this.edge.style?.label
if (label !== undefined && label.trim() !== '' && this.label === undefined) {
this.label = new Text(this.renderer.assets, this.renderer.labelsContainer, label, style, DEFAULT_LABEL_STYLE)
this.label.rotation = this.theta
this.label.moveTo(...this.center)
}

return this
}
}
52 changes: 37 additions & 15 deletions src/renderers/webgl/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,22 @@ export class NodeRenderer {
}

update(node: Node) {
if (node.label === undefined || node.label.trim() === '') {
if (this.label) {
if (this.label) {
if (node.label === undefined || node.label.trim() === '') {
this.managers.labels.delete(this.label)
this.label = undefined
} else {
this.label.update(node.label, node.style?.label)
}
} else if (this.label === undefined) {
this.label = new Text(this.renderer.assets, this.renderer.labelsContainer, node.label, node.style?.label, DEFAULT_LABEL_STYLE)
} else {
this.label.update(node.label, node.style?.label)
}

if (node.style?.icon === undefined) {
if (this.icon) {
if (this.icon) {
if (node.style?.icon === undefined) {
this.icon.delete()
this.icon = undefined
} else {
this.icon.update(node.style.icon)
}
} else if (this.icon === undefined) {
this.icon = new Icon(this.renderer.assets, this.renderer.textIcon, this.renderer.nodesContainer, this.fill, node.style.icon)
} else {
this.icon.update(node.style.icon)
}

/**
Expand Down Expand Up @@ -140,6 +136,10 @@ export class NodeRenderer {

const isVisible = this.visible()

if (isVisible) {
this.applyLabel().applyIcon()
}

// TODO - disable events if node has no event handlers
// TODO - disable events if node pixel width < ~5px
// TODO - disable events when dragging/scrolling
Expand All @@ -155,9 +155,9 @@ export class NodeRenderer {
const fillMounted = this.managers.nodes.isMounted(this.fill)

if (isVisible && !fillMounted) {
this.fill.mount()
} else if (!isVisible && this.fill.mounted) {
this.fill.unmount()
this.managers.nodes.mount(this.fill)
} else if (!isVisible && fillMounted) {
this.managers.nodes.unmount(this.fill)
}

const shouldStrokesMount = isVisible && this.renderer.zoom > MIN_NODE_STROKE_ZOOM
Expand Down Expand Up @@ -520,4 +520,26 @@ export class NodeRenderer {
private get managers() {
return this.renderer.managers
}

private applyLabel() {
const label = this.node.label
const style = this.node.style?.label
if (label !== undefined && label.trim() !== '' && this.label === undefined) {
this.label = new Text(this.renderer.assets, this.renderer.labelsContainer, label, style, DEFAULT_LABEL_STYLE)
this.label.offset = this.strokes.radius
this.label.moveTo(this.x, this.y)
}

return this
}

private applyIcon() {
const icon = this.node.style?.icon
if (icon !== undefined && this.icon === undefined) {
this.icon = new Icon(this.renderer.assets, this.renderer.textIcon, this.renderer.nodesContainer, this.fill, icon)
this.icon.moveTo(this.x, this.y)
}

return this
}
}
Loading

0 comments on commit ad0ba22

Please sign in to comment.