Skip to main content

ferroui_base/media/
media_context_clock.rs

1//! The animation clock of the media context.
2
3use super::media_context::MediaContext;
4use crate::animation::{IClock, IGlobalClock, PlayState, TimeSpan};
5use crate::reactive::{Disposable, IDisposable, IObservable, IObserver};
6use std::cell::{Cell, RefCell};
7use std::collections::VecDeque;
8use std::rc::{Rc, Weak};
9
10type AnimationFrame = Box<dyn FnOnce(TimeSpan)>;
11
12/// The global animation clock: pulsed once per frame by the media context
13/// with the time elapsed since the media context started.
14///
15/// Subscribing schedules a render pass, so that the clock keeps ticking
16/// while anything observes it.
17pub struct MediaContextClock {
18    this: Weak<MediaContextClock>,
19    parent: Weak<MediaContext>,
20    observers: RefCell<Vec<Rc<dyn IObserver<TimeSpan>>>>,
21    new_observers: RefCell<Vec<Rc<dyn IObserver<TimeSpan>>>>,
22    queued_animation_frames: RefCell<VecDeque<AnimationFrame>>,
23    current_animation_timestamp: Cell<TimeSpan>,
24}
25
26impl MediaContextClock {
27    pub(crate) fn new(parent: Weak<MediaContext>) -> Rc<Self> {
28        Rc::new_cyclic(|this| Self {
29            this: this.clone(),
30            parent,
31            observers: RefCell::new(Vec::new()),
32            new_observers: RefCell::new(Vec::new()),
33            queued_animation_frames: RefCell::new(VecDeque::new()),
34            current_animation_timestamp: Cell::new(TimeSpan::ZERO),
35        })
36    }
37
38    /// Whether observers subscribed since the last pulse.
39    pub fn has_new_subscriptions(&self) -> bool {
40        !self.new_observers.borrow().is_empty()
41    }
42
43    /// Whether anything is waiting for the next pulse.
44    pub fn has_subscriptions(&self) -> bool {
45        !self.observers.borrow().is_empty() || !self.queued_animation_frames.borrow().is_empty()
46    }
47
48    /// Runs `action` with the frame time on the next pulse.
49    pub fn request_animation_frame(&self, action: impl FnOnce(TimeSpan) + 'static) {
50        if let Some(parent) = self.parent.upgrade() {
51            parent.schedule_render(false);
52        }
53        self.queued_animation_frames.borrow_mut().push_back(Box::new(action));
54    }
55
56    pub(crate) fn pulse(&self, now: TimeSpan) {
57        self.new_observers.borrow_mut().clear();
58        self.current_animation_timestamp.set(now);
59
60        // The queue is taken before it is run: frames requested by a
61        // callback are for the next pulse.
62        let animation_frames = std::mem::take(&mut *self.queued_animation_frames.borrow_mut());
63        for callback in animation_frames {
64            callback(now);
65        }
66
67        let observers = self.observers.borrow().clone();
68        for observer in observers {
69            observer.on_next(self.current_animation_timestamp.get());
70        }
71    }
72
73    pub(crate) fn pulse_new_subscriptions(&self) {
74        let new_observers = self.new_observers.borrow().clone();
75        for observer in new_observers {
76            observer.on_next(self.current_animation_timestamp.get());
77        }
78        self.new_observers.borrow_mut().clear();
79    }
80}
81
82impl IObservable<TimeSpan> for MediaContextClock {
83    fn subscribe(&self, observer: Rc<dyn IObserver<TimeSpan>>) -> Rc<dyn IDisposable> {
84        if let Some(parent) = self.parent.upgrade() {
85            parent.schedule_render(false);
86            parent.verify_access();
87        }
88        self.observers.borrow_mut().push(observer.clone());
89        self.new_observers.borrow_mut().push(observer.clone());
90        let weak = self.this.clone();
91        Disposable::create(move || {
92            let Some(this) = weak.upgrade() else { return };
93            if let Some(parent) = this.parent.upgrade() {
94                parent.verify_access();
95            }
96            let mut observers = this.observers.borrow_mut();
97            if let Some(index) = observers.iter().position(|o| Rc::ptr_eq(o, &observer)) {
98                observers.remove(index);
99            }
100        })
101    }
102}
103
104impl IClock for MediaContextClock {
105    fn play_state(&self) -> PlayState {
106        PlayState::Run
107    }
108
109    fn set_play_state(&self, _value: PlayState) {
110        panic!("The play state of the global clock cannot be changed.");
111    }
112}
113
114impl IGlobalClock for MediaContextClock {}