perf(editor): skip floorplan viewport sync while the 2D panel is hidden
Camera-zoom hitch: the FloorplanPanel stays mounted (display:none in 3D mode), so its navigation-pose subscriber fired every camera onUpdate. During zoom the view-width changes continuously, so the epsilon guard never short-circuited and `syncFloorplanViewportToNavigationPose` ran each frame → setViewport/ setFloorplanUserRotationDeg → a full re-render of the ~10k-line floorplan SVG, even though nothing is visible (React reconciles display:none subtrees). Gate the viewport sync on `isFloorplanOpen` via a ref the per-frame subscriber reads, and re-run the mount catch-up effect when the panel reopens so the viewport snaps to the current camera. The compass is unaffected — it's portaled to the always-visible viewer area and still receives the pose; only the panel's own viewport sync is skipped while hidden. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
bbe5b9a8eb
commit
b384bc8178
@@ -5046,6 +5046,11 @@ export function FloorplanPanel({
|
|||||||
// the user closes and re-opens the 2D editor instead of restoring the
|
// the user closes and re-opens the 2D editor instead of restoring the
|
||||||
// stale viewport from before they closed it.
|
// stale viewport from before they closed it.
|
||||||
const isFloorplanOpen = useEditor((state) => state.isFloorplanOpen)
|
const isFloorplanOpen = useEditor((state) => state.isFloorplanOpen)
|
||||||
|
// Mirror for callbacks that fire outside React's render (the per-frame
|
||||||
|
// navigation-pose subscriber): when the 2D panel is hidden (`display:none` in
|
||||||
|
// 3D mode) it must NOT re-render on every camera-zoom frame.
|
||||||
|
const isFloorplanOpenRef = useRef(isFloorplanOpen)
|
||||||
|
isFloorplanOpenRef.current = isFloorplanOpen
|
||||||
const selectedReferenceId = useEditor((state) => state.selectedReferenceId)
|
const selectedReferenceId = useEditor((state) => state.selectedReferenceId)
|
||||||
const setSelectedReferenceId = useEditor((state) => state.setSelectedReferenceId)
|
const setSelectedReferenceId = useEditor((state) => state.setSelectedReferenceId)
|
||||||
const setMode = useEditor((state) => state.setMode)
|
const setMode = useEditor((state) => state.setMode)
|
||||||
@@ -6441,6 +6446,13 @@ export function FloorplanPanel({
|
|||||||
|
|
||||||
const syncFloorplanViewportToNavigationPose = useCallback(
|
const syncFloorplanViewportToNavigationPose = useCallback(
|
||||||
(pose: NavigationSyncPose) => {
|
(pose: NavigationSyncPose) => {
|
||||||
|
// Skip the viewport sync while the 2D panel is hidden (3D mode). It writes
|
||||||
|
// React state (`setViewport`) that re-renders the whole floorplan SVG, so
|
||||||
|
// doing it every camera-zoom frame for an invisible panel was a needless
|
||||||
|
// per-frame stall. The catch-up effect below re-syncs on reopen.
|
||||||
|
if (!isFloorplanOpenRef.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (floorplanRotationStateRef.current) {
|
if (floorplanRotationStateRef.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -6471,9 +6483,12 @@ export function FloorplanPanel({
|
|||||||
|
|
||||||
latestNavigationSyncPoseRef.current = pose
|
latestNavigationSyncPoseRef.current = pose
|
||||||
if (pose.source === '3d') {
|
if (pose.source === '3d') {
|
||||||
|
// Re-runs when the panel reopens (`isFloorplanOpen`) so the viewport
|
||||||
|
// catches up to the camera after the per-frame sync was skipped while
|
||||||
|
// hidden; a no-op while closed (the sync early-returns).
|
||||||
syncFloorplanViewportToNavigationPose(pose)
|
syncFloorplanViewportToNavigationPose(pose)
|
||||||
}
|
}
|
||||||
}, [syncFloorplanViewportToNavigationPose])
|
}, [isFloorplanOpen, syncFloorplanViewportToNavigationPose])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return useEditor.subscribe((state) => {
|
return useEditor.subscribe((state) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user