Skip to content

perf(renderer): native UI-thread camera interpolator (post-Rust-port) #39

@LeslieOA

Description

@LeslieOA

Summary

From the zoom-rendering audit (Workspace CC). Architectural improvement that's only realistic to do AFTER the Rust core port (#6) lands.

What the audit found

animateCamera uses requestAnimationFrame (CanvasView.tsx:356). 18 bridge crossings × 3 shared-value writes ≈ 54 round-trips per 300ms animation. Reanimated worklets would do this all on the UI thread, no bridge — but withTiming, withSpring, and useFrameCallback all corrupted shared values to ~10^16 on Fabric + react-native-macos (called out in the same comment block: "the reanimated animation queue corrupts shared values on Fabric + react-native-macos"). The JS-RAF approach is the workaround.

Why this is Rust-port-blocked

In the Rust-backed renderer (#6), the camera mutation pipeline is owned end-to-end by Rust:

  • JSI / wasm-bindgen exposes set_camera(tx, ty, scale) on a native handle
  • A native interpolator (running on the UI thread on iOS / Android / macOS, or in a wasm worker on web) drives the camera matrix per-frame
  • No bridge crossings; no Reanimated dependency for this code path

This isn't an improvement of the current code — it's a different architecture that the Rust port enables. Filing as a Rust-port follow-up so it doesn't get lost.

Proposed fix shape

After #6 lands and src/core/ is Rust-backed:

  1. Add a camera::AnimateTo { to_tx, to_ty, to_scale, duration_ms, easing } operation to the Rust core's API
  2. Native interpolator runs the tick loop in Rust, calling a JSI callback (or Reanimated SharedValue write) once per frame from the UI thread
  3. JS-side animateCamera becomes a thin shim that calls into the Rust API
  4. Drop the requestAnimationFrame path; the animCancelled SharedValue flag becomes Rust-side state

Acceptance criteria

  • Native interpolator runs on the UI thread (verified via React DevTools / Hermes profiler — no JS thread time during animation)
  • No regression in the visual feel of fit / recenter / zoom-to-node
  • animCancelled semantics preserved (pinch / pan during animation cancels cleanly)
  • Sub-1ms JS thread time per frame during animation (currently ~3-5ms across bridge)
  • Reanimated dependency for the camera path becomes optional / removed

Depends on

Notes

Until #6 lands, this is filed for tracking only. No work to do now.

Refs

Surfaced during the zoom-rendering improvements audit (Workspace CC).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions