Skip to content

Commit b726bfa

Browse files
committed
chore: add caching to get pointAtLength to help with trackmap performance
1 parent b3194e3 commit b726bfa

File tree

2 files changed

+73
-47
lines changed

2 files changed

+73
-47
lines changed

src/frontend/components/TrackMap/TrackCanvas.tsx

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,19 @@ export interface TrackDrawing {
3535
// currently its a bit messy with the turns, so we disable them for now
3636
const 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

4142
const TRACK_DRAWING_WIDTH = 1920;
4243
const 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+
4451
export 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

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,46 @@
1+
import { useMemo, useRef, useEffect, useState } from 'react';
12
import {
23
useDriverCarIdx,
34
useSessionDrivers,
45
useTelemetryValuesMapped,
56
} from '@irdashies/context';
67

8+
// Throttle updates to reduce processing load
9+
const UPDATE_THROTTLE_MS = 50; // 20fps instead of 60fps
10+
711
export const useDriverProgress = () => {
812
const driverIdx = useDriverCarIdx();
913
const drivers = useSessionDrivers();
1014
const driversLapDist = useTelemetryValuesMapped<number[]>(
1115
'CarIdxLapDistPct',
12-
(val) => Math.round(val * 1000) / 1000
16+
(val) => Math.round(val * 100) / 100 // Reduce precision to 2 decimal places to minimize unnecessary updates
1317
);
1418

15-
const driversTrackData =
16-
drivers
17-
?.map((driver) => ({
19+
// Throttled state to reduce update frequency
20+
const [throttledLapDist, setThrottledLapDist] = useState<number[]>([]);
21+
const lastUpdateRef = useRef<number>(0);
22+
23+
// Throttle the lap distance updates
24+
useEffect(() => {
25+
const now = Date.now();
26+
if (now - lastUpdateRef.current >= UPDATE_THROTTLE_MS) {
27+
setThrottledLapDist(driversLapDist || []);
28+
lastUpdateRef.current = now;
29+
}
30+
}, [driversLapDist]);
31+
32+
const driversTrackData = useMemo(() => {
33+
if (!drivers || !throttledLapDist.length) return [];
34+
35+
return drivers
36+
.map((driver) => ({
1837
driver: driver,
19-
progress: driversLapDist[driver.CarIdx],
38+
progress: throttledLapDist[driver.CarIdx] ?? -1,
2039
isPlayer: driver.CarIdx === driverIdx,
2140
}))
2241
.filter((d) => d.progress > -1) // ignore drivers not on track
23-
.filter((d) => d.driver.CarIdx > 0) ?? []; // ignore pace car for now
42+
.filter((d) => d.driver.CarIdx > 0); // ignore pace car for now
43+
}, [drivers, throttledLapDist, driverIdx]);
2444

2545
return driversTrackData;
2646
};

0 commit comments

Comments
 (0)