nullable-utils 0.2.1

Helpers for working with James Shore's Nullables
Documentation
use core::hint;
use core::mem;
use core::ops::Deref;

use alloc::sync::Arc;
use alloc::sync::Weak;
use once_cell::unsync::Lazy;
use parking_lot::Mutex;
use parking_lot::RwLock;
use parking_lot::RwLockReadGuard;

/// `Listener` implements the emitter side of the [Output Tracking pattern].
///
/// [Output Tracking]: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking
pub struct Listener<T> {
    trackers: Mutex<Vec<Weak<RwLock<Vec<T>>>>>,
}

impl<T> Listener<T> {
    /// Emit a new event to attached listeners.
    pub fn emit(&self, event: &T)
    where
        T: Clone,
    {
        self.emit_with(|| event.clone());
    }

    /// Emit a new event to attached listeners.
    ///
    /// Calls the given closure to generate the event for each tracker. This
    /// allows to skip expensive setup when no trackers are connected.
    pub fn emit_with(&self, mut event_source: impl FnMut() -> T) {
        let should_prune = self
            .trackers
            .lock()
            .iter()
            .fold(false, |should_prune, tracker| {
                let Some(tracker) = tracker.upgrade() else {
                    return true;
                };

                tracker.write().push(event_source());
                should_prune
            });

        if should_prune {
            self.prune();
        }
    }

    /// Emit a new event to attached listeners.
    ///
    /// Calls the given closure at most once to generate the event. Afterwards,
    /// the generated event is cached and reused for future trackers. This
    /// allows to skip expensive setup when no trackers are connected.
    pub fn emit_with_cached(&self, cached_event_source: impl FnOnce() -> T)
    where
        T: Clone,
    {
        self.emit_with({
            let initializer = Lazy::new(cached_event_source);
            move || initializer.clone()
        });
    }

    /// Create a new [`Tracker`], connected to this listener.
    pub fn track(&self) -> Tracker<T> {
        let events = Arc::default();
        self.trackers.lock().push(Arc::downgrade(&events));
        Tracker { events }
    }
}

impl<T> Listener<T> {
    fn prune(&self) {
        self.trackers.lock().retain(|arc| 0 < arc.strong_count());
    }
}

impl<T> Default for Listener<T> {
    fn default() -> Self {
        let trackers = Mutex::default();
        Self { trackers }
    }
}

/// `Tracker` implements the receiver side of the [Output Tracking pattern].
///
/// [Output Tracking]: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking
#[derive(Clone, Debug)]
pub struct Tracker<T> {
    events: Arc<RwLock<Vec<T>>>,
}

impl<T> Tracker<T> {
    /// Consume all current events.
    ///
    /// The consumed events will not be returned by future invocations of `data` or `consume`.
    #[must_use]
    pub fn consume(&self) -> Vec<T> {
        let mut events = self.events.write();
        mem::take(&mut events)
    }

    /// Get a read-only view of tracked data so far.
    ///
    /// <div class="warning">
    ///
    /// This will block the sender side while the returned handle exists, so it should not be held for a long time.
    /// Alternatively, you can copy out of the returned view or use [`Tracker::consume`] if you do not need to retain the
    /// data.
    ///
    /// </div>
    #[must_use]
    pub fn data(&self) -> TrackerData<T> {
        TrackerData(self.events.read())
    }

    /// Stop listening on this `Tracker`.
    ///
    /// This returns any remaining events in the tracker.
    #[must_use]
    pub fn stop(mut self) -> Vec<T> {
        loop {
            match Arc::try_unwrap(self.events) {
                Ok(events) => return events.into_inner(),
                Err(ev) => {
                    self.events = ev;
                    hint::spin_loop();
                }
            };
        }
    }
}

/// `TrackerData` is a read-only view of data tracked in an [`OutputTracker`] so far.
///
/// <div class="warning">
///
/// This will block the sender side while the handle exists, so it should not be held for a long time.
/// Alternatively, you can copy out of the returned view or use [`Tracker::consume`] if you do not need to retain the
/// data.
///
/// </div>
#[derive(Debug)]
pub struct TrackerData<'l, T>(RwLockReadGuard<'l, Vec<T>>);

impl<T> Deref for TrackerData<'_, T> {
    type Target = [T];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}