The whole-row memcmp settled an unchanged row, but on a changed row the cell
loop then read again the prefix memcmp had already compared, and the trailing
trim walked unchanged cells one by one. Chunks of eight cells now find the
first and last changed cell, so no cell is compared twice and only the span
between them is scanned cell by cell. Output is byte-identical.
Measured on frames captured from vif at 60 fps (median of 15, one core):
vanguard 120x40 73.7 -> 66.6 us a frame (-10%), vanguard 240x66
158.8 -> 131.8 (-17%), main with the gun firing 40.0 -> 34.1 (-15%), main
idle 20.3 -> 14.0 (-31%). Dropping the row compare instead costs +15% to
+120% on the same frames.
* tui: titles, labels and buttons drawn a column a rune
Ten places drew each rune of a title or label at its byte offset, so a
string holding runes of more than one byte drew with holes and ran past the
width it was measured and truncated to: the Pane title, the Modal title and
hint, the Divider label, TabBar titles, Table cells, Form labels, the
TextField placeholder, the ConfirmDialog and AlertDialog buttons, and the
ProgressOverlay title. Each now draws through Region.Text, which counts
columns by rune, within the bound its loop kept; a Form label stays within
its label column, as the byte count meant it to.
* an unchanged frame writes nothing
flush closed every frame with SGR 0, so a frame that changed no cell still
sent four bytes, and an app drawing an idle screen kept writing to the
terminal. The reset now follows only a frame that wrote cells: the next
cell written restates its whole style, and Fini resets on its own.
Each widget, given a title such as "● é · ñ", draws it whole where it drew
it with holes before; a frame flushed twice writes nothing the second time,
where it wrote ESC [ 0 m.