smithay/wayland/commit_timing/
mod.rs1use std::{cell::RefCell, collections::BinaryHeap, sync::Mutex};
83
84use rustix::fs::Timespec;
85use wayland_protocols::wp::commit_timing::v1::server::{
86 wp_commit_timer_v1::{self, WpCommitTimerV1},
87 wp_commit_timing_manager_v1::{self, WpCommitTimingManagerV1},
88};
89use wayland_server::{
90 backend::GlobalId, protocol::wl_surface::WlSurface, DataInit, Dispatch, DisplayHandle, GlobalDispatch,
91 New, Resource, Weak,
92};
93
94use crate::{
95 utils::Time,
96 wayland::compositor::{add_blocker, add_pre_commit_hook},
97};
98
99use super::compositor::{with_states, Barrier};
100
101#[derive(Debug)]
103pub struct CommitTimingManagerState {
104 global: GlobalId,
105 is_managed: bool,
106}
107
108impl CommitTimingManagerState {
109 pub fn new<D>(display: &DisplayHandle) -> Self
114 where
115 D: GlobalDispatch<WpCommitTimingManagerV1, bool>,
116 D: Dispatch<WpCommitTimingManagerV1, bool>,
117 D: 'static,
118 {
119 Self::new_internal::<D>(display, true)
120 }
121
122 pub fn unmanaged<D>(display: &DisplayHandle) -> Self
127 where
128 D: GlobalDispatch<WpCommitTimingManagerV1, bool>,
129 D: Dispatch<WpCommitTimingManagerV1, bool>,
130 D: 'static,
131 {
132 Self::new_internal::<D>(display, false)
133 }
134
135 fn new_internal<D>(display: &DisplayHandle, is_managed: bool) -> Self
136 where
137 D: GlobalDispatch<WpCommitTimingManagerV1, bool>,
138 D: Dispatch<WpCommitTimingManagerV1, bool>,
139 D: 'static,
140 {
141 let global = display.create_global::<D, WpCommitTimingManagerV1, _>(1, is_managed);
142
143 Self { global, is_managed }
144 }
145
146 pub fn global(&self) -> GlobalId {
148 self.global.clone()
149 }
150
151 pub fn is_managed(&self) -> bool {
153 self.is_managed
154 }
155}
156
157impl<D> GlobalDispatch<WpCommitTimingManagerV1, bool, D> for CommitTimingManagerState
158where
159 D: GlobalDispatch<WpCommitTimingManagerV1, bool>,
160 D: Dispatch<WpCommitTimingManagerV1, bool>,
161 D: 'static,
162{
163 fn bind(
164 _state: &mut D,
165 _dh: &DisplayHandle,
166 _client: &wayland_server::Client,
167 resource: New<WpCommitTimingManagerV1>,
168 global_data: &bool,
169 data_init: &mut DataInit<'_, D>,
170 ) {
171 data_init.init(resource, *global_data);
172 }
173}
174
175impl<D> Dispatch<WpCommitTimingManagerV1, bool, D> for CommitTimingManagerState
176where
177 D: Dispatch<WpCommitTimingManagerV1, bool>,
178 D: Dispatch<WpCommitTimerV1, Weak<WlSurface>>,
179 D: 'static,
180{
181 fn request(
182 _state: &mut D,
183 _client: &wayland_server::Client,
184 _resource: &WpCommitTimingManagerV1,
185 request: wp_commit_timing_manager_v1::Request,
186 data: &bool,
187 _dhandle: &DisplayHandle,
188 data_init: &mut DataInit<'_, D>,
189 ) {
190 let is_managed = *data;
191
192 match request {
193 wp_commit_timing_manager_v1::Request::GetTimer { id, surface } => {
194 let (is_initial, has_active_commit_timer) = with_states(&surface, |states| {
195 let marker = states.data_map.get::<RefCell<CommitTimerMarker>>();
196 (
197 marker.is_none(),
198 marker.map(|m| m.borrow().0.is_some()).unwrap_or(false),
199 )
200 });
201
202 if has_active_commit_timer {
204 surface.post_error(
205 wp_commit_timing_manager_v1::Error::CommitTimerExists,
206 "the surface has already a commit timer object associated",
207 );
208 return;
209 }
210
211 if is_managed && is_initial {
213 add_pre_commit_hook::<D, _>(&surface, |_, _, surface| {
214 let timestamp = with_states(surface, |states| {
215 states
216 .data_map
217 .get::<CommitTimerStateUserData>()
218 .and_then(|state| state.borrow_mut().timestamp.take())
219 });
220
221 if let Some(timestamp) = timestamp {
222 let barrier = with_states(surface, |states| {
223 let barrier_state = states
224 .data_map
225 .get_or_insert(CommitTimerBarrierStateUserData::default);
226 barrier_state.lock().unwrap().register(timestamp)
227 });
228
229 add_blocker(surface, barrier);
230 }
231 });
232 }
233
234 let commit_timer: WpCommitTimerV1 = data_init.init(id, surface.downgrade());
235
236 with_states(&surface, |states| {
237 states
238 .data_map
239 .get_or_insert(|| RefCell::new(CommitTimerMarker(None)))
240 .borrow_mut()
241 .0 = Some(commit_timer);
242 });
243 }
244 wp_commit_timing_manager_v1::Request::Destroy => (),
245 _ => unreachable!(),
246 }
247 }
248}
249
250struct CommitTimerMarker(Option<WpCommitTimerV1>);
253
254impl<D> Dispatch<WpCommitTimerV1, Weak<WlSurface>, D> for CommitTimingManagerState
255where
256 D: Dispatch<WpCommitTimerV1, Weak<WlSurface>>,
257 D: 'static,
258{
259 fn request(
260 _state: &mut D,
261 _client: &wayland_server::Client,
262 resource: &WpCommitTimerV1,
263 request: wp_commit_timer_v1::Request,
264 data: &Weak<WlSurface>,
265 _dhandle: &DisplayHandle,
266 _data_init: &mut DataInit<'_, D>,
267 ) {
268 match request {
269 wp_commit_timer_v1::Request::SetTimestamp {
270 tv_sec_hi,
271 tv_sec_lo,
272 tv_nsec,
273 } => {
274 let Ok(surface) = data.upgrade() else {
275 resource.post_error(
276 wp_commit_timer_v1::Error::SurfaceDestroyed as u32,
277 "the surface associated with this commit timer object has been destroyed".to_string(),
278 );
279 return;
280 };
281
282 let tv_sec = ((tv_sec_hi as u64) << 32) | tv_sec_lo as u64;
283
284 let timestamp = Timestamp(Timespec {
285 tv_sec: tv_sec as rustix::time::Secs,
286 tv_nsec: tv_nsec as rustix::time::Nsecs,
287 });
288
289 let already_has_timestamp = with_states(&surface, move |states| {
290 let mut commit_timer_state = states
291 .data_map
292 .get_or_insert(CommitTimerStateUserData::default)
293 .borrow_mut();
294
295 if commit_timer_state.timestamp.is_some() {
296 return true;
297 }
298
299 commit_timer_state.timestamp = Some(timestamp);
300 false
301 });
302
303 if already_has_timestamp {
304 resource.post_error(
305 wp_commit_timer_v1::Error::TimestampExists as u32,
306 "the surface already has a timestamp associated for this commit".to_string(),
307 );
308 }
309 }
310 wp_commit_timer_v1::Request::Destroy => {
311 if let Ok(surface) = data.upgrade() {
312 with_states(&surface, |states| {
313 states
314 .data_map
315 .get::<RefCell<CommitTimerMarker>>()
316 .unwrap()
317 .borrow_mut()
318 .0 = None;
319 });
320 }
321 }
322 _ => unreachable!(),
323 }
324 }
325}
326
327#[derive(Debug, PartialEq, Eq, Copy, Clone)]
329pub struct Timestamp(Timespec);
330
331impl Ord for Timestamp {
332 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
333 match self.0.tv_sec.cmp(&other.0.tv_sec) {
334 std::cmp::Ordering::Equal => self.0.tv_nsec.cmp(&other.0.tv_nsec),
335 other => other,
336 }
337 }
338}
339
340impl PartialOrd for Timestamp {
341 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
342 Some(self.cmp(other))
343 }
344}
345
346pub type CommitTimerStateUserData = RefCell<CommitTimerState>;
348
349#[derive(Debug, Default)]
351pub struct CommitTimerState {
352 pub timestamp: Option<Timestamp>,
354}
355
356pub type CommitTimerBarrierStateUserData = Mutex<CommitTimerBarrierState>;
358
359#[derive(Debug, Default)]
361pub struct CommitTimerBarrierState {
362 barriers: BinaryHeap<CommitTimerBarrier>,
363}
364
365impl<Kind> From<Time<Kind>> for Timestamp {
366 fn from(value: Time<Kind>) -> Self {
367 Self(value.into())
368 }
369}
370
371impl<Kind> From<Timestamp> for Time<Kind> {
372 fn from(value: Timestamp) -> Self {
373 Time::from(value.0)
374 }
375}
376
377impl CommitTimerBarrierState {
378 pub fn signal_until(&mut self, deadline: impl Into<Timestamp>) -> bool {
385 let deadline = deadline.into();
386
387 let num_barriers = self.barriers.len();
388 loop {
389 let Some(barrier) = self.barriers.peek() else {
390 break;
391 };
392
393 if barrier.timestamp > deadline {
394 break;
395 }
396
397 barrier.barrier.signal();
398 let _ = self.barriers.pop();
399 }
400
401 num_barriers != self.barriers.len()
402 }
403
404 pub fn register(&mut self, timestamp: Timestamp) -> Barrier {
406 let barrier = Barrier::new(false);
407 self.barriers.push(CommitTimerBarrier {
408 timestamp,
409 barrier: barrier.clone(),
410 });
411 barrier
412 }
413
414 pub fn next_deadline(&self) -> Option<Timestamp> {
416 self.barriers.peek().map(|barrier| barrier.timestamp)
417 }
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
421struct CommitTimerBarrier {
422 timestamp: Timestamp,
423 barrier: Barrier,
424}
425
426impl std::cmp::Ord for CommitTimerBarrier {
427 #[inline]
428 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
429 self.timestamp.cmp(&other.timestamp).reverse()
431 }
432}
433
434impl std::cmp::PartialOrd for CommitTimerBarrier {
435 #[inline]
436 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
437 Some(self.cmp(other))
438 }
439}
440
441#[macro_export]
443macro_rules! delegate_commit_timing {
444 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
445 $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
446 $crate::reexports::wayland_protocols::wp::commit_timing::v1::server::wp_commit_timing_manager_v1::WpCommitTimingManagerV1: bool
447 ] => $crate::wayland::commit_timing::CommitTimingManagerState);
448
449 $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
450 $crate::reexports::wayland_protocols::wp::commit_timing::v1::server::wp_commit_timing_manager_v1::WpCommitTimingManagerV1: bool
451 ] => $crate::wayland::commit_timing::CommitTimingManagerState);
452 $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
453 $crate::reexports::wayland_protocols::wp::commit_timing::v1::server::wp_commit_timer_v1::WpCommitTimerV1: $crate::reexports::wayland_server::Weak<$crate::reexports::wayland_server::protocol::wl_surface::WlSurface>
454 ] => $crate::wayland::commit_timing::CommitTimingManagerState);
455 };
456}