calc_graph/
lib.rs

1#![deny(missing_docs)]
2#![deny(warnings)]
3
4//! Use this crate to split a calculation into related sub-calculations, known as nodes.
5//!
6//! You can push information from outside into one or more source nodes, and you can read results from one or more
7//! output nodes. Values are only calculated as they're needed, and cached as long as their inputs don't change. This
8//! means that recalculations are efficient when you only change some of the inputs, and if you don't request the value
9//! from an output node, its value is never calculated.
10//!
11//! # Example
12//! ```
13//! # use calc_graph::Graph;
14//! let graph = Graph::new();                        // create a Graph object
15//! let mut source = graph.source(42);               // define one or more nodes for your inputs
16//! let mut output = source.clone().map(|x| x + 1);  // build one or more nodes for your outputs
17//! assert_eq!(43, output.get_mut());                // read values from your output nodes
18//!
19//! source.set(99);                                  // push new values to the input nodes...
20//! assert_eq!(100, output.get_mut());               // ...and read the output nodes
21//! ```
22//!
23//! # Sharing
24//! Func nodes (created by `Node::map`, `Node::zip` and related methods) own their inputs (precedent nodes). When you
25//! have a node that acts as an input to two or more func nodes, you need to use `shared()`
26//! first. You can then use this shared node multiple times via `clone()`:
27//!
28//! ```
29//! let input_node = calc_graph::const_(42).shared();
30//! let mut output1_node = input_node.clone().map(|x| x + 1);
31//! let mut output2_node = input_node.map(|x| x * x);
32//! assert_eq!(43, output1_node.get_mut());
33//! assert_eq!(1764, output2_node.get_mut());
34//! ```
35//!
36//! You can have multiple `Graph` objects in the same program, but when you define a new node, its precedents must
37//! come from the same graph.
38//!
39//! # Boxing
40//! A `Node` object remembers the full type information of its precedent nodes as well as the closure used to calculate
41//! its value. This means that the name of the `Node` type can be very long, or even impossible to write in the source
42//! code. In this situation you can use:
43//!
44//! ```
45//! # use calc_graph::{BoxNode, Node, Func1};
46//! # let input_node = calc_graph::const_(0);
47//! let func_node: Node<Func1<_, i32, _>> = input_node.map(|x| x + 1);
48//! let output_node: BoxNode<i32> = func_node.boxed();
49//! ```
50//!
51//! A call to `boxed()` is also needed if you want a variable that can hold either one or another node; these nodes can
52//! have different concrete types, and calling `boxed()` on each of them gives you a pair of nodes that have the same
53//! type.
54//!
55//! # Threading
56//! `Node<Source>`, `SharedNode` and `BoxedNode` objects are `Send` and `Sync`, meaning they can be passed between
57//! threads. Calculations are performed on the thread that calls `node.get()`. Calculations are not parallelised
58//! automatically, although you can read separate output nodes from separate threads, even if they share parts of the
59//! same graph as inputs.
60//!
61//! ```
62//! # use calc_graph::Graph;
63//! # use std::sync::{Arc, Mutex};
64//! # use std::thread;
65//! let graph = Graph::new();
66//! let input_node = graph.source(41);
67//! let output_node = input_node.clone().map(|x| x * x).shared();
68//! assert_eq!(1681, output_node.get());
69//!
70//! let t = thread::spawn({
71//!     let input_node = input_node.clone();
72//!     let output_node = output_node.clone();
73//!     move || {
74//!         input_node.update(|n| {
75//!             *n += 1;
76//!             true
77//!         });
78//!
79//!         output_node.get()
80//!     }
81//! });
82//!
83//! assert_eq!(1764, t.join().unwrap());
84//!
85//! input_node.update(|n| {
86//!     *n += 1;
87//!     true
88//! });
89//!
90//! assert_eq!(1849, output_node.get());
91//! ```
92//!
93//! # `zip`, `zip_update` and others
94//! Use `zip()`, `map2()` and related functions to create a new node that calculates its value from a `FnMut` provided
95//! by you and the values from one or more other nodes. For large objects, recalculating these nodes can be
96//! inefficient, as your `FnMut` returns a fresh object every time, which is cloned wherever it is needed.
97//!
98//! For more efficiency you can use `zip_update()`, `map2_update()` and related functions. These work the same as their
99//! non-`update` equivalents, except that:
100//! 1. You provide the initial value of the new node when you create it
101//! 2. Your `FnMut` takes a `&mut T` as its first parameter. You update this value in place.
102//! 3. Your `FnMut` returns `true` if it changed value in the `&mut T`, or `false` otherwise. In turn, this determines
103//!     whether dependent nodes are recalculated.
104//!
105//! A useful technique for large objects is to put an `Arc<T>` in the node. When you recalculate the node, use
106//! `Arc::make_mut` to update the object in place where possible and avoid allocating a new `Arc`.
107//!
108//! ```
109//! # use calc_graph::Graph;
110//! # use std::sync::Arc;
111//! # let graph = Graph::new();
112//! let input_node = graph.source(42);
113//!
114//! let mut output_node = input_node.clone().map_update(Arc::new([0; 1024]), |big_array, x| {
115//!     let new_value = x * x;
116//!     let big_array_ref = Arc::make_mut(big_array);
117//!     if big_array_ref[0] == new_value {
118//!         false
119//!     } else {
120//!         big_array_ref[0] = new_value;
121//!         true
122//!     }
123//! });
124//!
125//! assert_eq!(1764, output_node.get_mut()[0]);
126//!
127//! input_node.update(|n| {
128//!     *n += 1;
129//!     true
130//! });
131//!
132//! assert_eq!(1849, output_node.get_mut()[0]);
133
134extern crate bit_set;
135extern crate either;
136extern crate parking_lot;
137extern crate take_mut;
138
139use std::num::NonZeroUsize;
140use std::ops::DerefMut;
141use std::sync::atomic::{AtomicUsize, Ordering};
142use std::sync::Arc;
143
144use bit_set::BitSet;
145use either::Either;
146use parking_lot::Mutex;
147
148/// Calculates a node's value.
149pub trait Calc {
150    /// The type of values calculated by the node.
151    type Value;
152
153    /// When this node is used as a precedent, `add_dep` is called by dependent nodes when they are created.
154    ///
155    /// Func nodes forward calls to `add_dep` to their precedents. Source nodes remember their dependencies so that they
156    /// can mark them dirty when the source node changes.
157    ///
158    /// # Arguments
159    /// * `seen` - A `BitSet` that can be used to skip a call to `add_dep` when this node is reachable from a dependency
160    ///     via multiple routes.
161    /// * `dep` - The id of the dependent node.
162    fn add_dep(&mut self, seen: &mut BitSet, dep: NonZeroUsize);
163
164    /// Returns the value held within the node and the version number of the inputs used to calcuate that value.
165    /// The value is recalculated if needed.
166    ///
167    /// To calculate a node as a function of some precedent nodes:
168    /// 1. On creation, each func node is assigned a numerical id. If this id is not contained within the `dirty` bitset,
169    ///     immediately return the cached version number and value. Otherwise, remove this id from the `dirty` bitset.
170    /// 2. Call `eval` on each of the precedent nodes and remember the version number and value returned by each precedent.
171    /// 3. Calculate `version = max(prec1_version, prec2_version, ...)`. If this version is lower than or equal to the
172    ///     cached version number, immediately return the cached version number and value.
173    /// 4. Calculate a new value for this node: `value = f(prec1_value, prec2_value, ...)`. Update the cache with the
174    ///     calculated `version` and the new `value`.
175    /// 5. Return `(version, value.clone())`.
176    ///
177    /// Returns a tuple containing:
178    /// - A `NonZeroUsize` version number indicating the highest version number of this node's precedents
179    /// - A `Clone` of the value calculated
180    ///
181    /// # Arguments
182    /// * `dirty` - A `BitSet` that indicates the nodes that were marked dirty due to an update to a `Node<Source>`.
183    fn eval(&mut self, dirty: &mut BitSet) -> (NonZeroUsize, Self::Value);
184}
185
186struct Counter(AtomicUsize);
187
188impl Counter {
189    pub fn new(first_value: NonZeroUsize) -> Self {
190        Counter(AtomicUsize::new(first_value.get()))
191    }
192
193    pub fn next(&self) -> NonZeroUsize {
194        let next = self.0.fetch_add(1, Ordering::SeqCst);
195        unsafe { NonZeroUsize::new_unchecked(next) }
196    }
197}
198
199struct GraphInner {
200    dirty: Mutex<BitSet>,
201    next_id: Counter,
202}
203
204const CONST_VERSION: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1) };
205const FIRST_VERSION: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(2) };
206const FIRST_ID: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1) };
207
208/// Represents a value within the graph.
209///
210/// Nodes can calculate their value automatically based on other nondes.
211#[derive(Clone)]
212pub struct Node<C> {
213    calc: C,
214    graph: Option<Arc<GraphInner>>,
215}
216
217/// Returns a node whose value never changes.
218pub fn const_<T>(value: T) -> Node<Const<T>> {
219    Node {
220        calc: Const(value),
221        graph: None,
222    }
223}
224
225/// Returns a node whose value is calculated once on demand and cached.
226pub fn lazy<T, F: FnOnce() -> T>(f: F) -> Node<Lazy<T, F>> {
227    Node {
228        calc: Lazy(Either::Right(f)),
229        graph: None,
230    }
231}
232
233fn with_graph<T>(graph: &Option<Arc<GraphInner>>, f: impl FnOnce(&mut BitSet) -> T) -> T {
234    let mut dirty = graph.as_ref().map(|graph| graph.dirty.lock());
235    let dirty = dirty.as_mut().map(DerefMut::deref_mut);
236    let mut no_dirty = BitSet::new();
237    f(dirty.unwrap_or(&mut no_dirty))
238}
239
240impl<C: Calc> Node<C>
241where
242    C::Value: Clone,
243{
244    /// Returns the node's value, recalculating it if needed.
245    pub fn get_mut(&mut self) -> C::Value {
246        let calc = &mut self.calc;
247        with_graph(&self.graph, move |dirty| calc.eval(dirty).1)
248    }
249}
250
251/// A node returned by `Node::shared`.
252pub type SharedNode<C> = Node<Arc<Mutex<C>>>;
253
254impl<C: Calc> Node<C> {
255    /// Wraps this node so that it can be used as an input to two or more dependent nodes.
256    pub fn shared(self) -> SharedNode<C> {
257        let calc = Arc::new(Mutex::new(self.calc));
258        Node {
259            calc,
260            graph: self.graph,
261        }
262    }
263}
264
265/// A node returned by `Node::boxed`.
266pub type BoxNode<T> = Node<Box<Calc<Value = T> + Send>>;
267
268impl<C: Calc + Send + 'static> Node<C> {
269    /// Wraps this node so that its `Calc` type is hidden.
270    ///
271    /// Boxing is needed when:
272    /// - you need to write the type of the node, but you can't write the name of the concrete `Calc` type (for instance,
273    ///     it's a func node involving a closure)
274    /// - you have a choice of types for a node (for instance, `if a { a_node.boxed() } else { b_node.boxed() }`)
275    pub fn boxed(self) -> BoxNode<C::Value> {
276        let calc: Box<Calc<Value = C::Value> + Send> = Box::new(self.calc);
277        Node {
278            calc,
279            graph: self.graph,
280        }
281    }
282}
283
284impl<C: Calc> SharedNode<C> {
285    /// Returns the shared node's value, recalculating it if needed.
286    pub fn get(&self) -> C::Value {
287        with_graph(&self.graph, move |dirty| self.calc.lock().eval(dirty).1)
288    }
289}
290
291impl<T: Clone> Node<Const<T>> {
292    /// Returns the const node's value.
293    pub fn get(&self) -> T {
294        self.calc.0.clone()
295    }
296}
297
298impl<T: Clone> Node<Source<T>> {
299    /// Returns the source node's value.
300    pub fn get(&self) -> T {
301        self.calc.inner.lock().value.clone()
302    }
303}
304
305impl<T> Node<Source<T>> {
306    /// Changes the value held within the source node based on the current value.
307    pub fn update(&self, updater: impl FnOnce(&mut T) -> bool) {
308        let mut dirty = self.graph.as_ref().unwrap().dirty.lock();
309        let mut inner = self.calc.inner.lock();
310        if !updater(&mut inner.value) {
311            return;
312        }
313
314        inner.version = self.calc.next_version.next();
315        dirty.union_with(&inner.deps);
316    }
317
318    /// Replaces the value held within the source node.
319    pub fn set(&self, value: T) {
320        self.update(move |value_cell| {
321            *value_cell = value;
322            true
323        })
324    }
325}
326
327fn alloc_id(graph: &Arc<GraphInner>) -> NonZeroUsize {
328    let id = graph.next_id.next();
329    graph.dirty.lock().insert(id.get());
330    id
331}
332
333struct SourceInner<T> {
334    version: NonZeroUsize,
335    value: T,
336    deps: BitSet,
337}
338
339/// Holds a value that can be updated directly from outside the graph.
340#[derive(Clone)]
341pub struct Source<T> {
342    inner: Arc<Mutex<SourceInner<T>>>,
343    next_version: Arc<Counter>,
344}
345
346impl<T: Clone> Calc for Source<T> {
347    type Value = T;
348
349    fn add_dep(&mut self, _seen: &mut BitSet, dep: NonZeroUsize) {
350        self.inner.lock().deps.insert(dep.get());
351    }
352
353    fn eval(&mut self, _dirty: &mut BitSet) -> (NonZeroUsize, T) {
354        let inner = self.inner.lock();
355        (inner.version, inner.value.clone())
356    }
357}
358
359/// Calculates a node's value by returning the same value every time.
360pub struct Const<T>(T);
361
362impl<T: Clone> Calc for Const<T> {
363    type Value = T;
364
365    fn add_dep(&mut self, _seen: &mut BitSet, _dep: NonZeroUsize) {}
366
367    fn eval(&mut self, _dirty: &mut BitSet) -> (NonZeroUsize, T) {
368        (CONST_VERSION, self.0.clone())
369    }
370}
371
372/// Calculates a node's value by calling a function on demand and caching the result.
373pub struct Lazy<T, F>(Either<T, F>);
374
375impl<T: Clone, F: FnOnce() -> T> Calc for Lazy<T, F> {
376    type Value = T;
377
378    fn add_dep(&mut self, _seen: &mut BitSet, _dep: NonZeroUsize) {}
379
380    fn eval(&mut self, _dirty: &mut BitSet) -> (NonZeroUsize, T) {
381        take_mut::take(&mut self.0, |value_or_f| match value_or_f {
382            Either::Left(value) => Either::Left(value),
383            Either::Right(f) => Either::Left(f()),
384        });
385
386        match &self.0 {
387            Either::Left(value) => (CONST_VERSION, value.clone()),
388            Either::Right(_) => unreachable!(),
389        }
390    }
391}
392
393/// Provides the opportunity to inspect a node's value without changing it.
394pub struct Inspect<C, F> {
395    f: F,
396    last_version: usize,
397    prec: C,
398}
399
400impl<C: Calc, F: FnMut(&C::Value)> Calc for Inspect<C, F> {
401    type Value = C::Value;
402
403    fn add_dep(&mut self, seen: &mut BitSet<u32>, dep: NonZeroUsize) {
404        self.prec.add_dep(seen, dep)
405    }
406
407    fn eval(&mut self, dirty: &mut BitSet<u32>) -> (NonZeroUsize, C::Value) {
408        let (version, value) = self.prec.eval(dirty);
409        if version.get() > self.last_version {
410            self.last_version = version.get();
411            (self.f)(&value);
412        }
413
414        (version, value)
415    }
416}
417
418impl<C: Calc> Node<C> {
419    /// Wraps the node with a function, whicih can inspect the node's value each time it is calculated.
420    pub fn inspect<F: FnMut(&C::Value)>(self, f: F) -> Node<Inspect<C, F>> {
421        Node {
422            calc: Inspect {
423                f,
424                last_version: 0,
425                prec: self.calc,
426            },
427            graph: self.graph,
428        }
429    }
430}
431
432fn eval_func<A, T: Clone + PartialEq>(
433    dirty: &mut BitSet,
434    id: Option<NonZeroUsize>,
435    value_cell: &mut Option<(NonZeroUsize, T)>,
436    f1: impl FnOnce(&mut BitSet) -> (NonZeroUsize, A),
437    f2: impl FnOnce(A) -> T,
438) -> (NonZeroUsize, T) {
439    if let Some(id) = id {
440        let id = id.get();
441        if dirty.contains(id) {
442            dirty.remove(id);
443        } else {
444            let (version, value) = value_cell.as_ref().unwrap();
445            return (*version, value.clone());
446        }
447    } else if let Some((version, value)) = value_cell.as_ref() {
448        return (*version, value.clone());
449    }
450
451    let (prec_version, precs) = f1(dirty);
452
453    if let Some((version, value)) = value_cell {
454        if prec_version > *version {
455            let new_value = f2(precs);
456
457            if new_value != *value {
458                *version = prec_version;
459                *value = new_value.clone();
460                return (prec_version, new_value);
461            }
462        }
463
464        (*version, value.clone())
465    } else {
466        let value = f2(precs);
467        *value_cell = Some((prec_version, value.clone()));
468        (prec_version, value)
469    }
470}
471
472fn eval_update<A, T: Clone>(
473    dirty: &mut BitSet,
474    id: Option<NonZeroUsize>,
475    version_cell: &mut Option<NonZeroUsize>,
476    value_cell: &mut T,
477    f1: impl FnOnce(&mut BitSet) -> (NonZeroUsize, A),
478    f2: impl FnOnce(&mut T, A) -> bool,
479) -> (NonZeroUsize, T) {
480    if let Some(id) = id {
481        let id = id.get();
482        if dirty.contains(id) {
483            dirty.remove(id);
484        } else {
485            let version = version_cell.as_ref().unwrap();
486            return (*version, value_cell.clone());
487        }
488    } else if let Some(version) = version_cell.as_ref() {
489        return (*version, value_cell.clone());
490    }
491
492    let (prec_version, precs) = f1(dirty);
493
494    if let Some(version) = version_cell {
495        if prec_version > *version {
496            if f2(value_cell, precs) {
497                *version = prec_version;
498                return (prec_version, value_cell.clone());
499            }
500        }
501
502        (*version, value_cell.clone())
503    } else {
504        f2(value_cell, precs);
505        *version_cell = Some(prec_version);
506        (prec_version, value_cell.clone())
507    }
508}
509
510/// Implements `Calc` for `SharedNode`.
511impl<C: Calc> Calc for Arc<Mutex<C>> {
512    type Value = C::Value;
513
514    fn add_dep(&mut self, seen: &mut BitSet, dep: NonZeroUsize) {
515        self.lock().add_dep(seen, dep)
516    }
517
518    fn eval(&mut self, dirty: &mut BitSet) -> (NonZeroUsize, C::Value) {
519        self.lock().eval(dirty)
520    }
521}
522
523/// Implements `Calc` for `BoxedNode`.
524impl<T> Calc for Box<Calc<Value = T> + Send> {
525    type Value = T;
526
527    fn add_dep(&mut self, seen: &mut BitSet, dep: NonZeroUsize) {
528        (**self).add_dep(seen, dep)
529    }
530
531    fn eval(&mut self, dirty: &mut BitSet) -> (NonZeroUsize, T) {
532        (**self).eval(dirty)
533    }
534}
535
536/// Returns new `Node<Source>` objects, which act as inputs to the rest of the graph.
537#[derive(Clone)]
538pub struct Graph {
539    inner: Arc<GraphInner>,
540    next_version: Arc<Counter>,
541}
542
543impl Graph {
544    /// Returns a new `Graph`.
545    pub fn new() -> Self {
546        Graph {
547            inner: Arc::new(GraphInner {
548                dirty: Mutex::new(BitSet::new()),
549                next_id: Counter::new(FIRST_ID),
550            }),
551            next_version: Arc::new(Counter::new(FIRST_VERSION)),
552        }
553    }
554
555    /// Defines a new `Node<Source>` containing an initial value.
556    pub fn source<T>(&self, value: T) -> Node<Source<T>> {
557        let version = self.next_version.next();
558
559        let inner = SourceInner {
560            deps: BitSet::new(),
561            version,
562            value,
563        };
564
565        let calc = Source {
566            inner: Arc::new(Mutex::new(inner)),
567            next_version: self.next_version.clone(),
568        };
569
570        Node {
571            calc,
572            graph: Some(self.inner.clone()),
573        }
574    }
575}
576
577include!(concat!(env!("OUT_DIR"), "/funcs.rs"));
578
579#[test]
580fn test_nodes_are_send_sync() {
581    fn assert_send_sync<T: Send + Sync>(value: T) -> T {
582        value
583    }
584
585    let graph = assert_send_sync(Graph::new());
586    let c = const_("const");
587    let l = lazy(|| "lazy");
588    let s = assert_send_sync(graph.source("source".to_owned()));
589
590    let mut m = assert_send_sync(Node::zip3(c, l, s.clone(), |a, b, c| {
591        format!("{a} {b} {c}", a = a, b = b, c = c)
592    }));
593
594    assert_eq!("const lazy source", m.get_mut());
595
596    s.update(|text| {
597        *text += "2";
598        true
599    });
600
601    assert_eq!("const lazy source2", m.get_mut());
602}
603
604#[test]
605fn test_source() {
606    let graph = Graph::new();
607    let source = graph.source(1);
608    assert_eq!(1, source.get());
609
610    source.set(2);
611
612    assert_eq!(2, source.get());
613}
614
615#[test]
616fn test_const() {
617    let c = const_("hello");
618
619    assert_eq!("hello", c.get());
620}
621
622#[test]
623fn test_lazy() {
624    let mut lazy1 = lazy(|| "hello");
625    let _lazy2 = lazy(|| unreachable!());
626
627    assert_eq!("hello", lazy1.get_mut());
628}
629
630#[test]
631fn test_inspect() {
632    let graph = Graph::new();
633    let source = graph.source(1);
634    let inspect_count = AtomicUsize::new(0);
635
636    let mut map = source.clone().map(|n| n * n).inspect(|_| {
637        inspect_count.fetch_add(1, Ordering::SeqCst);
638    });
639
640    assert_eq!(0, inspect_count.load(Ordering::SeqCst));
641
642    assert_eq!(1, map.get_mut());
643    assert_eq!(1, inspect_count.load(Ordering::SeqCst));
644
645    source.set(2);
646    assert_eq!(1, inspect_count.load(Ordering::SeqCst));
647
648    assert_eq!(4, map.get_mut());
649    assert_eq!(2, inspect_count.load(Ordering::SeqCst));
650
651    source.set(2);
652    assert_eq!(2, inspect_count.load(Ordering::SeqCst));
653
654    assert_eq!(4, map.get_mut());
655    assert_eq!(2, inspect_count.load(Ordering::SeqCst));
656}
657
658#[test]
659fn test_map() {
660    let graph = Graph::new();
661    let source = graph.source(1);
662    let c = const_(2);
663    let map1 = source.clone().zip(c, |n, c| n * c);
664    let mut map2 = map1.map(|m| -m);
665
666    assert_eq!(-2, map2.get_mut());
667
668    source.set(2);
669
670    assert_eq!(-4, map2.get_mut());
671}
672
673#[test]
674fn test_map_cache() {
675    let graph = Graph::new();
676    let source = graph.source("hello");
677    let c = const_::<usize>(1);
678    let calc_count1 = AtomicUsize::new(0);
679    let calc_count2 = AtomicUsize::new(0);
680    let calc_count3 = AtomicUsize::new(0);
681
682    let calc_counts = || {
683        (
684            calc_count1.load(Ordering::SeqCst),
685            calc_count2.load(Ordering::SeqCst),
686            calc_count3.load(Ordering::SeqCst),
687        )
688    };
689
690    let map1 = source
691        .clone()
692        .map(|s| {
693            calc_count1.fetch_add(1, Ordering::SeqCst);
694            s.len()
695        })
696        .shared();
697
698    let map2 = Node::zip(source.clone(), c, |s, c| {
699        calc_count2.fetch_add(1, Ordering::SeqCst);
700        s.as_bytes()[c] as usize
701    });
702
703    let mut map3 = Node::zip3(map1.clone(), map2, map1, |x, y, z| {
704        calc_count3.fetch_add(1, Ordering::SeqCst);
705        x + y + z
706    });
707
708    assert_eq!((0, 0, 0), calc_counts());
709
710    assert_eq!(111, map3.get_mut());
711    assert_eq!((1, 1, 1), calc_counts());
712
713    source.set("jello");
714
715    assert_eq!(111, map3.get_mut());
716    assert_eq!((2, 2, 1), calc_counts());
717
718    source.set("jollo");
719
720    assert_eq!(121, map3.get_mut());
721    assert_eq!((3, 3, 2), calc_counts());
722}
723
724#[test]
725fn test_map_lazy() {
726    let graph = Graph::new();
727    let source = graph.source(1);
728    let _map = source.clone().map(|_| unreachable!());
729
730    assert_eq!(1, source.get());
731
732    source.set(2);
733
734    assert_eq!(2, source.get());
735}