aboutsummaryrefslogtreecommitdiff
path: root/src/server/render_stream.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/render_stream.rs')
-rw-r--r--src/server/render_stream.rs91
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