From 5bec83c40629a183a2763f7a5dfa7d3f6c303504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 11 May 2021 17:43:17 +0200 Subject: [PATCH 01/12] grid: add compile-time define to enable timing of the reflow operation --- grid.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/grid.c b/grid.c index e92a3b01..8f0bb99f 100644 --- a/grid.c +++ b/grid.c @@ -12,6 +12,8 @@ #include "util.h" #include "xmalloc.h" +#define TIME_REFLOW 1 + struct grid * grid_snapshot(const struct grid *grid) { @@ -391,6 +393,11 @@ grid_resize_and_reflow( size_t compose_count, const struct composed composed[static compose_count]) { +#if defined(TIME_REFLOW) && TIME_REFLOW + struct timeval start; + gettimeofday(&start, NULL); +#endif + struct row *const *old_grid = grid->rows; const int old_rows = grid->num_rows; const int old_cols = grid->num_cols; @@ -676,6 +683,16 @@ grid_resize_and_reflow( tll_free(untranslated_sixels); tll_free(tracking_points); + +#if defined(TIME_REFLOW) && TIME_REFLOW + struct timeval stop; + gettimeofday(&stop, NULL); + + struct timeval diff; + timersub(&stop, &start, &diff); + LOG_INFO("reflowed %d -> %d rows in %lds %ldµs", + old_rows, new_rows, diff.tv_sec, diff.tv_usec); +#endif } static void From d9e1aefb9173857f0632227de5ac368a505c69bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 May 2021 14:41:02 +0200 Subject: [PATCH 02/12] term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition Instead of using CELL_SPACER for *all* cells that previously used CELL_MULT_COL_SPACER, include the remaining number of spacers following, and including, itself. This is encoded by adding to the CELL_SPACER value. So, a double width character will now store the character itself in the first cell (just like before), and CELL_SPACER+1 in the second cell. A three-cell character would store the character itself, then CELL_SPACER+2, and finally CELL_SPACER+1. In other words, the last spacer is always CELL_SPACER+1. CELL_SPACER+0 is used when padding at the right margin. I.e. when writing e.g. a double width character in the last column, we insert a CELL_SPACER+0 pad character, and then write the double width character in the first column on the next row. --- extract.c | 2 +- grid.c | 10 +++++----- ime.c | 4 ++-- render.c | 10 +++++----- search.c | 4 ++-- selection.c | 26 +++++++++++++------------- terminal.c | 8 ++++---- terminal.h | 2 +- vt.c | 2 +- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/extract.c b/extract.c index 145ef03f..ac5f3897 100644 --- a/extract.c +++ b/extract.c @@ -113,7 +113,7 @@ extract_one(const struct terminal *term, const struct row *row, { struct extraction_context *ctx = context; - if (cell->wc == CELL_MULT_COL_SPACER) + if (cell->wc >= CELL_SPACER) return true; if (ctx->last_row != NULL && row != ctx->last_row) { diff --git a/grid.c b/grid.c index 8f0bb99f..ab8135f0 100644 --- a/grid.c +++ b/grid.c @@ -473,9 +473,9 @@ grid_resize_and_reflow( grid, new_grid, new_row, &new_row_idx, &new_col_idx, \ new_rows, new_cols) -#define print_spacer() \ +#define print_spacer(remaining) \ do { \ - new_row->cells[new_col_idx].wc = CELL_MULT_COL_SPACER; \ + new_row->cells[new_col_idx].wc = CELL_SPACER + (remaining); \ new_row->cells[new_col_idx].attrs = old_cell->attrs; \ new_row->cells[new_col_idx].attrs.clean = 1; \ } while (0) @@ -541,7 +541,7 @@ grid_resize_and_reflow( const struct cell *old_cell = &old_row->cells[c - empty_count + i]; wc = old_cell->wc; - if (wc == CELL_MULT_COL_SPACER) + if (wc >= CELL_SPACER) continue; if (wc >= CELL_COMB_CHARS_LO && @@ -554,7 +554,7 @@ grid_resize_and_reflow( if (new_col_idx + max(1, wcwidth(wc)) > new_cols) { /* Pad to end-of-line with spacers, then line-wrap */ for (;new_col_idx < new_cols; new_col_idx++) - print_spacer(); + print_spacer(0); line_wrap(); } @@ -585,7 +585,7 @@ grid_resize_and_reflow( const struct cell *old_cell = &old_row->cells[c]; for (size_t i = 0; i < width - 1; i++) { xassert(new_col_idx < new_cols); - print_spacer(); + print_spacer(width - i + 1); new_col_idx++; } diff --git a/ime.c b/ime.c index b1852ed3..81e63884 100644 --- a/ime.c +++ b/ime.c @@ -199,7 +199,7 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, for (int j = 1; j < width; j++) { cell = &seat->ime.preedit.cells[cell_idx + j]; - cell->wc = CELL_MULT_COL_SPACER; + cell->wc = CELL_SPACER + width - j; cell->attrs = (struct attributes){.clean = 1}; } @@ -280,7 +280,7 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, /* Expand cursor end to end of glyph */ while (cell_end > cell_begin && cell_end < cell_count && - seat->ime.preedit.cells[cell_end].wc == CELL_MULT_COL_SPACER) + seat->ime.preedit.cells[cell_end].wc >= CELL_SPACER) { cell_end++; } diff --git a/render.c b/render.c index 8481d330..30f9b20e 100644 --- a/render.c +++ b/render.c @@ -594,7 +594,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, if (has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus) draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols); - if (cell->wc == 0 || cell->wc == CELL_MULT_COL_SPACER || + if (cell->wc == 0 || cell->wc >= CELL_SPACER || (unlikely(cell->attrs.conceal) && !is_selected)) { goto draw_cursor; @@ -1237,7 +1237,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, /* Make sure we don't start in the middle of a character */ while (ime_ofs < cells_needed && - seat->ime.preedit.cells[ime_ofs].wc == CELL_MULT_COL_SPACER) + seat->ime.preedit.cells[ime_ofs].wc >= CELL_SPACER) { ime_ofs++; } @@ -1249,7 +1249,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, struct row *row = grid_row_in_view(term->grid, row_idx); /* Don't start pre-edit text in the middle of a double-width character */ - while (col_idx > 0 && row->cells[col_idx].wc == CELL_MULT_COL_SPACER) { + while (col_idx > 0 && row->cells[col_idx].wc >= CELL_SPACER) { cells_used++; col_idx--; } @@ -1268,11 +1268,11 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, row->dirty = true; /* Render pre-edit text */ - xassert(seat->ime.preedit.cells[ime_ofs].wc != CELL_MULT_COL_SPACER); + xassert(seat->ime.preedit.cells[ime_ofs].wc < CELL_SPACER); for (int i = 0, idx = ime_ofs; idx < seat->ime.preedit.count; i++, idx++) { const struct cell *cell = &seat->ime.preedit.cells[idx]; - if (cell->wc == CELL_MULT_COL_SPACER) + if (cell->wc >= CELL_SPACER) continue; int width = max(1, wcwidth(cell->wc)); diff --git a/search.c b/search.c index 02cea9e0..911b13c0 100644 --- a/search.c +++ b/search.c @@ -337,7 +337,7 @@ search_find_next(struct terminal *term) row = term->grid->rows[end_row]; } - if (row->cells[end_col].wc == CELL_MULT_COL_SPACER) { + if (row->cells[end_col].wc >= CELL_SPACER) { end_col++; continue; } @@ -461,7 +461,7 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) bool done = false; for (; end_col < term->cols; end_col++) { wchar_t wc = row->cells[end_col].wc; - if (wc == CELL_MULT_COL_SPACER) + if (wc >= CELL_SPACER) continue; const struct composed *composed = NULL; diff --git a/selection.c b/selection.c index f5a114c8..5bb386ab 100644 --- a/selection.c +++ b/selection.c @@ -234,7 +234,7 @@ find_word_boundary_left(struct terminal *term, struct coord *pos, const struct row *r = grid_row_in_view(term->grid, pos->row); wchar_t c = r->cells[pos->col].wc; - while (c == CELL_MULT_COL_SPACER) { + while (c >= CELL_SPACER) { xassert(pos->col > 0); if (pos->col == 0) return; @@ -268,7 +268,7 @@ find_word_boundary_left(struct terminal *term, struct coord *pos, const struct row *row = grid_row_in_view(term->grid, next_row); c = row->cells[next_col].wc; - while (c == CELL_MULT_COL_SPACER) { + while (c >= CELL_SPACER) { xassert(next_col > 0); if (--next_col < 0) return; @@ -306,7 +306,7 @@ find_word_boundary_right(struct terminal *term, struct coord *pos, const struct row *r = grid_row_in_view(term->grid, pos->row); wchar_t c = r->cells[pos->col].wc; - while (c == CELL_MULT_COL_SPACER) { + while (c >= CELL_SPACER) { xassert(pos->col > 0); if (pos->col == 0) return; @@ -340,7 +340,7 @@ find_word_boundary_right(struct terminal *term, struct coord *pos, const struct row *row = grid_row_in_view(term->grid, next_row); c = row->cells[next_col].wc; - while (c == CELL_MULT_COL_SPACER) { + while (c >= CELL_SPACER) { if (++next_col >= term->cols) { next_col = 0; if (++next_row >= term->rows) @@ -554,7 +554,7 @@ set_pivot_point_for_block_and_char_wise(struct terminal *term, const struct row *row = term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)]; const struct cell *cell = &row->cells[pivot_start->col]; - if (cell->wc != CELL_MULT_COL_SPACER) + if (cell->wc < CELL_SPACER) break; /* Multi-column chars don’t cross rows */ @@ -579,7 +579,7 @@ set_pivot_point_for_block_and_char_wise(struct terminal *term, const struct row *row = term->grid->rows[pivot_end->row & (term->grid->num_rows - 1)]; const wchar_t wc = row->cells[pivot_end->col].wc; - keep_going = wc == CELL_MULT_COL_SPACER; + keep_going = wc >= CELL_SPACER; if (pivot_end->col == 0) { if (pivot_end->row - term->grid->view <= 0) @@ -596,7 +596,7 @@ set_pivot_point_for_block_and_char_wise(struct terminal *term, const wchar_t wc = pivot_start->col < term->cols - 1 ? row->cells[pivot_start->col + 1].wc : 0; - keep_going = wc == CELL_MULT_COL_SPACER; + keep_going = wc >= CELL_SPACER; if (pivot_start->col >= term->cols - 1) { if (pivot_start->row - term->grid->view >= term->rows - 1) @@ -609,9 +609,9 @@ set_pivot_point_for_block_and_char_wise(struct terminal *term, } xassert(term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)]-> - cells[pivot_start->col].wc != CELL_MULT_COL_SPACER); + cells[pivot_start->col].wc < CELL_SPACER); xassert(term->grid->rows[pivot_end->row & (term->grid->num_rows - 1)]-> - cells[pivot_end->col].wc != CELL_MULT_COL_SPACER); + cells[pivot_end->col].wc < CELL_SPACER); } void @@ -743,17 +743,17 @@ selection_update(struct terminal *term, int col, int row) (new_start.row == new_end.row && new_start.col <= new_end.col)) { while (new_start.col >= 1 && - row_start->cells[new_start.col].wc == CELL_MULT_COL_SPACER) + row_start->cells[new_start.col].wc >= CELL_SPACER) new_start.col--; while (new_end.col < term->cols - 1 && - row_end->cells[new_end.col + 1].wc == CELL_MULT_COL_SPACER) + row_end->cells[new_end.col + 1].wc >= CELL_SPACER) new_end.col++; } else { while (new_end.col >= 1 && - row_end->cells[new_end.col].wc == CELL_MULT_COL_SPACER) + row_end->cells[new_end.col].wc >= CELL_SPACER) new_end.col--; while (new_start.col < term->cols - 1 && - row_start->cells[new_start.col + 1].wc == CELL_MULT_COL_SPACER) + row_start->cells[new_start.col + 1].wc >= CELL_SPACER) new_start.col++; } diff --git a/terminal.c b/terminal.c index a9712789..b4abbb0f 100644 --- a/terminal.c +++ b/terminal.c @@ -2764,12 +2764,12 @@ print_insert(struct terminal *term, int width) } static void -print_spacer(struct terminal *term, int col) +print_spacer(struct terminal *term, int col, int remaining) { struct row *row = term->grid->cur_row; struct cell *cell = &row->cells[col]; - cell->wc = CELL_MULT_COL_SPACER; + cell->wc = CELL_SPACER + remaining; cell->attrs = term->vt.attrs; cell->attrs.clean = 0; } @@ -2803,7 +2803,7 @@ term_print(struct terminal *term, wchar_t wc, int width) /* Multi-column character that doesn't fit on current line - * pad with spacers */ for (size_t i = term->grid->cursor.point.col; i < term->cols; i++) - print_spacer(term, i); + print_spacer(term, i, 0); /* And force a line-wrap */ term->grid->cursor.lcf = 1; @@ -2826,7 +2826,7 @@ term_print(struct terminal *term, wchar_t wc, int width) /* Advance cursor the 'additional' columns while dirty:ing the cells */ for (int i = 1; i < width && term->grid->cursor.point.col < term->cols - 1; i++) { term->grid->cursor.point.col++; - print_spacer(term, term->grid->cursor.point.col); + print_spacer(term, term->grid->cursor.point.col, width - i); } /* Advance cursor */ diff --git a/terminal.h b/terminal.h index 0f8f52da..85ba90eb 100644 --- a/terminal.h +++ b/terminal.h @@ -48,7 +48,7 @@ static_assert(sizeof(struct attributes) == 8, "VT attribute struct too large"); #define CELL_COMB_CHARS_LO 0x40000000ul #define CELL_COMB_CHARS_HI 0x400ffffful -#define CELL_MULT_COL_SPACER 0x40100000ul +#define CELL_SPACER 0x40100000ul struct cell { wchar_t wc; diff --git a/vt.c b/vt.c index 6ef993c3..88ad3d8b 100644 --- a/vt.c +++ b/vt.c @@ -569,7 +569,7 @@ action_utf8_print(struct terminal *term, wchar_t wc) if (!term->grid->cursor.lcf) base_col--; - while (row->cells[base_col].wc == CELL_MULT_COL_SPACER && base_col > 0) + while (row->cells[base_col].wc >= CELL_SPACER && base_col > 0) base_col--; xassert(base_col >= 0 && base_col < term->cols); From 8e05f42a1ccdc882a10e88f85a10cab61605a88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 May 2021 16:32:06 +0200 Subject: [PATCH 03/12] =?UTF-8?q?grid:=20don=E2=80=99t=20depend=20on=20wcw?= =?UTF-8?q?idth()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calling wcwidth() on every character in the entire scrollback history is slow. We already have the character width encoded in the grid; it’s in the CELL_SPACERs following a multi-column character. Thus, when we see a non-SPACER character, that isn’t in the last column, peek the next character. If it’s a SPACER, get the current characters width from it. The only thing we need the width for, is to be able to print padding SPACERS in the right margin, if the there isn’t enough space on the current row for the current character. --- grid.c | 88 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/grid.c b/grid.c index ab8135f0..45f4d37f 100644 --- a/grid.c +++ b/grid.c @@ -525,6 +525,17 @@ grid_resize_and_reflow( if (new_cols_left < cols_needed && new_cols_left >= old_cols_left) empty_count = max(0, empty_count - (cols_needed - new_cols_left)); + for (int i = 0; i < empty_count; i++) { + if (new_col_idx + 1 > new_cols) + line_wrap(); + + new_row->cells[new_col_idx] = old_row->cells[c - empty_count + i]; + new_row->cells[new_col_idx].attrs.clean = 1; + new_col_idx++; + } + + empty_count = 0; + wchar_t wc = old_row->cells[c].wc; if (wc >= CELL_COMB_CHARS_LO && wc < (CELL_COMB_CHARS_LO + compose_count)) @@ -532,65 +543,58 @@ grid_resize_and_reflow( wc = composed[wc - CELL_COMB_CHARS_LO].base; } - int width = max(1, wcwidth(wc)); + const struct cell *old_cell = &old_row->cells[c]; + wc = old_cell->wc; - /* Multi-column characters are never cut in half */ - xassert(c + width <= old_cols); + if (wc == CELL_SPACER) + continue; - for (int i = 0; i < empty_count + 1; i++) { - const struct cell *old_cell = &old_row->cells[c - empty_count + i]; - wc = old_cell->wc; + if (wc >= CELL_COMB_CHARS_LO && + wc < (CELL_COMB_CHARS_LO + compose_count)) + { + wc = composed[wc - CELL_COMB_CHARS_LO].base; + } - if (wc >= CELL_SPACER) - continue; - - if (wc >= CELL_COMB_CHARS_LO && - wc < (CELL_COMB_CHARS_LO + compose_count)) - { - wc = composed[wc - CELL_COMB_CHARS_LO].base; - } + if (wc < CELL_SPACER && + c + 1 < old_cols && + old_row->cells[c + 1].wc > CELL_SPACER) + { + int width = old_row->cells[c + 1].wc - CELL_SPACER + 1; + assert(wcwidth(wc) == width); /* Out of columns on current row in new grid? */ - if (new_col_idx + max(1, wcwidth(wc)) > new_cols) { + if (new_col_idx + width > new_cols) { /* Pad to end-of-line with spacers, then line-wrap */ for (;new_col_idx < new_cols; new_col_idx++) print_spacer(0); line_wrap(); } + } - xassert(new_row != NULL); - xassert(new_col_idx >= 0); - xassert(new_col_idx < new_cols); + if (new_col_idx + 1 > new_cols) + line_wrap(); - new_row->cells[new_col_idx] = *old_cell; - new_row->cells[new_col_idx].attrs.clean = 1; + xassert(new_row != NULL); + xassert(new_col_idx >= 0); + xassert(new_col_idx < new_cols); - /* Translate tracking point(s) */ - if (is_tracking_point && i >= empty_count) { - tll_foreach(tracking_points, it) { - if (it->item->row == old_row_idx && it->item->col == c) { - it->item->row = new_row_idx; - it->item->col = new_col_idx; - tll_remove(tracking_points, it); - } + new_row->cells[new_col_idx] = *old_cell; + new_row->cells[new_col_idx].attrs.clean = 1; + + /* Translate tracking point(s) */ + if (is_tracking_point) { + tll_foreach(tracking_points, it) { + if (it->item->row == old_row_idx && it->item->col == c) { + it->item->row = new_row_idx; + it->item->col = new_col_idx; + tll_remove(tracking_points, it); } - - reflow_uri_ranges(old_row, new_row, c, new_col_idx); } - new_col_idx++; + + reflow_uri_ranges(old_row, new_row, c, new_col_idx); } - /* For multi-column characters, insert spacers in the - * subsequent cells */ - const struct cell *old_cell = &old_row->cells[c]; - for (size_t i = 0; i < width - 1; i++) { - xassert(new_col_idx < new_cols); - print_spacer(width - i + 1); - new_col_idx++; - } - - c += width - 1; - empty_count = 0; + new_col_idx++; } if (old_row->linebreak) { From a5ec26ccc91051dc25628906b9cbce542e4c7402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 00:12:51 +0200 Subject: [PATCH 04/12] grid: reflow: no need to check for combining characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we no longer call wcwidth(), we don’t need the base character. --- grid.c | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/grid.c b/grid.c index 45f4d37f..38bce98c 100644 --- a/grid.c +++ b/grid.c @@ -491,6 +491,8 @@ grid_resize_and_reflow( /* Walk current line of the old grid */ for (int c = 0; c < old_cols; c++) { + const struct cell *old_cell = &old_row->cells[c]; + wchar_t wc = old_cell->wc; /* Check if this cell is one of the tracked cells */ bool is_tracking_point = false; @@ -512,7 +514,7 @@ grid_resize_and_reflow( } } - if (old_row->cells[c].wc == 0 && !is_tracking_point) { + if (wc == 0 && !is_tracking_point) { empty_count++; continue; } @@ -529,35 +531,22 @@ grid_resize_and_reflow( if (new_col_idx + 1 > new_cols) line_wrap(); - new_row->cells[new_col_idx] = old_row->cells[c - empty_count + i]; + size_t idx = c - empty_count + i; + + new_row->cells[new_col_idx].wc = 0; + new_row->cells[new_col_idx].attrs = old_row->cells[idx].attrs; new_row->cells[new_col_idx].attrs.clean = 1; new_col_idx++; } empty_count = 0; - wchar_t wc = old_row->cells[c].wc; - if (wc >= CELL_COMB_CHARS_LO && - wc < (CELL_COMB_CHARS_LO + compose_count)) - { - wc = composed[wc - CELL_COMB_CHARS_LO].base; - } - - const struct cell *old_cell = &old_row->cells[c]; - wc = old_cell->wc; - if (wc == CELL_SPACER) continue; - if (wc >= CELL_COMB_CHARS_LO && - wc < (CELL_COMB_CHARS_LO + compose_count)) - { - wc = composed[wc - CELL_COMB_CHARS_LO].base; - } - - if (wc < CELL_SPACER && - c + 1 < old_cols && - old_row->cells[c + 1].wc > CELL_SPACER) + if (unlikely(wc < CELL_SPACER && + c + 1 < old_cols && + old_row->cells[c + 1].wc > CELL_SPACER)) { int width = old_row->cells[c + 1].wc - CELL_SPACER + 1; assert(wcwidth(wc) == width); From d2c0a65b70705b3905f7d10937709b0869956868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 11:39:59 +0200 Subject: [PATCH 05/12] =?UTF-8?q?render:=20set=20tracking=20point=20count?= =?UTF-8?q?=20to=200=20if=20there=E2=80=99s=20no=20active=20selection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 30f9b20e..3f6e5637 100644 --- a/render.c +++ b/render.c @@ -3242,7 +3242,7 @@ maybe_resize(struct terminal *term, int width, int height, bool force) /* Resize grids */ grid_resize_and_reflow( &term->normal, new_normal_grid_rows, new_cols, old_rows, new_rows, - ALEN(tracking_points), tracking_points, + term->selection.end.row >= 0 ? ALEN(tracking_points) : 0, tracking_points, term->composed_count, term->composed); grid_resize_without_reflow( From 0d6abf15150828aa844c7371ec3fa7ba809233b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 11:40:39 +0200 Subject: [PATCH 06/12] grid: reflow: use a sorted array for tracking points Instead of iterating a linked list of tracking points, for *each and every* cell in the old grid, use a sorted array. This allows us to step through the array of tracking points as we walk the old grid; each time we match a tracking point, we move to the next one. This means we only have to check a single tracking point for each cell. --- grid.c | 93 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/grid.c b/grid.c index 38bce98c..a580dfff 100644 --- a/grid.c +++ b/grid.c @@ -384,6 +384,28 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, return new_row; } +static int +tp_cmp(const void *_a, const void *_b) +{ + const struct coord *a = *(const struct coord **)_a; + const struct coord *b = *(const struct coord **)_b; + + if (a->row < b->row) + return -1; + if (a->row > b->row) + return 1; + + xassert(a->row == b->row); + + if (a->col < b->col) + return -1; + if (a->col > b->col) + return 1; + + xassert(a->col == b->col); + return 0; +} + void grid_resize_and_reflow( struct grid *grid, int new_rows, int new_cols, @@ -433,16 +455,28 @@ grid_resize_and_reflow( saved_cursor.row += grid->offset; saved_cursor.row &= old_rows - 1; - tll(struct coord *) tracking_points = tll_init(); - tll_push_back(tracking_points, &cursor); - tll_push_back(tracking_points, &saved_cursor); + size_t tp_count = + tracking_points_count + + 1 + /* cursor */ + 1 + /* saved cursor */ + !view_follows + /* viewport */ + 1; /* terminator */ + + struct coord *tracking_points[tp_count]; + memcpy(tracking_points, _tracking_points, tracking_points_count * sizeof(_tracking_points[0])); + tracking_points[tracking_points_count] = &cursor; + tracking_points[tracking_points_count + 1] = &saved_cursor; struct coord viewport = {0, grid->view}; if (!view_follows) - tll_push_back(tracking_points, &viewport); + tracking_points[tracking_points_count + 2] = &viewport; - for (size_t i = 0; i < tracking_points_count; i++) - tll_push_back(tracking_points, _tracking_points[i]); + qsort(tracking_points, tp_count - 1, sizeof(tracking_points[0]), &tp_cmp); + + /* NULL terminate */ + struct coord terminator = {-1, -1}; + tracking_points[tp_count - 1] = &terminator; + struct coord **next_tp = &tracking_points[0]; /* * Walk the old grid @@ -477,7 +511,6 @@ grid_resize_and_reflow( do { \ new_row->cells[new_col_idx].wc = CELL_SPACER + (remaining); \ new_row->cells[new_col_idx].attrs = old_cell->attrs; \ - new_row->cells[new_col_idx].attrs.clean = 1; \ } while (0) /* @@ -496,25 +529,24 @@ grid_resize_and_reflow( /* Check if this cell is one of the tracked cells */ bool is_tracking_point = false; - tll_foreach(tracking_points, it) { - if (it->item->row == old_row_idx && it->item->col == c) { - is_tracking_point = true; - break; - } - } + + struct coord *tp = *next_tp; + if (tp->row == old_row_idx && tp->col == c) + is_tracking_point = true; /* If there’s an URI start/end point here, we need to make * sure we handle it */ + bool on_uri = false; if (old_row->extra != NULL) { tll_foreach(old_row->extra->uri_ranges, it) { if (it->item.start == c || it->item.end == c) { - is_tracking_point = true; + on_uri = true; break; } } } - if (wc == 0 && !is_tracking_point) { + if (wc == 0 && !(is_tracking_point | on_uri)) { empty_count++; continue; } @@ -535,7 +567,6 @@ grid_resize_and_reflow( new_row->cells[new_col_idx].wc = 0; new_row->cells[new_col_idx].attrs = old_row->cells[idx].attrs; - new_row->cells[new_col_idx].attrs.clean = 1; new_col_idx++; } @@ -568,21 +599,29 @@ grid_resize_and_reflow( xassert(new_col_idx < new_cols); new_row->cells[new_col_idx] = *old_cell; - new_row->cells[new_col_idx].attrs.clean = 1; /* Translate tracking point(s) */ if (is_tracking_point) { - tll_foreach(tracking_points, it) { - if (it->item->row == old_row_idx && it->item->col == c) { - it->item->row = new_row_idx; - it->item->col = new_col_idx; - tll_remove(tracking_points, it); - } - } + do { + xassert(tp != NULL); + xassert(tp->row == old_row_idx); + xassert(tp->col == c); - reflow_uri_ranges(old_row, new_row, c, new_col_idx); + tp->row = new_row_idx; + tp->col = new_col_idx; + + next_tp++; + tp = *next_tp; + } while (tp->row == old_row_idx && tp->col == c); + + xassert((tp->row < 0 && tp->col < 0) || + tp->row > old_row_idx || + (tp->row == old_row_idx && tp->col > c)); } + if (on_uri) + reflow_uri_ranges(old_row, new_row, c, new_col_idx); + new_col_idx++; } @@ -595,6 +634,8 @@ grid_resize_and_reflow( #undef line_wrap } + xassert(old_rows == 0 || *next_tp == &terminator); + #if defined(_DEBUG) /* Verify all URI ranges have been “closed” */ for (int r = 0; r < new_rows; r++) { @@ -675,8 +716,6 @@ grid_resize_and_reflow( sixel_destroy(&it->item); tll_free(untranslated_sixels); - tll_free(tracking_points); - #if defined(TIME_REFLOW) && TIME_REFLOW struct timeval stop; gettimeofday(&stop, NULL); From a5d7f2e5929d11e4c7c146002f7704cb4a65907a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 11:44:13 +0200 Subject: [PATCH 07/12] grid: reflow: tag tracking point if-statements with likely/unlikely --- grid.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/grid.c b/grid.c index a580dfff..d888508e 100644 --- a/grid.c +++ b/grid.c @@ -531,7 +531,7 @@ grid_resize_and_reflow( bool is_tracking_point = false; struct coord *tp = *next_tp; - if (tp->row == old_row_idx && tp->col == c) + if (unlikely(tp->row == old_row_idx && tp->col == c)) is_tracking_point = true; /* If there’s an URI start/end point here, we need to make @@ -539,14 +539,14 @@ grid_resize_and_reflow( bool on_uri = false; if (old_row->extra != NULL) { tll_foreach(old_row->extra->uri_ranges, it) { - if (it->item.start == c || it->item.end == c) { + if (unlikely(it->item.start == c || it->item.end == c)) { on_uri = true; break; } } } - if (wc == 0 && !(is_tracking_point | on_uri)) { + if (wc == 0 && likely(!(is_tracking_point | on_uri))) { empty_count++; continue; } @@ -601,7 +601,7 @@ grid_resize_and_reflow( new_row->cells[new_col_idx] = *old_cell; /* Translate tracking point(s) */ - if (is_tracking_point) { + if (unlikely(is_tracking_point)) { do { xassert(tp != NULL); xassert(tp->row == old_row_idx); @@ -619,7 +619,7 @@ grid_resize_and_reflow( (tp->row == old_row_idx && tp->col > c)); } - if (on_uri) + if (unlikely(on_uri)) reflow_uri_ranges(old_row, new_row, c, new_col_idx); new_col_idx++; From 60a55d04ac5d42182ac041e3901661aa4c5ef161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 12:11:58 +0200 Subject: [PATCH 08/12] grid: fix 32-bit compilation --- grid.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/grid.c b/grid.c index d888508e..bb8bc577 100644 --- a/grid.c +++ b/grid.c @@ -722,8 +722,10 @@ grid_resize_and_reflow( struct timeval diff; timersub(&stop, &start, &diff); - LOG_INFO("reflowed %d -> %d rows in %lds %ldµs", - old_rows, new_rows, diff.tv_sec, diff.tv_usec); + LOG_INFO("reflowed %d -> %d rows in %llds %lldµs", + old_rows, new_rows, + (long long)diff.tv_sec, + (long long)diff.tv_usec); #endif } From 528e91aece2c073b38c0fd85e4efb16a5cc62555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 12:54:59 +0200 Subject: [PATCH 09/12] grid: take scrollback start into account when sorting the tracking points array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The row numbers in the tracking points are in absolute numbers. However, when we walk the old grid, we do so starting in the beginning of the scrollback history. We must ensure the tracking points are sorted such that the *first* one we see is the “oldest” one. I.e. the one furthest back in the scrollback history. --- grid.c | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/grid.c b/grid.c index bb8bc577..fbc9820d 100644 --- a/grid.c +++ b/grid.c @@ -384,18 +384,32 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, return new_row; } +struct tp_cmp_ctx { + int scrollback_start; + int rows; +}; + static int -tp_cmp(const void *_a, const void *_b) +tp_cmp(const void *_a, const void *_b, void *_arg) { const struct coord *a = *(const struct coord **)_a; const struct coord *b = *(const struct coord **)_b; + const struct tp_cmp_ctx *ctx = _arg; - if (a->row < b->row) + int a_row = (a->row - ctx->scrollback_start + ctx->rows) & (ctx->rows - 1); + int b_row = (b->row - ctx->scrollback_start + ctx->rows) & (ctx->rows - 1); + + xassert(a_row >= 0); + xassert(a_row < ctx->rows); + xassert(b_row >= 0); + xassert(b_row < ctx->rows); + + if (a_row < b_row) return -1; - if (a->row > b->row) + if (a_row > b_row) return 1; - xassert(a->row == b->row); + xassert(a_row == b_row); if (a->col < b->col) return -1; @@ -471,13 +485,23 @@ grid_resize_and_reflow( if (!view_follows) tracking_points[tracking_points_count + 2] = &viewport; - qsort(tracking_points, tp_count - 1, sizeof(tracking_points[0]), &tp_cmp); + if (old_rows > 0) { + qsort_r( + tracking_points, tp_count - 1, sizeof(tracking_points[0]), &tp_cmp, + &(struct tp_cmp_ctx){.scrollback_start = offset, .rows = old_rows}); + } /* NULL terminate */ struct coord terminator = {-1, -1}; tracking_points[tp_count - 1] = &terminator; struct coord **next_tp = &tracking_points[0]; + LOG_DBG("scrollback-start=%d", offset); + for (size_t i = 0; i < tp_count - 1; i++) { + LOG_DBG("TP #%zu: row=%d, col=%d", + i, tracking_points[i]->row, tracking_points[i]->col); + } + /* * Walk the old grid */ @@ -613,10 +637,6 @@ grid_resize_and_reflow( next_tp++; tp = *next_tp; } while (tp->row == old_row_idx && tp->col == c); - - xassert((tp->row < 0 && tp->col < 0) || - tp->row > old_row_idx || - (tp->row == old_row_idx && tp->col > c)); } if (unlikely(on_uri)) From c7e51bdf72dd3f51468516f6e4497d01593cbcef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 13:00:46 +0200 Subject: [PATCH 10/12] grid: reflow: always run qsort_r(), handle rows == 0 in tp_cmp() instead --- grid.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/grid.c b/grid.c index fbc9820d..c7ec6752 100644 --- a/grid.c +++ b/grid.c @@ -400,9 +400,9 @@ tp_cmp(const void *_a, const void *_b, void *_arg) int b_row = (b->row - ctx->scrollback_start + ctx->rows) & (ctx->rows - 1); xassert(a_row >= 0); - xassert(a_row < ctx->rows); + xassert(a_row < ctx->rows || ctx->rows == 0); xassert(b_row >= 0); - xassert(b_row < ctx->rows); + xassert(b_row < ctx->rows || ctx->rows == 0); if (a_row < b_row) return -1; @@ -485,11 +485,9 @@ grid_resize_and_reflow( if (!view_follows) tracking_points[tracking_points_count + 2] = &viewport; - if (old_rows > 0) { - qsort_r( - tracking_points, tp_count - 1, sizeof(tracking_points[0]), &tp_cmp, - &(struct tp_cmp_ctx){.scrollback_start = offset, .rows = old_rows}); - } + qsort_r( + tracking_points, tp_count - 1, sizeof(tracking_points[0]), &tp_cmp, + &(struct tp_cmp_ctx){.scrollback_start = offset, .rows = old_rows}); /* NULL terminate */ struct coord terminator = {-1, -1}; From aa1f589e3f788126d39e0eb720069e1aaaa03ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 13:32:10 +0200 Subject: [PATCH 11/12] grid: include , for qsort_r() --- grid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/grid.c b/grid.c index c7ec6752..3eabeeb3 100644 --- a/grid.c +++ b/grid.c @@ -1,5 +1,6 @@ #include "grid.h" +#include #include #define LOG_MODULE "grid" From 8d1b72405662ed43007fd99d97921a71ee91eb9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 15 May 2021 13:37:46 +0200 Subject: [PATCH 12/12] grid: reflow: qsort_r() is not portable Replace with qsort() + global variable. Not thread safe! --- grid.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/grid.c b/grid.c index 3eabeeb3..eb923756 100644 --- a/grid.c +++ b/grid.c @@ -385,25 +385,27 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, return new_row; } -struct tp_cmp_ctx { +static struct { int scrollback_start; int rows; -}; +} tp_cmp_ctx; static int -tp_cmp(const void *_a, const void *_b, void *_arg) +tp_cmp(const void *_a, const void *_b) { const struct coord *a = *(const struct coord **)_a; const struct coord *b = *(const struct coord **)_b; - const struct tp_cmp_ctx *ctx = _arg; - int a_row = (a->row - ctx->scrollback_start + ctx->rows) & (ctx->rows - 1); - int b_row = (b->row - ctx->scrollback_start + ctx->rows) & (ctx->rows - 1); + int scrollback_start = tp_cmp_ctx.scrollback_start; + int num_rows = tp_cmp_ctx.rows; + + int a_row = (a->row - scrollback_start + num_rows) & (num_rows - 1); + int b_row = (b->row - scrollback_start + num_rows) & (num_rows - 1); xassert(a_row >= 0); - xassert(a_row < ctx->rows || ctx->rows == 0); + xassert(a_row < num_rows || num_rows == 0); xassert(b_row >= 0); - xassert(b_row < ctx->rows || ctx->rows == 0); + xassert(b_row < num_rows || num_rows == 0); if (a_row < b_row) return -1; @@ -486,9 +488,11 @@ grid_resize_and_reflow( if (!view_follows) tracking_points[tracking_points_count + 2] = &viewport; - qsort_r( - tracking_points, tp_count - 1, sizeof(tracking_points[0]), &tp_cmp, - &(struct tp_cmp_ctx){.scrollback_start = offset, .rows = old_rows}); + /* Not thread safe! */ + tp_cmp_ctx.scrollback_start = offset; + tp_cmp_ctx.rows = old_rows; + qsort( + tracking_points, tp_count - 1, sizeof(tracking_points[0]), &tp_cmp); /* NULL terminate */ struct coord terminator = {-1, -1};