From 334688db815d9d69cf092417f5ebbdcba573d664 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 7 Feb 2021 19:59:21 +0000 Subject: [PATCH] Apply UPDATE_DELAY in arrears, not in advance. The original aim of the rate limit was to avoid having too many updates per second. I implemented this by a deferment mechanism: when any change occurs that makes the terminal want an update, it instead sets a timer to go off after UPDATE_DELAY (1/50 second), and does the update at the end of that interval. Now it's done the other way round: if there has not been an update within the last UPDATE_DELAY, then we can simply do an update _right now_, in immediate response to whatever triggered it. And _then_ we set a timer to track a cooldown period, within which any further requests for updates will be deferred until the end of the cooldown. This mechanism should still rate-limit updates, but now the latency in normal interactive use should be lowered, because terminal updates in response to keystrokes (which typically arrive separated by more than UPDATE_DELAY) can now each be enacted as soon as possible after the triggering keystroke. This also reverses (in the common case) the slowdown of non-textual window modifications introduced by the previous commit, in which lots of them were brought under the umbrella of term_update and therefore became subject to UPDATE_DELAY. Now they'll only be delayed in conditions of high traffic, and not in interactive use. --- terminal.c | 32 +++++++++++++++++++++++++------- terminal.h | 14 ++++++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/terminal.c b/terminal.c index 52f8d5e2..7b4384e0 100644 --- a/terminal.c +++ b/terminal.c @@ -1187,41 +1187,58 @@ static void check_line_size(Terminal *term, termline *line) static void term_schedule_tblink(Terminal *term); static void term_schedule_cblink(Terminal *term); +static void term_update_callback(void *ctx); static void term_timer(void *ctx, unsigned long now) { Terminal *term = (Terminal *)ctx; - bool update = false; if (term->tblink_pending && now == term->next_tblink) { term->tblinker = !term->tblinker; term->tblink_pending = false; term_schedule_tblink(term); - update = true; + term->window_update_pending = true; } if (term->cblink_pending && now == term->next_cblink) { term->cblinker = !term->cblinker; term->cblink_pending = false; term_schedule_cblink(term); - update = true; + term->window_update_pending = true; } if (term->in_vbell && now == term->vbell_end) { term->in_vbell = false; - update = true; + term->window_update_pending = true; } - if (update || - (term->window_update_pending && now == term->next_update)) + if (term->window_update_cooldown && + now == term->window_update_cooldown_end) { + term->window_update_cooldown = false; + } + + if (term->window_update_pending) + term_update_callback(term); +} + +static void term_update_callback(void *ctx) +{ + Terminal *term = (Terminal *)ctx; + if (!term->window_update_pending) + return; + if (!term->window_update_cooldown) { term_update(term); + term->window_update_cooldown = true; + term->window_update_cooldown_end = schedule_timer( + UPDATE_DELAY, term_timer, term); + } } static void term_schedule_update(Terminal *term) { if (!term->window_update_pending) { term->window_update_pending = true; - term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term); + queue_toplevel_callback(term_update_callback, term); } } @@ -1940,6 +1957,7 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) term->wcFromTo_size = 0; term->window_update_pending = false; + term->window_update_cooldown = false; term->bidi_cache_size = 0; term->pre_bidi_cache = term->post_bidi_cache = NULL; diff --git a/terminal.h b/terminal.h index 3841ef5a..c463f7a8 100644 --- a/terminal.h +++ b/terminal.h @@ -259,11 +259,17 @@ struct terminal_tag { bool in_term_out; /* - * We schedule a window update shortly after receiving terminal - * data. This tracks whether one is currently pending. + * We don't permit window updates too close together, to avoid CPU + * churn pointlessly redrawing the window faster than the user can + * read. So after an update, we set window_update_cooldown = true + * and schedule a timer to reset it to false. In between those + * times, window updates are not performed, and instead we set + * window_update_pending = true, which will remind us to perform + * the deferred redraw when the cooldown period ends and + * window_update_cooldown is reset to false. */ - bool window_update_pending; - long next_update; + bool window_update_pending, window_update_cooldown; + long window_update_cooldown_end; /* * Track pending blinks and tblinks.