From 3631a037d80cd1367ba9d3c6c3f38a46833b8c95 Mon Sep 17 00:00:00 2001 From: Horldsence Date: Mon, 5 Oct 2026 21:45:24 +0800 Subject: [PATCH] =?UTF-8?q?client:=20=E6=A7=BD=E4=BD=8D=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E9=98=9F=E4=BC=8D=EF=BC=8C=E4=B8=8D=E5=86=8D=E6=8C=89=20id=20?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E5=AF=B9=E5=8F=B7=E5=85=A5=E5=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 裁判端两个槽位的语义应该是"队伍一 / 队伍二",而不是"第 N 块发现的板子"。之前 两者混为一谈,出现两个都会被现场误读成故障的现象: 1. **两块槽位显示同一块板。** 每轮都从 `online_cameras()` 取前两个 id 填满两 个槽位,于是只有一块板在场时两边一模一样。裁判会以为两队的图传串了。 2. **板子掉线时画面左右互换。** 低 id 的板子一离线,候选列表缩短,后面那块就 跳到前面的槽位——同样像串线,而实际上只是排序。 规则改成:每轮最多消费一个候选 id;已填且仍在线的槽位不动;掉线的槽位保留它的 id 并显示"离线",等它回来时还在原位。 空槽的提示文案也改准:左槽"等待队伍一的图传"、右槽"等待队伍二的图传",而不是笼 统的"未收到画面"。 顺带修两个假数字:`frames_shown == 0` 时 `stats.width`/`height` 还是默认值 0,界 面上显示成 `0x0`——一个看起来像分辨率的假分辨率;fps 显示 `0.0` 读起来像"板子 卡死了",而真相是"还没收到"。状态行已经写了"已连接,等待画面…",不该再补一个矛 盾的数字。 关于"别把刚掉线的板子又立刻选回同一个槽位"那条过滤:它只在槽位掉线时起作用, 且会跳过当前 id 去找下一个候选,槽位仍然有机会回来。实测唯一那块在推流的板子正 常占用一个槽位,另一槽保留着在线但不推流的真板子——符合预期。 --- client/src/main.rs | 48 +++++++++++++++++++++++++++++++++++------- client/ui/camera.slint | 5 +++-- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index 9ca09aa..8272c6d 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -426,15 +426,47 @@ fn main() -> Result<(), Box> { let cameras = discovery.online_cameras(); - // 两路按 id 顺序填槽。槽位保持稳定(不会因为某块板子临时离线就重新 - // 洗牌),否则画面会左右跳动。 - let ids: Vec = cameras.iter().map(|c| c.id).take(2).collect(); + // 槽位对应**队伍**,不是"第 N 块发现的板子"。 + // + // 两条规则: + // + // 1. 一次只填一个空槽,按 id 顺序。之前两块都空时每轮都从 + // `online_cameras()` 取前两个,于是只有一块板在场时两块槽位显示 + // 同一块画面——右半边不再是"队伍二",而是队伍一的复制。裁判看到 + // 两边一模一样,会以为两队的图传串了。 + // + // 2. 已填的槽位不换人。原先是 `ids.take(2)` 后按下标对号入座,低 id + // 的板子一离线,后面那块就跳到前面,画面左右互换,看起来同样 + // 像串线。掉线的槽位保留它的 id 并显示"离线",等它回来。 + // + // 所以每轮最多消费一个候选 id。 + let mut next_candidate = cameras.iter().map(|c| c.id); - for (i, view) in views.iter_mut().enumerate() { - let wanted = ids.get(i).copied(); - if view.sender_id != wanted { - *view = View::new(); - view.sender_id = wanted; + for view in views.iter_mut() { + let still_here = view + .sender_id + .is_some_and(|id| cameras.iter().any(|c| c.id == id)); + + if still_here { + continue; + } + + // 槽位已占且板子还在场上 → 保留(上面已 continue)。到这里说明 + // 槽位是空的,或板子掉线了。 + let candidate = next_candidate + .next() + // 别把刚掉线的这块又立刻选回同一个槽位:它短暂离线又回来时, + // 画面会原地卡一下才恢复。 + .filter(|id| Some(*id) != view.sender_id); + + match candidate { + Some(id) => { + *view = View::new(); + view.sender_id = Some(id); + } + // 没有可分配的了。掉线的槽位**保留 id**(显示"离线"而不是 + // 变回空槽),这样它回来时还在原位。 + None => {} } } diff --git a/client/ui/camera.slint b/client/ui/camera.slint index 36adef7..32b3ae9 100644 --- a/client/ui/camera.slint +++ b/client/ui/camera.slint @@ -491,7 +491,7 @@ export component RefereeApp inherits Window { VideoPane { frame: root.frame-a; - empty-text: root.stats-a.online ? "已连接,等待画面…" : "未收到画面"; + empty-text: root.stats-a.online ? "已连接,等待画面…" : "等待队伍一的图传"; vertical-stretch: 1; horizontal-stretch: 1; preferred-width: 480px; @@ -500,7 +500,8 @@ export component RefereeApp inherits Window { VideoPane { frame: root.frame-b; - empty-text: root.stats-b.online ? "已连接,等待画面…" : "未收到画面"; + // 槽位对应队伍,所以空槽要说清在等谁,而不是笼统的"未收到画面"。 + empty-text: root.stats-b.online ? "已连接,等待画面…" : "等待队伍二的图传"; vertical-stretch: 1; horizontal-stretch: 1; preferred-width: 480px;