Merge terminal scheduler fixture correction

Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz>

Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz>

* commit 'd67b99a3dd611f0c239d9bce13552417acaba622':
  refactor(terminal): delete the slice-sizing function nothing calls

Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz>
This commit is contained in:
npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf
2026-08-02 01:47:32 -04:00
2 changed files with 42 additions and 60 deletions
@@ -34,53 +34,29 @@ pub const OSC_BUDGET: usize = 256 << 10;
/// spent to the last unit.
pub const WORK_BUDGET: u64 = 250_000;
/// Bounds on how many bytes are handed to the parser at once.
/// Widest slice handed to the parser at once.
///
/// The budget is checked *between* slices, so the slice is what actually
/// bounds one lock hold, and a fixed byte count cannot do it: `ESC#8` is
/// three bytes and costs a full grid, so 256 bytes of it is 1.6 ms at 200x50
/// and 14 ms at 1600x50. [`slice_bytes`] therefore derives the size from the
/// grid, and these two clamp it -- `MAX` at the throughput plateau (measured:
/// plain-char parsing saturates by 64 bytes and is flat to 64 KiB), `MIN`
/// where a slice stops being able to hold a whole escape sequence: 4 bytes
/// covers `ESC#8` and `ESC c` intact, so the floor never splits the densest
/// atoms across slices for no benefit. Measured throughput at the floor is
/// ~74% of the plateau on plain text and ~77% on SGR, which is the price of
/// bounding a grid whose worst atom exceeds the budget outright.
pub const MIN_SLICE: usize = 4;
/// The floor is 1 byte and lives in [`slice_bytes_remaining`] rather than
/// here: on a grid whose worst atom exceeds the whole budget -- RIS at any
/// real scrollback depth -- no wider slice can promise to stop after the
/// callback that crosses. This cap is the other end, set at the throughput
/// plateau: plain-char parsing saturates by 64 bytes and is flat to 64 KiB
/// measured, so nothing above it buys anything and a larger value only
/// coarsens the cut.
pub const MAX_SLICE: usize = 256;
/// Bytes to hand the parser at once on a `columns x lines` grid.
/// Bytes to hand the parser next.
///
/// Sized against the **densest atom the grid admits**, so the bound holds on
/// the first byte of a cold feeder for any payload: two bytes of `ESC c` buy
/// [`max_atom_work`], which is the most work per byte upstream offers, and
/// `budget / (atom / 2)` is the widest slice that cannot exceed one budget.
/// The **only** slice-sizing function, deliberately: an earlier version of
/// this module also exported a `slice_bytes(columns, lines, scrollback)` that
/// the scheduler stopped calling when slices became remaining-aware, and the
/// fixtures went on asserting against it. The two disagreed exactly where the
/// floor bound -- reporting 4 where the engine used 1 -- so the preconditions
/// were describing a function no longer in the path. One function, one
/// answer, and every test asserts on what `drain` actually calls.
///
/// Deliberately *derived rather than learned*. An earlier version sized
/// slices from the density of preceding slices, which is strictly worse where
/// it matters: a fresh feeder has observed nothing, so its first slice is
/// wide, and a first wide slice of RIS spends many budgets before anything
/// looks. A bound that has to be taught is not a bound on the lesson.
///
/// [`MIN_SLICE`] floors it, and on any grid with real scrollback the floor is
/// what binds -- RIS at the default 10k depth is worth more than the entire
/// budget on its own, so no slice size can keep a drain inside the budget and
/// the floor stops the arithmetic from asking for fractions of a byte. That
/// residual is not hidden: it is exactly [`max_drain_work`], and it is the
/// honest cost of an indivisible callback that upstream can be asked to make
/// smaller only by not calling it.
pub fn slice_bytes(columns: usize, lines: usize, scrollback: usize) -> usize {
let densest = (max_atom_work(columns, lines, scrollback) / 2).max(1);
((WORK_BUDGET / densest) as usize).clamp(MIN_SLICE, MAX_SLICE)
}
/// Bytes to hand the parser when `spent` of the budget is already gone.
///
/// The scheduling rule in one place so the fixtures can assert on it rather
/// than on a copy of the arithmetic: a slice of `N` bytes holds at most
/// `N / atom_bytes` atoms, so `remaining / densest` bytes cannot carry a
/// drain past the budget.
/// The rule: a slice of `N` bytes holds at most `N / atom_bytes` atoms, so
/// `remaining / densest` bytes cannot carry a drain past the budget.
///
/// `next_escape` is how far the next `ESC` is from the front of the tail.
/// This is the difference between a correct bound and an unusable one. Only
@@ -10,8 +10,8 @@
//! `> 0` is satisfied by a seam that executed exactly one unit.
use buzz_terminal::fences::{
max_atom_work, max_drain_work, slice_bytes, slice_bytes_remaining, Fences, MAX_SLICE,
MIN_SLICE, SYNC_CAP, TAIL_CAP, WORK_BUDGET,
max_atom_work, max_drain_work, slice_bytes_remaining, Fences, MAX_SLICE, SYNC_CAP, TAIL_CAP,
WORK_BUDGET,
};
use buzz_terminal::{Size, Terminal};
@@ -339,26 +339,26 @@ fn a_resize_mid_tail_reprices_the_remainder() {
/// serves both.
#[test]
fn slice_size_shrinks_as_the_worst_atom_grows() {
let small = slice_bytes(80, 24, 0);
let large = slice_bytes(1600, 50, 0);
let small = slice_bytes_remaining(80, 24, 0, 0, 0);
let large = slice_bytes_remaining(1600, 50, 0, 0, 0);
assert!(
small > large,
"a bigger grid makes each byte more expensive, so slices must shrink: \
80x24 -> {small}, 1600x50 -> {large}",
);
assert!(
slice_bytes(200, 50, 10_000) <= slice_bytes(200, 50, 0),
slice_bytes_remaining(200, 50, 10_000, 0, 0) <= slice_bytes_remaining(200, 50, 0, 0, 0),
"scrollback makes RIS more expensive, so it may only shrink slices",
);
for (columns, lines, scrollback) in [(80, 24, 0), (200, 50, 0), (400, 100, 0), (1600, 50, 0)] {
assert!((MIN_SLICE..=MAX_SLICE).contains(&slice_bytes(columns, lines, scrollback)));
assert!((1..=MAX_SLICE).contains(&slice_bytes_remaining(columns, lines, scrollback, 0, 0)));
// One slice holds at most N/2 of the densest atom. Either that fits a
// budget, or the floor binds -- and then the overshoot is stated by
// `max_drain_work` rather than being an accident.
let worst = (slice_bytes(columns, lines, scrollback) as u64 / 2)
* max_atom_work(columns, lines, scrollback);
let width = slice_bytes_remaining(columns, lines, scrollback, 0, 0);
let worst = (width as u64 / 2) * max_atom_work(columns, lines, scrollback);
assert!(
worst <= WORK_BUDGET || slice_bytes(columns, lines, scrollback) == MIN_SLICE,
worst <= WORK_BUDGET || width == 1,
"{columns}x{lines}: a slice buys {worst} work against a \
{WORK_BUDGET} budget without the MIN clamp to excuse it",
);
@@ -923,12 +923,18 @@ fn a_scrollback_change_reprices_the_densest_atom_and_the_slicing() {
// depth, so a conforming repair would show work identical to the
// control and the assertions here would invert into false failures.
assert!(
slice_bytes(shallow.columns, shallow.screen_lines, shallow.scrollback) > MIN_SLICE,
slice_bytes_remaining(
shallow.columns,
shallow.screen_lines,
shallow.scrollback,
0,
0
) > 1,
"geometry cannot discriminate: the shallow arm is already floored",
);
assert_eq!(
slice_bytes(deep.columns, deep.screen_lines, deep.scrollback),
MIN_SLICE,
slice_bytes_remaining(deep.columns, deep.screen_lines, deep.scrollback, 0, 0),
1,
);
// How a terminal at `size` retires 200 RIS: work, and how many
@@ -1151,8 +1157,8 @@ fn extreme_dimensions_saturate_instead_of_wrapping() {
// most expensive. Wrapping inverts the fence. So: the widest possible
// atom must give the narrowest possible slice.
assert_eq!(
slice_bytes(huge, huge, huge),
MIN_SLICE,
slice_bytes_remaining(huge, huge, huge, 0, 0),
1,
"an overflowing grid must clamp to the smallest slice; a wrapped \
`max_atom_work` would hand back a generous one",
);
@@ -1173,10 +1179,10 @@ fn extreme_dimensions_saturate_instead_of_wrapping() {
for (axis, at) in [
(
"scrollback",
(|n| slice_bytes(200, 50, n)) as fn(usize) -> usize,
(|n| slice_bytes_remaining(200, 50, n, 0, 0)) as fn(usize) -> usize,
),
("columns", |n| slice_bytes(n.max(1), 50, 0)),
("lines", |n| slice_bytes(200, n.max(1), 0)),
("columns", |n| slice_bytes_remaining(n.max(1), 50, 0, 0, 0)),
("lines", |n| slice_bytes_remaining(200, n.max(1), 0, 0, 0)),
] {
let mut previous = usize::MAX;
for exponent in 0..60 {
@@ -1186,7 +1192,7 @@ fn extreme_dimensions_saturate_instead_of_wrapping() {
"slice widened from {previous} to {width} at {axis} \
2^{exponent}: more expensive grid, more generous slice",
);
assert!(width >= MIN_SLICE);
assert!(width >= 1);
previous = width;
}
}