Skip to content

Run DFM in the eval worker, not on the main thread #189

@ecto

Description

@ecto

Problem

packages/engine/src/dfm.ts::runDfm evaluates the document and walks every part calling Solid.runDfm on the main thread. With the BVH-raymarched samplers (added on the claude/dfm-tools-integration-HcsqQ branch), thickness + accessibility + overhang each cost roughly O(F log F) per face, so a ~12-part assembly with ~50 faces per part can spend 100+ ms in DFM during parameter scrubbing — enough to drop frames in the viewport.

The eval worker already exists at packages/engine/src/eval-worker.ts but only handles type === "evaluate" (line 178). DFM has no worker path, so the live-check loop in packages/app/src/components/DfmPanel.tsx blocks the main thread on every debounced re-run.

Why now

Live DFM during scrubbing is the user-facing differentiator vs. competitors' button-press DFM. If we ship it but it stutters, the experience regresses. Worker-hosted DFM is the right way to keep the headline UX promise.

Proposed approach

  1. Extend the worker protocol with a type === "dfm" message:
    { type: "dfm", id, docJson, process, rulePackToml }
     { type: "dfm-result", id, reportJson }
  2. Inside the worker, evaluate the doc (it already has the kernel handle for evaluate), then call Solid.runDfm per part — same logic that's currently in runDfm on the main thread.
  3. Update runDfm in packages/engine/src/dfm.ts to post to the worker via the existing evaluateInWorker plumbing pattern in Engine (packages/engine/src/index.ts:497), then resolve with the parsed DfmReport.
  4. Add a worker pool or per-doc cache so concurrent runs (e.g., user toggles process while previous run is in flight) don't pile up.

Acceptance criteria

  • DFM runs no longer block the main thread; scrubbing stays at 60fps with live DFM enabled on a 50-face part.
  • The MCP dfm_check tool keeps working (it can either still call the synchronous in-process path or use the worker — either is fine).
  • A perf test in packages/engine/__tests__/ confirms runDfm returns within ~50ms for a single primitive and doesn't block when run repeatedly.

References

  • packages/engine/src/eval-worker.ts:178 — current case "evaluate" switch where the new dfm message slots in
  • packages/engine/src/index.ts:497evaluateInWorker plumbing pattern to mirror
  • packages/engine/src/dfm.ts — current main-thread runDfm implementation
  • packages/app/src/stores/dfm-store.ts — debounced re-run loop that benefits

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions