diff options
| author | user <user@clank> | 2026-08-29 21:31:41 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-29 21:31:41 +0200 |
| commit | 8a8b5e1c7acd269d4a639a40712412a84480ad18 (patch) | |
| tree | dd73377a2b0a8b424dc8f151ced00f702812edca /web/build-site | |
| parent | docs: less wordy (diff) | |
| download | slosh-8a8b5e1c7acd269d4a639a40712412a84480ad18.tar.gz slosh-8a8b5e1c7acd269d4a639a40712412a84480ad18.tar.xz slosh-8a8b5e1c7acd269d4a639a40712412a84480ad18.zip | |
graphics: a frame the client never got must be said again
A pane image is re-emitted to the client as decoded RGBA, which for one
screenshot is ~4MB of base64 in a single indivisible message -- and
conn_send silently dropped anything that would overflow the 4MB outbox,
while push_graphics ignored the refusal and the model advanced anyway.
Everything stateful in that dropped frame was lost for good: the image
was marked sent (so its placements drew nothing, forever), and the
deletions rode out with it (so a placement the client had was never
revoked). Reported as screenshots in a recovered pi transcript parked
at a fixed spot on the screen, immune to scrolling and pane swaps --
those were placements whose deletion left with a dropped frame. The
1000x800 one could never be delivered at all: its transmission alone
was bigger than the cap.
So the frame is now transactional. Deletions queue and are repeated
every flush until gfx_commit() confirms the client heard them;
transmissions recorded in a frame are unwound by an undelivered commit
so they are sent again. A flush nobody commits counts as undelivered at
the next gfx_begin(), which is what the `graphics` control command is:
bytes rendered for a CLI, not the client. The server reports the
verdict and keeps the paint pending until it lands.
The outbox cap now bounds the backlog rather than backlog-plus-message,
so one big transmission on a drained queue always fits -- and that
surfaced a second way to lose: a queued image legitimately holds the
backlog over the cap until the client swallows it, and push_frame read
that as a dead client and closed the connection. The session
auto-detached every time a screenshot crossed the wire. A full outbox
is a client that is *behind*, not gone: push_frame now marks the screen
for a full repaint (the dropped diff means the client's cells are
unknown) and retries as POLLOUT drains the queue; a client that truly
left still shows up as a write error there.
The headless suite pins the owed-until-committed contract; the new
tests/live_graphics.py drives the real socket with a
bigger-than-the-cap image and a reader that stalls, and would have
caught both bugs: the flood must not detach, the transmission must
arrive whole, and a placement scrolled away must be deleted -- then
come back on scroll without a second copy of the pixels.
Diffstat (limited to 'web/build-site')
0 files changed, 0 insertions, 0 deletions
