aboutsummaryrefslogtreecommitdiff
path: root/src/render_signal.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-28 23:29:11 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-28 23:29:11 +0300
commite27a4ea427c9ed6248c0ee80570e94ed7b1928bd (patch)
tree0cae21a2b3939293be129aaaecb23641f68bab54 /src/render_signal.rs
parente16d7d8c07a20f5ee0b4111808680bbcfd7df9ac (diff)
perf: skip rendering hidden pty updates
Diffstat (limited to 'src/render_signal.rs')
-rw-r--r--src/render_signal.rs91
1 files changed, 91 insertions, 0 deletions
diff --git a/src/render_signal.rs b/src/render_signal.rs
new file mode 100644
index 00000000..d4544e5f
--- /dev/null
+++ b/src/render_signal.rs
@@ -0,0 +1,91 @@
+use std::collections::HashSet;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Mutex;
+
+use crate::layout::PaneId;
+
+#[derive(Debug, Default)]
+pub(crate) struct RenderRequest {
+ pub(crate) generic: bool,
+ pub(crate) pty_sources: HashSet<PaneId>,
+}
+
+/// Coalesces render requests while retaining enough origin information for the
+/// headless server to discard PTY-only updates hidden from every client.
+#[derive(Debug, Default)]
+pub(crate) struct RenderSignal {
+ pending: AtomicBool,
+ request: Mutex<RenderRequest>,
+}
+
+impl RenderSignal {
+ pub(crate) fn new() -> Self {
+ Self::default()
+ }
+
+ pub(crate) fn is_pending(&self) -> bool {
+ self.pending.load(Ordering::Acquire)
+ }
+
+ pub(crate) fn request_generic(&self) {
+ let mut request = self
+ .request
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ request.generic = true;
+ self.pending.store(true, Ordering::Release);
+ }
+
+ /// Returns true when this request transitions the signal from idle to pending.
+ pub(crate) fn request_pty(&self, pane_id: PaneId) -> bool {
+ let mut request = self
+ .request
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ request.pty_sources.insert(pane_id);
+ !self.pending.swap(true, Ordering::AcqRel)
+ }
+
+ pub(crate) fn take(&self) -> RenderRequest {
+ let mut request = self
+ .request
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ self.pending.store(false, Ordering::Release);
+ std::mem::take(&mut *request)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn coalesces_pty_sources_until_taken() {
+ let signal = RenderSignal::new();
+ let first = PaneId::from_raw(10);
+ let second = PaneId::from_raw(20);
+
+ assert!(signal.request_pty(first));
+ assert!(!signal.request_pty(first));
+ assert!(!signal.request_pty(second));
+
+ let request = signal.take();
+ assert!(!request.generic);
+ assert_eq!(request.pty_sources, HashSet::from([first, second]));
+ assert!(!signal.is_pending());
+ }
+
+ #[test]
+ fn keeps_generic_and_pty_requests_distinct() {
+ let signal = RenderSignal::new();
+ let pane_id = PaneId::from_raw(10);
+
+ signal.request_generic();
+ assert!(!signal.request_pty(pane_id));
+
+ let request = signal.take();
+ assert!(request.generic);
+ assert_eq!(request.pty_sources, HashSet::from([pane_id]));
+ }
+}