From fd6fb3047b4bb197d084b8bc8b4b809a2ae5380a Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Wed, 28 Oct 2026 11:25:15 -0700 Subject: [PATCH] perf(animation): do not wake the ghost thread every 8ms for nothing The thread that owns the ghost host window looked for messages every 8ms for as long as komorebi ran once ghost movement was on, about 125 wakeups a second doing nothing, which keeps a processor from sleeping. Poll at that rate only while there are ghosts, and every 250ms while there are none: it is woken at once by a command either way. --- komorebi/src/animation/ghost.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/komorebi/src/animation/ghost.rs b/komorebi/src/animation/ghost.rs index 94f91ffe..e0fd1a5a 100644 --- a/komorebi/src/animation/ghost.rs +++ b/komorebi/src/animation/ghost.rs @@ -273,6 +273,11 @@ impl GhostHost { (self.bounds.0, self.bounds.1) } + /// Whether there are no ghosts + fn is_idle(&self) -> bool { + self.thumbnails.is_empty() + } + fn register( &mut self, snapshot: &Snapshot, @@ -396,6 +401,22 @@ unsafe fn init_layered_transparent(hwnd: HWND) { } } +/// How often the owner thread looks for messages for its windows when no +/// command comes. While there are ghosts that is often, so that they are never +/// kept waiting; while there are none the windows it has are a host nobody can +/// see and nothing is sent to, and waking up every few milliseconds for them +/// keeps the processor from sleeping. +fn poll_interval(idle: bool) -> Duration { + if idle { + IDLE_POLL_INTERVAL + } else { + BUSY_POLL_INTERVAL + } +} + +const BUSY_POLL_INTERVAL: Duration = Duration::from_millis(8); +const IDLE_POLL_INTERVAL: Duration = Duration::from_millis(250); + fn run_owner_loop(cmd_rx: crossbeam_channel::Receiver) { let mut host = match GhostHost::create() { Ok(host) => Some(host), @@ -415,7 +436,7 @@ fn run_owner_loop(cmd_rx: crossbeam_channel::Receiver) { } } - match cmd_rx.recv_timeout(Duration::from_millis(8)) { + match cmd_rx.recv_timeout(poll_interval(host.as_ref().is_some_and(GhostHost::is_idle))) { Ok(GhostCmd::Create { snapshot, start_rect, @@ -658,6 +679,13 @@ mod tests { assert!(!is_swap_chain_class("")); } + #[test] + fn the_owner_thread_polls_slowly_when_there_are_no_ghosts() { + assert_eq!(poll_interval(false), BUSY_POLL_INTERVAL); + assert_eq!(poll_interval(true), IDLE_POLL_INTERVAL); + assert!(IDLE_POLL_INTERVAL >= BUSY_POLL_INTERVAL * 10); + } + #[test] fn the_ghost_is_opaque_until_the_window_is_shown_and_gone_at_the_end() { assert_eq!(ghost_opacity(0.0, REVEAL_AT), 255);