Crate raii_change_tracker [] [src]

Track changes to data and notify listeners.

The main API feature is the DataTracker struct, which takes ownership of a value.

To listen for changes to an instance of the DataTracker struct, the function DataTracker::add_listener() returns a futures::Stream. This Stream is supplied with previous and new values immediately after each change and integrates with the tokio ecosystem.

The principle of operation is that DataTracker::as_tracked_mut() returns a mutable Modifier. Modifier is a RAII scoped guard with two key properties:

  • It implements the DerefMut trait to allow ergonomic access to the underlying data.
  • It implements the Drop trait which checks if the underlying data was changed and, if so, notifies the listeners.

Futhermore, DataTracker::as_ref() returns a (non-mutable) reference to the data for cases when only read-only access to the data is needed.

To implement tracking, when Modifier is created, a copy of the original data is made and when Modifier is dropped, an equality check is performed. If the original and the new data are not equal, the callbacks are called with references to the old and the new values.

example

extern crate futures;
extern crate tokio_core;
extern crate raii_change_tracker;

use futures::{Future, IntoFuture, Stream};
use tokio_core::reactor::{Core, Timeout};
use std::time::Duration;
use std::rc::Rc;
use std::cell::RefCell;

use raii_change_tracker::DataTracker;

#[derive(Clone,PartialEq,Debug)]
struct StoreType {
    val: i32,
}

fn main() {

    // Create our DataTracker instance.
    let data_store = DataTracker::new(StoreType { val: 123 });
    // Wrap it so we can clone it.
    let data_store_rc = Rc::new(RefCell::new(data_store));
    // Create a listener futures::Stream to receive all changes.
    let rx = data_store_rc.borrow_mut().add_listener();
    // For each change notification, do this.
    let rx_printer = rx.for_each(|(old_value, new_value)| {
                                     // In this example, we just verify things work.
                                     assert!(old_value.val == 123);
                                     assert!(new_value.val == 124);
                                     futures::future::err(()) // return error to abort stream
                                 });

    // Create an instance of a tokio reactor.
    let mut core = Core::new().unwrap();

    // Clone our DataTracker instance.
    let dsclone = data_store_rc.clone();
    // Create a timeout and then, when it fires, update the data store.
    let cause_change = Timeout::new(Duration::from_millis(0), &core.handle())
        .into_future()
        .flatten()
        .and_then(move |_| {
            {
                let mut data_store = dsclone.borrow_mut();
                let mut scoped_store = data_store.as_tracked_mut();
                assert!((*scoped_store).val == 123);
                (*scoped_store).val += 1;
            }
            Ok(())
        })
        .map_err(|_| ());

    // Run our futures in the tokio event loop.
    core.handle().spawn(cause_change);
    match core.run(rx_printer) {
        Ok(_) => unreachable!(),
        Err(()) => (),
    }

    // Check that the value was incremented.
    assert!(data_store_rc.borrow().as_ref().val == 124);
}

Structs

DataTracker

Tracks changes to data and notifies listeners.

Modifier

Allow viewing and modifying data owned by DataTracker.