Skip to content

Commit aca3a43

Browse files
msilivonik-scclaude
andcommitted
fix: separate measurement lifecycle from polling interval
Changing the polling interval no longer restarts the measurement and discards accumulated stats. The measurement lifecycle is now keyed only on session/enabled, while the polling interval is managed in a separate effect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d043cf4 commit aca3a43

2 files changed

Lines changed: 41 additions & 10 deletions

File tree

src/useLensFrameMetrics.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,30 @@ describe("useLensFrameMetrics", () => {
221221
expect(session.metrics.beginMeasurement).toHaveBeenCalledTimes(1);
222222
});
223223

224+
it("does not restart measurement when interval changes", () => {
225+
const { measurement, session } = setupContext();
226+
227+
const { rerender } = renderHook(({ interval }) => useLensFrameMetrics({ interval }), {
228+
initialProps: { interval: 500 },
229+
});
230+
231+
expect(session.metrics.beginMeasurement).toHaveBeenCalledTimes(1);
232+
measurement.end.mockClear();
233+
234+
rerender({ interval: 1000 });
235+
236+
// Should NOT end/restart the measurement
237+
expect(measurement.end).not.toHaveBeenCalled();
238+
expect(session.metrics.beginMeasurement).toHaveBeenCalledTimes(1);
239+
240+
// But should poll at the new interval
241+
act(() => {
242+
jest.advanceTimersByTime(1000);
243+
});
244+
245+
expect(measurement.measure).toHaveBeenCalled();
246+
});
247+
224248
it("clears metrics state when session is lost", () => {
225249
const { measurement } = setupContext();
226250
measurement.measure.mockReturnValue(createMockMetrics({ avgFps: 60 }));

src/useLensFrameMetrics.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ interface UseLensFrameMetricsOptions {
3131
* ```
3232
*/
3333
export function useLensFrameMetrics(options: UseLensFrameMetricsOptions): ComputedFrameMetrics | undefined {
34-
const { enabled = true } = options;
34+
const { enabled = true, interval } = options;
3535
const { currentSession, lens } = useInternalCameraKit();
3636
const [metrics, setMetrics] = useState<ComputedFrameMetrics | undefined>(undefined);
37-
const measurementRef = useRef<LensPerformanceMeasurement | null>(null);
37+
const measurementRef = useRef<LensPerformanceMeasurement | undefined>(undefined);
3838
const isFirstLensRender = useRef(true);
3939

40-
// Begin/end measurement and set up polling interval
40+
// Begin/end measurement lifecycle
4141
useEffect(() => {
4242
if (!currentSession || !enabled) {
4343
setMetrics(undefined);
@@ -47,17 +47,24 @@ export function useLensFrameMetrics(options: UseLensFrameMetricsOptions): Comput
4747
const measurement = currentSession.metrics.beginMeasurement();
4848
measurementRef.current = measurement;
4949

50-
const intervalId = setInterval(() => {
51-
setMetrics(measurement.measure());
52-
}, options.interval);
53-
5450
return () => {
55-
clearInterval(intervalId);
5651
measurement.end();
57-
measurementRef.current = null;
52+
measurementRef.current = undefined;
5853
isFirstLensRender.current = true;
5954
};
60-
}, [currentSession, options.interval, enabled]);
55+
}, [currentSession, enabled]);
56+
57+
// Poll measurement on interval (separate so changing interval doesn't restart measurement)
58+
useEffect(() => {
59+
if (!currentSession || !enabled) return;
60+
61+
const intervalId = setInterval(() => {
62+
const result = measurementRef.current?.measure();
63+
if (result) setMetrics(result);
64+
}, interval);
65+
66+
return () => clearInterval(intervalId);
67+
}, [currentSession, enabled, interval]);
6168

6269
// Reset measurement when lens changes (skip on initial mount)
6370
useEffect(() => {

0 commit comments

Comments
 (0)