Reduce polling retries and repeated compositor lookups
This commit is contained in:
1 parent
dcca5fec88
commit
917379d548
4 files changed
+145
-23
No files matched your search
@@ -1,4 +1,7 @@
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use smithay::utils::{Physical, Rectangle};
|
||||
|
||||
@@ -16,7 +19,7 @@ pub(crate) struct RenderMetrics {
|
||||
damaged_pixels: u64,
|
||||
total_render_time: Duration,
|
||||
longest_render_time: Duration,
|
||||
frame_times: Vec<Duration>,
|
||||
frame_times: VecDeque<Duration>,
|
||||
}
|
||||
|
||||
impl RenderMetrics {
|
||||
@@ -37,7 +40,7 @@ impl RenderMetrics {
|
||||
damaged_pixels: 0,
|
||||
total_render_time: Duration::ZERO,
|
||||
longest_render_time: Duration::ZERO,
|
||||
frame_times: Vec::new(),
|
||||
frame_times: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +60,9 @@ impl RenderMetrics {
|
||||
self.total_render_time += render_time;
|
||||
self.longest_render_time = self.longest_render_time.max(render_time);
|
||||
if self.frame_times.len() == 512 {
|
||||
self.frame_times.remove(0);
|
||||
self.frame_times.pop_front();
|
||||
}
|
||||
self.frame_times.push(render_time);
|
||||
self.frame_times.push_back(render_time);
|
||||
|
||||
let now = Instant::now();
|
||||
let elapsed = now.saturating_duration_since(self.interval_started);
|
||||
@@ -72,7 +75,7 @@ impl RenderMetrics {
|
||||
.as_micros()
|
||||
.checked_div(u128::from(self.rendered_frames))
|
||||
.unwrap_or(0);
|
||||
self.frame_times.sort_unstable();
|
||||
self.frame_times.make_contiguous().sort_unstable();
|
||||
let percentile = |percent: usize| {
|
||||
let index = (self.frame_times.len() * percent)
|
||||
.div_ceil(100)
|
||||
@@ -159,4 +162,27 @@ mod tests {
|
||||
}
|
||||
assert_eq!(metrics.frame_times.len(), 512);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profiling_retains_the_most_recent_samples_in_order() {
|
||||
let mut metrics = RenderMetrics::new("test", true, Instant::now());
|
||||
for micros in 0..600 {
|
||||
metrics.record_frame(Duration::from_micros(micros), &[], 0, FrameEffectMetrics);
|
||||
}
|
||||
assert_eq!(
|
||||
metrics.frame_times.front(),
|
||||
Some(&Duration::from_micros(88))
|
||||
);
|
||||
assert_eq!(
|
||||
metrics.frame_times.back(),
|
||||
Some(&Duration::from_micros(599))
|
||||
);
|
||||
assert!(
|
||||
metrics
|
||||
.frame_times
|
||||
.iter()
|
||||
.copied()
|
||||
.eq((88..600).map(Duration::from_micros))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use ferese_layout::WindowId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(crate) fn layer_priority(floating: bool, zooming: bool, fullscreen: bool) -> u8 {
|
||||
if fullscreen {
|
||||
@@ -16,17 +17,24 @@ pub(crate) fn layer_priority(floating: bool, zooming: bool, fullscreen: bool) ->
|
||||
#[derive(Default)]
|
||||
pub(crate) struct WindowStack {
|
||||
order: Vec<WindowId>,
|
||||
ranks: HashMap<WindowId, usize>,
|
||||
}
|
||||
|
||||
impl WindowStack {
|
||||
pub fn insert(&mut self, id: WindowId) {
|
||||
if !self.order.contains(&id) {
|
||||
if let std::collections::hash_map::Entry::Vacant(entry) = self.ranks.entry(id) {
|
||||
entry.insert(self.order.len());
|
||||
self.order.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, id: WindowId) {
|
||||
self.order.retain(|candidate| *candidate != id);
|
||||
if let Some(rank) = self.ranks.remove(&id) {
|
||||
self.order.remove(rank);
|
||||
for (rank, candidate) in self.order.iter().enumerate().skip(rank) {
|
||||
self.ranks.insert(*candidate, rank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raise(&mut self, id: WindowId) {
|
||||
@@ -35,10 +43,7 @@ impl WindowStack {
|
||||
}
|
||||
|
||||
pub fn rank(&self, id: WindowId) -> usize {
|
||||
self.order
|
||||
.iter()
|
||||
.position(|candidate| *candidate == id)
|
||||
.unwrap_or(usize::MAX)
|
||||
self.ranks.get(&id).copied().unwrap_or(usize::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +51,43 @@ impl WindowStack {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cached_ranks_match_reference_order_after_mixed_operations() {
|
||||
let mut stack = WindowStack::default();
|
||||
let mut reference = Vec::new();
|
||||
let mut random = 42_u64;
|
||||
for _ in 0..2000 {
|
||||
random = random.wrapping_mul(6364136223846793005).wrapping_add(1);
|
||||
let id = WindowId((random >> 32) % 32);
|
||||
match random % 3 {
|
||||
0 => {
|
||||
stack.insert(id);
|
||||
if !reference.contains(&id) {
|
||||
reference.push(id);
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
stack.remove(id);
|
||||
reference.retain(|candidate| *candidate != id);
|
||||
}
|
||||
_ => {
|
||||
stack.raise(id);
|
||||
reference.retain(|candidate| *candidate != id);
|
||||
reference.push(id);
|
||||
}
|
||||
}
|
||||
for id in (0..33).map(WindowId) {
|
||||
assert_eq!(
|
||||
stack.rank(id),
|
||||
reference
|
||||
.iter()
|
||||
.position(|candidate| *candidate == id)
|
||||
.unwrap_or(usize::MAX)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_windows_stay_above_tiled_and_maximized_windows() {
|
||||
assert!(layer_priority(true, false, false) > layer_priority(false, true, false));
|
||||
|
||||
@@ -1428,6 +1428,13 @@ fn overview_strip_elements(
|
||||
let cards = state.overview_workspace_cards(output);
|
||||
let mut elements = Vec::new();
|
||||
let programs = rounded_clip_program(state, renderer);
|
||||
// Resolve IDs once per strip, rather than scanning all managed windows
|
||||
// separately for every miniature. Own the handles so rendering can mutate state.
|
||||
let windows_by_id: std::collections::HashMap<_, _> = state
|
||||
.window_ids
|
||||
.iter()
|
||||
.map(|(window, id)| (*id, window.clone()))
|
||||
.collect();
|
||||
for card in cards {
|
||||
// Front-to-back: outline and live miniatures above each card, all above
|
||||
// the strip. Nothing is painted behind the main overview window grid.
|
||||
@@ -1451,14 +1458,10 @@ fn overview_strip_elements(
|
||||
}
|
||||
if let Some(programs) = &programs {
|
||||
for (id, rect) in &card.windows {
|
||||
if let Some(window) = state
|
||||
.window_ids
|
||||
.iter()
|
||||
.find_map(|(window, candidate)| (*candidate == *id).then(|| window.clone()))
|
||||
{
|
||||
if let Some(window) = windows_by_id.get(id) {
|
||||
elements.extend(rounded_window_elements(
|
||||
renderer,
|
||||
&window,
|
||||
window,
|
||||
rounded_visual_rect(*rect, output_geometry.loc),
|
||||
physical_rect(*rect, output_geometry.loc, scale),
|
||||
scale,
|
||||
|
||||
@@ -90,6 +90,16 @@ pub struct Service {
|
||||
pub generation: u64,
|
||||
}
|
||||
|
||||
type PollState = (Mutex<(u64, bool, Option<String>, bool)>, Condvar);
|
||||
|
||||
fn wait_for_poll(shared: &PollState) -> Option<u64> {
|
||||
let state = shared
|
||||
.1
|
||||
.wait_while(shared.0.lock().unwrap(), |state| state.1 && !state.3)
|
||||
.unwrap();
|
||||
(!state.3).then_some(state.0)
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub fn start(settings: Option<Vec<String>>) -> Self {
|
||||
let live_settings = Arc::new(Mutex::new(settings));
|
||||
@@ -105,12 +115,11 @@ impl Service {
|
||||
let polling = shared.clone();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
let before = {
|
||||
let state = polling.0.lock().unwrap();
|
||||
if state.3 {
|
||||
// A poll overlapping a write is discarded below. Wait for the
|
||||
// write to finish before retrying instead of launching commands
|
||||
// repeatedly while a slow control operation is still running.
|
||||
let Some(before) = wait_for_poll(&polling) else {
|
||||
break;
|
||||
}
|
||||
state.0
|
||||
};
|
||||
let snapshot = poll();
|
||||
let state = polling.0.lock().unwrap();
|
||||
@@ -476,6 +485,48 @@ fn execute(action: &Action, settings: Option<&[String]>) -> Result<(), String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn polling_waits_for_a_write_and_uses_the_completed_generation() {
|
||||
let shared = Arc::new((Mutex::new((7, true, None, false)), Condvar::new()));
|
||||
let worker_state = shared.clone();
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let worker = thread::spawn(move || {
|
||||
sender.send(wait_for_poll(&worker_state)).unwrap();
|
||||
});
|
||||
assert!(matches!(
|
||||
receiver.recv_timeout(Duration::from_millis(30)),
|
||||
Err(mpsc::RecvTimeoutError::Timeout)
|
||||
));
|
||||
{
|
||||
let mut state = shared.0.lock().unwrap();
|
||||
state.0 = 8;
|
||||
state.1 = false;
|
||||
shared.1.notify_one();
|
||||
}
|
||||
assert_eq!(
|
||||
receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
|
||||
Some(8)
|
||||
);
|
||||
worker.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shutdown_unblocks_polling_even_during_a_write() {
|
||||
let shared = Arc::new((Mutex::new((7, true, None, false)), Condvar::new()));
|
||||
let worker_state = shared.clone();
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let worker = thread::spawn(move || {
|
||||
sender.send(wait_for_poll(&worker_state)).unwrap();
|
||||
});
|
||||
{
|
||||
let mut state = shared.0.lock().unwrap();
|
||||
state.3 = true;
|
||||
shared.1.notify_one();
|
||||
}
|
||||
assert_eq!(receiver.recv_timeout(Duration::from_secs(2)).unwrap(), None);
|
||||
worker.join().unwrap();
|
||||
}
|
||||
#[test]
|
||||
fn audio_parser_handles_mute_and_rejects_invalid_values() {
|
||||
assert_eq!(parse_audio("Volume: 0.42 [MUTED]"), Some((42, true)));
|
||||
|
||||
Reference in new issue
Block a user