display-demo: stop drawing a cursor and stop blocking on the mouse
Two real bugs visible in a normal `zig build run-x86-64` boot (but not in the
display-demo test, which spawns no input service):
- Two cursors. The demo drew its own cursor layer while the display service
now draws one too (its mouse-listener thread). The demo's went through the
client IPC protocol and lagged; the service's is in-process and tracks
tightly — "one responds better than the other."
- Animation frozen until the mouse moves. The demo called a *blocking*
`mouse.next()` (ipc_reply_wait) inside its animation loop, so the sliding
box advanced only one frame per mouse event. In the display-demo test
there is no input service, so subscribeMouse() returned null and the loop
ran free on its 30ms timer — which is exactly why the test passed while
the real boot was broken.
The compositor now owns the cursor (docs/display.md), so the demo should draw
none and read no input: it becomes a pure client-animation proof whose loop is
independent of the mouse. Removes its cursor layer, mouse subscription, and the
blocking read.
Also harden displayDemoTest to spawn the `input` service alongside the demo
(matching real boot): a client that blocks its animation loop on a mouse read
would now stall before `display-demo: ok` and fail the test, instead of
passing because no input service happened to be present.
Verified: display / display-service / display-demo / display-cursor all green.
This commit is contained in:
+7
-4
@@ -277,11 +277,14 @@ test/qemu_test.py <case>`), each layering on the last:
|
|||||||
layer — logging `display: compositor self-check ok`.
|
layer — logging `display: compositor self-check ok`.
|
||||||
- **`display-demo`** — the full pipeline from a separate process: the hardware-free
|
- **`display-demo`** — the full pipeline from a separate process: the hardware-free
|
||||||
[`display-demo`](../system/services/display-demo/) client (the
|
[`display-demo`](../system/services/display-demo/) client (the
|
||||||
[`input-source`](../system/services/input-source/) analog) drives layers — a wallpaper, a
|
[`input-source`](../system/services/input-source/) analog) drives layers — a wallpaper and
|
||||||
sliding rectangle, a cursor — through the layer client API and heartbeats
|
a sliding rectangle — through the layer client API and heartbeats
|
||||||
`display-demo: ok`, proving a frame travelled client → compositor → screen, exactly as
|
`display-demo: ok`, proving a frame travelled client → compositor → screen, exactly as
|
||||||
the [input test](input.md) proves an event travels source → service → subscriber. The
|
the [input test](input.md) proves an event travels source → service → subscriber. It draws
|
||||||
visible motion itself is a screenshot away via `zig build run-x86-64`.
|
no cursor and reads no input — the cursor is the service's own (below), and the demo
|
||||||
|
animates on its own frame timer, independent of the mouse (the test spawns `input`
|
||||||
|
alongside it to keep that independence honest). The visible motion itself is a screenshot
|
||||||
|
away via `zig build run-x86-64`.
|
||||||
- **`display-cursor`** — the mouse-listener thread end to end: with the `input` service up,
|
- **`display-cursor`** — the mouse-listener thread end to end: with the `input` service up,
|
||||||
`input-source mouse` publishes pure motion, and the display's listener thread accumulates
|
`input-source mouse` publishes pure motion, and the display's listener thread accumulates
|
||||||
it into a cursor position handed to the render loop over the `CursorChannel`. Once the
|
it into a cursor position handed to the render loop over the `CursorChannel`. Once the
|
||||||
|
|||||||
@@ -2850,6 +2850,11 @@ fn displayDemoTest(boot_information: *const BootInformation) void {
|
|||||||
result();
|
result();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Spawn the input service too — real boot has it, and it guards the demo's
|
||||||
|
// independence from input: the demo must animate to `display-demo: ok` on its own
|
||||||
|
// frame timer even with the input service available (a client that blocks its
|
||||||
|
// animation loop on a mouse read would stall here, never reaching the marker).
|
||||||
|
_ = spawnNamed(rd, "input");
|
||||||
_ = spawnNamed(rd, "display-demo");
|
_ = spawnNamed(rd, "display-demo");
|
||||||
scheduler.setPriority(1); // below the service + demo, so they run
|
scheduler.setPriority(1); // below the service + demo, so they run
|
||||||
while (true) scheduler.yield();
|
while (true) scheduler.yield();
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
//! system/services/display-demo — a hardware-free client of the display service, the
|
//! system/services/display-demo — a hardware-free client of the display service, the
|
||||||
//! `input-source` analog for the compositor. It creates a wallpaper, a rectangle it moves
|
//! `input-source` analog for the compositor. It creates a wallpaper and a rectangle it
|
||||||
//! each frame, and a small cursor, then drives the compositor in a present loop — proof
|
//! slides each frame, then drives the compositor in a present loop — proof that a
|
||||||
//! that a *separate process* can compose a moving scene through the display service over
|
//! *separate process* can compose a moving scene through the display service over IPC,
|
||||||
//! IPC, exercising the layer client API and damage-driven present end to end
|
//! exercising the layer client API and damage-driven present end to end
|
||||||
//! (docs/display.md). It logs `display-demo: ok` once it has driven a run of frames.
|
//! (docs/display.md). It logs `display-demo: ok` once it has driven a run of frames.
|
||||||
|
//!
|
||||||
|
//! It draws no cursor and reads no input: the on-screen cursor is the display service's
|
||||||
|
//! own, tracked by the service's mouse-listener thread (docs/display.md). The demo's job
|
||||||
|
//! is only to prove client-driven animation, so its loop runs on its own frame timer and
|
||||||
|
//! is deliberately independent of the mouse.
|
||||||
|
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
const display = runtime.display;
|
const display = runtime.display;
|
||||||
const system = runtime.system;
|
const system = runtime.system;
|
||||||
const time = runtime.time;
|
const time = runtime.time;
|
||||||
const input = runtime.input;
|
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
const mode = display.info() orelse {
|
const mode = display.info() orelse {
|
||||||
@@ -28,15 +32,6 @@ pub fn main() void {
|
|||||||
const box = display.createLayer(0, box_y, box_w, box_h, 1) orelse return createFailed();
|
const box = display.createLayer(0, box_y, box_w, box_h, 1) orelse return createFailed();
|
||||||
_ = box.fill(0, 0, box_w, box_h, display.color(0xE0, 0x60, 0x40));
|
_ = box.fill(0, 0, box_w, box_h, display.color(0xE0, 0x60, 0x40));
|
||||||
|
|
||||||
// A little cursor on top. Its position is signed (the layer API is i32) and clamped to
|
|
||||||
// the screen; mouse motion arrives as relative deltas we accumulate below.
|
|
||||||
var cursor_x: i32 = @intCast(mode.width / 2);
|
|
||||||
var cursor_y: i32 = @intCast(mode.height / 2);
|
|
||||||
const cursor_max_x: i32 = @as(i32, @intCast(mode.width)) - 12;
|
|
||||||
const cursor_max_y: i32 = @as(i32, @intCast(mode.height)) - 12;
|
|
||||||
const cursor = display.createLayer(cursor_x, cursor_y, 12, 12, 2) orelse return createFailed();
|
|
||||||
_ = cursor.fill(0, 0, 12, 12, display.color(0xF0, 0xF0, 0xF0));
|
|
||||||
|
|
||||||
_ = display.present();
|
_ = display.present();
|
||||||
_ = system.write("display-demo: scene up; animating\n");
|
_ = system.write("display-demo: scene up; animating\n");
|
||||||
|
|
||||||
@@ -45,18 +40,7 @@ pub fn main() void {
|
|||||||
var dx: i32 = 8;
|
var dx: i32 = 8;
|
||||||
var frame: u32 = 0;
|
var frame: u32 = 0;
|
||||||
|
|
||||||
var mouse = input.subscribeMouse(); // type: ?input.MouseSubscriber
|
|
||||||
if (mouse == null) _ = system.write("display-demo: no mouse; animating without it\n");
|
|
||||||
|
|
||||||
while (true) : (frame += 1) {
|
while (true) : (frame += 1) {
|
||||||
if (mouse) |*ms| {
|
|
||||||
if (ms.next()) |event| {
|
|
||||||
cursor_x = clamp(cursor_x + event.dx, 0, cursor_max_x);
|
|
||||||
cursor_y = clamp(cursor_y + event.dy, 0, cursor_max_y);
|
|
||||||
_ = cursor.configure(cursor_x, cursor_y, 2, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
x += dx;
|
x += dx;
|
||||||
if (x <= 0) {
|
if (x <= 0) {
|
||||||
x = 0;
|
x = 0;
|
||||||
@@ -74,13 +58,6 @@ pub fn main() void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clamp `v` to the inclusive range [lo, hi].
|
|
||||||
fn clamp(v: i32, lo: i32, hi: i32) i32 {
|
|
||||||
if (v < lo) return lo;
|
|
||||||
if (v > hi) return hi;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn createFailed() void {
|
fn createFailed() void {
|
||||||
_ = system.write("display-demo: create failed\n");
|
_ = system.write("display-demo: create failed\n");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user