use std::time::Duration;
use web_time::Instant;
type TimerFn<Msg> = Box<dyn Fn(Instant) -> Msg + Send>;
pub(crate) enum Kind<Msg> {
Every {
interval: Duration,
make: TimerFn<Msg>,
},
}
pub(crate) struct Entry<Msg> {
pub(crate) id: u64,
pub(crate) kind: Kind<Msg>,
}
pub struct Subscription<Msg> {
entries: Vec<Entry<Msg>>,
}
fn every_id(interval: Duration) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
"every".hash(&mut hasher);
interval.as_millis().hash(&mut hasher);
hasher.finish()
}
impl<Msg: Send + 'static> Subscription<Msg> {
pub fn none() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn batch(subscriptions: impl IntoIterator<Item = Subscription<Msg>>) -> Self {
let mut entries = Vec::new();
for subscription in subscriptions {
entries.extend(subscription.entries);
}
Self { entries }
}
pub fn every(interval: Duration, make: impl Fn(Instant) -> Msg + Send + 'static) -> Self {
Self {
entries: vec![Entry {
id: every_id(interval),
kind: Kind::Every {
interval,
make: Box::new(make),
},
}],
}
}
pub fn every_keyed(
key: u64,
interval: Duration,
make: impl Fn(Instant) -> Msg + Send + 'static,
) -> Self {
let id = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
"every-keyed".hash(&mut hasher);
key.hash(&mut hasher);
interval.as_millis().hash(&mut hasher);
hasher.finish()
};
Self {
entries: vec![Entry {
id,
kind: Kind::Every {
interval,
make: Box::new(make),
},
}],
}
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn ids(&self) -> Vec<u64> {
self.entries.iter().map(|entry| entry.id).collect()
}
pub(crate) fn into_entries(self) -> Vec<Entry<Msg>> {
self.entries
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn none_is_empty() {
assert!(Subscription::<u32>::none().is_empty());
}
#[test]
fn every_id_is_stable_per_duration() {
let a = Subscription::every(Duration::from_secs(1), |_| 0u32);
let b = Subscription::every(Duration::from_secs(1), |_| 0u32);
let c = Subscription::every(Duration::from_secs(2), |_| 0u32);
assert_eq!(a.ids(), b.ids(), "same duration → same id");
assert_ne!(a.ids(), c.ids(), "different duration → different id");
}
#[test]
fn a_key_tells_two_timers_of_one_period_apart() {
let period = Duration::from_secs(1);
let a = Subscription::every_keyed(1, period, |_| 0u32);
let b = Subscription::every_keyed(2, period, |_| 0u32);
let again = Subscription::every_keyed(1, period, |_| 0u32);
assert_ne!(a.ids(), b.ids());
assert_eq!(
a.ids(),
again.ids(),
"and one that keeps its key keeps its identity"
);
assert_ne!(a.ids(), Subscription::every(period, |_| 0u32).ids());
}
#[test]
fn batch_combines_entries() {
let combined = Subscription::batch([
Subscription::every(Duration::from_secs(1), |_| 0u32),
Subscription::none(),
Subscription::every(Duration::from_secs(2), |_| 0u32),
]);
assert_eq!(combined.ids().len(), 2);
}
}