diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index d9b45a0..1767974 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -76,17 +76,73 @@ const monitorScene = async () => { // 用于存储 requestAnimationFrame 的 ID,方便在组件卸载或 WebSocket 关闭时取消动画循环。 let animationFrameId: number; + /** + * 批量更新机器人数据,减少渲染调用次数 + * @param updates 需要更新的机器人数据数组 + */ + const batchUpdateRobots = (updates: Array<{ id: string; data: RobotRealtimeInfo }>) => { + if (!editor.value || updates.length === 0) return; + + // 先更新所有机器人的状态信息,包括光圈渲染所需的状态 + updates.forEach(({ id, data }) => { + const { x, y, active, angle, path: points, isWaring, isFault, ...rest } = data; + + // 更新机器人基本信息 + editor.value?.updateRobot(id, rest); + + // 处理路径坐标转换,参考refreshRobot方法的逻辑 + let processedPath: Array<{ x: number; y: number }> | undefined; + if (points?.length) { + // 新路径:相对于机器人中心的坐标 + const cx = x || 37; // 机器人中心X坐标,默认37 + const cy = y || 37; // 机器人中心Y坐标,默认37 + processedPath = points.map((p) => ({ x: p.x - cx, y: p.y - cy })); + } + + // 更新机器人状态,触发光圈渲染 + const robotState = { active, isWaring, isFault, path: processedPath, angle }; + if (Object.values(robotState).some((v) => v !== undefined)) { + editor.value?.setValue({ id, robot: robotState }, { render: false, history: false, doEvent: false }); + } + }); + + // 批量设置位置和可见性,只渲染一次 + const positionUpdates = updates.map(({ id, data }) => { + const { x, y, angle } = data; + if (isNil(x) || isNil(y)) { + return { id, visible: false }; + } else { + const newX = x - 60; + const newY = y - 60; + const rotate = angle; + return { id, x: newX, y: newY, rotate, visible: true }; + } + }); + + // 使用Meta2D的批量更新方法,减少渲染调用 + positionUpdates.forEach((update) => { + editor.value?.setValue(update, { render: false, history: false, doEvent: false }); + }); + + // 批量更新完成后,统一渲染一次 + editor.value?.render(); + }; + /** * 渲染循环函数。 * 通过 requestAnimationFrame 以浏览器的刷新频率被调用。 * 采用时间分片(Time Slicing)策略,为每一帧的渲染操作设定一个时间预算(frameBudget), * 避免单帧处理过多数据导致UI阻塞。 + * 新增批量渲染优化,减少渲染调用次数。 */ const renderLoop = () => { const frameBudget = 8; // 每一帧的渲染预算,单位:毫秒。保守设置为8ms,为其他任务留出时间。 const startTime = performance.now(); - // 在时间预算内,持续处理机器人数据 + // 收集所有需要更新的机器人数据,但不立即渲染 + const updates: Array<{ id: string; data: RobotRealtimeInfo }> = []; + + // 在时间预算内,持续收集机器人数据 while (performance.now() - startTime < frameBudget && latestRobotData.size > 0) { // 获取并移除 Map 中的第一条数据 const entry = latestRobotData.entries().next().value; @@ -94,23 +150,17 @@ const monitorScene = async () => { const [id, data] = entry; latestRobotData.delete(id); - const { x, y, active, angle, path, isWaring, isFault, ...rest } = data; // 确保机器人仍然存在于编辑器中 if (editor.value?.checkRobotById(id)) { - // 更新机器人的非位置属性(如状态等) - editor.value?.updateRobot(id, rest); - - // 更新机器人的位置和可见性 - if (isNil(x) || isNil(y)) { - // 如果坐标为空,则隐藏机器人 - editor.value.updatePen(id, { visible: false }); - } else { - // 如果坐标有效,则刷新机器人在画布上的位置、角度等 - editor.value.refreshRobot(id, { x, y, active, angle, path, isWaring, isFault }); - } + updates.push({ id, data }); } } + // 批量更新机器人,减少渲染调用次数 + if (updates.length > 0) { + batchUpdateRobots(updates); + } + // 处理缓冲的自动门点数据 autoDoorSimulationService.processBufferedData(frameBudget, startTime);