Skip to content

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

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions