可视化面板重构:动态玩家、环形拓扑与 30 秒到 100 毫秒

上一篇写了 Flow 粒子动画的实现。这一篇记录可视化面板的后续大改——从固定 5 玩家到动态玩家管理,从底部单侧 port 到四边环绕 port,以及一次 INP 性能修复。
动态玩家管理:从固定 5 人到无限扩展
之前的可视化面板硬编码了 5 个玩家(p1~p5),调试时只能看预设场景。实际联机房间里玩家是动态加入离开的,固定人数的面板根本不够用。
Create/Join 弹窗
现在点”添加玩家”会弹出一个对话框,输入玩家名后选择 Create(创建新房间)或 Join(加入已有房间):
1 | function showAddPlayerDialog() { |
Join 按钮旁边有个下拉框,实时列出当前所有房间。选好房间点 Join,玩家就加入去了。
动态 Store
Store 里加了 testPlayers Map,支持动态增删:
1 | // store.js |
不再受限于 PLAYER_IDS 数组。创建几个玩家就有几个,删除后 room card 也跟着消失。
Room Card 内嵌操作按钮
之前的测试操作都在侧边栏,切来切去很麻烦。现在直接在 room card 的每个玩家行里放了三个按钮:
1 | +-- Room A ----------------------+ |
发送按钮旁边有内联消息输入框,Enter 可快捷发送。这些按钮只在 test 模式下显示,monitor 模式隐藏。
实现上的坑:输入框要阻止 mousedown 事件,否则拖拽 room card 时会误触输入框焦点。setMode 切换时要递增 _v 版本号触发 html() 重渲染,否则按钮状态不会更新。
INP 性能修复:30 秒到 100 毫秒
这次改动里最头疼的问题。之前点击”发送”按钮后,界面卡死 30 秒以上才能操作。
根因分析
debugAction 函数里有三个严重阻塞:
waitForAnimationsDone(30s)— 等所有边动画跑完才释放按钮锁syncRooms重试循环 — 每次触发全量 graph sync,3 次重试 × 300ms = 900ms+- 末尾
renderPlayerCards()— 全量重建所有玩家卡片 DOM
三个加起来,一次操作要等 30 秒以上。
修复方案
1 | // 修复前: |
核心改动:
- 移除
waitForAnimationsDone— burst 队列已经处理了并发动画,不需要额外等待 syncRooms从 3 次重试改为单次 — send/heartbeat 不改变房间结构,SSE 会处理 flow 事件- 移除末尾
renderPlayerCards— SSE 推送会触发数据更新,不需要手动重建 DOM
实测效果:create ~118ms,send ~103ms。从 30 秒降到 100 毫秒,体验完全不同。
四边 Port 系统:从 5 个到 20 个
之前的 proxy 只在底部有 5 个 port(p0~p4),所有玩家连线都从底部出发。房间多了之后连线全挤在一起,视觉上很乱。
新设计:四边各 5 个 port
现在 proxy 的上、下、左、右四边各有 5 个 port,总共 20 个。port id 格式改为 ${side}-p${slot},例如 top-p0、right-p3。
1 | ["top", "right", "bottom", "left"].forEach((side) => { |
port 间距:相邻圆心距离 2 * portR + portClusterGap = 19px,圆球边缘视觉间隔 5px。
环形房间布局
房间不再排成一列,而是环绕 proxy 四周排列。room 按顺序分配到 top/right/bottom/left 四侧:
- top/bottom 侧横向排列
- left/right 侧纵向排列
- 6 房以内
ringGap = 74,尽量落在首屏 - 超过 6 房或 Monitor 模式
ringGap = 96
这样每个房间的连线方向不同,视觉上不会全挤在一起。
地铁线路由:玩家边的走线规则
之前用 X6 的 manhattan 自动 router 做玩家边,结果连线经常穿过 proxy 节点或者互相重叠。现在改成手写的地铁线路由。
核心规则
1 | function computeMetroEdgeGeometry(source, target, side, rank) { |
connector 改用 jumpover 类型,遇到其他边时画弧线跳过:
1 | connector: { |
每条边的 label 是 playerId@portId,例如 A00@9201,方便定位。
心跳功能:后端 + 前端联动
联机中心跳包是业务层的,proxy 主路径无法识别(心跳包夹在普通数据包里,没有独立 magic)。所以走 debug 显式触发:
后端
新增 /proxy/debug/heartbeat 入口:
1 | // cmd/proxy/debug.go |
前端
心跳事件用半透明灰粒子渲染(区别于 ingress 的绿色),点击❤️按钮触发。
UI/UX 改进
- 中文 i18n:侧边栏和 UI 文本全部改为中文
- Loading 动画:侧边栏数据加载时显示 loading
- Tooltip 图标:说明文本改为可点击 ? 图标 tooltip
- 指标栏整合:Proxy PID 和运行时间从侧边栏迁移到顶部指标栏
- 侧边栏始终展开:移除收缩功能,避免误触
- 消息输入框加宽:从 80px 逐步加宽到 210px,适配 350px 卡片宽度
浏览器几何检查脚本
拓扑改完后需要验证连线几何是否正确。注入 6 房 × 5 玩家后跑这段脚本:
1 | const graph = window.MM.x6Topology.getGraph(); |
验收条件:所有 player edge 的首段和末段都不能是纯水平或纯垂直(firstDx !== 0 && firstDy !== 0)。
commit 时间线
| commit | 类型 | 内容 |
|---|---|---|
dd6024e |
feat | 动态玩家管理——不限人数、Create/Join 弹窗 |
6e9db3b |
feat | room card 内嵌玩家操作按钮 |
9f9d5b9 |
perf | INP 卡顿修复:30s → 100ms |
aaddb38 |
fix | manhattan 路由替换 orth 路由 |
e349378 |
fix | jumpover 连接器改用 arc 类型 |
0921bf8 |
docs | 记录拓扑绘制规则 |
5d5943f |
refactor | 移除侧边栏测试面板 |
d3bde5f |
feat | heartbeat 入口 + SSE 事件 |
4a8227c |
i18n | UI 文本全部改为中文 |
下一步打算接入真实的多人联机数据做压力测试,或者加 Prometheus 指标监控。如果你有其他想法,欢迎提 issue。