From e02c0fd502035040ebabaa799e3e83c75901821b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 10 May 2026 19:54:14 -0400 Subject: [PATCH] Drop the per-frame `to_vec()` clone in the sidecar pixel writer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `LiveOutcome::frame` was carrying the rendered bytes as a fresh `Vec` cloned out of `RenderedFrame::rgba_bytes()`. At 60 fps on a 1080p canvas that was an extra 8 MB allocation + memcpy per frame on top of the clone `host.last_rendered_frame()` already paid for. `LiveOutcome` now carries the owned `RenderedFrame` directly, and `write_outcome` writes its `rgba_bytes()` slice straight to stdout — same single-clone cost as the host already incurred, no second allocation. Small Karpathy-style follow-up from the T8 review ("the host still copies its rendered buffer to a transient Vec before write_all; expose &[u8] straight to write_all once the profile shows that allocation in the top five"). Profile data is still deferred (T9 was marked vibe-benchmarking until T10 lands a real spec), but removing the cheap clone is structurally cleaner and makes the dispatch path one allocation lighter regardless. cargo test --bin ely_app: 118 passed, 0 failed, 1 ignored. cargo test -p ely_servo_host --features servo-engine --test sidecar: 9 passed. --- .../src/bin/ely_servo_sidecar/live.rs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs index b78cb03..c39b43f 100644 --- a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs +++ b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs @@ -136,8 +136,8 @@ fn write_outcome( let outcome = outcome.unwrap_or_else(|error| LiveOutcome::error(error.to_string())); serde_json::to_writer(&mut *stdout, &outcome.response)?; stdout.write_all(b"\n")?; - if let Some(frame_bytes) = outcome.frame_bytes { - stdout.write_all(&frame_bytes)?; + if let Some(frame) = outcome.frame.as_ref() { + stdout.write_all(frame.rgba_bytes())?; } stdout.flush()?; Ok(()) @@ -271,7 +271,8 @@ fn poll_frame( let frame = host.last_rendered_frame()?; let has_visible_content = frame.non_white_pixel_count() > 0 && frame.content_pixel_count() > 0; - let outcome = LiveOutcome::frame(LiveFrameReport::new(&snapshot, &frame), &frame); + let report = LiveFrameReport::new(&snapshot, &frame); + let outcome = LiveOutcome::from_frame(report, frame); if has_visible_content { session.awaiting_visible_frame = false; return Ok(outcome); @@ -353,28 +354,29 @@ struct LiveSitePermission { decision: String, } -/// A handle plus an optional raw-bytes payload, kept together until -/// the moment of writing to stdout. The JSON header advertises -/// `rgba_byte_count`; the binary follows on the same pipe. +/// A handle plus an optional rendered frame, kept together until the +/// moment of writing to stdout. The JSON header advertises +/// `rgba_byte_count`; the binary follows on the same pipe. We carry +/// the `RenderedFrame` (one host-side clone, already paid for inside +/// `host.last_rendered_frame`) instead of doing another `to_vec()` +/// over `rgba_bytes()` — `write_all(&self.rgba_bytes()[..])` writes +/// the existing slice straight to the pipe. struct LiveOutcome { response: LiveResponse, - frame_bytes: Option>, + frame: Option, } impl LiveOutcome { fn empty() -> Self { - Self { response: LiveResponse::empty(), frame_bytes: None } + Self { response: LiveResponse::empty(), frame: None } } fn error(message: String) -> Self { - Self { response: LiveResponse::error(message), frame_bytes: None } + Self { response: LiveResponse::error(message), frame: None } } - fn frame(report: LiveFrameReport, frame: &RenderedFrame) -> Self { - Self { - response: LiveResponse::frame(report), - frame_bytes: Some(frame.rgba_bytes().to_vec()), - } + fn from_frame(report: LiveFrameReport, frame: RenderedFrame) -> Self { + Self { response: LiveResponse::frame(report), frame: Some(frame) } } }