地图编辑器仿真 tick 重建抑制

07/27综合

UI 重建频率 = 事件分发的可靠性上限。 只要有一帧 SetRoot 销毁了正在处理 pointer 事件的控件,跨重建边界的 Down/Up 对就会被吞。高频仿真 tick 必须在 UI 边界按页面过滤。
horizontal linehorizontal line

关键代码(scripts/main.lua HandleUpdate)

四步走:先原地处理人员 → 聚合所有仿真 changed → 地图页强制压制 → 只放行显式/登录态变化。
lua
-- main.lua (HandleUpdate) -- 0) 各仿真服务独立更新,各自返回 changed 标志 local dailyChanged = CampusDaily.Update(timeStep) local playerChanged = PlayerComponentService.Update(timeStep) local planChanged = PersonalPlanService.Update(timeStep) local explorationChanged= RoomExplorationService.Update(timeStep) local hygieneChanged = CampusHygieneService.Update(timeStep) local personnelChanged = CampusPersonnel.Update(timeStep) local behaviorChanged = BehaviorObservationService.Update(timeStep) local observationChanged= ObservationTaskService.Update(timeStep) -- 1) 人员移动在地图页内原地刷新,走独立通道,不重建 if personnelChanged and currentPage_ == "mapEditor" then MapEditorPage.UpdateDynamicCharacters() personnelChanged = false end -- 2) 聚合所有仿真 tick 的 changed 标志(电平型,NPC 移动/思考时会"长期 true") local simulationChanged = dailyChanged or playerChanged or planChanged or explorationChanged or hygieneChanged or personnelChanged or behaviorChanged or observationChanged -- 3) 地图编辑器页强制压制——编辑页绝不因后台仿真每帧重建 if currentPage_ == "mapEditor" then simulationChanged = false end -- 4) 只允许三类显式信号触发整树重建 if simulationChanged or context_.rebuildUIPending -- RequestUIRebuild() 显式调用 or uid ~= lastLoggedUserId_ -- 登录用户切换 or isAdmin ~= lastLoggedAdminState_ -- 管理员状态变化 then context_.rebuildUIPending = false RebuildUI() -- 内部调用 UI.SetRoot(root, true) end
horizontal linehorizontal line

原地刷新关键代码(地图编辑模式.lua

人员位置每帧变,但不重建 UI,通过保留 widget 引用用 SetStyle 原地更新:
lua
-- 已放置人员的 dot widget 在进入编辑页时就建好,缓存到 dynamicCharDots_ function M.UpdateDynamicCharacters() for id, dot in pairs(dynamicCharDots_) do local char = MapData.data.characters[id] local sx, sy = WorldToScreenLocal(char.x, char.y) dot:SetStyle({ left = sx, top = sy }) -- 原地改 transform,无重建 end end
horizontal linehorizontal line

显式重建入口(被业务代码在结构变化时调用)

lua
function RequestUIRebuild() context_.rebuildUIPending = true end -- 调用场景:新增/删除房间、切换工具、打开/关闭面板(结构性变化,非每帧)
horizontal linehorizontal line

根因(为什么"放完房间要等一会才能再点左栏")

层级现象
仿真层CampusNavigation.Update:NPC 在校日常移动时每帧返回 changed=true
UI 层每帧 UI.SetRoot(root, true) → 整棵 widget 树(含左栏 VirtualList)以 ~60 fps 销毁重建
事件层pointerDown 落在旧控件 A → 下一帧 A 被销毁 → pointerUp 到达时 A 不存在 → OnClick 不触发
用户感知点"物品"按钮无响应;等待 1–2 秒后 NPC 恰好集体到达目的地,重建频率下降,点击恢复
horizontal linehorizontal line

新增仿真服务时的检查清单

  1. ☐ 该 changed 是否会在 NPC 持续移动/思考时"每帧 true"?→并入 simulationChanged 聚合,不要单独加进重建条件
  2. ☐ changed 是否驱动视觉?位置→原地 SetStyle文案/列表结构→走 RequestUIRebuild()
  3. ☐ 地图编辑器/菜单页等静态页一律压制 simulationChanged
  4. ☐ 如需"状态切换瞬间"刷新,让服务返回脉冲型 changed(切换瞬间 true,下一帧 false),脉冲可在所有页面放行
horizontal linehorizontal line

被否决方案(避免再踩)

  • 只压制 dailyChanged,放过 explorationChanged/behaviorChanged → 治标不治本
  • CampusNavigation.Update 改成只在状态变化瞬间返回 true → 破坏仿真语义(其他页面依赖每帧 changed 刷 HUD)
  • 给 VirtualList 加 key 复用 DOM 身份 → UI 库不支持跨 SetRoot 复用控件,事件监听器仍是新闭包
horizontal linehorizontal line
4