Skip to main content

ferroui_base/threading/
dispatcher_priority.rs

1use std::fmt;
2
3/// Defines the priorities with which jobs can be invoked on a
4/// [`Dispatcher`](super::Dispatcher).
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
6pub struct DispatcherPriority {
7    value: i32,
8}
9
10impl DispatcherPriority {
11    const fn new(value: i32) -> Self {
12        Self { value }
13    }
14
15    /// The numeric value of the priority.
16    #[inline]
17    pub const fn value(self) -> i32 {
18        self.value
19    }
20
21    /// The lowest foreground dispatcher priority.
22    pub const DEFAULT: Self = Self::new(0);
23
24    pub(crate) const MINIMUM_FOREGROUND_PRIORITY: Self = Self::DEFAULT;
25
26    /// The job will be processed with the same priority as input.
27    pub const INPUT: Self = Self::new(Self::DEFAULT.value - 1);
28
29    /// The job will be processed after other non-idle operations have completed.
30    pub const BACKGROUND: Self = Self::new(Self::INPUT.value - 1);
31
32    /// The job will be processed after background operations have completed.
33    pub const CONTEXT_IDLE: Self = Self::new(Self::BACKGROUND.value - 1);
34
35    /// The job will be processed when the application is idle.
36    pub const APPLICATION_IDLE: Self = Self::new(Self::CONTEXT_IDLE.value - 1);
37
38    /// The job will be processed when the system is idle.
39    pub const SYSTEM_IDLE: Self = Self::new(Self::APPLICATION_IDLE.value - 1);
40
41    /// Minimum possible priority that's actually dispatched, default value.
42    pub(crate) const MINIMUM_ACTIVE_VALUE: Self = Self::new(Self::SYSTEM_IDLE.value);
43
44    /// A dispatcher priority for jobs that shouldn't be executed yet.
45    pub const INACTIVE: Self = Self::new(Self::MINIMUM_ACTIVE_VALUE.value - 1);
46
47    /// Minimum valid priority.
48    pub(crate) const MIN_VALUE: Self = Self::new(Self::INACTIVE.value);
49
50    /// Used internally in dispatcher code.
51    pub const INVALID: Self = Self::new(Self::MINIMUM_ACTIVE_VALUE.value - 2);
52
53    /// The job will be processed after layout and render but before input.
54    pub const LOADED: Self = Self::new(Self::DEFAULT.value + 1);
55
56    /// A special priority for platforms with a UI render timer or for forced
57    /// full rasterization requests.
58    pub const UI_THREAD_RENDER: Self = Self::new(Self::LOADED.value + 1);
59
60    /// A special priority to synchronize native control host positions,
61    /// IME, etc. Renderer results have to be reported before any user code
62    /// and most of the system input callbacks.
63    pub const AFTER_RENDER: Self = Self::new(Self::UI_THREAD_RENDER.value + 1);
64
65    /// The job will be processed with the same priority as render.
66    pub const RENDER: Self = Self::new(Self::AFTER_RENDER.value + 1);
67
68    /// A special platform hook for jobs to be executed before the normal
69    /// render cycle.
70    pub const BEFORE_RENDER: Self = Self::new(Self::RENDER.value + 1);
71
72    /// A special priority for platforms that resize the render target in
73    /// asynchronous-commit mode.
74    pub const ASYNC_RENDER_TARGET_RESIZE: Self = Self::new(Self::BEFORE_RENDER.value + 1);
75
76    /// The job will be processed with the same priority as data binding.
77    #[deprecated(note = "WPF compatibility")]
78    pub const DATA_BIND: Self = Self::new(Self::ASYNC_RENDER_TARGET_RESIZE.value + 1);
79
80    /// The job will be processed with normal priority.
81    pub const NORMAL: Self = Self::new(Self::ASYNC_RENDER_TARGET_RESIZE.value + 2);
82
83    /// The job will be processed before other asynchronous operations.
84    pub const SEND: Self = Self::new(Self::NORMAL.value + 1);
85
86    /// Maximum possible priority.
87    pub const MAX_VALUE: Self = Self::SEND;
88
89    /// Creates a priority from its numeric value.
90    ///
91    /// # Panics
92    /// Panics when `value` is outside the valid range; use
93    /// [`TryFrom<i32>`] for untrusted input.
94    pub fn from_value(value: i32) -> Self {
95        match Self::try_from(value) {
96            Ok(priority) => priority,
97            Err(_) => panic!("value: specified argument was out of the range of valid values"),
98        }
99    }
100
101    /// Verifies that `priority` can be used to queue work.
102    ///
103    /// # Panics
104    /// Panics when the priority is outside `INACTIVE..=MAX_VALUE`.
105    pub fn validate(priority: DispatcherPriority, parameter_name: &str) {
106        if priority < Self::INACTIVE || priority > Self::MAX_VALUE {
107            panic!("Invalid DispatcherPriority value (parameter '{parameter_name}')");
108        }
109    }
110}
111
112impl Default for DispatcherPriority {
113    fn default() -> Self {
114        Self::DEFAULT
115    }
116}
117
118/// The error returned when converting an out-of-range number to a
119/// [`DispatcherPriority`].
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
121pub struct DispatcherPriorityOutOfRangeError(pub i32);
122
123impl fmt::Display for DispatcherPriorityOutOfRangeError {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "{} is not a valid dispatcher priority", self.0)
126    }
127}
128
129impl std::error::Error for DispatcherPriorityOutOfRangeError {}
130
131impl TryFrom<i32> for DispatcherPriority {
132    type Error = DispatcherPriorityOutOfRangeError;
133
134    fn try_from(value: i32) -> Result<Self, Self::Error> {
135        if !(Self::MIN_VALUE.value..=Self::MAX_VALUE.value).contains(&value) {
136            Err(DispatcherPriorityOutOfRangeError(value))
137        } else {
138            Ok(Self::new(value))
139        }
140    }
141}
142
143impl From<DispatcherPriority> for i32 {
144    fn from(priority: DispatcherPriority) -> i32 {
145        priority.value
146    }
147}
148
149#[allow(deprecated)]
150impl fmt::Display for DispatcherPriority {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        let name = match *self {
153            Self::INVALID => "Invalid",
154            Self::INACTIVE => "Inactive",
155            Self::SYSTEM_IDLE => "SystemIdle",
156            Self::CONTEXT_IDLE => "ContextIdle",
157            Self::APPLICATION_IDLE => "ApplicationIdle",
158            Self::BACKGROUND => "Background",
159            Self::INPUT => "Input",
160            Self::DEFAULT => "Default",
161            Self::LOADED => "Loaded",
162            Self::UI_THREAD_RENDER => "UiThreadRender",
163            Self::AFTER_RENDER => "AfterRender",
164            Self::RENDER => "Render",
165            Self::BEFORE_RENDER => "BeforeRender",
166            Self::ASYNC_RENDER_TARGET_RESIZE => "AsyncRenderTargetResize",
167            Self::DATA_BIND => "DataBind",
168            Self::NORMAL => "Normal",
169            Self::SEND => "Send",
170            _ => return write!(f, "{}", self.value),
171        };
172        f.write_str(name)
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    #[allow(deprecated)]
182    fn values_match_the_documented_ordering() {
183        assert_eq!(DispatcherPriority::INVALID.value(), -7);
184        assert_eq!(DispatcherPriority::INACTIVE.value(), -6);
185        assert_eq!(DispatcherPriority::SYSTEM_IDLE.value(), -5);
186        assert_eq!(DispatcherPriority::APPLICATION_IDLE.value(), -4);
187        assert_eq!(DispatcherPriority::CONTEXT_IDLE.value(), -3);
188        assert_eq!(DispatcherPriority::BACKGROUND.value(), -2);
189        assert_eq!(DispatcherPriority::INPUT.value(), -1);
190        assert_eq!(DispatcherPriority::DEFAULT.value(), 0);
191        assert_eq!(DispatcherPriority::LOADED.value(), 1);
192        assert_eq!(DispatcherPriority::UI_THREAD_RENDER.value(), 2);
193        assert_eq!(DispatcherPriority::AFTER_RENDER.value(), 3);
194        assert_eq!(DispatcherPriority::RENDER.value(), 4);
195        assert_eq!(DispatcherPriority::BEFORE_RENDER.value(), 5);
196        assert_eq!(DispatcherPriority::ASYNC_RENDER_TARGET_RESIZE.value(), 6);
197        assert_eq!(DispatcherPriority::DATA_BIND.value(), 7);
198        assert_eq!(DispatcherPriority::NORMAL.value(), 8);
199        assert_eq!(DispatcherPriority::SEND.value(), 9);
200        assert_eq!(DispatcherPriority::default(), DispatcherPriority::DEFAULT);
201        assert!(DispatcherPriority::RENDER > DispatcherPriority::INPUT);
202    }
203
204    #[test]
205    fn from_value_validates_the_range() {
206        assert_eq!(DispatcherPriority::try_from(4), Ok(DispatcherPriority::RENDER));
207        assert!(DispatcherPriority::try_from(-7).is_err());
208        assert!(DispatcherPriority::try_from(10).is_err());
209        assert_eq!(i32::from(DispatcherPriority::SEND), 9);
210    }
211
212    #[test]
213    fn display_uses_the_priority_name() {
214        assert_eq!(DispatcherPriority::RENDER.to_string(), "Render");
215        assert_eq!(DispatcherPriority::INVALID.to_string(), "Invalid");
216        assert_eq!(DispatcherPriority::UI_THREAD_RENDER.to_string(), "UiThreadRender");
217    }
218
219    #[test]
220    #[should_panic(expected = "Invalid DispatcherPriority value")]
221    fn validate_rejects_invalid() {
222        DispatcherPriority::validate(DispatcherPriority::INVALID, "priority");
223    }
224}