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:
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user