#![warn(bare_trait_objects)]
mod traits;
pub mod binding_context;
mod binding;
mod computed;
mod bindref;
mod notify_fn;
pub mod releasable;
mod watcher;
mod map_binding;
mod ext;
#[cfg(feature = "stream")]
mod follow;
#[cfg(feature = "stream")]
mod bind_stream;
#[cfg(feature = "rope")]
mod rope_binding;
pub use self::traits::*;
pub use self::binding::*;
pub use self::computed::*;
pub use self::bindref::*;
pub use self::notify_fn::*;
pub use self::watcher::*;
pub use self::map_binding::*;
#[cfg(feature = "stream")]
pub use self::follow::*;
#[cfg(feature = "stream")]
pub use self::bind_stream::*;
#[cfg(feature = "rope")]
pub use self::rope_binding::*;
pub fn bind<Value>(val: Value) -> Binding<Value>
where
Value: Clone + PartialEq,
{
Binding::new(val)
}
pub fn computed<Value, TFn>(calculate_value: TFn) -> ComputedBinding<Value, TFn>
where
Value: Clone + Send,
TFn: 'static + Send + Sync + Fn() -> Value,
{
ComputedBinding::new(calculate_value)
}
#[cfg(test)]
mod test {
use super::*;
use super::binding_context::*;
use std::thread;
use std::sync::*;
use std::time::Duration;
#[test]
fn can_create_binding() {
let bound = bind(1);
assert!(bound.get() == 1);
}
#[test]
fn can_update_binding() {
let bound = bind(1);
bound.set(2);
assert!(bound.get() == 2);
}
#[test]
fn notified_on_change() {
let bound = bind(1);
let changed = bind(false);
let notify_changed = changed.clone();
bound.when_changed(notify(move || notify_changed.set(true))).keep_alive();
assert!(changed.get() == false);
bound.set(2);
assert!(changed.get() == true);
}
#[test]
fn not_notified_on_no_change() {
let bound = bind(1);
let changed = bind(false);
let notify_changed = changed.clone();
bound.when_changed(notify(move || notify_changed.set(true))).keep_alive();
assert!(changed.get() == false);
bound.set(1);
assert!(changed.get() == false);
}
#[test]
fn notifies_after_each_change() {
let bound = bind(1);
let change_count = bind(0);
let notify_count = change_count.clone();
bound.when_changed(notify(move || { let count = notify_count.get(); notify_count.set(count+1) })).keep_alive();
assert!(change_count.get() == 0);
bound.set(2);
assert!(change_count.get() == 1);
bound.set(3);
assert!(change_count.get() == 2);
bound.set(4);
assert!(change_count.get() == 3);
}
#[test]
fn watcher_notifies_after_get() {
let bound = bind(1);
let change_count = bind(0);
let notify_count = change_count.clone();
let watcher = bound.watch(notify(move || { let count = notify_count.get(); notify_count.set(count+1) }));
assert!(change_count.get() == 0);
bound.set(2);
assert!(change_count.get() == 0);
watcher.get();
bound.set(3);
assert!(change_count.get() == 1);
bound.set(4);
assert!(change_count.get() == 1);
bound.set(5);
assert!(change_count.get() == 1);
watcher.get();
bound.set(6);
assert!(change_count.get() == 2);
}
#[test]
fn watcher_does_not_notify_for_other_watchers() {
let bound = bind(1);
let change_count = bind(0);
let notify_count = change_count.clone();
let watcher = bound.watch(notify(move || { let count = notify_count.get(); notify_count.set(count+1) }));
let other_watcher = bound.watch(notify(move || { }));
assert!(change_count.get() == 0);
bound.set(2);
assert!(change_count.get() == 0);
other_watcher.get();
bound.set(3);
assert!(change_count.get() == 0);
bound.set(4);
assert!(change_count.get() == 0);
bound.set(5);
assert!(change_count.get() == 0);
watcher.get();
bound.set(6);
assert!(change_count.get() == 1);
}
#[test]
fn dispatches_multiple_notifications() {
let bound = bind(1);
let change_count = bind(0);
let notify_count = change_count.clone();
let notify_count2 = change_count.clone();
bound.when_changed(notify(move || { let count = notify_count.get(); notify_count.set(count+1) })).keep_alive();
bound.when_changed(notify(move || { let count = notify_count2.get(); notify_count2.set(count+1) })).keep_alive();
assert!(change_count.get() == 0);
bound.set(2);
assert!(change_count.get() == 2);
bound.set(3);
assert!(change_count.get() == 4);
bound.set(4);
assert!(change_count.get() == 6);
}
#[test]
fn stops_notifying_after_release() {
let bound = bind(1);
let change_count = bind(0);
let notify_count = change_count.clone();
let mut lifetime = bound.when_changed(notify(move || { let count = notify_count.get(); notify_count.set(count+1) }));
assert!(change_count.get() == 0);
bound.set(2);
assert!(change_count.get() == 1);
lifetime.done();
assert!(change_count.get() == 1);
bound.set(3);
assert!(change_count.get() == 1);
}
#[test]
fn release_only_affects_one_notification() {
let bound = bind(1);
let change_count = bind(0);
let notify_count = change_count.clone();
let notify_count2 = change_count.clone();
let mut lifetime = bound.when_changed(notify(move || { let count = notify_count.get(); notify_count.set(count+1) }));
bound.when_changed(notify(move || { let count = notify_count2.get(); notify_count2.set(count+1) })).keep_alive();
assert!(change_count.get() == 0);
bound.set(2);
assert!(change_count.get() == 2);
bound.set(3);
assert!(change_count.get() == 4);
bound.set(4);
assert!(change_count.get() == 6);
lifetime.done();
bound.set(5);
assert!(change_count.get() == 7);
bound.set(6);
assert!(change_count.get() == 8);
bound.set(7);
assert!(change_count.get() == 9);
}
#[test]
fn binding_context_is_notified() {
let bound = bind(1);
bound.set(2);
let (value, context) = BindingContext::bind(|| bound.get());
assert!(value == 2);
let changed = bind(false);
let notify_changed = changed.clone();
context.when_changed(notify(move || notify_changed.set(true))).keep_alive();
assert!(changed.get() == false);
bound.set(3);
assert!(changed.get() == true);
}
#[test]
fn can_compute_value() {
let bound = bind(1);
let computed_from = bound.clone();
let computed = computed(move || computed_from.get() + 1);
assert!(computed.get() == 2);
}
#[test]
fn can_recompute_value() {
let bound = bind(1);
let computed_from = bound.clone();
let computed = computed(move || computed_from.get() + 1);
assert!(computed.get() == 2);
bound.set(2);
assert!(computed.get() == 3);
bound.set(3);
assert!(computed.get() == 4);
}
#[test]
fn map_binding_value() {
let bound = bind(1);
let mapped = bound.map_binding(|val| val + 1);
assert!(mapped.get() == 2);
bound.set(2);
assert!(mapped.get() == 3);
bound.set(3);
assert!(mapped.get() == 4);
}
#[test]
fn watch_mapped_bindings() {
let bound = bind(1);
let mapped = bound.map_binding(|val| val + 1);
let change_count = bind(0);
let change_counter = change_count.clone();
let watcher = mapped.watch(notify(move || change_counter.set(change_counter.get() + 1)));
assert!(watcher.get() == 2);
bound.set(2);
assert!(watcher.get() == 3);
assert!(change_count.get() == 1);
bound.set(3);
assert!(watcher.get() == 4);
assert!(change_count.get() == 2);
}
#[test]
fn can_recursively_compute_values() {
let bound = bind(1);
let computed_from = bound.clone();
let computed_val = computed(move || computed_from.get() + 1);
let more_computed_from = computed_val.clone();
let more_computed = computed(move || more_computed_from.get() + 1);
assert!(computed_val.get() == 2);
assert!(more_computed.get() == 3);
bound.set(2);
assert!(computed_val.get() == 3);
assert!(more_computed.get() == 4);
bound.set(3);
assert!(computed_val.get() == 4);
assert!(more_computed.get() == 5);
}
#[test]
fn can_recursively_compute_values_2() {
let bound = bind(1);
let computed_from = bound.clone();
let computed_val = computed(move || computed_from.get() + 1);
let more_computed = computed(move || computed_val.get() + 1);
assert!(more_computed.get() == 3);
bound.set(2);
assert!(more_computed.get() == 4);
bound.set(3);
assert!(more_computed.get() == 5);
}
#[test]
fn can_recursively_compute_values_3() {
let bound = bind(1);
let computed_from = bound.clone();
let computed_val = computed(move || computed_from.get() + 1);
let more_computed = computed(move || computed_val.get() + 1);
let even_more_computed = computed(move || more_computed.get() + 1);
assert!(even_more_computed.get() == 4);
bound.set(2);
assert!(even_more_computed.get() == 5);
bound.set(3);
assert!(even_more_computed.get() == 6);
}
#[test]
#[should_panic]
fn panics_if_computed_generated_during_binding() {
let bound = bind(1);
let computed_from = bound.clone();
let computed_val = computed(move || computed_from.get() + 1);
let even_more_computed = computed(move || {
let computed_val = computed_val.clone();
let more_computed = computed(move || computed_val.get() + 1);
more_computed.get() + 1
});
assert!(even_more_computed.get() == 4);
bound.set(2);
assert!(even_more_computed.get() == 5);
bound.set(3);
assert!(even_more_computed.get() == 6);
}
#[test]
fn computed_only_recomputes_as_needed() {
let bound = bind(1);
let counter = Arc::new(Mutex::new(0));
let compute_counter = counter.clone();
let computed_from = bound.clone();
let computed = computed(move || {
let mut counter = compute_counter.lock().unwrap();
*counter = *counter + 1;
computed_from.get() + 1
});
assert!(computed.get() == 2);
{
let counter = counter.lock().unwrap();
assert!(counter.clone() == 1);
}
assert!(computed.get() == 2);
{
let counter = counter.lock().unwrap();
assert!(counter.clone() == 1);
}
bound.set(2);
assert!(computed.get() == 3);
{
let counter = counter.lock().unwrap();
assert!(counter.clone() == 2);
}
}
#[test]
fn computed_caches_values() {
let update_count = Arc::new(Mutex::new(0));
let bound = bind(1);
let computed_update_count = Arc::clone(&update_count);
let computed_from = bound.clone();
let computed = computed(move || {
let mut computed_update_count = computed_update_count.lock().unwrap();
*computed_update_count += 1;
computed_from.get() + 1
});
assert!(computed.get() == 2);
assert!(*update_count.lock().unwrap() == 1);
assert!(computed.get() == 2);
assert!(*update_count.lock().unwrap() == 1);
bound.set(2);
assert!(computed.get() == 3);
assert!(*update_count.lock().unwrap() == 2);
bound.set(3);
assert!(*update_count.lock().unwrap() == 2);
assert!(computed.get() == 4);
assert!(*update_count.lock().unwrap() == 3);
}
#[test]
fn computed_notifies_of_changes() {
let bound = bind(1);
let computed_from = bound.clone();
let computed = computed(move || computed_from.get() + 1);
let changed = bind(false);
let notify_changed = changed.clone();
computed.when_changed(notify(move || notify_changed.set(true))).keep_alive();
assert!(computed.get() == 2);
assert!(changed.get() == false);
bound.set(2);
assert!(changed.get() == true);
assert!(computed.get() == 3);
changed.set(false);
bound.set(3);
assert!(changed.get() == true);
assert!(computed.get() == 4);
}
#[test]
fn computed_watcher() {
let bound = bind(1);
let computed_from = bound.clone();
let computed = computed(move || computed_from.get() + 1);
let changed = bind(false);
let notify_changed = changed.clone();
let watcher = computed.watch(notify(move || notify_changed.set(true)));
assert!(watcher.get() == 2);
assert!(changed.get() == false);
bound.set(2);
assert!(changed.get() == true);
assert!(watcher.get() == 3);
changed.set(false);
bound.set(3);
assert!(changed.get() == true);
assert!(watcher.get() == 4);
}
#[test]
fn computed_switches_dependencies() {
let switch = bind(false);
let val1 = bind(1);
let val2 = bind(2);
let computed_switch = switch.clone();
let computed_val1 = val1.clone();
let computed_val2 = val2.clone();
let computed = computed(move || {
if computed_switch.get() {
computed_val2.get() + 1
} else {
computed_val1.get() + 1
}
});
let changed = bind(false);
let notify_changed = changed.clone();
computed.when_changed(notify(move || notify_changed.set(true))).keep_alive();
assert!(computed.get() == 2);
assert!(changed.get() == false);
val2.set(3);
assert!(changed.get() == false);
assert!(computed.get() == 2);
val1.set(2);
assert!(changed.get() == true);
assert!(computed.get() == 3);
changed.set(false);
switch.set(true);
assert!(changed.get() == true);
assert!(computed.get() == 4);
changed.set(false);
val2.set(4);
assert!(changed.get() == true);
assert!(computed.get() == 5);
changed.set(false);
val1.set(5);
assert!(changed.get() == false);
assert!(computed.get() == 5);
}
#[test]
fn change_during_computation_recomputes() {
let some_binding = bind(1);
let some_computed = {
let some_binding = some_binding.clone();
computed(move || {
let result = some_binding.get() + 1;
thread::sleep(Duration::from_millis(250));
result
})
};
{
let some_computed = some_computed.clone();
thread::spawn(move || {
assert!(some_computed.get() == 2);
});
}
thread::sleep(Duration::from_millis(10));
some_binding.set(2);
assert!(some_computed.get() == 3);
}
#[test]
fn computed_propagates_changes() {
let bound = bind(1);
let computed_from = bound.clone();
let propagates_from = computed(move || computed_from.get() + 1);
let computed_propagated = propagates_from.clone();
let computed = computed(move || computed_propagated.get() + 1);
let changed = bind(false);
let notify_changed = changed.clone();
computed.when_changed(notify(move || notify_changed.set(true))).keep_alive();
assert!(propagates_from.get() == 2);
assert!(computed.get() == 3);
assert!(changed.get() == false);
bound.set(2);
assert!(propagates_from.get() == 3);
assert!(computed.get() == 4);
assert!(changed.get() == true);
changed.set(false);
bound.set(3);
assert!(changed.get() == true);
assert!(propagates_from.get() == 4);
assert!(computed.get() == 5);
}
#[test]
fn computed_stops_notifying_when_released() {
let bound = bind(1);
let computed_from = bound.clone();
let computed = computed(move || computed_from.get() + 1);
let changed = bind(false);
let notify_changed = changed.clone();
let mut lifetime = computed.when_changed(notify(move || notify_changed.set(true)));
assert!(computed.get() == 2);
assert!(changed.get() == false);
bound.set(2);
assert!(changed.get() == true);
assert!(computed.get() == 3);
changed.set(false);
lifetime.done();
bound.set(3);
assert!(changed.get() == false);
assert!(computed.get() == 4);
bound.set(4);
assert!(changed.get() == false);
assert!(computed.get() == 5);
}
#[test]
fn computed_doesnt_notify_more_than_once() {
let bound = bind(1);
let computed_from = bound.clone();
let computed = computed(move || computed_from.get() + 1);
let changed = bind(false);
let notify_changed = changed.clone();
computed.when_changed(notify(move || notify_changed.set(true))).keep_alive();
assert!(computed.get() == 2);
assert!(changed.get() == false);
bound.set(2);
assert!(changed.get() == true);
changed.set(false);
bound.set(3);
assert!(changed.get() == false);
assert!(computed.get() == 4);
bound.set(4);
assert!(changed.get() == true);
}
#[test]
fn computed_stops_notifying_once_out_of_scope() {
let bound = bind(1);
let changed = bind(false);
{
let computed_from = bound.clone();
let computed = computed(move || computed_from.get() + 1);
let notify_changed = changed.clone();
computed.when_changed(notify(move || notify_changed.set(true))).keep_alive();
assert!(computed.get() == 2);
assert!(changed.get() == false);
bound.set(2);
assert!(changed.get() == true);
assert!(computed.get() == 3);
};
changed.set(false);
bound.set(3);
assert!(changed.get() == false);
}
}