@@ -423,6 +423,32 @@ export function MeshTopologyMap({
423423 const dragViewport = useRef ( { x : 0 , y : 0 } )
424424 const lastPinchDist = useRef ( 0 )
425425
426+ // ---- Adaptive quality state ----
427+ const [ qualityLevel , setQualityLevel ] = useState ( 1 )
428+ const frameTimesRef = useRef < number [ ] > ( [ ] )
429+
430+ // ---- Adaptive Quality Helper (defined before use in render) ----
431+ const getAdaptiveQualityNodes = useCallback (
432+ ( allNodes : NodePosition [ ] , quality : number ) : NodePosition [ ] => {
433+ if ( quality >= 1 ) return allNodes
434+ const currentEdges = data . edges
435+ const sorted = [ ...allNodes ] . sort ( ( a , b ) => {
436+ const aScore =
437+ ( a . label ? 30 : 0 ) +
438+ ( a . r ? a . r * 2 : 0 ) +
439+ ( currentEdges . filter ( ( e ) => e . source === a . id || e . target === a . id ) . length * 5 )
440+ const bScore =
441+ ( b . label ? 30 : 0 ) +
442+ ( b . r ? b . r * 2 : 0 ) +
443+ ( currentEdges . filter ( ( e ) => e . source === b . id || e . target === b . id ) . length * 5 )
444+ return bScore - aScore
445+ } )
446+ const count = Math . max ( 1 , Math . floor ( allNodes . length * quality ) )
447+ return sorted . slice ( 0 , count )
448+ } ,
449+ [ data . edges ] ,
450+ )
451+
426452 const layout = useMeshLayout ( layoutConfig )
427453
428454 const nodes = useMemo (
@@ -490,16 +516,24 @@ export function MeshTopologyMap({
490516 const renderCallbackRef = useRef < ( ) => void > ( ( ) => { } )
491517
492518 const render = useCallback ( ( ) => {
519+ const frameStart = performance . now ( )
493520 const canvas = canvasRef . current
494521 if ( ! canvas ) return
495522 const currentViewport = viewportRef . current
496523 const si = spatialIndexRef . current
497524 const visibleNodes = si ? si . queryViewport ( currentViewport ) : nodes
525+
526+ // Adaptive quality: reduce visible nodes when frame budget exceeded
527+ let renderNodes = visibleNodes
528+ if ( cfg . adaptiveQualityEnabled ) {
529+ renderNodes = getAdaptiveQualityNodes ( visibleNodes , qualityLevel )
530+ }
531+
498532 const lod = getLodLevel ( currentViewport . zoom , cfg )
499533
500534 const gl = glRef . current
501535 if ( gl && renderModeRef . current === 'webgl2' ) {
502- renderWebGL2 ( gl , visibleNodes , edges , currentViewport , cfg , lod , false )
536+ renderWebGL2 ( gl , renderNodes , edges , currentViewport , cfg , lod , false )
503537 } else {
504538 const ctx2d = canvas . getContext ( '2d' )
505539 if ( ctx2d ) {
@@ -509,12 +543,32 @@ export function MeshTopologyMap({
509543 window . devicePixelRatio || 1 ,
510544 0 , 0 ,
511545 )
512- renderCanvas2D ( ctx2d , visibleNodes , edges , currentViewport , cfg , lod , selectedNodeId )
546+ renderCanvas2D ( ctx2d , renderNodes , edges , currentViewport , cfg , lod , selectedNodeId )
547+ }
548+ }
549+
550+ // Adaptive quality: track frame times and adjust quality
551+ if ( cfg . adaptiveQualityEnabled ) {
552+ const frameDuration = performance . now ( ) - frameStart
553+ frameTimesRef . current . push ( frameDuration )
554+ if ( frameTimesRef . current . length > 30 ) frameTimesRef . current . shift ( )
555+
556+ const avgFrameTime =
557+ frameTimesRef . current . reduce ( ( s , t ) => s + t , 0 ) /
558+ frameTimesRef . current . length
559+ if ( avgFrameTime > cfg . adaptiveQualityThreshold ) {
560+ setQualityLevel ( ( prev ) =>
561+ Math . max ( 0.25 , prev - cfg . adaptiveQualityReduceFraction ) ,
562+ )
563+ } else if ( avgFrameTime < cfg . adaptiveQualityThreshold * 0.5 && qualityLevel < 1 ) {
564+ setQualityLevel ( ( prev ) =>
565+ Math . min ( 1 , prev + cfg . adaptiveQualityReduceFraction ) ,
566+ )
513567 }
514568 }
515569
516570 animFrameRef . current = requestAnimationFrame ( ( ) => renderCallbackRef . current ( ) )
517- } , [ nodes , edges , cfg , selectedNodeId ] )
571+ } , [ nodes , edges , cfg , selectedNodeId , qualityLevel , getAdaptiveQualityNodes ] )
518572
519573 useEffect ( ( ) => { renderCallbackRef . current = render } , [ render ] )
520574
0 commit comments