reax 0.2.0

A reactivity system for Rust that infers dependencies between functions.
Documentation
//! Handlers for variables becoming dirty.

use std::cell::{RefCell, RefMut};
use std::sync::Arc;
use std::task::{Waker, Poll, Context};
use std::pin::Pin;
use std::future::Future;
use std::mem::ManuallyDrop;

use crate::Node;

/// Any type which can react actively to nodes becoming dirty.
///
/// To actually receive a dirtied notification, this handler must be registered
/// with
/// [`Node::send_dirtied_signal_to`](../struct.Node.html#method.send_dirtied_signal_to).
pub trait DirtiedHandler {
    /// Called when the node with the given id transitions from clean to dirty
    /// (usually by a upstream call to
    /// [`Node::on_write`](../struct.Node.html#method.on_write)) so that an update
    /// can be scheduled for a later time.
    ///
    /// This method *must not* update any variables directly. Nodes are
    /// generally transitioned to dirty just *before* the mutation which caused
    /// the transition is actually made. Thus, recomputing a variable inside
    /// this function could panic (because the mutated variable's `RefCell` is
    /// still locked down) or silently use outdated values.
    fn on_dirtied(&self, node_id: usize);
}

/// A shared, dynamically typed dirtied handler which can be passed to the reax
/// runtime.
pub type SharedDirtiedHandler = Arc<dyn DirtiedHandler>;

struct ReaxChannelData<T> {
    items: Vec<T>,
    waker: Option<Waker>,
}

/// A primitive non-Sync channel that is used by reax to wake up computed
/// variables and handle events. Consider using
/// [`DirtiedList`](struct.DirtiedList.html) or
/// [`EagerCompute`](../struct.EagerCompute.html) instead.
pub struct ReaxChannel<T> {
    data: RefCell<ReaxChannelData<T>>,
}

struct NonEmpty<'a, T>(&'a ReaxChannel<T>);

impl<'a, T> Future for NonEmpty<'a, T> {
    type Output = ();

    fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
        let mut data = self.0.data.borrow_mut();
        if data.items.is_empty() {
            data.waker = Some(ctx.waker().clone());
            Poll::Pending
        } else {
            data.waker = None;
            Poll::Ready(())
        }
    }
}

impl<T> ReaxChannel<T> {
    /// Creates an empty channel.
    pub fn new() -> Self {
        ReaxChannel {
            data: RefCell::new(ReaxChannelData {
                items: Vec::new(),
                waker: None,
            }),
        }
    }

    /// Sends an item to this channel, waking up an [`items`](#method.items)
    /// future. This method will panic if the internal buffer is actively
    /// borrowed.
    pub fn send(&self, item: T) {
        let mut data = self.data.borrow_mut();
        let is_empty = data.items.is_empty();
        data.items.push(item);
        if is_empty {
            data.waker.as_ref().map(Waker::wake_by_ref);
        }
    }

    /// Locks the channel and returns a mutable reference to the internal buffer.
    pub fn poll_items<'a>(&'a self) -> RefMut<'a, Vec<T>> {
        RefMut::map(self.data.borrow_mut(), |data| &mut data.items)
    }

    /// Waits for this channel to be non-empty, then locks the channel and
    /// returns a mutable reference to the internal buffer.
    ///
    /// Note that this function is fairly difficult to use correctly. For
    /// example, concurrent calls to `send` are liable to panic if made at the
    /// wrong time and only the most recent invocation of this function will
    /// even return.
    pub async fn items<'a>(&'a self) -> RefMut<'a, Vec<T>> {
        NonEmpty(self).await;
        RefMut::map(self.data.borrow_mut(), |data| &mut data.items)
    }
}

impl<T> Default for ReaxChannel<T> {
    fn default() -> Self { ReaxChannel::new() }
}

impl<T: From<usize>> DirtiedHandler for ReaxChannel<T> {
    fn on_dirtied(&self, node_id: usize) {
        self.send(T::from(node_id));
    }
}

fn move_from_vec<'b, T: Copy>(vec: &mut Vec<T>, into: &'b mut [T]) -> &'b [T] {
    let range = 0..vec.len().min(into.len());
    into[range.clone()].copy_from_slice(&vec[range.clone()]);
    vec.drain(range.clone());
    &into[range]
}

/// An asynchronous, pollable list of node ids which have transitioned from
/// clean to dirty.
#[derive(Default)]
pub struct DirtiedList {
    chan: Arc<ReaxChannel<usize>>,
}

impl DirtiedList {
    /// Create an empty list which is not attached to any node.
    pub fn new() -> Self {
        DirtiedList::default()
    }

    /// Create a new shared event handler which can be passed to
    /// [`Node::send_dirtied_signal_to`](../struct.Node.html#method.send_dirtied_signal_to).
    pub fn new_shared(&self) -> SharedDirtiedHandler {
        self.chan.clone() as _
    }

    /// Listens to dirtied events on given node, un-attaching the node from any
    /// other handler.
    pub fn attach(&self, node: &Node) {
        node.send_dirtied_signal_to(self.new_shared());
    }

    /// If any attached nodes have transitioned from clean to dirty, some of
    /// those node ids are moved into the given buffer. A potentially empty
    /// slice of those ids is returned. If an async runtime is available,
    /// [dirtied_ids](#method.dirtied_ids) should generally be used instead
    /// since it will intelligently wake the async schedular.
    pub fn poll_dirtied_ids<'a>(&mut self, buffer: &'a mut [usize]) -> &'a [usize] {
        move_from_vec(&mut *self.chan.poll_items(), buffer)
    }

    /// Yields until some attached nodes have transitioned from clean to dirty
    /// and then moves some of those node ids into the given buffer. A non-empty
    /// slice of those dirty ids is returned. Any ids which do not fit in the
    /// buffer will be included in following calls to `dirtied_ids` or
    /// `poll_dirtied_ids`.
    pub async fn dirtied_ids<'a>(&mut self, buffer: &'a mut [usize]) -> &'a [usize] {
        move_from_vec(&mut *self.chan.items().await, buffer)
    }
}

struct FfiDirtiedHandler {
    dirty_ids: RefCell<Vec<usize>>,
    waker: fn(*const ()),
    finalizer: fn(*const ()),
    data: *const (),
}

impl Drop for FfiDirtiedHandler {
    fn drop(&mut self) {
        (self.finalizer)(self.data);
    }
}

impl DirtiedHandler for FfiDirtiedHandler {
    fn on_dirtied(&self, node_id: usize) {
        let mut ids = self.dirty_ids.borrow_mut();
        let is_empty = ids.is_empty();
        ids.push(node_id);
        if is_empty {
            (self.waker)(self.data);
        }
    }
}

#[no_mangle]
extern "C" fn reax_dirtied_handler_create(
    waker: fn(*const ()),
    finalizer: fn(*const ()),
    data: *const (),
) -> *const FfiDirtiedHandler {
    Arc::into_raw(Arc::new(FfiDirtiedHandler {
        dirty_ids: RefCell::new(Vec::new()),
        waker,
        finalizer,
        data,
    }))
}

#[no_mangle]
unsafe extern "C" fn reax_dirtied_handler_poll(
    handler: *const FfiDirtiedHandler,
    ids: *mut usize,
    capacity: usize,
 ) -> usize {
    let arc = ManuallyDrop::new(Arc::from_raw(handler));
    let mut dirty = arc.dirty_ids.borrow_mut();
    move_from_vec(
        &mut *dirty,
        std::slice::from_raw_parts_mut(ids, capacity),
    ).len()
}

#[no_mangle]
unsafe extern "C" fn reax_dirtied_handler_reference(
    handler: *const FfiDirtiedHandler,
) {
    let arc = ManuallyDrop::new(Arc::from_raw(handler));
    ManuallyDrop::new(arc.clone());
}

#[no_mangle]
unsafe extern "C" fn reax_dirtied_handler_release(
    handler: *const FfiDirtiedHandler,
) {
    Arc::from_raw(handler);
}

#[no_mangle]
unsafe extern "C" fn reax_node_send_dirtied_signal_to(
    node: usize,
    handler: *const FfiDirtiedHandler,
) {
    let arc = ManuallyDrop::new(Arc::from_raw(handler));
    Node::from_id(node).send_dirtied_signal_to(Arc::clone(&arc) as _);
}