page: the scheduler turn pumps the compositor only when it has something to move

Undertale's game process pumped 7,000 times a second, nearly always with
nothing to move, and the pump was 311 of the 546 ms a 10 s window held the
kernel lock for the scheduler turn -- the turn that is 70% of the game's lock
wait (census, worldmachine). needs_pump asks first: a client socket with
bytes or a hang-up to read, bytes still owed to one, an X window manager to
drive, input in the ring for a client that takes it (wl_input_pending, a
peek), or 50 ms since the last pump.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_ea9f71488b6e448da93d6c2dabf4879c
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-12 07:16:14 -04:00
1 parent c043aa5503
commit 6cf33b9c4e
4 files changed
+49 -1

No files matched your search

+2
View File
@@ -1313,6 +1313,8 @@ export class Gex {
// surface the compositor's own hit test picked.
wl_poll_input: (ptr, max) =>
this.#wl?.pollInput(this.#memory, ptr >>> 0, max >>> 0) ?? 0,
// Whether that ring holds anything, without draining it.
wl_input_pending: () => (this.#wl?.inputPending() ? 1 : 0),
};
}
+7
View File
@@ -419,6 +419,13 @@ export class WlProxy {
return count;
}
/** Whether this process's input ring holds an event. */
inputPending() {
if (this.#block < 0) return false;
const a = this.#a;
return Atomics.load(a, this.#ring + K_HEAD) !== Atomics.load(a, this.#ring + K_TAIL);
}
/** Give the block back, so a later process can have it. */
release() {
if (this.#block < 0) return;
+3 -1
View File
@@ -10607,7 +10607,9 @@ fn schedule() -> i32 {
// pixels), so it pumps inside `with_host`; the device is a
// cheap Arc clone that aliases none of the host's mutable state.
let pick = with_host(|h| {
if let Some(w) = h.wayland.clone() {
// Only when it has anything to move (`needs_pump`): most turns it
// has not.
if let Some(w) = h.wayland.clone().filter(|w| w.needs_pump()) {
let gm = GuestMem::new(guest_bytes());
// What the pump wrote to a client's socket (a frame callback,
// a buffer release, input) is readiness for a thread parked in
+37
View File
@@ -44,8 +44,14 @@ extern "C" {
/// 8 close, 9 dismiss popup. Coordinates are surface-local pixels;
/// button/key codes are Linux `BTN_*`/evdev numbers.
fn wl_poll_input(ptr: u32, max: u32) -> u32;
/// Whether this process's input ring holds an event, without taking it.
fn wl_input_pending() -> u32;
}
/// The floor under [`WaylandDevice::needs_pump`]: whatever it does not look
/// at still gets a pump at least this often.
const PUMP_FLOOR_NS: u64 = 50_000_000;
/// Words in one published surface record. Fixed so both sides can index it
/// without a schema: see `wl-bridge.mjs`, which reads the same numbers.
const REC: usize = 28;
@@ -296,6 +302,8 @@ struct Inner {
/// How many clients were live at the last publish, so a client going away
/// is a scene change even when no one drew.
published: Mutex<usize>,
/// When a pump last ran (host ns), for `PUMP_FLOOR_NS`.
last_pump: std::sync::atomic::AtomicU64,
}
/// The compositor as the guest sees it: a name that accepts connections.
@@ -341,6 +349,7 @@ impl WaylandDevice {
/// compositor is told is the address of that range -- nothing is copied
/// here, and nothing is composited here either.
pub fn pump(&self, kernel: &Kernel, mem: &GuestMem<'_>) -> bool {
self.inner.last_pump.store(crate::now_ns(), std::sync::atomic::Ordering::Relaxed);
let mut sessions = self.inner.sessions.lock().unwrap();
let mut sent_any = false;
// Compositor input and control for this turn, addressed by client: a
@@ -591,6 +600,34 @@ impl WaylandDevice {
sent_any
}
/// Whether a pump now would do anything: a client socket with bytes or a
/// hang-up to read, bytes still owed to one, an X window manager to
/// drive, or input in the ring for a client that takes it -- or the floor
/// (`PUMP_FLOOR_NS`) has passed. Asked on every scheduler turn of every
/// Worker, under the lock as the pump was: Undertale's game process pumped 7,000
/// times a second, nearly all with nothing to move, and the pump was 311
/// of the 546 ms a 10 s window held the lock for the turn (census,
/// worldmachine) -- the turn that is 70% of the game's lock wait.
pub fn needs_pump(&self) -> bool {
let now = crate::now_ns();
if now.saturating_sub(self.inner.last_pump.load(std::sync::atomic::Ordering::Relaxed)) >= PUMP_FLOOR_NS {
return true;
}
// Held elsewhere (an accept, a GL attach): pump, as before.
let Ok(sessions) = self.inner.sessions.try_lock() else {
return true;
};
let mut wants = false;
for s in sessions.iter() {
if s.xwm.is_some() || s.sent < s.out.len() || s.socket.poll_readable() || s.socket.poll_hup() {
return true;
}
wants |= s.client.wants_input();
}
// SAFETY: a host import taking nothing.
wants && unsafe { wl_input_pending() } != 0
}
/// Write every client's surfaces into the scene arena.
///
/// The tail of `pump`, callable on its own for a change that arrives from