Skip to main content

gdt_cpus/
capabilities.rs

1//! Querying what the priority ladder can actually deliver right now.
2//!
3//! On Windows and macOS all seven [`ThreadPriority`] levels are always
4//! distinct. On Linux, negative nice needs privilege - without it (and
5//! without rtkit) `AboveNormal`, `Highest` and `TimeCritical` all resolve to
6//! `nice(0)`, i.e. `Normal`. [`priority_capabilities`] predicts the outcome
7//! so an engine can pick its threading strategy up front instead of
8//! discovering the collapse from frame times.
9
10use crate::ThreadPriority;
11
12/// What each [`ThreadPriority`] level will effectively deliver, as opaque
13/// ranks. Returned by [`priority_capabilities`].
14///
15/// `effective_rank[level as usize]` grows with effective strength; the
16/// absolute numbers carry no meaning beyond ordering. Two levels with equal
17/// rank currently resolve to the same scheduler behavior.
18///
19/// The snapshot is point-in-time: it reflects this process's rlimits and the
20/// reachability of the system's priority broker (rtkit) at the moment of the
21/// call. rtkit in particular can silently withdraw cooperation later (its
22/// watchdog demotes a process that starves the canary), so treat the result
23/// as a planning hint, not a contract.
24#[must_use = "priority_capabilities() has no side effect; its return value is the whole point -- \
25              inspect distinct()/rank() to plan the threading strategy"]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct PriorityCaps {
28    /// Effective strength rank per level, indexed by `ThreadPriority as usize`
29    /// (`Background` = 0 … `TimeCritical` = 6).
30    pub effective_rank: [u8; 7],
31}
32
33impl PriorityCaps {
34    /// The effective strength rank of `priority` (higher = stronger).
35    #[must_use]
36    pub fn rank(&self, priority: ThreadPriority) -> u8 {
37        self.effective_rank[priority as usize]
38    }
39
40    /// `true` when `a` and `b` currently resolve to different scheduler
41    /// behavior. `distinct(Highest, Normal) == false` is the classic
42    /// unprivileged-Linux-without-rtkit signal: your render thread will NOT
43    /// outrank your workers, plan accordingly.
44    #[must_use]
45    pub fn distinct(&self, a: ThreadPriority, b: ThreadPriority) -> bool {
46        self.rank(a) != self.rank(b)
47    }
48
49    /// Number of effectively distinct levels (7 = the full ladder works).
50    #[must_use]
51    pub fn distinct_levels(&self) -> u8 {
52        let mut ranks: Vec<u8> = self.effective_rank.to_vec();
53
54        ranks.sort_unstable();
55        ranks.dedup();
56
57        ranks.len() as u8
58    }
59}
60
61/// Predicts what each [`ThreadPriority`] level will resolve to under this
62/// process's current privileges. Touches no thread state.
63///
64/// Linux: computed from `RLIMIT_NICE` plus the rtkit daemon's `MinNiceLevel`
65/// when the daemon is reachable (feature `rtkit`). Windows and macOS: all
66/// seven levels are always distinct.
67///
68/// ```
69/// use gdt_cpus::{ThreadPriority, priority_capabilities};
70///
71/// let caps = priority_capabilities();
72/// if !caps.distinct(ThreadPriority::Highest, ThreadPriority::Normal) {
73///     eprintln!("priority is flat here - consider promote_thread_to_realtime() for the audio feeder");
74/// }
75/// ```
76pub fn priority_capabilities() -> PriorityCaps {
77    #[cfg(target_os = "linux")]
78    {
79        crate::platform::linux::capabilities::priority_capabilities()
80    }
81    #[cfg(not(target_os = "linux"))]
82    {
83        PriorityCaps {
84            effective_rank: [0, 1, 2, 3, 4, 5, 6],
85        }
86    }
87}