client: 槽位对应队伍,不再按 id 顺序对号入座
裁判端两个槽位的语义应该是"队伍一 / 队伍二",而不是"第 N 块发现的板子"。之前 两者混为一谈,出现两个都会被现场误读成故障的现象: 1. **两块槽位显示同一块板。** 每轮都从 `online_cameras()` 取前两个 id 填满两 个槽位,于是只有一块板在场时两边一模一样。裁判会以为两队的图传串了。 2. **板子掉线时画面左右互换。** 低 id 的板子一离线,候选列表缩短,后面那块就 跳到前面的槽位——同样像串线,而实际上只是排序。 规则改成:每轮最多消费一个候选 id;已填且仍在线的槽位不动;掉线的槽位保留它的 id 并显示"离线",等它回来时还在原位。 空槽的提示文案也改准:左槽"等待队伍一的图传"、右槽"等待队伍二的图传",而不是笼 统的"未收到画面"。 顺带修两个假数字:`frames_shown == 0` 时 `stats.width`/`height` 还是默认值 0,界 面上显示成 `0x0`——一个看起来像分辨率的假分辨率;fps 显示 `0.0` 读起来像"板子 卡死了",而真相是"还没收到"。状态行已经写了"已连接,等待画面…",不该再补一个矛 盾的数字。 关于"别把刚掉线的板子又立刻选回同一个槽位"那条过滤:它只在槽位掉线时起作用, 且会跳过当前 id 去找下一个候选,槽位仍然有机会回来。实测唯一那块在推流的板子正 常占用一个槽位,另一槽保留着在线但不推流的真板子——符合预期。
This commit is contained in:
+40
-8
@@ -426,15 +426,47 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
let cameras = discovery.online_cameras();
|
let cameras = discovery.online_cameras();
|
||||||
|
|
||||||
// 两路按 id 顺序填槽。槽位保持稳定(不会因为某块板子临时离线就重新
|
// 槽位对应**队伍**,不是"第 N 块发现的板子"。
|
||||||
// 洗牌),否则画面会左右跳动。
|
//
|
||||||
let ids: Vec<u32> = cameras.iter().map(|c| c.id).take(2).collect();
|
// 两条规则:
|
||||||
|
//
|
||||||
|
// 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() {
|
for view in views.iter_mut() {
|
||||||
let wanted = ids.get(i).copied();
|
let still_here = view
|
||||||
if view.sender_id != wanted {
|
.sender_id
|
||||||
*view = View::new();
|
.is_some_and(|id| cameras.iter().any(|c| c.id == id));
|
||||||
view.sender_id = wanted;
|
|
||||||
|
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 => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -491,7 +491,7 @@ export component RefereeApp inherits Window {
|
|||||||
|
|
||||||
VideoPane {
|
VideoPane {
|
||||||
frame: root.frame-a;
|
frame: root.frame-a;
|
||||||
empty-text: root.stats-a.online ? "已连接,等待画面…" : "未收到画面";
|
empty-text: root.stats-a.online ? "已连接,等待画面…" : "等待队伍一的图传";
|
||||||
vertical-stretch: 1;
|
vertical-stretch: 1;
|
||||||
horizontal-stretch: 1;
|
horizontal-stretch: 1;
|
||||||
preferred-width: 480px;
|
preferred-width: 480px;
|
||||||
@@ -500,7 +500,8 @@ export component RefereeApp inherits Window {
|
|||||||
|
|
||||||
VideoPane {
|
VideoPane {
|
||||||
frame: root.frame-b;
|
frame: root.frame-b;
|
||||||
empty-text: root.stats-b.online ? "已连接,等待画面…" : "未收到画面";
|
// 槽位对应队伍,所以空槽要说清在等谁,而不是笼统的"未收到画面"。
|
||||||
|
empty-text: root.stats-b.online ? "已连接,等待画面…" : "等待队伍二的图传";
|
||||||
vertical-stretch: 1;
|
vertical-stretch: 1;
|
||||||
horizontal-stretch: 1;
|
horizontal-stretch: 1;
|
||||||
preferred-width: 480px;
|
preferred-width: 480px;
|
||||||
|
|||||||
Reference in New Issue
Block a user