fix(handtracking): absorb React StrictMode double-mount
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (push) Has been cancelled
📊 Quality / 🔒 Security Audit (push) Has been cancelled
📊 Quality / 📋 Dependency Freshness (push) Has been cancelled
📊 Quality / 📦 Bundle Size (push) Has been cancelled
🔍 Lint / 🏗 Build (push) Has been cancelled
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (push) Has been cancelled
📊 Quality / 🔒 Security Audit (push) Has been cancelled
📊 Quality / 📋 Dependency Freshness (push) Has been cancelled
📊 Quality / 📦 Bundle Size (push) Has been cancelled
🔍 Lint / 🏗 Build (push) Has been cancelled
In dev, <StrictMode> intentionally mounts → unmounts → remounts each effect to surface non-idempotent code. The hand tracking hooks were calling getUserMedia and creating MediaPipe / WebSocket runtimes on every mount, which in practice ran the full start/stop/start cycle inside a few milliseconds and pushed WebGL over its limit on top of the loaded scene → context lost. Add HAND_TRACKING_RUNTIME_START_DELAY_MS (80ms) and delay the actual start() call behind a setTimeout in both useBrowserHandTracking and useRemoteHandTracking. The cleanup clears the timer, so a fast mount/unmount never reaches start(). 80ms is invisible to the user (<5 frames at 60fps) and also absorbs rapid `nearby` toggles at trigger borders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,3 +9,8 @@ export const HAND_TRACKING_BROWSER_WASM_URL =
|
||||
export const HAND_TRACKING_BROWSER_MODEL_URL =
|
||||
"https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";
|
||||
export const HAND_TRACKING_BROWSER_DELEGATE: "CPU" | "GPU" = "CPU";
|
||||
|
||||
// Delay before the runtime actually starts after `enabled` flips to true.
|
||||
// Absorbs React StrictMode's mount/unmount/mount cycle in dev and rapid
|
||||
// `nearby` toggles at trigger borders. Invisible to the user (~5 frames).
|
||||
export const HAND_TRACKING_RUNTIME_START_DELAY_MS = 80;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
HAND_TRACKING_FRAME_HEIGHT,
|
||||
HAND_TRACKING_FRAME_WIDTH,
|
||||
HAND_TRACKING_RUNTIME_START_DELAY_MS,
|
||||
HAND_TRACKING_TARGET_FPS,
|
||||
} from "@/data/handTrackingConfig";
|
||||
import {
|
||||
@@ -169,10 +170,17 @@ export function useBrowserHandTracking({
|
||||
}
|
||||
};
|
||||
|
||||
void start();
|
||||
// Delay the actual start so that a StrictMode mount/unmount/mount
|
||||
// cycle, or a rapid `enabled` toggle at a trigger border, does not
|
||||
// spin up the camera + MediaPipe twice in a few milliseconds.
|
||||
const startTimer = window.setTimeout(() => {
|
||||
if (cancelled) return;
|
||||
void start();
|
||||
}, HAND_TRACKING_RUNTIME_START_DELAY_MS);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearTimeout(startTimer);
|
||||
cleanup();
|
||||
};
|
||||
}, [enabled]);
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
HAND_TRACKING_FRAME_WIDTH,
|
||||
HAND_TRACKING_JPEG_QUALITY,
|
||||
HAND_TRACKING_RESPONSE_TIMEOUT_MS,
|
||||
HAND_TRACKING_RUNTIME_START_DELAY_MS,
|
||||
HAND_TRACKING_TARGET_FPS,
|
||||
} from "@/data/handTrackingConfig";
|
||||
import { getHandTrackingWsUrl } from "@/utils/handTracking/handTrackingEndpoint";
|
||||
@@ -330,10 +331,17 @@ export function useRemoteHandTracking({
|
||||
}
|
||||
};
|
||||
|
||||
void start();
|
||||
// Delay the actual start so that a StrictMode mount/unmount/mount
|
||||
// cycle, or a rapid `enabled` toggle at a trigger border, does not
|
||||
// open the camera + WebSocket twice in a few milliseconds.
|
||||
const startTimer = window.setTimeout(() => {
|
||||
if (cancelled) return;
|
||||
void start();
|
||||
}, HAND_TRACKING_RUNTIME_START_DELAY_MS);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearTimeout(startTimer);
|
||||
cleanup();
|
||||
};
|
||||
}, [enabled, websocketUrl]);
|
||||
|
||||
Reference in New Issue
Block a user