From ac3978ebb4412d44ea2e6910887c6e9268383385 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Tue, 17 Oct 2023 21:39:08 -0700 Subject: [PATCH] docs: Document that EventListeners must be listen'd on In retrospect it is sometimes unclear in the documentation that the new event listener needs to be pinned and inserted into the list before it can receive events. This PR adds documentation that should clarify this issue. Closes #89 Signed-off-by: John Nunley --- src/lib.rs | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/notify.rs | 4 ++ 2 files changed, 111 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index f21c2a5..4a80cc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,6 +237,25 @@ impl Event { /// let event = Event::new(); /// let listener = event.listen(); /// ``` + /// + /// # Caveats + /// + /// The above example is equivalent to this code: + /// + /// ``` + /// use event_listener::{Event, EventListener}; + /// + /// let event = Event::new(); + /// let mut listener = Box::pin(EventListener::new(&event)); + /// listener.as_mut().listen(); + /// ``` + /// + /// It creates a new listener, pins it to the heap, and inserts it into the linked list + /// of listeners. While this type of usage is simple, it may be desired to eliminate this + /// heap allocation. In this case, consider using the [`EventListener::new`] constructor + /// directly, which allows for greater control over where the [`EventListener`] is + /// allocated. However, users of this `new` method must be careful to ensure that the + /// [`EventListener`] is `listen`ing before waiting on it; panics may occur otherwise. #[cold] pub fn listen(&self) -> Pin>> { let mut listener = Box::pin(EventListener::new(self)); @@ -638,6 +657,77 @@ pin_project_lite::pin_project! { /// If a notified listener is dropped without receiving a notification, dropping will notify /// another active listener. Whether one *additional* listener will be notified depends on what /// kind of notification was delivered. + /// + /// The listener is not registered into the linked list inside of the [`Event`] by default. It + /// needs to be pinned first before being inserted using the `listen()` method. After the + /// listener has begun `listen`ing, the user can `await` it like a future or call `wait()` + /// to block the current thread until it is notified. + /// + /// ## Examples + /// + /// ``` + /// use event_listener::{Event, EventListener}; + /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; + /// use std::thread; + /// use std::time::Duration; + /// + /// // Some flag to wait on. + /// let flag = Arc::new(AtomicBool::new(false)); + /// + /// // Create an event to wait on. + /// let event = Event::new(); + /// + /// thread::spawn({ + /// let flag = flag.clone(); + /// move || { + /// thread::sleep(Duration::from_secs(2)); + /// flag.store(true, Ordering::SeqCst); + /// + /// // Wake up the listener. + /// event.notify_additional(std::usize::MAX); + /// } + /// }); + /// + /// let listener = EventListener::new(&event); + /// + /// // Make sure that the event listener is pinned before doing anything else. + /// // + /// // We pin the listener to the stack here, as it lets us avoid a heap allocation. + /// futures_lite::pin!(listener); + /// + /// // Wait for the flag to become ready. + /// loop { + /// if flag.load(Ordering::Acquire) { + /// // We are done. + /// break; + /// } + /// + /// if listener.is_listening() { + /// // We are inserted into the linked list and we can now wait. + /// listener.as_mut().wait(); + /// } else { + /// // We need to insert ourselves into the list. Since this insertion is an atomic + /// // operation, we should check the flag again before waiting. + /// listener.as_mut().listen(); + /// } + /// } + /// ``` + /// + /// The above example is equivalent to the one provided in the crate level example. However, + /// it has some advantages. By directly creating the listener with `EventListener::new()`, + /// we have control over how the listener is handled in memory. We take advantage of this by + /// pinning the `listener` variable to the stack using the [`futures_lite::pin`] macro. In + /// contrast, `Event::listen` binds the listener to the heap. + /// + /// However, this additional power comes with additional responsibility. By default, the + /// event listener is created in an "uninserted" state. This property means that any + /// notifications delivered to the [`Event`] by default will not wake up this listener. + /// Before any notifications can be received, the `listen()` method must be called on + /// `EventListener` to insert it into the list of listeners. After a `.await` or a `wait()` + /// call has completed, `listen()` must be called again if the user is still interested in + /// any events. + /// + /// [`futures_lite::pin`]: https://docs.rs/futures-lite/latest/futures_lite/macro.pin.html #[project(!Unpin)] // implied by Listener, but can generate better docs pub struct EventListener { #[pin] @@ -656,6 +746,23 @@ impl fmt::Debug for EventListener { impl EventListener { /// Create a new `EventListener` that will wait for a notification from the given [`Event`]. + /// + /// This function does not register the `EventListener` into the linked list of listeners + /// contained within the [`Event`]. Make sure to call `listen` before `await`ing on + /// this future or calling `wait()`. + /// + /// ## Examples + /// + /// ``` + /// use event_listener::{Event, EventListener}; + /// + /// let event = Event::new(); + /// let listener = EventListener::new(&event); + /// + /// // Make sure that the listener is pinned and listening before doing anything else. + /// let mut listener = Box::pin(listener); + /// listener.as_mut().listen(); + /// ``` pub fn new(event: &Event) -> Self { let inner = event.inner(); diff --git a/src/notify.rs b/src/notify.rs index 8082bc1..9484668 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -9,6 +9,8 @@ pub(crate) use __private::Internal; /// The type of notification to use with an [`Event`]. /// /// This is hidden and sealed to prevent changes to this trait from being breaking. +/// +/// [`Event`]: crate::Event #[doc(hidden)] pub trait NotificationPrivate { /// The tag data associated with a notification. @@ -52,6 +54,8 @@ pub trait NotificationPrivate { /// /// notify(&Event::new(), 1.additional()); /// ``` +/// +/// [`Event`]: crate::Event pub trait Notification: NotificationPrivate {} impl Notification for N {}