display_utils/
controller.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::Context;
6use display_types::IMAGE_TILING_TYPE_LINEAR;
7
8use fidl::endpoints::ClientEnd;
9use fidl_fuchsia_hardware_display::{
10    self as display, CoordinatorListenerRequest, LayerId as FidlLayerId,
11};
12use fidl_fuchsia_hardware_display_types::{self as display_types};
13use fuchsia_async::{DurationExt as _, TimeoutExt as _};
14use fuchsia_component::client::Service;
15use fuchsia_sync::RwLock;
16use futures::channel::mpsc;
17use futures::{future, TryFutureExt, TryStreamExt};
18use std::fmt;
19use std::sync::Arc;
20use zx::{self as zx, HandleBased};
21
22use crate::config::{DisplayConfig, LayerConfig};
23use crate::error::{ConfigError, Error, Result};
24use crate::types::{BufferCollectionId, DisplayId, DisplayInfo, Event, EventId, ImageId, LayerId};
25use crate::INVALID_EVENT_ID;
26
27const TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(2);
28
29/// Client abstraction for the `fuchsia.hardware.display.Coordinator` protocol. Instances can be
30/// safely cloned and passed across threads.
31#[derive(Clone)]
32pub struct Coordinator {
33    inner: Arc<RwLock<CoordinatorInner>>,
34}
35
36struct CoordinatorInner {
37    displays: Vec<DisplayInfo>,
38    proxy: display::CoordinatorProxy,
39    listener_requests: Option<display::CoordinatorListenerRequestStream>,
40
41    // All subscribed vsync listeners and their optional ID filters.
42    vsync_listeners: Vec<(mpsc::UnboundedSender<VsyncEvent>, Option<DisplayId>)>,
43
44    // Simple counter to generate client-assigned integer identifiers.
45    id_counter: u64,
46
47    // Generate stamps for `apply_config()`.
48    stamp_counter: u64,
49}
50
51/// A vsync event payload.
52#[derive(Debug)]
53pub struct VsyncEvent {
54    /// The ID of the display that generated the vsync event.
55    pub id: DisplayId,
56
57    /// The monotonic timestamp of the vsync event.
58    pub timestamp: zx::MonotonicInstant,
59
60    /// The stamp of the latest fully applied display configuration.
61    pub config: display::ConfigStamp,
62}
63
64impl Coordinator {
65    /// Establishes a connection to the display-coordinator device and initialize a `Coordinator`
66    /// instance with the initial set of available displays. The returned `Coordinator` will
67    /// maintain FIDL connection to the underlying device as long as it is alive or the connection
68    /// is closed by the peer.
69    ///
70    /// Returns an error if
71    /// - No display-coordinator device is found within `TIMEOUT`.
72    /// - An initial OnDisplaysChanged event is not received from the display driver within
73    ///   `TIMEOUT` seconds.
74    ///
75    /// Current limitations:
76    ///   - This function connects to the first display-coordinator device that it observes. It
77    ///   currently does not support selection of a specific device if multiple display-coordinator
78    ///   devices are present.
79    // TODO(https://fxbug.dev/42168593): This will currently result in an error if no displays are present on
80    // the system (or if one is not attached within `TIMEOUT`). It wouldn't be neceesary to rely on
81    // a timeout if the display driver sent en event with no displays.
82    pub async fn init() -> Result<Coordinator> {
83        let service_proxy = Service::open(display::ServiceMarker)
84            .context("failed to open display Service")
85            .map_err(Error::DeviceConnectionError)?
86            .watch_for_any()
87            .map_err(Error::DeviceConnectionError)
88            .on_timeout(TIMEOUT.after_now(), || Err(Error::DeviceNotFound))
89            .await?;
90
91        let provider_proxy = service_proxy
92            .connect_to_provider()
93            .context("failed to connect to FIDL provider")
94            .map_err(|x| Error::DeviceConnectionError(x.into()))?;
95
96        let (coordinator_proxy, coordinator_server_end) =
97            fidl::endpoints::create_proxy::<display::CoordinatorMarker>();
98        let (coordinator_listener_client_end, coordinator_listener_requests) =
99            fidl::endpoints::create_request_stream::<display::CoordinatorListenerMarker>();
100
101        // TODO(https://fxbug.dev/42075865): Consider supporting virtcon client
102        // connections.
103        let payload = display::ProviderOpenCoordinatorWithListenerForPrimaryRequest {
104            coordinator: Some(coordinator_server_end),
105            coordinator_listener: Some(coordinator_listener_client_end),
106            __source_breaking: fidl::marker::SourceBreaking,
107        };
108        let () = provider_proxy
109            .open_coordinator_with_listener_for_primary(payload)
110            .await?
111            .map_err(zx::Status::from_raw)?;
112
113        Self::init_with_proxy_and_listener_requests(
114            coordinator_proxy,
115            coordinator_listener_requests,
116        )
117        .await
118    }
119
120    /// Initialize a `Coordinator` instance from pre-established Coordinator and
121    /// CoordinatorListener channels.
122    ///
123    /// Returns an error if
124    /// - An initial OnDisplaysChanged event is not received from the display driver within
125    ///   `TIMEOUT` seconds.
126    // TODO(https://fxbug.dev/42168593): This will currently result in an error if no displays are
127    // present on the system (or if one is not attached within `TIMEOUT`). It wouldn't be neceesary
128    // to rely on a timeout if the display driver sent en event with no displays.
129    pub async fn init_with_proxy_and_listener_requests(
130        coordinator_proxy: display::CoordinatorProxy,
131        mut listener_requests: display::CoordinatorListenerRequestStream,
132    ) -> Result<Coordinator> {
133        let displays = wait_for_initial_displays(&mut listener_requests)
134            .on_timeout(TIMEOUT.after_now(), || Err(Error::NoDisplays))
135            .await?
136            .into_iter()
137            .map(DisplayInfo)
138            .collect::<Vec<_>>();
139        Ok(Coordinator {
140            inner: Arc::new(RwLock::new(CoordinatorInner {
141                proxy: coordinator_proxy,
142                listener_requests: Some(listener_requests),
143                displays,
144                vsync_listeners: Vec::new(),
145                id_counter: 0,
146                stamp_counter: 0,
147            })),
148        })
149    }
150
151    /// Returns a copy of the list of displays that are currently known to be present on the system.
152    pub fn displays(&self) -> Vec<DisplayInfo> {
153        self.inner.read().displays.clone()
154    }
155
156    /// Returns a clone of the underlying FIDL client proxy.
157    ///
158    /// Note: This can be helpful to prevent holding the inner RwLock when awaiting a chained FIDL
159    /// call over a proxy.
160    pub fn proxy(&self) -> display::CoordinatorProxy {
161        self.inner.read().proxy.clone()
162    }
163
164    /// Registers a channel to listen to vsync events.
165    pub fn add_vsync_listener(
166        &self,
167        id: Option<DisplayId>,
168    ) -> Result<mpsc::UnboundedReceiver<VsyncEvent>> {
169        // TODO(armansito): Switch to a bounded channel instead.
170        let (sender, receiver) = mpsc::unbounded::<VsyncEvent>();
171        self.inner.write().vsync_listeners.push((sender, id));
172        Ok(receiver)
173    }
174
175    /// Returns a Future that represents the FIDL event handling task. Once scheduled on an
176    /// executor, this task will continuously handle incoming FIDL events from the display stack
177    /// and the returned Future will not terminate until the FIDL channel is closed.
178    ///
179    /// This task can be scheduled safely on any thread.
180    pub async fn handle_events(&self) -> Result<()> {
181        let inner = self.inner.clone();
182        let mut events = inner.write().listener_requests.take().ok_or(Error::AlreadyRequested)?;
183        while let Some(msg) = events.try_next().await? {
184            match msg {
185                CoordinatorListenerRequest::OnDisplaysChanged {
186                    added,
187                    removed,
188                    control_handle: _,
189                } => {
190                    let removed =
191                        removed.into_iter().map(|id| id.into()).collect::<Vec<DisplayId>>();
192                    inner.read().handle_displays_changed(added, removed);
193                }
194                CoordinatorListenerRequest::OnVsync {
195                    display_id,
196                    timestamp,
197                    applied_config_stamp,
198                    cookie,
199                    control_handle: _,
200                } => {
201                    inner.write().handle_vsync(
202                        display_id.into(),
203                        timestamp,
204                        applied_config_stamp,
205                        cookie,
206                    )?;
207                }
208                _ => continue,
209            }
210        }
211        Ok(())
212    }
213
214    /// Allocates a new virtual hardware layer that is not associated with any display and has no
215    /// configuration.
216    pub async fn create_layer(&self) -> Result<LayerId> {
217        Ok(self.proxy().create_layer().await?.map_err(zx::Status::from_raw)?.into())
218    }
219
220    /// Creates and registers a zircon event with the display driver. The returned event can be
221    /// used as a fence in a display configuration.
222    pub fn create_event(&self) -> Result<Event> {
223        let event = zx::Event::create();
224        let remote = event.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
225        let id = self.inner.write().next_free_event_id()?;
226
227        self.inner.read().proxy.import_event(zx::Event::from(remote), &id.into())?;
228        Ok(Event::new(id, event))
229    }
230
231    /// Apply a display configuration. The client is expected to receive a vsync event once the
232    /// configuration is successfully applied. Returns an error if the FIDL message cannot be sent.
233    pub async fn apply_config(
234        &self,
235        configs: &[DisplayConfig],
236    ) -> std::result::Result<u64, ConfigError> {
237        let proxy = self.proxy();
238        for config in configs {
239            proxy.set_display_layers(
240                &config.id.into(),
241                &config.layers.iter().map(|l| l.id.into()).collect::<Vec<FidlLayerId>>(),
242            )?;
243            for layer in &config.layers {
244                match &layer.config {
245                    LayerConfig::Color { color, display_destination } => {
246                        let fidl_color = fidl_fuchsia_hardware_display_types::Color::from(color);
247                        proxy.set_layer_color_config(
248                            &layer.id.into(),
249                            &fidl_color,
250                            display_destination,
251                        )?;
252                    }
253                    LayerConfig::Primary { image_id, image_metadata, unblock_event } => {
254                        proxy.set_layer_primary_config(&layer.id.into(), &image_metadata)?;
255                        proxy.set_layer_image2(
256                            &layer.id.into(),
257                            &(*image_id).into(),
258                            &unblock_event.unwrap_or(INVALID_EVENT_ID).into(),
259                        )?;
260                    }
261                }
262            }
263        }
264
265        let result = proxy.check_config().await?;
266        if result != display_types::ConfigResult::Ok {
267            return Err(ConfigError::invalid(result));
268        }
269
270        let config_stamp = self.inner.write().next_config_stamp().unwrap();
271        let payload = fidl_fuchsia_hardware_display::CoordinatorApplyConfig3Request {
272            stamp: Some(fidl_fuchsia_hardware_display::ConfigStamp { value: config_stamp }),
273            ..Default::default()
274        };
275        match proxy.apply_config3(payload) {
276            Ok(()) => Ok(config_stamp),
277            Err(err) => Err(ConfigError::from(err)),
278        }
279    }
280
281    /// Get the config stamp value of the most recent applied config in
282    /// `apply_config`. Returns an error if the FIDL message cannot be sent.
283    pub async fn get_recent_applied_config_stamp(&self) -> std::result::Result<u64, Error> {
284        let proxy = self.proxy();
285        let response = proxy.get_latest_applied_config_stamp().await?;
286        Ok(response.value)
287    }
288
289    /// Import a sysmem buffer collection. The returned `BufferCollectionId` can be used in future
290    /// API calls to refer to the imported collection.
291    pub(crate) async fn import_buffer_collection(
292        &self,
293        token: ClientEnd<fidl_fuchsia_sysmem2::BufferCollectionTokenMarker>,
294    ) -> Result<BufferCollectionId> {
295        let id = self.inner.write().next_free_collection_id()?;
296        let proxy = self.proxy();
297
298        // First import the token.
299        proxy.import_buffer_collection(&id.into(), token).await?.map_err(zx::Status::from_raw)?;
300
301        // Tell the driver to assign any device-specific constraints.
302        // TODO(https://fxbug.dev/42166207): These fields are effectively unused except for `type` in the case
303        // of IMAGE_TYPE_CAPTURE.
304        proxy
305            .set_buffer_collection_constraints(
306                &id.into(),
307                &display_types::ImageBufferUsage { tiling_type: IMAGE_TILING_TYPE_LINEAR },
308            )
309            .await?
310            .map_err(zx::Status::from_raw)?;
311        Ok(id)
312    }
313
314    /// Notify the display driver to release its handle on a previously imported buffer collection.
315    pub(crate) fn release_buffer_collection(&self, id: BufferCollectionId) -> Result<()> {
316        self.inner.read().proxy.release_buffer_collection(&id.into()).map_err(Error::from)
317    }
318
319    /// Register a sysmem buffer collection backed image to the display driver.
320    pub(crate) async fn import_image(
321        &self,
322        collection_id: BufferCollectionId,
323        image_id: ImageId,
324        image_metadata: display_types::ImageMetadata,
325    ) -> Result<()> {
326        self.proxy()
327            .import_image(
328                &image_metadata,
329                &collection_id.into(),
330                0, // buffer_index
331                &image_id.into(),
332            )
333            .await?
334            .map_err(zx::Status::from_raw)?;
335        Ok(())
336    }
337}
338
339// fmt::Debug implementation to allow a `Coordinator` instance to be used with a debug format
340// specifier. We use a custom implementation as not all `Coordinator` members derive fmt::Debug.
341impl fmt::Debug for Coordinator {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        f.debug_struct("Coordinator").field("displays", &self.displays()).finish()
344    }
345}
346
347impl CoordinatorInner {
348    fn next_free_collection_id(&mut self) -> Result<BufferCollectionId> {
349        self.id_counter = self.id_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
350        Ok(BufferCollectionId(self.id_counter))
351    }
352
353    fn next_free_event_id(&mut self) -> Result<EventId> {
354        self.id_counter = self.id_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
355        Ok(EventId(self.id_counter))
356    }
357
358    fn next_config_stamp(&mut self) -> Result<u64> {
359        self.stamp_counter = self.stamp_counter.checked_add(1).ok_or(Error::IdsExhausted)?;
360        Ok(self.stamp_counter)
361    }
362
363    fn handle_displays_changed(&self, _added: Vec<display::Info>, _removed: Vec<DisplayId>) {
364        // TODO(armansito): update the displays list and notify clients. Terminate vsync listeners
365        // that are attached to a removed display.
366    }
367
368    fn handle_vsync(
369        &mut self,
370        display_id: DisplayId,
371        timestamp: zx::MonotonicInstant,
372        applied_config_stamp: display::ConfigStamp,
373        cookie: display::VsyncAckCookie,
374    ) -> Result<()> {
375        if cookie.value != 0 {
376            self.proxy.acknowledge_vsync(cookie.value)?;
377        }
378
379        let mut listeners_to_remove = Vec::new();
380        for (pos, (sender, filter)) in self.vsync_listeners.iter().enumerate() {
381            // Skip the listener if it has a filter that does not match `display_id`.
382            if filter.as_ref().map_or(false, |id| *id != display_id) {
383                continue;
384            }
385            let payload = VsyncEvent { id: display_id, timestamp, config: applied_config_stamp };
386            if let Err(e) = sender.unbounded_send(payload) {
387                if e.is_disconnected() {
388                    listeners_to_remove.push(pos);
389                } else {
390                    return Err(e.into());
391                }
392            }
393        }
394
395        // Clean up disconnected listeners.
396        listeners_to_remove.into_iter().for_each(|pos| {
397            self.vsync_listeners.swap_remove(pos);
398        });
399
400        Ok(())
401    }
402}
403
404// Waits for a single fuchsia.hardware.display.Coordinator.OnDisplaysChanged event and returns the
405// reported displays. By API contract, this event will fire at least once upon initial channel
406// connection if any displays are present. If no displays are present, then the returned Future
407// will not resolve until a display is plugged in.
408async fn wait_for_initial_displays(
409    listener_requests: &mut display::CoordinatorListenerRequestStream,
410) -> Result<Vec<display::Info>> {
411    let mut stream = listener_requests.try_filter_map(|event| match event {
412        CoordinatorListenerRequest::OnDisplaysChanged { added, removed: _, control_handle: _ } => {
413            future::ok(Some(added))
414        }
415        _ => future::ok(None),
416    });
417    stream.try_next().await?.ok_or(Error::NoDisplays)
418}
419
420#[cfg(test)]
421mod tests {
422    use super::{Coordinator, DisplayId, VsyncEvent};
423    use anyhow::{format_err, Context, Result};
424    use assert_matches::assert_matches;
425    use display_mocks::{create_proxy_and_mock, MockCoordinator};
426    use fuchsia_async::TestExecutor;
427    use futures::task::Poll;
428    use futures::{pin_mut, select, FutureExt, StreamExt};
429    use {
430        fidl_fuchsia_hardware_display as display,
431        fidl_fuchsia_hardware_display_types as display_types,
432    };
433
434    async fn init_with_proxy_and_listener_requests(
435        coordinator_proxy: display::CoordinatorProxy,
436        listener_requests: display::CoordinatorListenerRequestStream,
437    ) -> Result<Coordinator> {
438        Coordinator::init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests)
439            .await
440            .context("failed to initialize Coordinator")
441    }
442
443    // Returns a Coordinator and a connected mock FIDL server. This function sets up the initial
444    // "OnDisplaysChanged" event with the given list of `displays`, which `Coordinator` requires
445    // before it can resolve its initialization Future.
446    async fn init_with_displays(
447        displays: &[display::Info],
448    ) -> Result<(Coordinator, MockCoordinator)> {
449        let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
450        mock.assign_displays(displays.to_vec())?;
451
452        Ok((
453            init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?,
454            mock,
455        ))
456    }
457
458    #[fuchsia::test]
459    async fn test_init_fails_with_no_device_dir() {
460        let result = Coordinator::init().await;
461        assert_matches!(result, Err(_));
462    }
463
464    #[fuchsia::test]
465    async fn test_init_with_no_displays() -> Result<()> {
466        let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
467        mock.assign_displays([].to_vec())?;
468
469        let coordinator =
470            init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?;
471        assert!(coordinator.displays().is_empty());
472
473        Ok(())
474    }
475
476    // TODO(https://fxbug.dev/42075852): We should have an automated test verifying that
477    // the service provided by driver framework can be opened correctly.
478
479    #[fuchsia::test]
480    async fn test_init_with_displays() -> Result<()> {
481        let displays = [
482            display::Info {
483                id: display_types::DisplayId { value: 1 },
484                modes: Vec::new(),
485                pixel_format: Vec::new(),
486                manufacturer_name: "Foo".to_string(),
487                monitor_name: "what".to_string(),
488                monitor_serial: "".to_string(),
489                horizontal_size_mm: 0,
490                vertical_size_mm: 0,
491                using_fallback_size: false,
492            },
493            display::Info {
494                id: display_types::DisplayId { value: 2 },
495                modes: Vec::new(),
496                pixel_format: Vec::new(),
497                manufacturer_name: "Bar".to_string(),
498                monitor_name: "who".to_string(),
499                monitor_serial: "".to_string(),
500                horizontal_size_mm: 0,
501                vertical_size_mm: 0,
502                using_fallback_size: false,
503            },
504        ]
505        .to_vec();
506        let (coordinator_proxy, listener_requests, mut mock) = create_proxy_and_mock()?;
507        mock.assign_displays(displays.clone())?;
508
509        let coordinator =
510            init_with_proxy_and_listener_requests(coordinator_proxy, listener_requests).await?;
511        assert_eq!(coordinator.displays().len(), 2);
512        assert_eq!(coordinator.displays()[0].0, displays[0]);
513        assert_eq!(coordinator.displays()[1].0, displays[1]);
514
515        Ok(())
516    }
517
518    #[test]
519    fn test_vsync_listener_single() -> Result<()> {
520        // Drive an executor directly for this test to avoid having to rely on timeouts for cases
521        // in which no events are received.
522        let mut executor = TestExecutor::new();
523        let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
524        let mut vsync = coordinator.add_vsync_listener(None)?;
525
526        const ID: DisplayId = DisplayId(1);
527        const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
528        let event_handlers = async {
529            select! {
530                event = vsync.next() => event.ok_or(format_err!("did not receive vsync event")),
531                result = coordinator.handle_events().fuse() => {
532                    result.context("FIDL event handler failed")?;
533                    Err(format_err!("FIDL event handler completed before client vsync event"))
534                },
535            }
536        };
537        pin_mut!(event_handlers);
538
539        // Send a single event.
540        mock.emit_vsync_event(ID.0, STAMP)?;
541        let vsync_event = executor.run_until_stalled(&mut event_handlers);
542        assert_matches!(
543            vsync_event,
544            Poll::Ready(Ok(VsyncEvent { id: ID, timestamp: _, config: STAMP }))
545        );
546
547        Ok(())
548    }
549
550    #[test]
551    fn test_vsync_listener_multiple() -> Result<()> {
552        // Drive an executor directly for this test to avoid having to rely on timeouts for cases
553        // in which no events are received.
554        let mut executor = TestExecutor::new();
555        let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
556        let mut vsync = coordinator.add_vsync_listener(None)?;
557
558        let fidl_server = coordinator.handle_events().fuse();
559        pin_mut!(fidl_server);
560
561        const ID1: DisplayId = DisplayId(1);
562        const ID2: DisplayId = DisplayId(2);
563        const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
564
565        // Queue multiple events.
566        mock.emit_vsync_event(ID1.0, STAMP)?;
567        mock.emit_vsync_event(ID2.0, STAMP)?;
568        mock.emit_vsync_event(ID1.0, STAMP)?;
569
570        // Process the FIDL events. The FIDL server Future should not complete as it runs
571        // indefinitely.
572        let fidl_server_result = executor.run_until_stalled(&mut fidl_server);
573        assert_matches!(fidl_server_result, Poll::Pending);
574
575        // Process the vsync listener.
576        let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
577        assert_matches!(
578            vsync_event,
579            Poll::Ready(Some(VsyncEvent { id: ID1, timestamp: _, config: STAMP }))
580        );
581
582        let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
583        assert_matches!(
584            vsync_event,
585            Poll::Ready(Some(VsyncEvent { id: ID2, timestamp: _, config: STAMP }))
586        );
587
588        let vsync_event = executor.run_until_stalled(&mut Box::pin(async { vsync.next().await }));
589        assert_matches!(
590            vsync_event,
591            Poll::Ready(Some(VsyncEvent { id: ID1, timestamp: _, config: STAMP }))
592        );
593
594        Ok(())
595    }
596
597    #[test]
598    fn test_vsync_listener_display_id_filter() -> Result<()> {
599        // Drive an executor directly for this test to avoid having to rely on timeouts for cases
600        // in which no events are received.
601        let mut executor = TestExecutor::new();
602        let (coordinator, mock) = executor.run_singlethreaded(init_with_displays(&[]))?;
603
604        const ID1: DisplayId = DisplayId(1);
605        const ID2: DisplayId = DisplayId(2);
606        const STAMP: display::ConfigStamp = display::ConfigStamp { value: 1 };
607
608        // Listen to events from ID2.
609        let mut vsync = coordinator.add_vsync_listener(Some(ID2))?;
610        let event_handlers = async {
611            select! {
612                event = vsync.next() => event.ok_or(format_err!("did not receive vsync event")),
613                result = coordinator.handle_events().fuse() => {
614                    result.context("FIDL event handler failed")?;
615                    Err(format_err!("FIDL event handler completed before client vsync event"))
616                },
617            }
618        };
619        pin_mut!(event_handlers);
620
621        // Event from ID1 should get filtered out and the client should not receive any events.
622        mock.emit_vsync_event(ID1.0, STAMP)?;
623        let vsync_event = executor.run_until_stalled(&mut event_handlers);
624        assert_matches!(vsync_event, Poll::Pending);
625
626        // Event from ID2 should be received.
627        mock.emit_vsync_event(ID2.0, STAMP)?;
628        let vsync_event = executor.run_until_stalled(&mut event_handlers);
629        assert_matches!(
630            vsync_event,
631            Poll::Ready(Ok(VsyncEvent { id: ID2, timestamp: _, config: STAMP }))
632        );
633
634        Ok(())
635    }
636}