From f58ed387ddbb83296ae1bddb3f87affd18b1f78f Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 15 Jan 2020 12:36:31 +0200 Subject: [PATCH] ui: add ratelimiting in UI notifications and drawing --- src/bin.rs | 2 +- ui/src/components/notifications.rs | 17 +++++++++- ui/src/state.rs | 14 ++++++++ ui/src/types.rs | 53 ++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 953b03ffe..50cc96c11 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -254,7 +254,7 @@ fn run_app() -> Result<()> { let status_bar = Box::new(StatusBar::new(window)); state.register_component(status_bar); - let xdg_notifications = Box::new(ui::components::notifications::XDGNotifications {}); + let xdg_notifications = Box::new(ui::components::notifications::XDGNotifications::new()); state.register_component(xdg_notifications); state.register_component(Box::new( ui::components::notifications::NotificationFilter {}, diff --git a/ui/src/components/notifications.rs b/ui/src/components/notifications.rs index ec4c0ae41..e05646ba1 100644 --- a/ui/src/components/notifications.rs +++ b/ui/src/components/notifications.rs @@ -22,6 +22,7 @@ /*! Notification handling components. */ +use crate::types::RateLimit; use notify_rust; use std::process::{Command, Stdio}; @@ -29,7 +30,9 @@ use super::*; /// Passes notifications to the OS using the XDG specifications. #[derive(Debug)] -pub struct XDGNotifications {} +pub struct XDGNotifications { + rate_limit: RateLimit, +} impl fmt::Display for XDGNotifications { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -38,10 +41,22 @@ impl fmt::Display for XDGNotifications { } } +impl XDGNotifications { + pub fn new() -> Self { + XDGNotifications { + rate_limit: RateLimit::new(3, 1000), + } + } +} + impl Component for XDGNotifications { fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {} fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { if let UIEvent::Notification(ref title, ref body, ref kind) = event { + if !self.rate_limit.tick() { + return true; + } + let settings = &context.runtime_settings.notifications; let mut notification = notify_rust::Notification::new(); notification diff --git a/ui/src/state.rs b/ui/src/state.rs index 518c242f2..248b50a40 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -176,6 +176,7 @@ pub struct State { rows: usize, grid: CellBuffer, + draw_rate_limit: RateLimit, stdout: Option, child: Option, pub mode: UIMode, @@ -272,6 +273,7 @@ impl State { mode: UIMode::Normal, components: Vec::with_capacity(1), timer, + draw_rate_limit: RateLimit::new(1, 3), context: Context { accounts, @@ -293,6 +295,9 @@ impl State { }, }, }; + s.draw_rate_limit + .timer + .set_value(std::time::Duration::from_millis(3)); if s.context.settings.terminal.ascii_drawing { s.grid.set_ascii_drawing(true); } @@ -429,6 +434,10 @@ impl State { /// Force a redraw for all dirty components. pub fn redraw(&mut self) { + if !self.draw_rate_limit.tick() { + return; + } + for i in 0..self.components.len() { self.draw_component(i); } @@ -677,6 +686,11 @@ impl State { self.context.input_to_raw(); } } + UIEvent::Timer(id) if id == self.draw_rate_limit.id() => { + self.draw_rate_limit.reset(); + self.redraw(); + return; + } _ => {} } /* inform each component */ diff --git a/ui/src/types.rs b/ui/src/types.rs index c2e2a9855..1a5131190 100644 --- a/ui/src/types.rs +++ b/ui/src/types.rs @@ -242,3 +242,56 @@ pub mod segment_tree { assert_eq!(segment_tree.get_max(6, 9), 37); } } + +#[derive(Debug)] +pub struct RateLimit { + last_tick: std::time::Instant, + pub timer: crate::timer::PosixTimer, + rate: std::time::Duration, + reqs: u64, + millis: std::time::Duration, + pub active: bool, +} + +impl RateLimit { + pub fn new(reqs: u64, millis: u64) -> Self { + RateLimit { + last_tick: std::time::Instant::now(), + timer: crate::timer::PosixTimer::new_with_signal( + std::time::Duration::from_secs(0), + std::time::Duration::from_secs(1), + nix::sys::signal::Signal::SIGALRM, + ) + .unwrap(), + + rate: std::time::Duration::from_millis(millis / reqs), + reqs, + millis: std::time::Duration::from_millis(millis), + active: false, + } + } + + pub fn reset(&mut self) { + self.last_tick = std::time::Instant::now(); + self.active = false; + } + + pub fn tick(&mut self) -> bool { + let now = std::time::Instant::now(); + self.last_tick += self.rate; + if self.last_tick < now { + self.last_tick = now + self.rate; + } else if self.last_tick > now + self.millis { + self.timer.rearm(); + self.active = true; + return false; + } + self.active = false; + true + } + + #[inline(always)] + pub fn id(&self) -> u8 { + self.timer.si_value + } +}