use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::cell::{Cell, RefCell};
use alloc::collections::BTreeMap;
use crate::scope::{OwnedDisposable, adopt_into_current};
use crate::{NodeId, NodeKind, Reactor, current, trace_targets};
type SubscriberFn<T> = dyn Fn(&T) + 'static;
#[track_caller]
pub fn event<T: 'static>() -> Event<T> {
current().event()
}
#[track_caller]
pub fn event_in<T: 'static>(reactor: &Reactor) -> Event<T> {
reactor.event()
}
#[must_use = "subscriptions created with `on` must be used to stay active"]
#[track_caller]
pub fn on<T: Clone + 'static>(event: &Event<T>, handler: impl Fn(&T) + 'static) -> Subscription {
current().on(event, handler)
}
#[must_use = "subscriptions created with `on_in` must be used to stay active"]
#[track_caller]
pub fn on_in<T: Clone + 'static>(
reactor: &Reactor,
event: &Event<T>,
handler: impl Fn(&T) + 'static,
) -> Subscription {
reactor.on(event, handler)
}
pub struct Event<T> {
inner: Rc<EventInner<T>>,
}
impl<T> Clone for Event<T> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
}
}
}
#[derive(Clone)]
#[must_use = "an unowned subscription is cancelled when its handle is dropped; hold the handle, leak it, or subscribe inside a scope"]
pub struct Subscription {
inner: Rc<SubscriptionInner>,
}
impl Reactor {
#[track_caller]
pub fn event<T: 'static>(&self) -> Event<T> {
Event::new(self.clone())
}
#[must_use = "subscriptions created with `on` must be used to stay active"]
#[track_caller]
pub fn on<T: Clone + 'static>(
&self,
event: &Event<T>,
handler: impl Fn(&T) + 'static,
) -> Subscription {
let queue = Rc::new(RefCell::new(Vec::new()));
let direct = event.subscribe({
let queue = Rc::clone(&queue);
move |value| queue.borrow_mut().push(value.clone())
});
let effect = self.effect({
let event = event.clone();
let queue = Rc::clone(&queue);
move || {
event.observe();
let drained = {
let mut queued = queue.borrow_mut();
queued.drain(..).collect::<Vec<_>>()
};
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::EVENT,
event = "drain_event_queue",
event_id = event.inner.id.0,
drained = drained.len(),
"draining queued event values reactively"
);
crate::untrack(|| {
for value in &drained {
handler(value);
}
});
}
});
Subscription::new(move || {
direct.unsubscribe();
effect.dispose();
})
}
}
impl<T: 'static> Event<T> {
pub fn id(&self) -> NodeId {
self.inner.id
}
#[track_caller]
fn new(reactor: Reactor) -> Self {
let id = reactor.allocate_node(NodeKind::Event);
tracing::debug!(
target: trace_targets::EVENT,
event = "create_event",
node_id = id.0,
"created reactive event"
);
Self {
inner: Rc::new(EventInner {
reactor,
id,
next_subscriber: Cell::new(1),
subscribers: RefCell::new(Default::default()),
}),
}
}
fn observe(&self) {
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::EVENT,
event = "observe_event",
event_id = self.inner.id.0,
"observing event reactively"
);
self.inner.reactor.observe(self.inner.id);
}
#[track_caller]
pub fn emit(&self, value: T) {
let subscribers = self
.inner
.subscribers
.borrow()
.values()
.cloned()
.collect::<Vec<_>>();
tracing::debug!(
target: trace_targets::EVENT,
event = "emit_event",
event_id = self.inner.id.0,
subscriber_count = subscribers.len(),
"emitting event value"
);
crate::untrack(|| {
for subscriber in subscribers {
subscriber(&value);
}
});
self.inner.reactor.trigger(self.inner.id);
}
pub fn subscribe(&self, handler: impl Fn(&T) + 'static) -> Subscription {
let id = self.inner.next_subscriber.get();
self.inner.next_subscriber.set(id.wrapping_add(1));
self.inner
.subscribers
.borrow_mut()
.insert(id, Rc::new(handler) as Rc<SubscriberFn<T>>);
tracing::debug!(
target: trace_targets::EVENT,
event = "subscribe_event",
event_id = self.inner.id.0,
subscription_id = id,
subscriber_count = self.inner.subscribers.borrow().len(),
"added event subscriber"
);
let inner = Rc::clone(&self.inner);
Subscription::new(move || {
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::EVENT,
event = "unsubscribe_event",
event_id = inner.id.0,
subscription_id = id,
"removing event subscriber"
);
inner.subscribers.borrow_mut().remove(&id);
})
}
}
impl<T> Drop for EventInner<T> {
fn drop(&mut self) {
self.reactor.dispose(self.id);
}
}
impl Subscription {
#[track_caller]
fn new(cancel: impl FnOnce() + 'static) -> Self {
let inner = Rc::new(SubscriptionInner {
active: Cell::new(true),
cancel: RefCell::new(Some(Box::new(cancel))),
});
let owned: Rc<dyn OwnedDisposable> = inner.clone();
let _ = adopt_into_current(owned);
Self { inner }
}
pub fn unsubscribe(&self) {
self.inner.unsubscribe();
}
pub fn is_active(&self) -> bool {
self.inner.active.get()
}
pub fn leak(self) {
core::mem::forget(self);
}
}
impl<T> core::fmt::Debug for Event<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Event")
.field("id", &self.inner.id)
.finish_non_exhaustive()
}
}
impl core::fmt::Debug for Subscription {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Subscription")
.field("active", &self.inner.active.get())
.finish()
}
}
struct EventInner<T> {
reactor: Reactor,
id: NodeId,
next_subscriber: Cell<usize>,
subscribers: RefCell<BTreeMap<usize, Rc<SubscriberFn<T>>>>,
}
struct SubscriptionInner {
active: Cell<bool>,
cancel: RefCell<Option<Box<dyn FnOnce() + 'static>>>,
}
impl OwnedDisposable for SubscriptionInner {
fn dispose_owned(&self) {
self.unsubscribe();
}
fn is_disposed_owned(&self) -> bool {
!self.active.get()
}
}
impl SubscriptionInner {
fn unsubscribe(&self) {
if !self.active.replace(false) {
return;
}
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::EVENT,
event = "unsubscribe",
"cancelling subscription"
);
let cancel = self.cancel.borrow_mut().take();
if let Some(cancel) = cancel {
cancel();
}
}
}
impl Drop for SubscriptionInner {
fn drop(&mut self) {
self.unsubscribe();
}
}
#[cfg(test)]
mod ordering_tests {
use alloc::rc::Rc;
use core::cell::RefCell;
use crate::{Reactor, event_in};
#[test]
fn immediate_subscribers_run_in_registration_order() {
let reactor = Reactor::new();
let stream = event_in::<u32>(&reactor);
let seen = Rc::new(RefCell::new(Vec::new()));
let _subs = (0..8)
.map(|i| {
let seen = Rc::clone(&seen);
stream.subscribe(move |_| seen.borrow_mut().push(i))
})
.collect::<Vec<_>>();
stream.emit(1);
assert_eq!(*seen.borrow(), [0, 1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn a_re_entrant_emit_still_drains_in_emission_order() {
let reactor = Reactor::new();
let stream = event_in::<u32>(&reactor);
let drained = Rc::new(RefCell::new(Vec::new()));
let _drain = reactor.on(&stream, {
let drained = Rc::clone(&drained);
move |value| drained.borrow_mut().push(*value)
});
let _echo = stream.subscribe({
let stream = stream.clone();
move |value| {
if *value < 100 {
stream.emit(value + 100);
}
}
});
stream.emit(1);
reactor.flush_now();
assert_eq!(
*drained.borrow(),
[1, 101],
"the value that triggered the nested emit must be delivered first"
);
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use runite::{queue_macrotask, run};
use crate::{Reactor, event_in};
use super::{Subscription, on_in};
#[test]
fn emit_delivers_immediately_and_on_drains_reactively() {
let immediate = Rc::new(RefCell::new(Vec::new()));
let reactive = Rc::new(RefCell::new(Vec::new()));
let keep_alive = Rc::new(RefCell::new(Vec::<Subscription>::new()));
queue_macrotask({
let immediate = Rc::clone(&immediate);
let reactive = Rc::clone(&reactive);
let keep_alive = Rc::clone(&keep_alive);
move || {
let reactor = Reactor::new();
let event = event_in::<usize>(&reactor);
let direct = event.subscribe({
let immediate = Rc::clone(&immediate);
move |value| immediate.borrow_mut().push(*value)
});
let draining = on_in(&reactor, &event, {
let reactive = Rc::clone(&reactive);
move |value| reactive.borrow_mut().push(*value)
});
event.emit(1);
event.emit(2);
assert_eq!(&*immediate.borrow(), &[1, 2]);
assert!(
reactive.borrow().is_empty(),
"reactive drain should be deferred"
);
keep_alive.borrow_mut().extend([direct, draining]);
}
});
run();
assert_eq!(&*reactive.borrow(), &[1, 2]);
}
#[test]
fn on_handlers_run_untracked() {
let drains = Rc::new(RefCell::new(0usize));
queue_macrotask({
let drains = Rc::clone(&drains);
move || {
let reactor = Reactor::new();
let event = event_in::<usize>(&reactor);
let unrelated = crate::signal_in(&reactor, 0usize);
let subscription = on_in(&reactor, &event, {
let drains = Rc::clone(&drains);
let unrelated = unrelated.clone();
move |_value| {
let _ = unrelated.get();
*drains.borrow_mut() += 1;
}
});
event.emit(1);
reactor.flush_now();
assert_eq!(*drains.borrow(), 1);
unrelated.set(99);
reactor.flush_now();
assert_eq!(*drains.borrow(), 1, "handler reads must not be tracked");
event.emit(2);
reactor.flush_now();
assert_eq!(*drains.borrow(), 2);
subscription.leak();
}
});
run();
}
#[test]
fn subscriptions_created_inside_a_scope_are_cancelled_with_it() {
let seen = Rc::new(RefCell::new(Vec::new()));
queue_macrotask({
let seen = Rc::clone(&seen);
move || {
let reactor = Reactor::new();
let event = event_in::<usize>(&reactor);
let (scope_handle, subscription) = crate::scope({
let reactor = reactor.clone();
let event = event.clone();
let seen = Rc::clone(&seen);
move || {
on_in(&reactor, &event, move |value| {
seen.borrow_mut().push(*value);
})
}
});
event.emit(1);
reactor.flush_now();
assert!(subscription.is_active());
scope_handle.dispose();
assert!(
!subscription.is_active(),
"the owner must cancel the subscription"
);
event.emit(2);
reactor.flush_now();
}
});
run();
assert_eq!(
&*seen.borrow(),
&[1],
"events after scope disposal must not be delivered"
);
}
#[test]
fn self_unsubscribe_during_emit_applies_to_the_next_emit() {
let seen = Rc::new(RefCell::new(Vec::new()));
let subscription_slot = Rc::new(RefCell::new(None::<Subscription>));
let reactor = Reactor::new();
let event = event_in::<usize>(&reactor);
let subscription = event.subscribe({
let seen = Rc::clone(&seen);
let subscription_slot = Rc::clone(&subscription_slot);
move |value| {
seen.borrow_mut().push(*value);
if let Some(subscription) = subscription_slot.borrow().as_ref() {
subscription.unsubscribe();
}
}
});
*subscription_slot.borrow_mut() = Some(subscription);
event.emit(1);
event.emit(2);
assert_eq!(&*seen.borrow(), &[1]);
}
#[test]
fn dropping_a_subscription_cancels_it() {
let seen = Rc::new(RefCell::new(Vec::new()));
let reactor = Reactor::new();
let event = event_in::<usize>(&reactor);
let subscription = event.subscribe({
let seen = Rc::clone(&seen);
move |value| seen.borrow_mut().push(*value)
});
event.emit(1);
assert!(subscription.is_active());
drop(subscription);
event.emit(2);
assert_eq!(&*seen.borrow(), &[1], "dropped subscription must not fire");
}
#[test]
fn leaked_subscriptions_stay_active() {
let seen = Rc::new(RefCell::new(Vec::new()));
let reactor = Reactor::new();
let event = event_in::<usize>(&reactor);
event
.subscribe({
let seen = Rc::clone(&seen);
move |value| seen.borrow_mut().push(*value)
})
.leak();
event.emit(1);
event.emit(2);
assert_eq!(&*seen.borrow(), &[1, 2]);
}
}