atagen

monolith

public
Star0
Watch0
9b084abcb9c000083d3ffaac90179e6fd3de5060
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
design/review-notes.mdMarkdown42 lines5.4 KB

Notes for the codebase review

Collected while building M5 to M7 and the follow-ups. Each item names where to look. Ordered roughly by how much it would pay back.

Structure

  • State is a god object. It has 70 public fields in state.rs. They cover backend handles, every smithay protocol state, layout, animation, capture, imports, Xwayland, lock, idle, activation and launch tracking. Every module is impl State, so nothing has a boundary and any function can touch anything. The protocol states are mostly inert after construction. They could live in one Protocols struct, with the live subsystems (lock, captures, imports, xwayland, toplevel handles) owning their own data and taking narrow arguments.
  • Three copies of “draw an output”. drm::render_surface, nested::render and capture::output_elements each assemble cursor plus lock-or-scene, and each destructures State field by field to satisfy the borrow checker. capture::output_elements is the general version. The backends should call one function and keep only presentation.
  • scene::build takes ten arguments and carries #[expect(clippy::too_many_arguments)]. Most of them are one borrowed view of the world. A SceneInput struct, or the split above, removes it.
  • drm::render_surface does too much. It handles VRR switching, timing and budget, scene assembly, lock confirmation, capture damage, frame callbacks and the slow-frame log. VRR and stats could be methods on Surface.
  • handlers.rs is a grab bag. It holds compositor commit logic, xdg-shell, decorations, seat, selection, DnD, syncobj, hold_until_readable, workspace actions and fullscreen. The commit handler alone runs import, window commit, unmapped mapping, first-buffer, popups, layers and capture damage in sequence. A per-role dispatch would read better.
  • layout/src/lib.rs is 1000 lines. Tags, outputs, parking, scratchpads, focus history and arrangement share one impl Layout. Scratchpads and output parking look separable.
  • render-vk/src/renderer.rs is 1100 lines. It mixes the smithay trait impls, submission and stall handling, the shm cache, read-back and the new DmabufImporter.

Duplication

  • CLEAR is defined identically in drm.rs, nested/mod.rs and capture.rs.
  • Closing a window (xdg send_close or X11 close) is written out in both commands.rs and toplevels.rs. It belongs on a window helper next to root() and identity().
  • Window lookup is a linear scan: window_id by surface, x11_window_id, the toplevel key lookup in toplevels.rs, and ListHandles::window. That’s fine at desktop window counts, but four spellings of the same search should be one index.
  • Smoke-test shell scripts were rewritten for every protocol (/tmp/m4fix/smoke_*.sh). A small xtask or .nu harness that starts nested monolith with a client list and collects logs would make protocol checks repeatable, and could gate CI later.

Code quality

  • Per-dispatch work that doesn’t need to run. after_dispatch runs publish_workspaces and publish_toplevels after every batch of events. They rebuild full snapshots and lock every toplevel’s data for titles even when nothing changed. A dirty flag set by commits, layout changes and focus changes would make them nearly free.
  • Silent let _ = on X11 configure and set_* calls, on notifier.successful, and on several protocol sends. Most are fine, but the X11 ones hide real failures.
  • configure() in state.rs handles both xdg and X11 windows with two inline branches. Once more window kinds exist (none planned), it wants a small trait.
  • Xwayland lifecycle. A SIGKILLed monolith leaves its /tmp/.X*-lock and socket behind. claim handles stale locks by pid, but a recycled pid makes it skip that display for good. Low priority.
  • Focus::X11(Box<X11Surface>) exists only to satisfy large_enum_variant. That’s fine, but X11Surface is 432 bytes, which is worth knowing.
  • Comment density is uneven. Some modules carry a doc line on most items, others none. Check against the comment rules in one sweep.

Tests

  • The layout proptest pulls its weight. It caught the hidden root-leaf bug the moment the “hidden tiles have no area” invariant was added. More invariants in check_geometry (focus always visible, tags always viewed somewhere) would be cheap and high-yield.
  • Nothing tests the protocol servers. workspace.rs and toplevel.rs diff snapshots by hand, which is exactly the strange, racy logic the test policy wants covered. Candidates are a snapshot-diff test driving two update calls through a client, or a pure function that computes the diff and a test of that.
  • render-vk has golden and dmabuf tests. Nothing pins the submit-stall absorption logic, since StallPhase::saw is pure and tiny. It’s a candidate only if it ever changes.
  • monolith itself has no tests, so behaviour such as lock confirmation, capture waiting and import blockers is only smoke-tested. Most of it needs a display. The pure parts (the frame budget, floats(), workspace filtering) could be pulled out and tested if they ever break.

Smaller observations

  • state.rs free functions root, identity, configure, gaps and xkb_config are window, layout and config helpers living in the state module.
  • The layers.rs debug lines added to chase the quickshell report can be dropped or kept at trace.
  • design/parked.md and this file overlap a little. Parked is for behaviour and this is for structure.