From 706eee6b107aa3530fb9da93e6e421a0dd1a1822 Mon Sep 17 00:00:00 2001 From: James Conkling Date: Sun, 1 Oct 2023 22:24:25 -0700 Subject: [PATCH] stagger mounting/unmounting/deleting --- src/renderers/webgl/edge.ts | 45 ++++-- src/renderers/webgl/index.ts | 10 +- src/renderers/webgl/node.ts | 47 +++++-- src/renderers/webgl/objectManager.ts | 155 +++++++++++++++++++++ src/renderers/webgl/objects/label.ts | 20 ++- src/renderers/webgl/objects/nodeStrokes.ts | 5 + 6 files changed, 256 insertions(+), 26 deletions(-) create mode 100644 src/renderers/webgl/objectManager.ts diff --git a/src/renderers/webgl/edge.ts b/src/renderers/webgl/edge.ts index c4505d54..b10115b8 100644 --- a/src/renderers/webgl/edge.ts +++ b/src/renderers/webgl/edge.ts @@ -27,6 +27,9 @@ export class EdgeRenderer { targetRadius?: number private arrow?: { forward: Arrow; reverse?: undefined } | { forward?: undefined; reverse: Arrow } | { forward: Arrow; reverse: Arrow } + private lineMounted = false + private forwardArrowMounted = false + private reverseArrowMounted = false constructor(renderer: Renderer, edge: Graph.Edge, source: NodeRenderer, target: NodeRenderer) { this.renderer = renderer @@ -42,6 +45,8 @@ export class EdgeRenderer { if (arrow !== (this.edge?.style?.arrow ?? DEFAULT_ARROW)) { this.arrow?.forward?.delete() this.arrow?.reverse?.delete() + this.forwardArrowMounted = false + this.reverseArrowMounted = false this.arrow = undefined switch (arrow) { @@ -71,9 +76,18 @@ export class EdgeRenderer { const isVisible = this.visible(Math.min(x0, x1), Math.min(y0, y1), Math.max(x0, x1), Math.max(y0, y1)) if (this.renderer.zoom > MIN_EDGES_ZOOM && isVisible) { - this.lineSegment.mount() - this.arrow?.forward?.mount() - this.arrow?.reverse?.mount() + if (!this.lineMounted) { + this.renderer.edgeObjectManager.mount(this.lineSegment) + this.lineMounted = true + } + if (this.arrow?.forward && !this.forwardArrowMounted) { + this.renderer.edgeObjectManager.mount(this.arrow.forward) + this.forwardArrowMounted = true + } + if (this.arrow?.reverse && !this.reverseArrowMounted) { + this.renderer.edgeObjectManager.mount(this.arrow.reverse) + this.reverseArrowMounted = true + } // this.edgeGraphic.alpha = this.renderer.zoom <= MIN_EDGES_ZOOM + 0.1 ? // (this.renderer.zoom - MIN_EDGES_ZOOM) / MIN_EDGES_ZOOM + 0.1 : 1 @@ -130,16 +144,29 @@ export class EdgeRenderer { this.lineSegment.update(edgeX0, edgeY0, edgeX1, edgeY1, width, stroke, strokeOpacity) } } else { - this.lineSegment.unmount() - this.arrow?.forward?.unmount() - this.arrow?.reverse?.unmount() + if (this.lineMounted) { + this.renderer.edgeObjectManager.unmount(this.lineSegment) + this.lineMounted = false + } + if (this.arrow?.forward && this.forwardArrowMounted) { + this.renderer.edgeObjectManager.unmount(this.arrow.forward) + this.forwardArrowMounted = false + } + if (this.arrow?.reverse && this.reverseArrowMounted) { + this.renderer.edgeObjectManager.unmount(this.arrow.reverse) + this.reverseArrowMounted = false + } } } delete() { - this.lineSegment.delete() - this.arrow?.forward?.delete() - this.arrow?.reverse?.delete() + this.renderer.edgeObjectManager.delete(this.lineSegment) + if (this.arrow?.forward) { + this.renderer.edgeObjectManager.delete(this.arrow.forward) + } + if (this.arrow?.reverse) { + this.renderer.edgeObjectManager.delete(this.arrow.reverse) + } } private visible(minX: number, minY: number, maxX: number, maxY: number) { diff --git a/src/renderers/webgl/index.ts b/src/renderers/webgl/index.ts index f6217876..0a060f9f 100644 --- a/src/renderers/webgl/index.ts +++ b/src/renderers/webgl/index.ts @@ -12,6 +12,7 @@ import { CircleTexture } from './textures/circle' import { Font } from './objects/font' import { interpolate } from '../../utils' import { logUnknownEdgeError } from './utils' +import { ObjectManager } from './objectManager' export type Keys = { altKey?: boolean; ctrlKey?: boolean; metaKey?: boolean; shiftKey?: boolean } export type MousePosition = { x: number; y: number; clientX: number; clientY: number } @@ -130,6 +131,9 @@ export class Renderer { zoomInteraction = new Zoom(this) dragInteraction = new Drag(this) decelerateInteraction = new Decelerate(this) + nodeObjectManager = new ObjectManager(4000) + edgeObjectManager = new ObjectManager(4000) + labelObjectManager = new ObjectManager(4000) font = new Font() eventSystem: EventSystem nodes: Graph.Node[] = [] @@ -208,7 +212,7 @@ export class Renderer { autoDensity: true, powerPreference: 'high-performance', backgroundAlpha: 0, - forceCanvas: forceCanvas + forceCanvas: forceCanvas, }) this.width = width @@ -485,6 +489,10 @@ export class Renderer { this.edgeRenderersById[edge.id].render() } + this.nodeObjectManager.render() + this.edgeObjectManager.render() + this.labelObjectManager.render() + this.app.render() } diff --git a/src/renderers/webgl/node.ts b/src/renderers/webgl/node.ts index ca05398f..4def73b5 100644 --- a/src/renderers/webgl/node.ts +++ b/src/renderers/webgl/node.ts @@ -23,6 +23,9 @@ export class NodeRenderer { private interpolateX?: (dt: number) => { value: number; done: boolean } private interpolateY?: (dt: number) => { value: number; done: boolean } private interpolateRadius?: (dt: number) => { value: number; done: boolean } + private fillMounted = false + private strokeMounted = false + private labelMounted = false constructor(renderer: Renderer, node: Graph.Node) { this.renderer = renderer @@ -38,7 +41,9 @@ export class NodeRenderer { } } else { if (node.label === undefined) { - this.label = this.label.delete() + this.renderer.labelObjectManager.delete(this.label) + this.labelMounted = false + this.label = undefined } } @@ -128,33 +133,51 @@ export class NodeRenderer { } if (isVisible) { - this.fill.mount() + if (!this.fillMounted) { + this.renderer.nodeObjectManager.mount(this.fill) + this.fillMounted = true + } } else { - this.fill.unmount() + if (!this.fillMounted) { + this.renderer.nodeObjectManager.unmount(this.fill) + this.fillMounted = false + } } if (isVisible && this.renderer.zoom > MIN_NODE_STROKE_ZOOM) { - this.strokes.mount() + if (!this.strokeMounted) { + this.renderer.nodeObjectManager.mount(this.strokes) + this.strokeMounted = true + } } else { - this.strokes.unmount() + if (this.strokeMounted) { + this.renderer.nodeObjectManager.unmount(this.strokes) + this.strokeMounted = false + } } if (this.label) { if (isVisible && this.renderer.zoom > MIN_LABEL_ZOOM) { - // this.label.alpha = this.renderer.zoom <= MIN_LABEL_ZOOM + 0.1 ? - // (this.renderer.zoom - MIN_LABEL_ZOOM) / MIN_LABEL_ZOOM + 0.1 : 1 - this.label.mount() + if (!this.labelMounted) { + this.renderer.labelObjectManager.mount(this.label) + this.labelMounted = true + } } else { - this.label.unmount() + if (this.labelMounted) { + this.renderer.labelObjectManager.unmount(this.label) + this.labelMounted = false + } } } } delete() { clearTimeout(this.doubleClickTimeout) - this.fill.delete() - this.strokes.delete() - this.label?.delete() + this.renderer.nodeObjectManager.delete(this.fill) + this.renderer.nodeObjectManager.delete(this.strokes) + if (this.label) { + this.renderer.labelObjectManager.delete(this.label) + } } private setPosition(node: Graph.Node, x: number, y: number, radius: number) { diff --git a/src/renderers/webgl/objectManager.ts b/src/renderers/webgl/objectManager.ts new file mode 100644 index 00000000..8b723488 --- /dev/null +++ b/src/renderers/webgl/objectManager.ts @@ -0,0 +1,155 @@ +export interface RenderObject { + mounted: boolean + + mount(): this + + unmount(): this + + delete(): void +} + +export class ObjectManager { + + private batchSize: number + + private toMount = new Set() + + private toUnmount = new Set() + + private toDelete = new Set() + + constructor(batchSize: number) { + this.batchSize = batchSize + } + + mount(object: RenderObject) { + this.toUnmount.delete(object) + this.toMount.add(object) + } + + unmount(object: RenderObject) { + this.toMount.delete(object) + this.toUnmount.add(object) + } + + delete(object: RenderObject) { + this.toMount.delete(object) + this.toUnmount.delete(object) + this.toDelete.add(object) + } + + render() { + let toDeleteCount = 0 + let toMountCount = 0 + let toUnmountCount = 0 + + for (const object of this.toDelete) { + if (toMountCount === this.batchSize) { + break + } + + object.delete() + + this.toDelete.delete(object) + + toDeleteCount++ + } + + for (const object of this.toMount) { + if (toMountCount === this.batchSize) { + break + } + + object.mount() + + this.toMount.delete(object) + + toMountCount++ + } + + for (const object of this.toUnmount) { + if (toUnmountCount === this.batchSize) { + break + } + + object.unmount() + + this.toUnmount.delete(object) + + toUnmountCount++ + } + } +} + +// export class ObjectManager { + +// private batchSize: number + +// private toMount = new Set() + +// private toUnmount = new Set() + +// private toDelete = new Set() + +// constructor(batchSize: number) { +// this.batchSize = batchSize +// } + +// mount(object: RenderObject) { +// this.toUnmount.delete(object) +// this.toMount.add(object) +// } + +// unmount(object: RenderObject) { +// this.toMount.delete(object) +// this.toUnmount.add(object) +// } + +// delete(object: RenderObject) { +// this.toDelete.add(object) +// } + +// render() { +// let toDeleteCount = 0 +// let toMountCount = 0 +// let toUnmountCount = 0 + +// for (const object of this.toDelete) { +// if (toMountCount === this.batchSize) { +// break +// } + +// object.delete() + +// this.toMount.delete(object) +// this.toUnmount.delete(object) +// this.toDelete.delete(object) + +// toDeleteCount++ +// } + +// for (const object of this.toMount) { +// if (toMountCount === this.batchSize) { +// break +// } + +// object.mount() + +// this.toMount.delete(object) + +// toMountCount++ +// } + +// for (const object of this.toUnmount) { +// if (toUnmountCount === this.batchSize) { +// break +// } + +// object.unmount() + +// this.toUnmount.delete(object) + +// toUnmountCount++ +// } +// } +// } diff --git a/src/renderers/webgl/objects/label.ts b/src/renderers/webgl/objects/label.ts index f367f4a6..d135aa7e 100644 --- a/src/renderers/webgl/objects/label.ts +++ b/src/renderers/webgl/objects/label.ts @@ -31,7 +31,7 @@ export class Label { update(label: string, x: number, y: number, style?: LabelStyle) { if (label !== this.label) { - this.setLabel(label) + this.setText(label) this.label = label } @@ -105,28 +105,40 @@ export class Label { return undefined } - private setLabel(label: string) { + private setText(label: string) { if (this.text instanceof BitmapText) { if (isASCII(label)) { this.text.text = label } else { + const isMounted = this.mounted this.text.destroy() - this.unmount() + if (isMounted) { + this.unmount() + } this.text = new Text(label, TEXT_OUTLINE_STYLE) this.fontSize = undefined this.position = undefined this.x = undefined this.y = undefined + if (isMounted) { + this.mount() + } } } else { if (isASCII(label)) { + const isMounted = this.mounted this.text.destroy() - this.unmount() + if (isMounted) { + this.unmount() + } this.text = new BitmapText(label, { fontName: 'Label' }) this.fontSize = undefined this.position = undefined this.x = undefined this.y = undefined + if (isMounted) { + this.mount() + } } else { this.text.text = label } diff --git a/src/renderers/webgl/objects/nodeStrokes.ts b/src/renderers/webgl/objects/nodeStrokes.ts index 97ebfffe..280f398c 100644 --- a/src/renderers/webgl/objects/nodeStrokes.ts +++ b/src/renderers/webgl/objects/nodeStrokes.ts @@ -20,10 +20,12 @@ export class NodeStrokes { update(x: number, y: number, radius: number, style?: Graph.NodeStyle) { if (style?.stroke !== this.style?.stroke) { // exit + const isMounted = this.mounted this.delete() if (style?.stroke?.length) { // enter + this.strokes = Array(style.stroke.length) this.radius = radius @@ -38,6 +40,9 @@ export class NodeStrokes { circle.y = y this.strokes[i] = circle } + if (isMounted) { + this.mount() + } } } else if (this.strokes && this.style?.stroke) { // reposition