Fix floorplan preview update loop

This commit is contained in:
sudhir
2026-04-28 09:52:10 +05:30
parent bc93f09304
commit a64d6b86cc
7 changed files with 443 additions and 303 deletions
@@ -5361,6 +5361,7 @@ export function FloorplanPanel() {
const [shiftPressed, setShiftPressed] = useState(false)
const [rotationModifierPressed, setRotationModifierPressed] = useState(false)
const [movingFloorplanNodeRevision, setMovingFloorplanNodeRevision] = useState(0)
const movingFloorplanNodeRefreshFrameRef = useRef<number | null>(null)
const [stairBuildPreviewPoint, setStairBuildPreviewPoint] = useState<WallPlanPoint | null>(null)
const [stairBuildPreviewRotation, setStairBuildPreviewRotation] = useState(0)
const [isPanning, setIsPanning] = useState(false)
@@ -5388,6 +5389,27 @@ export function FloorplanPanel() {
setIsMacPlatform(navigator.platform.toUpperCase().includes('MAC'))
}, [])
const scheduleMovingFloorplanNodeRefresh = useCallback(() => {
if (movingFloorplanNodeRefreshFrameRef.current !== null) {
return
}
movingFloorplanNodeRefreshFrameRef.current = window.requestAnimationFrame(() => {
movingFloorplanNodeRefreshFrameRef.current = null
setMovingFloorplanNodeRevision((current) => current + 1)
})
}, [])
useEffect(
() => () => {
if (movingFloorplanNodeRefreshFrameRef.current !== null) {
window.cancelAnimationFrame(movingFloorplanNodeRefreshFrameRef.current)
movingFloorplanNodeRefreshFrameRef.current = null
}
},
[],
)
const sitePolygonEntry = useMemo(() => {
const polygonPoints = site?.polygon?.points
if (!(site && polygonPoints)) {
@@ -6913,10 +6935,35 @@ export function FloorplanPanel() {
return
}
if (!hasUserAdjustedViewportRef.current) {
// While the cursor drives live geometry (items, drafts, moves), `fittedViewport` changes every
// pointermove. Syncing `viewport` here would call setState in a tight loop (max update depth).
const transientFloorplanFit =
cursorPoint != null ||
movingNode != null ||
movingFenceEndpoint != null ||
curvingWall != null ||
curvingFence != null ||
slabVertexDragState != null ||
siteVertexDragState != null ||
zoneVertexDragState != null ||
isPolygonDraftBuildActive
if (!hasUserAdjustedViewportRef.current && !transientFloorplanFit) {
setViewport((current) => (floorplanViewportEquals(current, fittedViewport) ? current : fittedViewport))
}
}, [fittedViewport, levelId])
}, [
curvingFence,
curvingWall,
cursorPoint,
fittedViewport,
isPolygonDraftBuildActive,
levelId,
movingFenceEndpoint,
movingNode,
siteVertexDragState,
slabVertexDragState,
zoneVertexDragState,
])
const viewBox = useMemo(() => {
const currentViewport = viewport ?? fittedViewport
@@ -7661,7 +7708,7 @@ export function FloorplanPanel() {
}
const refreshFloorplanItemPreview = () => {
setMovingFloorplanNodeRevision((current) => current + 1)
scheduleMovingFloorplanNodeRefresh()
}
emitter.on('grid:move', refreshFloorplanItemPreview)
@@ -7687,21 +7734,15 @@ export function FloorplanPanel() {
emitter.off('item:move', refreshFloorplanItemPreview as any)
emitter.off('item:leave', refreshFloorplanItemPreview as any)
}
}, [isItemPlacementPreviewActive])
}, [isItemPlacementPreviewActive, scheduleMovingFloorplanNodeRefresh])
useEffect(() => {
if (!hasPendingItemMeshFootprints) {
return
}
const frameId = window.requestAnimationFrame(() => {
setMovingFloorplanNodeRevision((current) => current + 1)
})
return () => {
window.cancelAnimationFrame(frameId)
}
}, [hasPendingItemMeshFootprints])
scheduleMovingFloorplanNodeRefresh()
}, [hasPendingItemMeshFootprints, scheduleMovingFloorplanNodeRefresh])
useEffect(() => {
if (!(movingNode?.type === 'door' || movingNode?.type === 'window')) {
@@ -7710,7 +7751,7 @@ export function FloorplanPanel() {
const movingOpeningId = movingNode.id
const refreshOpeningPreview = () => {
setMovingFloorplanNodeRevision((current) => current + 1)
scheduleMovingFloorplanNodeRefresh()
}
refreshOpeningPreview()
@@ -7725,7 +7766,7 @@ export function FloorplanPanel() {
})
return unsubscribe
}, [movingNode])
}, [movingNode, scheduleMovingFloorplanNodeRefresh])
useEffect(() => {
if (movingNode?.type !== 'fence') {
@@ -7753,7 +7794,7 @@ export function FloorplanPanel() {
}
const refreshFencePreview = () => {
setMovingFloorplanNodeRevision((current) => current + 1)
scheduleMovingFloorplanNodeRefresh()
}
refreshFencePreview()
@@ -7768,7 +7809,7 @@ export function FloorplanPanel() {
})
return unsubscribe
}, [fences, movingNode])
}, [fences, movingNode, scheduleMovingFloorplanNodeRefresh])
useEffect(() => {
if (!(movingNode?.type === 'roof' || movingNode?.type === 'roof-segment')) {
@@ -7777,7 +7818,7 @@ export function FloorplanPanel() {
const movingRoofNodeId = movingNode.id
const refreshRoofPreview = () => {
setMovingFloorplanNodeRevision((current) => current + 1)
scheduleMovingFloorplanNodeRefresh()
}
refreshRoofPreview()
@@ -7792,7 +7833,7 @@ export function FloorplanPanel() {
})
return unsubscribe
}, [movingNode])
}, [movingNode, scheduleMovingFloorplanNodeRefresh])
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {