Vol.11
メインスレッドを止めない — 10 分固まるモデル読み込み、120fps の描画、録画に映る HUD
10 分以上、エラーなしで固まるモデル読み込み
杖のキーポイントモデルを TF.js の Layers 形式で読み込んでいた頃、モデルの構築がエラーを出さないまま 10 分を超えて終わらないことがありました(useWandKeypointDetector のコメントに「Layers-era hangs could exceed 10 minutes with no error」と残しています)。Vol.4 の node_index の不具合と同じく、TF.js のグラフ構築は壊れた入力に対して例外ではなく無限ループで応えることがあります。
対策は 3 段構えです。まず読み込みを Web Worker で行い、グラフ構築でメインスレッドが固まらないようにする。次に 90 秒のタイムアウトを付け、超えたら「杖モデルの読み込みがタイムアウトしました(90s)」と表示する。Worker で失敗した場合だけメインスレッドでの読み込みにフォールバックする。あわせて uint8 量子化と graph-model 形式への変換、読み込み進捗の表示も入れています。
1// Prefer Worker so graph construction does not freeze the page.2// Cap wait — Layers-era hangs could exceed 10 minutes with no error.3const LOAD_TIMEOUT_MS = 90_000;4 5try {6 const client = new WandModelWorkerClient();7 await withTimeout(client.load(modelPath, metadataPath, trackProgress));8 finishLoaded(Math.round(performance.now() - loadStarted), lastProgress, 'Wand model loaded (worker)');9 return;10} catch (workerError) {11 console.warn('[wand] worker load failed, falling back to main thread', workerError);12}13// ↓ メインスレッドで WandKeypointDetector を読み込む(同じタイムアウト付き)描画 120fps に Hands を合わせない
Call Studio の描画ループは、環境によって 120fps を超えることがあります(フックのコメントより)。描画のたびに Hands へ送ると、推論が描画の足を引っ張るうえに、MediaPipe 側は 15fps 程度しか返せないので送った大半が無駄になります。そこで Hands への送信は HANDS_SEND_INTERVAL_MS = 70(約 14fps)に間引き、間のフレームは damp(Vol.6)で補間しています。
重い同期処理の前には yieldToMain() でブラウザに描画と入力処理を返します。表示中のタブでは requestAnimationFrame の中で setTimeout(0)、バックグラウンドのタブや Worker では rAF が止まりうるので setTimeout(0) だけを使います。
計測 HUD を本体ループに載せない、録画に映さない
Performance HUD は DEV ビルド、?perf クエリ、localStorage のトグルのいずれかで表示します。本体の rAF ループには乗らず、500ms ごとにスナップショットを取るだけです。描画 FPS と手の追跡 FPS は別のカウンタで、追跡 FPS は parseTrackedHandsFromResults が呼ばれるたびに数えます。「描画が重いのか、推論が重いのか」を分けて見るためです。
Call Studio には別の事情があります。この画面は OBS にキャプチャされる側なので、カメラ映像の上に重ねたデバッグ表示はそのまま録画に映ります。2026-09-27 に Tasks Vision へ移行した際、認識状態や遮蔽・印のデバッグ表示をカメラのオーバーレイから外し、サイドパネル(getDebugHud)へ移しました。同じ日に、移行の切り分けで仕込んでいたランドマークのデバッグ HUD と送信経路のプローブも削除し、detectForVideo の所要時間を出す HUD だけを残しています。
実装から得た知見
- モデル読み込みには必ずタイムアウトを付ける。TF.js は壊れたグラフに対して例外ではなく無限ループで応えることがある。
- 推論への送信頻度は描画のフレームレートと切り離す。高リフレッシュレート環境では、描画ごとに送ると大半が無駄になる。
- 撮影される画面では、デバッグ表示の置き場所も設計の一部になる。カメラのオーバーレイに描いたものは全部録画に映る。
- 切り分けのために仕込んだ HUD やプローブは、原因が分かったら消す。残すのは今後も見る数字(detect の所要時間)だけにする。