如何减少终端重画时的闪烁?

问题描述 投票:0回答:2

我有一个程序可以显示一些并行运行的命令的状态

fmt    ✔
clippy cargo clippy --tests --color always ... 
tests  cargo test --color always ..

该程序是我的第一个依赖于多线程的程序,我有一些线程在这些程序“可用”后立即运行它们,并且我有一个线程(主线程)专门用于等待新结果(即非常罕见,因为作业往往会运行至少几秒钟,而且作业相对较少,最多并行 10 个)并循环删除和重新打印事物的状态。

在软件的这一部分,我不打印命令的输出,只打印正在运行的命令和一些 ascii 微调器。

我不知道应该如何完成这些事情,所以我设法将重绘限制为至少 40 毫秒:

const AWAIT_TIME: Duration = std::time::Duration::from_millis(40);
fn delay(&mut self) -> usize {
    let time_for = AWAIT_TIME
        - SystemTime::now()
            .duration_since(self.last_occurence)
            .unwrap();
    let millis: usize = std::cmp::max(time_for.as_millis() as usize, 0);
    if millis != 0 {
        sleep(time_for);
    }
    self.last_occurence = SystemTime::now();
    millis
}
while let Some(progress) = read(&rx) { ... }    
job_display.refresh(&tracker, delay);
delay = job_starter.delay();

所以我最终跟踪写入的行数和字符数并将它们全部删除:


struct TermWrapper {
    term: Box<StdoutTerminal>,
    written_lines: u16,
    written_chars: usize,
}

...

pub fn clear(&mut self) {
    (0..self.written_lines as usize).for_each(|_| {
        self.term.cursor_up().unwrap();
        self.term.carriage_return().unwrap();
        self.term.delete_line().unwrap();
    });
    self.written_lines = 0;
    self.written_chars = 0;
}

它可以工作,但它容易闪烁,尤其是在嵌入式终端中。

我的下一个想法是存储打印字符串的哈希值并跳过重绘(如果可以的话)。

我可以应用一些已知的模式来获得更好的输出吗?

我可以使用哪些常见策略?

rust
2个回答
3
投票

保证更新终端时不闪烁的最低要求是:不要发送一个东西,然后用其他东西覆盖它(在绘图的单个“框架”内)。在清除的情况下,我们可以更具体地重申该规则:不要清除要放入文本的区域。相反,仅清除您知道不会放入文本的区域(如果那里有以前的文字)。

传统的终端命令集包含一个非常有用的工具:“清除到行尾”命令。使用方法是:

  1. 将光标移至要替换其中文本的行的开头。
  2. 写入文本,末尾不带任何换行符或 CRLF
  3. 写“清除到行尾”。 (在
    crossterm
    中,那就是
    ClearType::UntilNewLine
    。)

发送清除命令后,该行的其余部分将被清除(就像您碰巧写了确切数量的空格来完全填充该行一样)。通过这种方式,您需要跟踪正在写入的行,但不需要跟踪所写入的每个字符串的确切宽度


除此之外,对任意 2D 屏幕布局有用的下一步是记住之前发送到终端的文本,并只发送需要更改的内容 - 在 Rust 中,

tui
板条箱提供了这一点,并且您还可以找到与著名 C 库
curses
的绑定以实现相同目的。


0
投票

要考虑的另一件事是尝试最小化写出的数量,并尝试在一个 write() 中写入(系统调用)整个更新序列,包括光标定位和绘制。

一个现实世界的例子是 lldb。当您在其中 ^?(DEL) 删除一个字符时,它不仅会执行不必要的光标位置并重绘,而且最重要的是刷新转义序列的每个字节上的输出。

在 GNU Bash 中,当您删除下一行开头的 char 时,它会执行大量

^[C
来移动光标,但它在一次写入中完成此操作,因此即使通过糟糕的高延迟 ssh 连接。

© www.soinside.com 2019 - 2024. All rights reserved.