diff options
Diffstat (limited to 'src/server/render_stream.rs')
| -rw-r--r-- | src/server/render_stream.rs | 91 |
1 files changed, 83 insertions, 8 deletions
diff --git a/src/server/render_stream.rs b/src/server/render_stream.rs index db613e96..ee874e52 100644 --- a/src/server/render_stream.rs +++ b/src/server/render_stream.rs @@ -7,7 +7,7 @@ use crate::app::state::AppState; use crate::protocol::render_ansi::{BlitEncoder, EncodedBlit}; use crate::protocol::{ CursorState, FrameData, PaneSurfaceFrame, PaneSurfacePatch, RenderEncoding, ServerMessage, - TerminalFrame, + SurfaceGraphicsAssetKey, SurfaceGraphicsScene, TerminalFrame, }; use crate::terminal::TerminalRuntimeRegistry; @@ -152,9 +152,18 @@ impl ClientRenderState { } } + #[cfg(test)] pub(crate) fn prepare_pane_surface( &mut self, + surface: PaneSurfaceFrame, + ) -> Option<PreparedRender> { + self.prepare_pane_surface_with_file(surface, false) + } + + pub(crate) fn prepare_pane_surface_with_file( + &mut self, mut surface: PaneSurfaceFrame, + has_file_upload: bool, ) -> Option<PreparedRender> { let Self::Semantic { last_surface, @@ -166,7 +175,8 @@ impl ClientRenderState { else { return None; }; - if !*recompute_pending + if !has_file_upload + && !*recompute_pending && surface.graphics.assets.is_empty() && last_surface.as_deref().is_some_and(|last| { last.projection_revision == surface.projection_revision @@ -182,6 +192,7 @@ impl ClientRenderState { } surface.surface_revision = surface_revision.saturating_add(1); let assets = std::mem::take(&mut surface.graphics.assets); + let queued_graphics_assets = assets.iter().map(|asset| asset.key.clone()).collect(); let committed_surface = surface.clone(); surface.graphics.assets = assets; let mut message = ServerMessage::PaneSurface(surface); @@ -217,6 +228,7 @@ impl ClientRenderState { Some(PreparedRender::Semantic { message: delta.or(reused).unwrap_or(message), committed_surface: Box::new(committed_surface), + queued_graphics_assets, }) } @@ -342,6 +354,7 @@ pub(crate) enum PreparedRender { Semantic { message: ServerMessage, committed_surface: Box<PaneSurfaceFrame>, + queued_graphics_assets: Vec<SurfaceGraphicsAssetKey>, }, SemanticPatch { message: ServerMessage, @@ -362,19 +375,56 @@ impl PreparedRender { } } - pub(crate) fn strip_pane_surface_assets(&mut self) -> bool { + /// Graphics metadata represented by this semantic update plus only the + /// asset keys whose pixel payloads were queued. This is independent of the + /// selected wire codec and avoids cloning asset byte vectors. + pub(crate) fn queued_surface_graphics( + &self, + ) -> Option<(&SurfaceGraphicsScene, &[SurfaceGraphicsAssetKey])> { + match self { + Self::Semantic { + committed_surface, + queued_graphics_assets, + .. + } => Some((&committed_surface.graphics, queued_graphics_assets)), + Self::SemanticPatch { .. } | Self::TerminalAnsi { .. } => None, + } + } + + pub(crate) fn has_queued_surface_assets(&self) -> bool { + matches!(self, Self::Semantic { queued_graphics_assets, .. } if !queued_graphics_assets.is_empty()) + } + + /// Removes the largest inline payload from a full semantic surface while + /// preserving placement metadata. Largest-first guarantees that a fitting + /// smaller asset is not discarded behind an oversized one. Equal sizes use + /// deterministic scene order. Encoded delta/reuse messages return `None`; callers + /// can invalidate that baseline and retry as a full surface. + pub(crate) fn pop_pane_surface_asset(&mut self) -> Option<SurfaceGraphicsAssetKey> { let Self::Semantic { message: ServerMessage::PaneSurface(surface), + queued_graphics_assets, .. } = self else { - return false; + return None; }; - if surface.graphics.assets.is_empty() { - return false; + let index = surface + .graphics + .assets + .iter() + .enumerate() + .max_by_key(|(index, asset)| (asset.data.len(), *index))? + .0; + let asset = surface.graphics.assets.remove(index); + let key = asset.key; + if let Some(index) = queued_graphics_assets + .iter() + .position(|queued| *queued == key) + { + queued_graphics_assets.remove(index); } - surface.graphics.assets.clear(); - true + Some(key) } } @@ -731,6 +781,31 @@ mod tests { } #[test] + fn deferred_file_upload_keeps_identical_metadata_and_retries_without_committing() { + for reuse in [false, true] { + let mut state = ClientRenderState::new(RenderEncoding::SemanticFrame); + state.enable_surface_reuse(reuse); + let surface = popup_surface("native"); + let first = state.prepare_pane_surface(surface.clone()).unwrap(); + state.commit_sent_frame(first); + assert!(state.prepare_pane_surface(surface.clone()).is_none()); + let file = state + .prepare_pane_surface_with_file(surface.clone(), true) + .unwrap(); + let retry = state + .prepare_pane_surface_with_file(surface.clone(), true) + .unwrap(); + let config = bincode::config::standard(); + assert_eq!( + bincode::serde::encode_to_vec(file.message(), config).unwrap(), + bincode::serde::encode_to_vec(retry.message(), config).unwrap() + ); + state.commit_sent_frame(retry); + assert!(state.prepare_pane_surface(surface).is_none()); + } + } + + #[test] fn popup_only_surface_changes_are_not_deduplicated() { let mut state = ClientRenderState::new(RenderEncoding::SemanticFrame); let prepared = state |
