aboutsummaryrefslogtreecommitdiff
path: root/src/server/headless.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-08 19:48:51 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-08 19:49:06 +0300
commita7cd7806f2fa0251e2dca41ffec61ceafb6f0e18 (patch)
tree37ea48c24e145e5514e44d78eb8807c661537948 /src/server/headless.rs
parent07222706c78943da814e39abece8b5ba5a10dfc3 (diff)
fix: reduce scroll CPU in headless server
refs #512
Diffstat (limited to 'src/server/headless.rs')
-rw-r--r--src/server/headless.rs88
1 files changed, 68 insertions, 20 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs
index 0f9f3bb6..edd1a388 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -2795,7 +2795,7 @@ impl HeadlessServer {
return false;
};
let prepare_started = crate::render_prof::timer();
- let Some(prepared) = client.render_state.prepare_frame(&frame) else {
+ let Some(prepared) = client.render_state.prepare_frame(frame) else {
client.render_pending = false;
crate::render_prof::event("retained_send.skip_identical");
crate::render_prof::duration_since("retained_send.prepare_frame", prepare_started);
@@ -2831,7 +2831,7 @@ impl HeadlessServer {
match writer.render.try_send(serialized) {
Ok(()) => {
client.render_pending = false;
- client.render_state.commit_sent_frame(frame, prepared);
+ client.render_state.commit_sent_frame(prepared);
crate::render_prof::event("retained_send.sent");
crate::render_prof::duration_since("retained_send.try_send", send_started);
true
@@ -3007,21 +3007,21 @@ impl HeadlessServer {
commit_graphics_cache = false;
}
+ let max_frame_size = if frame.graphics.is_empty() {
+ MAX_FRAME_SIZE
+ } else {
+ MAX_GRAPHICS_FRAME_SIZE
+ };
+ let has_graphics = !frame.graphics.is_empty();
let prepare_started = crate::render_prof::timer();
- let Some(mut prepared) = client.render_state.prepare_frame(&frame) else {
+ let Some(mut prepared) = client.render_state.prepare_frame(frame) else {
client.render_pending = false;
crate::render_prof::event("full_render.skip_identical");
crate::render_prof::duration_since("full_render.prepare_frame", prepare_started);
continue;
};
crate::render_prof::duration_since("full_render.prepare_frame", prepare_started);
- let mut frame_to_commit = frame.clone();
- let max_frame_size = if frame.graphics.is_empty() {
- MAX_FRAME_SIZE
- } else {
- MAX_GRAPHICS_FRAME_SIZE
- };
let serialize_started = crate::render_prof::timer();
let serialized = match Self::frame_server_message_with_max(
prepared.message(),
@@ -3031,17 +3031,22 @@ impl HeadlessServer {
crate::render_prof::duration_since("full_render.serialize", serialize_started);
framed
}
- Err(protocol::FramingError::Oversized { claimed, max })
- if !frame.graphics.is_empty() =>
- {
+ Err(protocol::FramingError::Oversized { claimed, max }) if has_graphics => {
warn!(
client_id,
claimed, max, "dropping graphics from oversized frame for client"
);
- let mut text_only_frame = frame.clone();
+ let Some(mut text_only_frame) = prepared.into_frame() else {
+ crate::render_prof::event("full_render.serialize_error");
+ crate::render_prof::duration_since(
+ "full_render.serialize",
+ serialize_started,
+ );
+ continue;
+ };
text_only_frame.graphics.clear();
let Some(text_only_prepared) =
- client.render_state.prepare_frame(&text_only_frame)
+ client.render_state.prepare_frame(text_only_frame)
else {
client.render_pending = false;
crate::render_prof::event("full_render.skip_identical_text_only");
@@ -3065,7 +3070,6 @@ impl HeadlessServer {
}
};
prepared = text_only_prepared;
- frame_to_commit = text_only_frame;
commit_graphics_cache = false;
crate::render_prof::duration_since("full_render.serialize", serialize_started);
framed
@@ -3097,9 +3101,7 @@ impl HeadlessServer {
client.graphics_cache = next_graphics_cache;
client.graphics_surface_reset_pending = false;
}
- client
- .render_state
- .commit_sent_frame(frame_to_commit, prepared);
+ client.render_state.commit_sent_frame(prepared);
crate::render_prof::event("full_render.sent");
crate::render_prof::duration_since("full_render.try_send", send_started);
}
@@ -4505,6 +4507,52 @@ next_tab = ""
}
#[test]
+ fn terminal_attach_page_key_forwards_in_alternate_screen_without_mouse_reporting() {
+ let rt = tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("test runtime");
+ let _runtime_guard = rt.enter();
+ let mut bytes = b"\x1b[?1049h".to_vec();
+ for line in 0..80 {
+ bytes.extend_from_slice(format!("line {line:02}\r\n").as_bytes());
+ }
+ let (runtime, mut input_rx) =
+ crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes(
+ 20, 5, 4096, &bytes, 4,
+ );
+ runtime.scroll_up(3);
+
+ apply_terminal_attach_scroll(
+ &runtime,
+ AttachScrollSource::PageKey {
+ input: b"\x1b[5~".to_vec(),
+ },
+ AttachScrollDirection::Up,
+ 4,
+ None,
+ None,
+ 0,
+ )
+ .expect("page key forward");
+
+ assert_eq!(
+ runtime
+ .scroll_metrics()
+ .expect("scroll metrics")
+ .offset_from_bottom,
+ 0
+ );
+ assert_eq!(
+ input_rx.try_recv().expect("forwarded page key"),
+ Bytes::from_static(b"\x1b[5~")
+ );
+ drop(runtime);
+ drop(_runtime_guard);
+ rt.shutdown_timeout(Duration::from_millis(100));
+ }
+
+ #[test]
fn headless_scheduled_tasks_expire_agent_metadata() {
let mut server = test_headless_server();
let workspace = crate::workspace::Workspace::test_new("metadata");
@@ -6384,9 +6432,9 @@ next_tab = ""
frame.cells[hyperlink_idx].hyperlink = Some(0);
let prepared = client
.render_state
- .prepare_frame(&frame)
+ .prepare_frame(frame)
.expect("hyperlink frame differs");
- client.render_state.commit_sent_frame(frame, prepared);
+ client.render_state.commit_sent_frame(prepared);
let runtime = server
.app