use std::collections::HashMap; use std::hash::{Hash, Hasher}; use crate::api::schema::{PaneGraphicsFormat, PaneGraphicsPlacementParams}; use crate::layout::PaneId; pub(crate) type Key = (PaneId, String); pub(crate) const DIRECT_DELIVERY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); pub(crate) const DIRECT_RESPONSE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3); pub(crate) const DIRECT_OUTER_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(9); #[derive(Debug)] pub(crate) enum Backing { Inline(Vec), Direct(crate::pane_graphics_files::Lease), Resident { len: usize, client_id: u64 }, } #[derive(Debug)] pub(crate) struct Layer { pub(crate) format: PaneGraphicsFormat, pub(crate) image_width: u32, pub(crate) image_height: u32, pub(crate) backing: Backing, pub(crate) data_fingerprint: u64, pub(crate) render: PaneGraphicsPlacementParams, pub(crate) z_index: i32, } #[derive(Debug)] pub(crate) struct DirectGate { pub(crate) transfer_id: u64, pub(crate) image_id: u32, pub(crate) client_id: u64, pub(crate) deadline: std::time::Instant, pub(crate) written: bool, pub(crate) success_response: String, pub(crate) respond_to: std::sync::mpsc::Sender, } #[derive(Debug)] pub(crate) struct Slot { pub(crate) host_image_id: u32, pub(crate) layer: Option, pub(crate) stream_owner: Option, pub(crate) stream_active: Option>, pub(crate) direct_gate: Option, } impl Slot { #[cfg(test)] pub(crate) fn test(host_image_id: u32, layer: Option) -> Self { Self { host_image_id, layer, stream_owner: None, stream_active: None, direct_gate: None, } } pub(crate) fn stream_is_active(&self) -> bool { self.stream_active .as_ref() .is_some_and(|active| active.load(std::sync::atomic::Ordering::Acquire)) } } impl Drop for Slot { fn drop(&mut self) { if let Some(active) = &self.stream_active { active.store(false, std::sync::atomic::Ordering::Release); } } } #[derive(Debug)] pub(crate) struct Runtime { pub(crate) slots: HashMap, revision: u64, next_host_image_id: u32, } impl Default for Runtime { fn default() -> Self { Self { slots: HashMap::new(), revision: 0, next_host_image_id: 1 << 31, } } } impl Runtime { pub(crate) fn revision(&self) -> u64 { self.revision } pub(crate) fn mark_changed(&mut self) { self.revision = self.revision.wrapping_add(1); } pub(crate) fn clear(&mut self) { self.slots.clear(); self.mark_changed(); } pub(crate) fn reserve_image_id(&mut self, key: &Key) -> Option { if let Some(id) = self.slots.get(key).map(|slot| slot.host_image_id) { return Some(id); } for _ in 0..=self.slots.len() { let id = self.next_host_image_id | (1 << 31); self.next_host_image_id = id.wrapping_add(1) | (1 << 31); if self.slots.values().all(|slot| slot.host_image_id != id) { return Some(id); } } None } pub(crate) fn layer_count(&self, pane_id: PaneId) -> usize { self.slots.keys().filter(|(id, _)| *id == pane_id).count() } pub(crate) fn can_add_slot(&self, key: &Key) -> bool { self.slots.contains_key(key) || self.slots.len() < crate::api::schema::panes::PANE_GRAPHICS_MAX_LAYERS_TOTAL } pub(crate) fn can_store_inline(&self, key: &Key, len: usize) -> bool { let replaced = self .slots .get(key) .and_then(|slot| slot.layer.as_ref()) .and_then(Layer::inline_data) .map_or(0, <[u8]>::len); self.inline_bytes() .saturating_sub(replaced) .saturating_add(len) <= crate::api::schema::panes::PANE_GRAPHICS_MAX_INLINE_BYTES_TOTAL } fn inline_bytes(&self) -> usize { self.slots .values() .filter_map(|slot| slot.layer.as_ref()) .filter_map(Layer::inline_data) .map(<[u8]>::len) .sum() } pub(crate) fn active_for_pane(&self, pane_id: PaneId) -> bool { self.slots .iter() .any(|((id, _), slot)| *id == pane_id && slot.layer.is_some()) } pub(crate) fn attach_stream_active( &mut self, key: &Key, owner: &str, active: std::sync::Arc, ) { if let Some(slot) = self .slots .get_mut(key) .filter(|slot| slot.stream_owner.as_deref() == Some(owner)) { slot.stream_active = Some(active); } } pub(crate) fn retain_live_panes(&mut self, state: &crate::app::state::AppState) -> bool { if self.slots.is_empty() { return false; } let before = self.slots.len(); self.slots.retain(|(pane_id, _), _| { state .workspaces .iter() .any(|workspace| workspace.pane_state(*pane_id).is_some()) }); if self.slots.len() == before { return false; } self.mark_changed(); true } } impl Layer { pub(crate) fn inline( format: PaneGraphicsFormat, image_width: u32, image_height: u32, data: Vec, render: PaneGraphicsPlacementParams, z_index: i32, ) -> Self { let mut hasher = std::collections::hash_map::DefaultHasher::new(); data.hash(&mut hasher); Self { format, image_width, image_height, backing: Backing::Inline(data), data_fingerprint: hasher.finish(), render, z_index, } } pub(crate) fn direct( image_width: u32, image_height: u32, lease: crate::pane_graphics_files::Lease, render: PaneGraphicsPlacementParams, z_index: i32, ) -> Self { let data_fingerprint = lease.fingerprint(); Self { format: PaneGraphicsFormat::Rgba, image_width, image_height, backing: Backing::Direct(lease), data_fingerprint, render, z_index, } } pub(crate) fn data_len(&self) -> usize { match &self.backing { Backing::Inline(data) => data.len(), Backing::Direct(lease) => lease.len(), Backing::Resident { len, .. } => *len, } } pub(crate) fn inline_data(&self) -> Option<&[u8]> { match &self.backing { Backing::Inline(data) => Some(data), Backing::Direct(_) | Backing::Resident { .. } => None, } } pub(crate) fn direct_lease(&self) -> Option<&crate::pane_graphics_files::Lease> { match &self.backing { Backing::Direct(lease) => Some(lease), Backing::Inline(_) | Backing::Resident { .. } => None, } } pub(crate) fn mark_resident(&mut self, client_id: u64) -> bool { let Backing::Direct(lease) = &self.backing else { return false; }; self.backing = Backing::Resident { len: lease.len(), client_id, }; true } pub(crate) fn resident_client(&self) -> Option { match self.backing { Backing::Resident { client_id, .. } => Some(client_id), Backing::Inline(_) | Backing::Direct(_) => None, } } pub(crate) fn terminal_only(&self) -> bool { matches!(self.backing, Backing::Direct(_) | Backing::Resident { .. }) } } #[cfg(test)] mod tests { use super::*; #[test] fn stream_liveness_attaches_only_to_the_exact_owned_layer() { let mut runtime = Runtime::default(); let first = (PaneId::from_raw(1), "first".into()); let second = (PaneId::from_raw(2), "second".into()); for (key, id) in [(&first, 1), (&second, 2)] { runtime.slots.insert( key.clone(), Slot { host_image_id: (1 << 31) | id, layer: None, stream_owner: Some("shared-owner".into()), stream_active: None, direct_gate: None, }, ); } let active = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)); runtime.attach_stream_active(&second, "shared-owner", active.clone()); assert!(runtime.slots[&first].stream_active.is_none()); assert!(std::sync::Arc::ptr_eq( runtime.slots[&second].stream_active.as_ref().unwrap(), &active )); } #[test] fn runtime_bounds_total_slots_and_inline_bytes() { let mut runtime = Runtime::default(); for index in 0..crate::api::schema::panes::PANE_GRAPHICS_MAX_LAYERS_TOTAL { let key = (PaneId::from_raw(1), format!("layer-{index}")); runtime .slots .insert(key, Slot::test((1 << 31) | index as u32, None)); } assert!(!runtime.can_add_slot(&(PaneId::from_raw(2), "extra".into()))); let mut runtime = Runtime::default(); let key = (PaneId::from_raw(1), "primary".into()); runtime.slots.insert( key.clone(), Slot::test( 1 << 31, Some(Layer::inline( PaneGraphicsFormat::Rgba, 1, 1, vec![0; 4], PaneGraphicsPlacementParams::default(), 0, )), ), ); assert!(runtime.can_store_inline( &key, crate::api::schema::panes::PANE_GRAPHICS_MAX_INLINE_BYTES_TOTAL, )); assert!(!runtime.can_store_inline( &(PaneId::from_raw(2), "primary".into()), crate::api::schema::panes::PANE_GRAPHICS_MAX_INLINE_BYTES_TOTAL, )); } }