diff options
Diffstat (limited to 'src/server/headless/pane_graphics.rs')
| -rw-r--r-- | src/server/headless/pane_graphics.rs | 284 |
1 files changed, 105 insertions, 179 deletions
diff --git a/src/server/headless/pane_graphics.rs b/src/server/headless/pane_graphics.rs index 151b26ee..bce53b72 100644 --- a/src/server/headless/pane_graphics.rs +++ b/src/server/headless/pane_graphics.rs @@ -1,21 +1,7 @@ use super::{HeadlessServer, RenderImpact}; use crate::api; use crate::protocol::{ServerMessage, MAX_GRAPHICS_FRAME_SIZE}; -use crate::server::clients::{render_targets, ClientConnectionMode, DeferredRender}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum RetainedGraphicsOutcome { - Sent, - Deferred, - Fallback, -} - -pub(super) fn frame_pane_graphics(bytes: Vec<u8>) -> Vec<u8> { - if bytes.is_empty() { - return bytes; - } - [b"\x1b7".as_slice(), &bytes, b"\x1b8"].concat() -} +use crate::server::clients::ClientConnectionMode; impl HeadlessServer { pub(super) fn pane_graphics_runtime_active(&self) -> bool { @@ -76,7 +62,6 @@ impl HeadlessServer { .and_then(crate::app::pane_graphics::Layer::direct_lease) .map(|lease| { ( - slot.host_image_id, lease.path().to_string_lossy().into_owned(), lease.len() as u64, lease.fingerprint(), @@ -84,21 +69,35 @@ impl HeadlessServer { }) }) }); - if let (Some(key), Some((image_id, path, expected_len, transfer_id))) = + if let (Some(key), Some((path, expected_len, transfer_id))) = (direct_key.clone(), direct_frame) { - let command = self.clients.get(&client_id).and_then(|client| { - crate::kitty_graphics::prepare_direct_file( - &self.app.state, - &self.app.pane_graphics, - self.app.state.view.tab_surface(), - client.cell_size, - !internal_changed, - &client.graphics_cache, - &key, - ) + let prepared = self.clients.get(&client_id).and_then(|client| { + matches!(client.mode, ClientConnectionMode::ClientShell).then_some(())?; + let layer = self + .app + .pane_graphics + .slots + .get(&key) + .and_then(|slot| slot.layer.as_ref())?; + let asset = crate::kitty_graphics::surface::pane_layer_asset_key( + &self.app, &key, layer, + )?; + let (image_id, control) = + crate::kitty_graphics::surface::direct_upload_control( + &self.client_shell_boot_id, + &asset, + ); + Some(( + crate::kitty_graphics::DirectFileCommand { + leading: Vec::new(), + control, + }, + Some(asset), + image_id, + )) }); - let Some(command) = command else { + let Some((command, surface_asset, image_id)) = prepared else { if self.install_inline_fallback(&key) { if msg.respond_to.send(response).is_err() { self.retire_direct_gate(&key); @@ -123,6 +122,7 @@ impl HeadlessServer { transfer_id, leading: command.leading, control: command.control, + surface_asset, }; let send = Self::frame_server_message_with_max(&message, MAX_GRAPHICS_FRAME_SIZE) @@ -144,6 +144,7 @@ impl HeadlessServer { if let Some(slot) = self.app.pane_graphics.slots.get_mut(&key) { slot.direct_gate = Some(crate::app::pane_graphics::DirectGate { transfer_id, + image_id, client_id, deadline: std::time::Instant::now() + crate::app::pane_graphics::DIRECT_DELIVERY_TIMEOUT, @@ -267,10 +268,14 @@ impl HeadlessServer { image_id: u32, ) -> bool { if let Some(gate) = self.app.pane_graphics.slots.values_mut().find_map(|slot| { - (slot.host_image_id == image_id && slot.stream_is_active()) + slot.stream_is_active() .then_some(slot.direct_gate.as_mut()) .flatten() - .filter(|gate| gate.client_id == client_id && gate.transfer_id == transfer_id) + .filter(|gate| { + gate.client_id == client_id + && gate.transfer_id == transfer_id + && gate.image_id == image_id + }) }) { gate.written = true; gate.deadline = @@ -297,7 +302,7 @@ impl HeadlessServer { slot.stream_is_active() && gate.client_id == client_id && gate.transfer_id == transfer_id - && slot.host_image_id == image_id + && gate.image_id == image_id && (!success || gate.written) }) .then(|| key.clone()) @@ -312,11 +317,11 @@ impl HeadlessServer { .iter() .any(|workspace| workspace.pane_state(key.0).is_some()); if !pane_is_live { - self.retire_direct_gate(&key); + self.retire_direct_gate_with_client_notice(&key); return false; } if success { - let (gate, host_image_id) = { + let gate = { let slot = self .app .pane_graphics @@ -329,23 +334,8 @@ impl HeadlessServer { if let Some(layer) = slot.layer.as_mut() { layer.mark_resident(client_id); } - ( - slot.direct_gate.take().expect("matched gate"), - slot.host_image_id, - ) + slot.direct_gate.take().expect("matched gate") }; - if let (Some(client), Some(layer)) = ( - self.clients.get_mut(&client_id), - self.app - .pane_graphics - .slots - .get(&key) - .and_then(|slot| slot.layer.as_ref()), - ) { - client - .graphics_cache - .trust_pane_layer(&key, host_image_id, layer); - } if gate.respond_to.send(gate.success_response).is_err() { self.retire_direct_gate(&key); return true; @@ -362,9 +352,6 @@ impl HeadlessServer { self.retire_all_direct_graphics(); return true; } - if let Some(client) = self.clients.get_mut(&client_id) { - client.graphics_cache.forget_pane_layer(&key, image_id); - } let gate = self .app .pane_graphics @@ -404,7 +391,7 @@ impl HeadlessServer { *client_id, ServerMessage::GraphicsTransmissionRetired { transfer_id: gate.transfer_id, - image_id: slot.host_image_id, + image_id: gate.image_id, }, ); } @@ -421,8 +408,53 @@ impl HeadlessServer { !expired.is_empty() } + fn retire_direct_gate_with_client_notice(&mut self, key: &crate::app::pane_graphics::Key) { + if let Some((client_id, transfer_id, image_id)) = self + .app + .pane_graphics + .slots + .get(key) + .and_then(|slot| slot.direct_gate.as_ref()) + .map(|gate| (gate.client_id, gate.transfer_id, gate.image_id)) + { + self.send_to_client( + client_id, + ServerMessage::GraphicsTransmissionRetired { + transfer_id, + image_id, + }, + ); + } + self.retire_direct_gate(key); + } + + pub(super) fn retain_live_pane_graphics(&mut self) -> bool { + let dead_direct = self + .app + .pane_graphics + .slots + .iter() + .filter(|((pane_id, _), slot)| { + slot.direct_gate.is_some() + && !self + .app + .state + .workspaces + .iter() + .any(|workspace| workspace.pane_state(*pane_id).is_some()) + }) + .map(|(key, _)| key.clone()) + .collect::<Vec<_>>(); + let changed = !dead_direct.is_empty(); + for key in dead_direct { + self.retire_direct_gate_with_client_notice(&key); + } + let retained = self.app.pane_graphics.retain_live_panes(&self.app.state); + changed || retained + } + pub(super) fn retire_all_direct_graphics(&mut self) { - let keys = self + let retired = self .app .pane_graphics .slots @@ -432,9 +464,26 @@ impl HeadlessServer { .as_ref() .is_some_and(crate::app::pane_graphics::Layer::terminal_only) }) - .map(|(key, _)| key.clone()) + .map(|(key, slot)| { + let notification = slot + .direct_gate + .as_ref() + .map(|gate| (gate.client_id, gate.transfer_id, gate.image_id)); + (key.clone(), notification) + }) .collect::<Vec<_>>(); - for key in keys { + for (_, notification) in &retired { + if let Some((client_id, transfer_id, image_id)) = notification { + self.send_to_client( + *client_id, + ServerMessage::GraphicsTransmissionRetired { + transfer_id: *transfer_id, + image_id: *image_id, + }, + ); + } + } + for (key, _) in retired { self.retire_direct_gate(&key); } } @@ -460,127 +509,4 @@ impl HeadlessServer { self.retire_direct_gate(&key); } } - - pub(super) fn render_retained_graphics_update_and_stream(&mut self) -> RetainedGraphicsOutcome { - crate::render_prof::event("retained_graphics.attempt"); - if self.app.full_redraw_pending { - crate::render_prof::event("retained_graphics_fallback.full_redraw_pending"); - return RetainedGraphicsOutcome::Fallback; - } - - let render_targets = render_targets(&self.clients, self.foreground_client_id); - let mut app_view_size = None; - for (_, terminal_size, _, _, mode) in &render_targets { - if !matches!(mode, ClientConnectionMode::App) { - continue; - } - if app_view_size.is_some_and(|size| size != *terminal_size) { - crate::render_prof::event("retained_graphics_fallback.mixed_app_geometry"); - return RetainedGraphicsOutcome::Fallback; - } - app_view_size = Some(*terminal_size); - } - let mut deferred = false; - let mut prepared = Vec::new(); - - for (client_id, (cols, rows), cell_size, _is_foreground, mode) in render_targets { - if !matches!(mode, ClientConnectionMode::App) { - continue; - } - let Some(client) = self.clients.get_mut(&client_id) else { - crate::render_prof::event("retained_graphics_fallback.client_missing"); - return RetainedGraphicsOutcome::Fallback; - }; - if client.deferred_render() != DeferredRender::None { - deferred = true; - continue; - } - if client.graphics_surface_reset_pending || !cell_size.is_known() { - crate::render_prof::event("retained_graphics_fallback.client_state"); - return RetainedGraphicsOutcome::Fallback; - } - let Some(last_frame) = client.render_state.last_frame() else { - crate::render_prof::event("retained_graphics_fallback.no_last_frame"); - return RetainedGraphicsOutcome::Fallback; - }; - if last_frame.width != cols || last_frame.height != rows { - crate::render_prof::event("retained_graphics_fallback.frame_size_mismatch"); - return RetainedGraphicsOutcome::Fallback; - } - if client.writer.is_none() { - crate::render_prof::event("retained_graphics_fallback.writer_missing"); - return RetainedGraphicsOutcome::Fallback; - } - - let mut next_graphics_cache = client.graphics_cache.clone(); - let encode_started = crate::render_prof::timer(); - let encoded = crate::kitty_graphics::encode_local_pane_graphics( - &self.app.state, - &self.app.pane_graphics, - &self.app.terminal_runtimes, - self.app.state.view.tab_surface(), - cell_size, - Some(crate::kitty_graphics::HEADLESS_GRAPHICS_TRANSACTION_BUDGET), - &mut next_graphics_cache, - ); - crate::render_prof::duration_since("retained_graphics.graphics_encode", encode_started); - prepared.push((client_id, encoded, next_graphics_cache)); - } - - let mut broken_clients = Vec::new(); - for (client_id, encoded, next_graphics_cache) in prepared { - let Some(client) = self.clients.get_mut(&client_id) else { - continue; - }; - let serialized = if encoded.bytes.is_empty() { - None - } else { - match Self::frame_server_message_with_max( - &ServerMessage::Graphics { - bytes: frame_pane_graphics(encoded.bytes), - }, - MAX_GRAPHICS_FRAME_SIZE, - ) { - Ok(serialized) => Some(serialized), - Err(_) => { - crate::render_prof::event("retained_graphics_fallback.oversized"); - return RetainedGraphicsOutcome::Fallback; - } - } - }; - let result = match (serialized, client.writer.as_ref()) { - (None, _) => Ok(()), - (Some(bytes), Some(writer)) => writer.render.try_send(bytes), - (Some(bytes), None) => Err(std::sync::mpsc::TrySendError::Disconnected(bytes)), - }; - match result { - Ok(()) => { - client.graphics_cache = next_graphics_cache; - if encoded.incomplete { - client.defer_full_render(); - deferred = true; - } else { - client.clear_deferred_render(); - } - crate::render_prof::event("retained_graphics.sent"); - } - Err(std::sync::mpsc::TrySendError::Full(_)) => { - client.defer_full_render(); - deferred = true; - } - Err(std::sync::mpsc::TrySendError::Disconnected(_)) => { - broken_clients.push(client_id) - } - } - } - for client_id in broken_clients { - self.remove_client_and_resize_if_needed(client_id); - } - - if deferred { - RetainedGraphicsOutcome::Deferred - } else { - RetainedGraphicsOutcome::Sent - } - } } |
