From 5f1a175ae768f93d2bc9c06952773d845634e969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=83=D0=BB?= Date: Mon, 28 Oct 2024 03:19:06 +0300 Subject: [PATCH] feat: improved perfomance, decreased rerenders, added color support for links --- .../Graph/Connections/LineComponent.tsx | 64 +++++++++-------- .../Universe/Graph/Connections/index.tsx | 41 ++--------- .../Universe/Graph/Cubes/NodePoints/index.tsx | 3 +- src/components/Universe/Graph/Cubes/index.tsx | 19 +++-- src/components/Universe/Graph/index.tsx | 72 ++++++++++--------- src/hooks/useTraceUpdate/index.ts | 24 +++++++ 6 files changed, 119 insertions(+), 104 deletions(-) create mode 100644 src/hooks/useTraceUpdate/index.ts diff --git a/src/components/Universe/Graph/Connections/LineComponent.tsx b/src/components/Universe/Graph/Connections/LineComponent.tsx index 9de348843..42d778bc3 100644 --- a/src/components/Universe/Graph/Connections/LineComponent.tsx +++ b/src/components/Universe/Graph/Connections/LineComponent.tsx @@ -1,42 +1,44 @@ import { Line } from '@react-three/drei' import gsap from 'gsap' -import { forwardRef, useEffect } from 'react' +import { memo, useEffect, useRef } from 'react' import { Color, Vector3 } from 'three' import { Line2 } from 'three-stdlib' type LineComponentProps = { - source: Vector3 - target: Vector3 isSelected: boolean lineWidth: number visible: boolean } -const LineComponent = forwardRef( - ({ source, target, isSelected, lineWidth, visible }, ref) => { - useEffect(() => { - const line = (ref as React.MutableRefObject).current - - if (line) { - gsap.fromTo( - line.material, - { linewidth: 5 }, - { - linewidth: isSelected ? 2 : lineWidth, - duration: 1, - }, - ) - } - }, [isSelected, lineWidth, ref]) - - const color = new Color(0xff0000) - - return ( - - ) - }, -) - -LineComponent.displayName = 'LineComponent' - -export default LineComponent +const VECTOR = new Vector3(0, 0, 0) + +// eslint-disable-next-line no-underscore-dangle +export const _LineComponent = (props: LineComponentProps) => { + const { isSelected, lineWidth, visible } = props + const ref = useRef(null) + + useEffect(() => { + const line = (ref as React.MutableRefObject).current + + if (line) { + gsap.fromTo( + line.material, + { linewidth: 5 }, + { + linewidth: isSelected ? 2 : lineWidth, + duration: 1, + }, + ) + } + }, [isSelected, lineWidth, ref]) + + const color = new Color(0xff0000) + + return ( + + ) +} + +_LineComponent.displayName = 'LineComponent' + +export const LineComponent = memo(_LineComponent) diff --git a/src/components/Universe/Graph/Connections/index.tsx b/src/components/Universe/Graph/Connections/index.tsx index 5d2b06930..4c4f47a3f 100644 --- a/src/components/Universe/Graph/Connections/index.tsx +++ b/src/components/Universe/Graph/Connections/index.tsx @@ -1,54 +1,25 @@ -import { memo, useRef } from 'react' -import { Vector3 } from 'three' -import { Line2 } from 'three-stdlib' +import { memo } from 'react' import { useDataStore } from '~/stores/useDataStore' import { useGraphStore, useSelectedNode } from '~/stores/useGraphStore' import { Link } from '~/types' -import { LinkPosition } from '..' -import LineComponent from './LineComponent' +import { LineComponent } from './LineComponent' -type Props = { - linksPositions: LinkPosition[] -} - -export const Connections = memo(({ linksPositions }: Props) => { +export const Connections = memo(() => { const data = useDataStore((s) => s.dataInitial) const { showSelectionGraph } = useGraphStore((s) => s) const selectedNode = useSelectedNode() - const lineRefs = useRef([]) + console.log('connection') return ( - {data?.links.map((l: Link, index) => { - const source = new Vector3( - linksPositions[index]?.sx || 0, - linksPositions[index]?.sy || 0, - linksPositions[index]?.sz || 0, - ) - - const target = new Vector3( - linksPositions[index]?.tx || 0, - linksPositions[index]?.ty || 0, - linksPositions[index]?.tz || 0, - ) - + {data?.links.map((l: Link) => { const isSelected = selectedNode?.ref_id === l.source || selectedNode?.ref_id === l.target const lineWidth = selectedNode ? 0 : 0.5 return ( - { - lineRefs.current[index] = el as Line2 - }} - isSelected={isSelected} - lineWidth={lineWidth} - source={source} - target={target} - visible={!showSelectionGraph} - /> + ) })} diff --git a/src/components/Universe/Graph/Cubes/NodePoints/index.tsx b/src/components/Universe/Graph/Cubes/NodePoints/index.tsx index 24a71c6a9..134a89d51 100644 --- a/src/components/Universe/Graph/Cubes/NodePoints/index.tsx +++ b/src/components/Universe/Graph/Cubes/NodePoints/index.tsx @@ -44,7 +44,7 @@ const _NodePoints = () => { const data = useDataStore((s) => s.dataInitial) const { normalizedSchemasByType } = useSchemaStore((s) => s) const nodeTypes = useNodeTypes() - const ringGeometry = useMemo(() => new TorusGeometry(30, 4, 16, 100), []) + const ringGeometry = useMemo(() => new TorusGeometry(30, 2, 16, 100), []) return ( <> @@ -53,7 +53,6 @@ const _NodePoints = () => { limit={1000} // Optional: max amount of items (for calculating buffer size) range={1000} visible={!selectedNode} - // Optional: draw-range > {data?.nodes.map((node: NodeExtended) => { diff --git a/src/components/Universe/Graph/Cubes/index.tsx b/src/components/Universe/Graph/Cubes/index.tsx index 317f48c4d..b547af67d 100644 --- a/src/components/Universe/Graph/Cubes/index.tsx +++ b/src/components/Universe/Graph/Cubes/index.tsx @@ -16,6 +16,7 @@ const POINTER_IN_DELAY = 200 export const Cubes = memo(() => { const selectedNode = useSelectedNode() const hoveredNode = useHoveredNode() + const relativeIds = useSelectedNodeRelativeIds() const { selectionGraphData, showSelectionGraph, setHoveredNode, setIsHovering } = useGraphStore((s) => s) @@ -56,15 +57,20 @@ export const Cubes = memo(() => { const onPointerOut = useCallback( (e: ThreeEvent) => { e.stopPropagation() - setIsHovering(false) - setHoveredNode(null) if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current) hoverTimeoutRef.current = null } + + if (!hoveredNode) { + return + } + + setIsHovering(false) + setHoveredNode(null) }, - [setIsHovering, setHoveredNode], + [setIsHovering, setHoveredNode, hoveredNode], ) const onPointerIn = useCallback( @@ -72,14 +78,19 @@ export const Cubes = memo(() => { const objects = e.intersections.map((i) => i.object) const object = objects[0] + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current) + hoverTimeoutRef.current = null + } + if (object?.userData?.ref_id) { const node = object.userData as NodeExtended if (!ignoreNodeEvent(node)) { e.stopPropagation() - setIsHovering(true) hoverTimeoutRef.current = setTimeout(() => { + setIsHovering(true) setHoveredNode(node) }, POINTER_IN_DELAY) } diff --git a/src/components/Universe/Graph/index.tsx b/src/components/Universe/Graph/index.tsx index a0a605ce7..728e611f0 100644 --- a/src/components/Universe/Graph/index.tsx +++ b/src/components/Universe/Graph/index.tsx @@ -4,6 +4,7 @@ import { Box3, Color, Group, Sphere, Vector3 } from 'three' import { Line2 } from 'three-stdlib' import { useDataStore } from '~/stores/useDataStore' import { useGraphStore } from '~/stores/useGraphStore' +import { useSchemaStore } from '~/stores/useSchemaStore' import { NodeExtended } from '~/types' import { Connections } from './Connections' import { Cubes } from './Cubes' @@ -25,6 +26,7 @@ export const Graph = () => { const groupRef = useRef(null) const cameraSettled = useRef(false) const linksPositionRef = useRef([]) + const { normalizedSchemasByType } = useSchemaStore((s) => s) const { setData, simulation, simulationCreate, simulationHelpers, graphStyle, setGraphRadius } = useGraphStore( (s) => s, @@ -88,13 +90,15 @@ export const Graph = () => { const grPoints = groupRef.current.getObjectByName('simulation-3d-group__node-points') as Group const grConnections = groupRef.current.getObjectByName('simulation-3d-group__connections') as Group - gr.children.forEach((mesh, index) => { - const simulationNode = simulation.nodes()[index] + if (gr) { + gr.children.forEach((mesh, index) => { + const simulationNode = simulation.nodes()[index] - if (simulationNode) { - mesh.position.set(simulationNode.x, simulationNode.y, simulationNode.z) - } - }) + if (simulationNode) { + mesh.position.set(simulationNode.x, simulationNode.y, simulationNode.z) + } + }) + } if (grPoints) { grPoints.children[0].children.forEach((mesh, index) => { @@ -106,35 +110,39 @@ export const Graph = () => { }) } - grConnections.children.forEach((r, i) => { - const link = dataInitial?.links[i] - const Line = r as Line2 + if (grConnections) { + grConnections.children.forEach((r, i) => { + const link = dataInitial?.links[i] + const Line = r as Line2 - if (link) { - const sourceNode = simulation.nodes().find((n: NodeExtended) => n.ref_id === link.source) - const targetNode = simulation.nodes().find((n: NodeExtended) => n.ref_id === link.target) + if (link) { + const sourceNode = simulation.nodes().find((n: NodeExtended) => n.ref_id === link.source) + const targetNode = simulation.nodes().find((n: NodeExtended) => n.ref_id === link.target) - const { x: sx, y: sy, z: sz } = sourceNode - const { x: tx, y: ty, z: tz } = targetNode + const { x: sx, y: sy, z: sz } = sourceNode + const { x: tx, y: ty, z: tz } = targetNode - linksPositionRef.current[i] = { - sx, - sy, - sz, - tx, - ty, - tz, - } + linksPositionRef.current[i] = { + sx, + sy, + sz, + tx, + ty, + tz, + } + + const lineColor = normalizedSchemasByType[sourceNode.node_type]?.primary_color || 'white' - Line.geometry.setPositions([sx, sy, sz, tx, ty, tz]) + Line.geometry.setPositions([sx, sy, sz, tx, ty, tz]) - const { material } = Line + const { material } = Line - material.color = new Color('white') - material.transparent = true - material.opacity = 0.1 - } - }) + material.color = new Color(lineColor) + material.transparent = true + material.opacity = 0.2 + } + }) + } } }) @@ -153,7 +161,7 @@ export const Graph = () => { cameraSettled.current = false }) - }, [dataInitial, simulation, setGraphRadius]) + }, [dataInitial, simulation, setGraphRadius, normalizedSchemasByType]) if (!simulation) { return null @@ -162,11 +170,11 @@ export const Graph = () => { return ( - + {graphStyle === 'earth' && } {(isLoadingNew || isFetching) && } - {graphStyle !== 'earth' && } + {graphStyle !== 'earth' && } ) diff --git a/src/hooks/useTraceUpdate/index.ts b/src/hooks/useTraceUpdate/index.ts new file mode 100644 index 000000000..fe1dd29d4 --- /dev/null +++ b/src/hooks/useTraceUpdate/index.ts @@ -0,0 +1,24 @@ +// @ts-nocheck +// @ts-ignore + +import { useEffect, useRef } from 'react' + +export function useTraceUpdate(props: { [s: string]: unknown } | ArrayLike) { + const prev = useRef(props) + + useEffect(() => { + const changedProps = Object.entries(props).reduce((ps, [k, v]) => { + if (prev.current[k] !== v) { + ps[k] = [prev.current[k], v] + } + + return ps + }, {}) + + if (Object.keys(changedProps).length > 0) { + console.log('Changed props:', changedProps) + } + + prev.current = props + }) +}