@@ -35,12 +35,19 @@ export interface TrackDrawing {
3535// currently its a bit messy with the turns, so we disable them for now 
3636const  ENABLE_TURNS  =  false ; 
3737
38- // Throttle position updates to 2fps (500ms interval) 
39- const  POSITION_UPDATE_INTERVAL  =  500 ; 
38+ // Optimized update intervals - reduce from 2fps to 10fps for smoother updates 
39+ const  POSITION_UPDATE_INTERVAL  =  100 ;  // 10fps instead of 2fps 
40+ const  RENDER_THROTTLE_MS  =  16 ;  // ~60fps for rendering 
4041
4142const  TRACK_DRAWING_WIDTH  =  1920 ; 
4243const  TRACK_DRAWING_HEIGHT  =  1080 ; 
4344
45+ // Threshold for position changes to trigger redraw 
46+ const  POSITION_CHANGE_THRESHOLD  =  0.5 ;  // pixels 
47+ 
48+ // Add a cache for getPointAtLength results 
49+ const  pointAtLengthCache  =  new  Map < string ,  {  x : number ;  y : number  } > ( ) ; 
50+ 
4451export  const  TrackCanvas  =  ( {  trackId,  drivers } : TrackProps )  =>  { 
4552  const  [ positions ,  setPositions ]  =  useState < 
4653    Record < number ,  TrackDriver  &  {  position : {  x : number ;  y : number  }  } > 
@@ -53,7 +60,9 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
5360    Record < number ,  TrackDriver  &  {  position : {  x : number ;  y : number  }  } > 
5461  > ( { } ) ; 
5562  const  lastUpdateTimeRef  =  useRef < number > ( 0 ) ; 
63+   const  lastRenderTimeRef  =  useRef < number > ( 0 ) ; 
5664  const  pendingDriversRef  =  useRef < TrackDriver [ ] > ( [ ] ) ; 
65+   const  needsRedrawRef  =  useRef < boolean > ( false ) ; 
5766
5867  // Memoize the SVG path element 
5968  const  line  =  useMemo ( ( )  =>  { 
@@ -81,7 +90,7 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
8190    } ; 
8291  } ,  [ trackDrawing ?. active ?. inside ,  trackDrawing ?. startFinish ?. line ] ) ; 
8392
84-   // Memoize color calculations 
93+   // Memoize color calculations - only recalculate when drivers change  
8594  const  driverColors  =  useMemo ( ( )  =>  { 
8695    const  colors : Record < number ,  {  fill : string ;  text : string  } >  =  { } ; 
8796
@@ -112,25 +121,37 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
112121    trackDrawing ?. startFinish ?. point ?. length , 
113122  ] ) ; 
114123
115-   // Optimized position calculation 
124+   // Helper to quantize progress 
125+   const  quantizeProgress  =  ( progress : number )  =>  Math . round ( progress  *  500 )  /  500 ;  // 0.002 steps 
126+ 
127+   // Optimized position calculation with caching 
116128  const  updateCarPosition  =  useCallback ( 
117129    ( percent : number )  =>  { 
118130      if  ( ! trackConstants )  return  {  x : 0 ,  y : 0  } ; 
131+       if  ( ! line )  return  {  x : 0 ,  y : 0  } ; 
119132
120133      const  {  direction,  intersectionLength,  totalLength }  =  trackConstants ; 
121-       const  adjustedLength  =  ( totalLength  *  percent )  %  totalLength ; 
134+       const  quantized  =  quantizeProgress ( percent ) ; 
135+       const  cacheKey  =  `${ trackId } ${ quantized }  ; 
136+       if  ( pointAtLengthCache . has ( cacheKey ) )  { 
137+         const  cached  =  pointAtLengthCache . get ( cacheKey ) ; 
138+         if  ( cached )  return  cached ; 
139+       } 
140+ 
141+       const  adjustedLength  =  ( totalLength  *  quantized )  %  totalLength ; 
122142      const  length  = 
123143        direction  ===  'anticlockwise' 
124144          ? ( intersectionLength  +  adjustedLength )  %  totalLength 
125145          : ( intersectionLength  -  adjustedLength  +  totalLength )  %  totalLength ; 
126-       const  point  =  line ?. getPointAtLength ( length ) ; 
127- 
128-       return  {  x : point ?. x  ||  0 ,  y : point ?. y  ||  0  } ; 
146+       const  point  =  line . getPointAtLength ( length ) ; 
147+       const  result  =  {  x : point ?. x  ||  0 ,  y : point ?. y  ||  0  } ; 
148+       pointAtLengthCache . set ( cacheKey ,  result ) ; 
149+       return  result ; 
129150    } , 
130-     [ trackConstants ,  line ] 
151+     [ trackConstants ,  line ,   trackId ] 
131152  ) ; 
132153
133-   // Check if drivers have actually changed  
154+   // Optimized driver change detection - only check essential properties  
134155  const  driversChanged  =  useMemo ( ( )  =>  { 
135156    if  ( drivers . length  !==  lastDriversRef . current . length )  return  true ; 
136157
@@ -145,7 +166,7 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
145166    } ) ; 
146167  } ,  [ drivers ] ) ; 
147168
148-   // Throttled  position update effect 
169+   // Optimized  position update effect - reduce frequency and batch updates  
149170  useEffect ( ( )  =>  { 
150171    if  ( ! trackConstants  ||  ! drivers ?. length )  return ; 
151172
@@ -172,34 +193,12 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
172193      lastDriversRef . current  =  drivers ; 
173194      lastPositionsRef . current  =  updatedPositions ; 
174195      lastUpdateTimeRef . current  =  now ; 
196+       needsRedrawRef . current  =  true ;  // Mark that we need to redraw 
175197    } 
176198  } ,  [ drivers ,  trackConstants ,  updateCarPosition ,  driversChanged ] ) ; 
177199
178-   // Set up a timer for periodic position updates 
179-   useEffect ( ( )  =>  { 
180-     if  ( ! trackConstants )  return ; 
181- 
182-     const  intervalId  =  setInterval ( ( )  =>  { 
183-       const  pendingDrivers  =  pendingDriversRef . current ; 
184-       if  ( ! pendingDrivers ?. length )  return ; 
185- 
186-       const  updatedPositions  =  pendingDrivers . reduce ( 
187-         ( acc ,  {  driver,  progress,  isPlayer } )  =>  { 
188-           const  position  =  updateCarPosition ( progress ) ; 
189-           return  { 
190-             ...acc , 
191-             [ driver . CarIdx ] : {  position,  driver,  isPlayer,  progress } , 
192-           } ; 
193-         } , 
194-         { }  as  Record < number ,  TrackDriver  &  {  position : {  x : number ;  y : number  }  } > 
195-       ) ; 
196- 
197-       setPositions ( updatedPositions ) ; 
198-       lastPositionsRef . current  =  updatedPositions ; 
199-     } ,  POSITION_UPDATE_INTERVAL ) ; 
200- 
201-     return  ( )  =>  clearInterval ( intervalId ) ; 
202-   } ,  [ trackConstants ,  updateCarPosition ] ) ; 
200+   // Remove the separate interval - we'll handle updates in the render loop 
201+   // This eliminates the competing timers that were blocking the event loop 
203202
204203  useEffect ( ( )  =>  { 
205204    const  ctx  =  canvasRef . current ?. getContext ( '2d' ) ; 
@@ -314,7 +313,7 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
314313      ctx . restore ( ) ; 
315314    } ; 
316315
317-     // Check if positions have actually changed  
316+     // Optimized position change detection  
318317    const  positionsChanged  =  ( )  =>  { 
319318      const  currentPositions  =  Object . keys ( positions ) ; 
320319      const  lastPositions  =  Object . keys ( lastPositionsRef . current ) ; 
@@ -329,20 +328,27 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
329328        if  ( ! current  ||  ! last )  return  true ; 
330329
331330        return  ( 
332-           Math . abs ( current . position . x  -  last . position . x )  >  0.1  || 
333-           Math . abs ( current . position . y  -  last . position . y )  >  0.1  || 
331+           Math . abs ( current . position . x  -  last . position . x )  >  POSITION_CHANGE_THRESHOLD  || 
332+           Math . abs ( current . position . y  -  last . position . y )  >  POSITION_CHANGE_THRESHOLD  || 
334333          current . isPlayer  !==  last . isPlayer  || 
335334          current . driver . CarNumber  !==  last . driver . CarNumber 
336335        ) ; 
337336      } ) ; 
338337    } ; 
339338
340-     // Only animate if positions have changed  
339+     // Optimized animation loop with throttling  
341340    const  animate  =  ( )  =>  { 
342-       if  ( positionsChanged ( ) )  { 
341+       const  now  =  Date . now ( ) ; 
342+       const  timeSinceLastRender  =  now  -  lastRenderTimeRef . current ; 
343+ 
344+       // Only redraw if enough time has passed AND we have changes 
345+       if  ( timeSinceLastRender  >=  RENDER_THROTTLE_MS  &&  ( needsRedrawRef . current  ||  positionsChanged ( ) ) )  { 
343346        draw ( ) ; 
344347        lastPositionsRef . current  =  {  ...positions  } ; 
348+         lastRenderTimeRef . current  =  now ; 
349+         needsRedrawRef . current  =  false ; 
345350      } 
351+ 
346352      animationFrameIdRef . current  =  requestAnimationFrame ( animate ) ; 
347353    } ; 
348354
0 commit comments