use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::time::Duration;
use crate::arena::WidgetArena;
use crate::motion_visibility;
use crate::widget_id::WidgetId;
const PER_FRAME_INTERVAL: Duration = Duration::from_micros(16_667);
#[derive(Clone, Copy)]
struct Subscriber {
id: WidgetId,
interval: Option<Duration>,
}
type SubscriberSet = Rc<RefCell<Vec<Subscriber>>>;
#[derive(Clone)]
pub struct FrameTickScheduler {
subscribers: SubscriberSet,
}
impl Default for FrameTickScheduler {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for FrameTickScheduler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FrameTickScheduler")
.field("subscriber_count", &self.subscribers.borrow().len())
.finish()
}
}
impl FrameTickScheduler {
pub fn new() -> Self {
Self {
subscribers: Rc::new(RefCell::new(Vec::new())),
}
}
pub fn subscribe(&self, owner: WidgetId) -> FrameTickSubscription {
self.subscribe_with(owner, None)
}
pub fn subscribe_throttled(
&self,
owner: WidgetId,
interval: Duration,
) -> FrameTickSubscription {
self.subscribe_with(owner, Some(interval))
}
fn subscribe_with(&self, owner: WidgetId, interval: Option<Duration>) -> FrameTickSubscription {
self.subscribers.borrow_mut().push(Subscriber {
id: owner,
interval,
});
FrameTickSubscription {
set: Rc::downgrade(&self.subscribers),
owner,
}
}
pub fn subscriber_count(&self) -> usize {
self.subscribers.borrow().len()
}
pub fn should_arm_frame_tick(&self, arena: &WidgetArena, paint_epoch: u64) -> bool {
self.subscribers
.borrow()
.iter()
.any(|s| motion_visibility::painted_this_frame(arena, s.id, paint_epoch))
}
pub fn min_visible_interval(&self, arena: &WidgetArena, paint_epoch: u64) -> Option<Duration> {
self.subscribers
.borrow()
.iter()
.filter(|s| motion_visibility::painted_this_frame(arena, s.id, paint_epoch))
.map(|s| s.interval.unwrap_or(PER_FRAME_INTERVAL))
.min()
}
pub fn has_running(&self) -> bool {
!self.subscribers.borrow().is_empty()
}
}
pub struct FrameTickSubscription {
set: Weak<RefCell<Vec<Subscriber>>>,
owner: WidgetId,
}
impl FrameTickSubscription {
pub fn owner(&self) -> WidgetId {
self.owner
}
}
impl Drop for FrameTickSubscription {
fn drop(&mut self) {
let Some(set) = self.set.upgrade() else {
return;
};
let mut subs = set.borrow_mut();
if let Some(pos) = subs.iter().position(|s| s.id == self.owner) {
subs.swap_remove(pos);
}
}
}
impl std::fmt::Debug for FrameTickSubscription {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FrameTickSubscription")
.field("owner", &self.owner)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widget_tree::WidgetTree;
fn fresh_ids() -> (WidgetId, WidgetId) {
let mut tree = WidgetTree::new();
let a = tree.add(crate::test_widgets::FillWidget::new());
let b = tree.add(crate::test_widgets::FillWidget::new());
(a, b)
}
#[test]
fn subscribe_then_drop_removes() {
let sched = FrameTickScheduler::new();
let (id_a, id_b) = fresh_ids();
let g_a = sched.subscribe(id_a);
let _g_b = sched.subscribe(id_b);
assert_eq!(sched.subscriber_count(), 2);
drop(g_a);
assert_eq!(sched.subscriber_count(), 1);
}
#[test]
fn drop_after_scheduler_is_no_op() {
let (id_a, _) = fresh_ids();
let g = {
let sched = FrameTickScheduler::new();
sched.subscribe(id_a)
};
drop(g);
}
#[test]
fn duplicate_owner_subscriptions_count_separately() {
let sched = FrameTickScheduler::new();
let (id, _) = fresh_ids();
let g1 = sched.subscribe(id);
let _g2 = sched.subscribe(id);
assert_eq!(sched.subscriber_count(), 2);
drop(g1);
assert_eq!(sched.subscriber_count(), 1);
}
}