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}