writeonce/docs/plan/exploration/alacritty/00-alacritty-parity.md
shoney.arickathil 8bcd24969e docs(lang42): story, spec and plan for bounded subprocess
- claim the lang42 prefix; iteration 42 story (readiness: ready), approved
  spec, and the 11-task implementation plan
- board: pending row for 42; graph: node 42 with green edges (11, 24)
- graph: porch track section added (same sweep)
- parity studies that motivated 42: alacritty, tmux, zen-browser under
  docs/plan/exploration/ — staged path, gap lists, refused routes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 22:19:25 +02:00

7 KiB
Raw Blame History

Alacritty parity — what the language needs to build a terminal-class application

Source: .dev/reference/alacritty/ (shallow clone, surveyed 2026-09-01). Companion study to fiber/00-fiber-parity.md, which asked the same question for the web-framework surface. This one asks it for a native, event-driven, GPU-rendered desktop application — the workload class writeonce is furthest from today.

What alacritty actually is (measured, not summarized from its README)

33,699 lines of Rust across a 4-crate workspace, ~22 direct dependencies:

  • alacritty_terminal — the portable core, no GUI anywhere in it: PTY creation and lifecycle (tty/unix.rs, 448 lines — openpty, fork/exec of the shell, window-size ioctls, child reaping), a byte-level escape-sequence parser (the vte crate), the grid data structure, and an event loop over the polling crate (epoll/kqueue on arbitrary fds).
  • alacritty — the application: windowing (winit), OpenGL context creation (glutin, EGL/WGL), glyph rasterization (crossfont), a timer-driven scheduler (scheduler.rs — cursor blink, repaint deadlines), config with live reload (notify file watching), clipboard, signal handling (signal-hook), CLI (clap).
  • alacritty_config/_derive — config plumbing (a proc-macro crate; writeonce's analogue would be compile-time codegen, which @table already does for a different domain).

The split matters more than the line count: the terminal CORE is a files-and-processes program with zero graphics, and the GUI shell around it is where every heavyweight dependency lives.

What already fits — the part that costs nothing

  • The concurrency model is a better fit than alacritty's own. Alacritty runs a PTY-reader thread and a window-event thread synchronized through parking_lot locks around the grid. Shard actors with ownership-move messages express this without shared state: a reader actor owns the parser, sends grid deltas; a display actor owns the grid. Iteration 24's chat app already proved the shape (fd-driven actor, fan-out, lifecycle).
  • Single self-contained binary — alacritty's distribution story is writeonce's existing one.
  • Bytes + bitwise operators (iterations 19, 36) — the VTE parser is a byte state machine; the primitive layer for it exists.
  • fn main, exit codes, env, fs, time (plan 9 stdlib) — config discovery, CLI-shaped startup.

The gaps, ordered by what unblocks what

Each maps onto an existing iteration where one exists; only two items are genuinely new.

  1. PTY + bounded subprocess — the heart of the core. Openpty, fork/exec with the child on the slave side as its controlling terminal, resize ioctls, orderly child shutdown. This is exactly iteration 28's named gap fan-out ("bounded subprocess, stdin/stdout transport") plus the PTY-specific ioctls. ~450 lines of Rust in the reference; a C-builtin family in writeonce's existing style (crypto/net precedent: small id-numbered builtins, no general FFI).
  2. Readiness on arbitrary fds. The runtime's io_uring loop watches sockets it created. A PTY master fd — and later a display-server fd — must be registrable in the same loop, parking the owning fiber until readable. Alacritty needs nothing fancier (its polling crate is the same shape); this is a seam widening, not a new subsystem.
  3. Signals as events. SIGCHLD (child died) and SIGWINCH (resize) must arrive as mailbox messages, the way iteration 24 handles fd events. Today signals are runtime-internal (SIGTERM drain). Small, but nothing else can substitute for it.
  4. Unicode width + UTF-8 decode in the stdlib. The grid is addressed in cells; every printed byte-run needs "how many columns". A data-table problem, not a design problem — but without it a terminal misrenders immediately. (Alacritty: unicode-width crate.)
  5. time.mono returns, plus a timer wheel. Cut 2026-08-10 with "returns when a workload needs monotonic math" — this workload is that consumer. time.after (id 90) exists; a repaint/blink scheduler needs monotonic deadlines that survive wall-clock jumps.
  6. Outbound connection + fd passing + shared-memory buffers. Iteration 38 already names net.connect. Wayland is a unix-socket protocol: with connect, SCM_RIGHTS fd passing, and an mmap/shm builtin, a Wayland client with software rendering (wl_shm) is expressible in pure .wo — a windowed terminal with no C dependency linked at all. This is the outside-the-box route the reference makes visible: alacritty predates it culturally (X11 era) and pays for GL instead.
  7. The GPU fork — decide late. OpenGL/EGL and font rasterization (freetype/fontconfig) cannot be spoken over a socket; they are C ABI or nothing. Three options, same fork iteration 28 already recorded as "FFI-vs-out-of-process": (a) general FFI — largest doctrine change, rejected until a second consumer demands it; (b) subsystem C builtins (the crypto precedent) — a gfx/font builtin family; (c) an out-of-process render server writeonce talks to over its own socket — fits the actor model, keeps the language pure, costs a second process. Software rendering via route 6 defers this fork entirely: glyph rasterization from a pre-baked bitmap font atlas is pure byte math.

The maturity path, as driving workloads (the project's own method)

Each stage is a shippable proof, ordered so no stage waits on the fork in 7:

  • Stage A — headless terminal: PTY builtins + signals + fd readiness (items 1–3). Proof: a .wo program spawns a shell, feeds it a script, captures and asserts the output — a script/expect clone. Closes iteration 28's bounded-subprocess gap as a side effect.
  • Stage B — VTE grid: parser + grid in pure .wo (item 4). Proof: replay recorded terminal sessions (vttest, asciinema casts) and assert final grid state against the reference implementation's.
  • Stage C — multiplexer: A + B + existing net = a tmux-lite: sessions survive detach, clients attach over a unix socket. No graphics, real product, exercises everything server-side writeonce is already good at. Corrected by the tmux study (2026-09-01): this stage also needs SCM_RIGHTS fd passing (item 6's builtin, promoted here), termios adoption of the client's own tty, and the terminfo fork — see ../tmux/00-tmux-parity.md.
  • Stage D — windowed, software-rendered: item 6 (connect, fd passing, shm) + a Wayland client library in .wo. First pixel on screen with zero linked C.
  • Stage E — GPU: only now decide item 7, with D as the measured baseline that says whether GL is worth an FFI doctrine change.

What this does NOT recommend

No general FFI now (one consumer, and stages A–D never need it); no bundled font rasterizer until D shows bitmap atlases failing; no attempt at winit-class cross-platform windowing — Linux/Wayland first, the same way the runtime is Linux/io_uring first.