/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/paint/refresh_driver.rs
259 строк
10 KB
Jonathan Schwender
refresh_driver: Optimize timeout code (#44970)
18 май 2026, 11:20
Не верифицирован
18 май 2026, 11:20
d69498a
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::{Cell, LazyCell, RefCell}; use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::thread::{self, JoinHandle}; use std::time::Duration; use crossbeam_channel::{RecvTimeoutError, Sender}; use embedder_traits::{EventLoopWaker, RefreshDriver}; use log::warn; use servo_constellation_traits::EmbedderToConstellationMessage; use timers::{BoxedTimerCallback, TimerEventRequest, TimerScheduler}; use crate::painter::Painter; use crate::webview_renderer::WebViewRenderer; /// The [`BaseRefreshDriver`] is a "base class" for [`RefreshDriver`] trait /// implementations. It encapsulates shared behavior so that it does not have to be /// implemented by all trait implementations. It is responsible for providing /// [`RefreshDriver`] implementations with a callback that is used to wake up the event /// loop and trigger frame readiness. pub(crate) struct BaseRefreshDriver { /// Whether or not the [`BaseRefreshDriver`] is waiting for a frame. Once the [`RefreshDriver`] /// informs the base that a frame start happened, this becomes false. waiting_for_frame: Arc<AtomicBool>, /// An [`EventLooperWaker`] which alerts the main UI event loop when a frame start occurs. event_loop_waker: Box<dyn EventLoopWaker>, /// A list of internal observers that watch for frame starts. observers: RefCell<Vec<Rc<dyn RefreshDriverObserver>>>, /// The implementation of the [`RefreshDriver`]. By default this is a simple timer, but the /// embedder can install a custom driver, such as one that is run via the hardware vsync signal. refresh_driver: Rc<dyn RefreshDriver>, } impl BaseRefreshDriver { pub(crate) fn new( event_loop_waker: Box<dyn EventLoopWaker>, refresh_driver: Option<Rc<dyn RefreshDriver>>, timer_refresh_driver: &LazyCell<Rc<TimerRefreshDriver>>, ) -> Self { let refresh_driver = refresh_driver.unwrap_or_else(|| (**timer_refresh_driver).clone()); Self { waiting_for_frame: Arc::new(AtomicBool::new(false)), event_loop_waker, observers: Default::default(), refresh_driver, } } pub(crate) fn add_observer(&self, observer: Rc<dyn RefreshDriverObserver>) { let mut observers = self.observers.borrow_mut(); observers.push(observer); // If this is the first observer, make sure to observe the next frame. if observers.len() == 1 { self.observe_next_frame(); } } pub(crate) fn notify_will_paint(&self, painter: &mut Painter) { // Limit the borrow of `self.observers` to the minimum here. let still_has_observers = { let mut observers = self.observers.borrow_mut(); observers.retain(|observer| observer.frame_started(painter)); !observers.is_empty() }; if still_has_observers { self.observe_next_frame(); } } fn observe_next_frame(&self) { self.waiting_for_frame.store(true, Ordering::Relaxed); let waiting_for_frame = self.waiting_for_frame.clone(); let event_loop_waker = self.event_loop_waker.clone_box(); self.refresh_driver.observe_next_frame(Box::new(move || { waiting_for_frame.store(false, Ordering::Relaxed); event_loop_waker.wake(); })); } /// Whether or not the renderer should trigger a message to the embedder to request a /// repaint. This might be true if we are animating and we are still waiting for a new /// frame from the `RefreshDriver`. pub(crate) fn wait_to_paint(&self) -> bool { !self.observers.borrow().is_empty() && self.waiting_for_frame.load(Ordering::Relaxed) } } /// A [`RefreshDriverObserver`] is an internal subscriber to frame start signals from the /// [`RefreshDriver`]. Examples of these kind of observers would be one that triggers new /// animation frames right after vsync signals or one that handles touch interactions once /// per frame. pub(crate) trait RefreshDriverObserver { /// Informs the observer that a new frame has started. The observer should return /// `true` to keep observing or `false` if wants to stop observing and should be /// removed by the [`BaseRefreshDriver`]. fn frame_started(&self, painter: &mut Painter) -> bool; } /// The [`AnimationRefreshDriverObserver`] is the default implementation of a /// [`RefreshDriver`] on systems without vsync hardware integration. It has a very basic /// way of triggering frames using a timer. It prevents new animation frames until the /// timer has fired. pub(crate) struct AnimationRefreshDriverObserver { /// The channel on which messages can be sent to the Constellation. pub(crate) constellation_sender: Sender<EmbedderToConstellationMessage>, /// Whether or not we are currently animating via a timer. pub(crate) animating: Cell<bool>, } impl AnimationRefreshDriverObserver { pub(crate) fn new(constellation_sender: Sender<EmbedderToConstellationMessage>) -> Self { Self { constellation_sender, animating: Default::default(), } } pub(crate) fn notify_animation_state_changed( &self, webview_renderer: &WebViewRenderer, ) -> bool { if !webview_renderer.animating() { // If no other WebView is animating we will officially stop animating once the // next frame has been painted. return false; } if let Err(error) = self.constellation_sender .send(EmbedderToConstellationMessage::TickAnimation(vec![ webview_renderer.id, ])) { warn!("Sending tick to constellation failed ({error:?})."); } if self.animating.get() { return false; } self.animating.set(true); true } } impl RefreshDriverObserver for AnimationRefreshDriverObserver { fn frame_started(&self, painter: &mut Painter) -> bool { // If any WebViews are animating ask them to paint again for another animation tick. let animating_webviews = painter.animating_webviews(); // If nothing is animating any longer, update our state and exit early without requesting // any new frames. if animating_webviews.is_empty() { self.animating.set(false); return false; } // Request new animation frames from all animating WebViews. if let Err(error) = self.constellation_sender .send(EmbedderToConstellationMessage::TickAnimation( animating_webviews, )) { warn!("Sending tick to constellation failed ({error:?})."); return false; } self.animating.set(true); true } } enum TimerThreadMessage { Request(TimerEventRequest), Quit, } /// A thread that manages a [`TimerScheduler`] running in the background of the /// [`RefreshDriver`]. This is necessary because we need a reliable way of waking up the /// embedder's main thread, which may just be sleeping until the `EventLoopWaker` asks it /// to wake up. /// /// It would be nice to integrate this somehow into the embedder thread, but it would /// require both some communication with the embedder and for all embedders to be well /// behave respecting wakeup timeouts -- a bit too much to ask at the moment. pub(crate) struct TimerRefreshDriver { sender: Sender<TimerThreadMessage>, join_handle: Option<JoinHandle<()>>, } impl Default for TimerRefreshDriver { fn default() -> Self { let (sender, receiver) = crossbeam_channel::unbounded::<TimerThreadMessage>(); let join_handle = thread::Builder::new() .name(String::from("PaintTimerThread")) .spawn(move || { let mut scheduler = TimerScheduler::default(); loop { let recv_result = match scheduler.next_deadline() { Some(deadline) => receiver.recv_deadline(deadline), None => receiver.recv().map_err(|_| RecvTimeoutError::Disconnected), }; match recv_result { Ok(TimerThreadMessage::Request(request)) => { scheduler.schedule_timer(request); }, Err(RecvTimeoutError::Timeout) => scheduler.dispatch_completed_timers(), Ok(TimerThreadMessage::Quit) | Err(RecvTimeoutError::Disconnected) => { return; }, } } }) .expect("Could not create RefreshDriver timer thread."); Self { sender, join_handle: Some(join_handle), } } } impl TimerRefreshDriver { pub(crate) fn queue_timer(&self, duration: Duration, callback: BoxedTimerCallback) { let _ = self .sender .send(TimerThreadMessage::Request(TimerEventRequest { callback, duration, })); } } impl RefreshDriver for TimerRefreshDriver { fn observe_next_frame(&self, new_start_frame_callback: Box<dyn Fn() + Send + 'static>) { const FRAME_DURATION: Duration = Duration::from_millis(1000 / 120); self.queue_timer(FRAME_DURATION, new_start_frame_callback); } } impl Drop for TimerRefreshDriver { fn drop(&mut self) { let _ = self.sender.send(TimerThreadMessage::Quit); if let Some(join_handle) = self.join_handle.take() { let _ = join_handle.join(); } } }