data_tracker/
lib.rs

1// Copyright 2017 Andrew D. Straw.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Track changes to data and notify listeners.
10//!
11//! The main API feature is the [`DataTracker`](./struct.DataTracker.html) struct, which
12//! takes ownership of a value.
13//!
14//! The principle of operation is that
15//! [`DataTracker::as_tracked_mut()`](./struct.DataTracker.html#method.as_tracked_mut)
16//! returns a mutable [`Modifier`](./struct.Modifier.html).
17//! `Modifier` has two key properties:
18//!
19//! - It implements the `DerefMut` trait and allows ergonomic access to the
20//! underlying data.
21//! - It implements the `Drop` trait which checks if the underlying
22//! data was changed and, if so, notifies the listeners.
23//!
24//! Futhermore, [`DataTracker::as_ref()`](./struct.DataTracker.html#method.as_ref)
25//! returns a (non-mutable) reference to the data for cases when only
26//! read-only access to the data is needed.
27//!
28//! To implement tracking, when [`Modifier`](./struct.Modifier.html) is created,
29//! a copy of the original data is made and when `Modifier` is dropped, an equality
30//! check is performed. If the original and the new data are not equal, the callbacks
31//! are called with references to the old and the new values.
32//!
33//! ```
34//! use data_tracker::DataTracker;
35//!
36//! // Create a new type to be tracked. Must implement Clone and PartialEq.
37//! #[derive(Debug, Clone, PartialEq)]
38//! struct MyData {
39//!     a: u8,
40//! }
41//!
42//! // Create some data.
43//! let data = MyData {a: 1};
44//!
45//! // Transfer ownership of data to the tracker.
46//! let mut tracked_data = DataTracker::new(data);
47//!
48//! // Register a listener which is called when a data change is noticed.
49//! let key = 0; // Keep the key to remove the callback later.
50//!
51//! // At the time or writing, the rust compiler could not infer the type
52//! // of the closure and therefore I needed to annotate the argument types.
53//! tracked_data.add_listener(key, Box::new(|old_value: &MyData, new_value: &MyData| {
54//!     println!("changed {:?} -> {:?}", old_value, new_value);
55//! }));
56//!
57//! {
58//!     // Create x, a (non-mutable) reference to original data.
59//!     let x = tracked_data.as_ref();
60//!     println!("x.a: {}",x.a);
61//! }
62//!
63//! {
64//!     // Create x, which allows modifying the original data and checks for changes when
65//!     // it goes out of scope.
66//!     let mut x = tracked_data.as_tracked_mut();
67//!     x.a = 10;
68//!     println!("x.a: {}",x.a);
69//!     // When we leave this scope, changes are detected and sent to the listeners.
70//! }
71//!
72//! // Remove our callback.
73//! tracked_data.remove_listener(&key);
74//! ```
75
76use std::collections::HashMap;
77use std::hash::Hash;
78use std::cmp::Eq;
79
80/// Trait defining change notification callback function.
81#[cfg(not(feature = "no_send"))]
82pub trait OnChanged<T>: Send {
83    fn on_changed(&self, old_value: &T, new_value: &T) -> ();
84}
85
86#[cfg(feature = "no_send")]
87pub trait OnChanged<T> {
88    fn on_changed(&self, old_value: &T, new_value: &T) -> ();
89}
90
91#[cfg(not(feature = "no_send"))]
92impl<F, T> OnChanged<T> for F
93    where F: Fn(&T, &T) -> () + Send
94{
95    fn on_changed(&self, old_value: &T, new_value: &T) -> () {
96        self(old_value, new_value)
97    }
98}
99
100#[cfg(feature = "no_send")]
101impl<F, T> OnChanged<T> for F
102    where F: Fn(&T, &T) -> ()
103{
104    fn on_changed(&self, old_value: &T, new_value: &T) -> () {
105        self(old_value, new_value)
106    }
107}
108
109struct Inner<T, K>
110    where T: Clone + PartialEq,
111          K: Hash + Eq
112{
113    value: T,
114    fn_map: HashMap<K, Box<OnChanged<T>>>,
115}
116
117impl<T, K> Inner<T, K>
118    where T: Clone + PartialEq,
119          K: Hash + Eq
120{
121    fn add_listener(&mut self, key: K, f: Box<OnChanged<T>>) -> Option<Box<OnChanged<T>>> {
122        self.fn_map.insert(key, f)
123    }
124    fn remove_listener(&mut self, key: &K) -> Option<Box<OnChanged<T>>> {
125        self.fn_map.remove(key)
126    }
127    fn notify_listeners(&self, modifier: &Modifier<T, K>) {
128        let orig_value = &modifier.orig_copy;
129        let new_value: &T = modifier;
130        for on_changed_obj in self.fn_map.values() {
131            on_changed_obj.on_changed(orig_value, new_value);
132        }
133    }
134}
135
136/// Allow viewing and modifying data owned by `DataTracker`.
137///
138/// Create an instance of this by calling
139/// [`DataTracker::as_tracked_mut()`](./struct.DataTracker.html#method.as_tracked_mut).
140pub struct Modifier<'a, T, K>
141    where T: 'a + Clone + PartialEq,
142          K: 'a + Hash + Eq
143{
144    orig_copy: T,
145    inner_ref: &'a mut Inner<T, K>,
146}
147
148impl<'a, T, K> Modifier<'a, T, K>
149    where T: 'a + Clone + PartialEq,
150          K: 'a + Hash + Eq
151{
152    fn new(inner: &'a mut Inner<T, K>) -> Modifier<'a, T, K> {
153        let orig_copy: T = inner.value.clone();
154        Modifier {
155            orig_copy: orig_copy,
156            inner_ref: inner,
157        }
158    }
159}
160
161impl<'a, T, K> std::ops::Deref for Modifier<'a, T, K>
162    where T: 'a + Clone + PartialEq,
163          K: 'a + Hash + Eq
164{
165    type Target = T;
166
167    fn deref(&self) -> &T {
168        &self.inner_ref.value
169    }
170}
171
172impl<'a, T, K> std::ops::DerefMut for Modifier<'a, T, K>
173    where T: 'a + Clone + PartialEq,
174          K: 'a + Hash + Eq
175{
176    fn deref_mut(&mut self) -> &mut T {
177        &mut self.inner_ref.value
178    }
179}
180
181impl<'a, T, K> Drop for Modifier<'a, T, K>
182    where T: 'a + Clone + PartialEq,
183          K: 'a + Hash + Eq
184{
185    fn drop(&mut self) {
186        if self.orig_copy != self.inner_ref.value {
187            self.inner_ref.notify_listeners(self);
188        }
189    }
190}
191
192/// Tracks changes to data and notifies listeners.
193///
194/// The data to be tracked is type `T`.
195///
196/// Callbacks are stored in a `HashMap` with keys of type `K`.
197///
198/// See the [module-level documentation](./) for more details.
199pub struct DataTracker<T, K>
200    where T: Clone + PartialEq,
201          K: Hash + Eq
202{
203    inner: Inner<T, K>,
204}
205
206impl<T, K> DataTracker<T, K>
207    where T: Clone + PartialEq,
208          K: Hash + Eq
209{
210    /// Create a new `DataTracker` which takes ownership
211    /// of the data of type `T`.
212    ///
213    /// Callbacks are registered via a key of type `K`.
214    pub fn new(value: T) -> DataTracker<T, K> {
215        DataTracker {
216            inner: Inner {
217                value: value,
218                fn_map: HashMap::new(),
219            },
220        }
221    }
222
223    /// Add a callback that will be called just after a data change is detected.
224    ///
225    /// If a previous callback exists with the `key`, the original callback is
226    /// returned as `Some(original_callback)`. Otherwise, `None` is returned.
227    pub fn add_listener(&mut self,
228                        key: K,
229                        callback: Box<OnChanged<T>>)
230                        -> Option<Box<OnChanged<T>>> {
231        self.inner.add_listener(key, callback)
232    }
233
234    /// Remove callback.
235    ///
236    /// If a callback exists with the `key`, it is removed and returned as
237    /// `Some(callback)`. Otherwise, `None` is returned.
238    pub fn remove_listener(&mut self, key: &K) -> Option<Box<OnChanged<T>>> {
239        self.inner.remove_listener(key)
240    }
241
242    /// Return a `Modifier` which can be used to modify the owned data.
243    pub fn as_tracked_mut(&mut self) -> Modifier<T, K> {
244        Modifier::new(&mut self.inner)
245    }
246}
247
248impl<T, K> AsRef<T> for DataTracker<T, K>
249    where T: Clone + PartialEq,
250          K: Hash + Eq
251{
252    fn as_ref(&self) -> &T {
253        &self.inner.value
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use std::sync::{Arc, Mutex};
260    use super::DataTracker;
261
262    #[test]
263    fn track_struct() {
264        #[derive(Clone, PartialEq)]
265        struct MyData {
266            a: u8,
267        }
268
269        let change_count = Arc::new(Mutex::new(0));
270        let mut tracked_data = DataTracker::new(MyData { a: 1 });
271
272        let cc2 = change_count.clone();
273        let key = 0;
274        tracked_data.add_listener(key,
275                                  Box::new(move |_: &MyData, _: &MyData| {
276                                      let ref mut data = *cc2.lock().unwrap();
277                                      *data = *data + 1;
278                                  }));
279
280        assert!(*change_count.lock().unwrap() == 0);
281
282        {
283            let _x = tracked_data.as_ref();
284        }
285        assert!(*change_count.lock().unwrap() == 0);
286
287        {
288            let mut x = tracked_data.as_tracked_mut();
289            x.a = 10;
290        }
291        assert!(*change_count.lock().unwrap() == 1);
292
293        {
294            let mut x = tracked_data.as_tracked_mut();
295            x.a += 10;
296        }
297        assert!(*change_count.lock().unwrap() == 2);
298
299        assert!(tracked_data.as_ref().a == 20);
300        assert!(*change_count.lock().unwrap() == 2);
301
302        tracked_data.remove_listener(&key);
303    }
304
305    #[test]
306    fn track_enum() {
307
308        #[derive(Clone, PartialEq)]
309        enum MyEnum {
310            FirstValue,
311            SecondValue,
312        }
313
314        let change_count = Arc::new(Mutex::new(0));
315        let mut tracked_data = DataTracker::new(MyEnum::FirstValue);
316
317        let cc2 = change_count.clone();
318        let key = 0;
319        tracked_data.add_listener(key,
320                                  Box::new(move |_: &MyEnum, _: &MyEnum| {
321                                      let ref mut data = *cc2.lock().unwrap();
322                                      *data = *data + 1;
323                                  }));
324
325        assert!(*change_count.lock().unwrap() == 0);
326
327        {
328            let _x = tracked_data.as_ref();
329        }
330        assert!(*change_count.lock().unwrap() == 0);
331
332        {
333            let mut x = tracked_data.as_tracked_mut();
334            *x = MyEnum::SecondValue;
335        }
336        assert!(*change_count.lock().unwrap() == 1);
337
338        assert!(tracked_data.as_ref() == &MyEnum::SecondValue);
339        assert!(*change_count.lock().unwrap() == 1);
340
341        tracked_data.remove_listener(&key);
342    }
343
344    #[test]
345    fn callback_arg_order() {
346
347        #[derive(Clone, PartialEq)]
348        enum MyEnum {
349            FirstValue,
350            SecondValue,
351        }
352
353        let mut tracked_data = DataTracker::new(MyEnum::FirstValue);
354
355        let did_run = Arc::new(Mutex::new(false));
356        let did_run_clone = did_run.clone();
357
358        tracked_data.add_listener(0,
359                                  Box::new(move |old_value: &MyEnum, new_value: &MyEnum| {
360                                      assert!(old_value == &MyEnum::FirstValue);
361                                      assert!(new_value == &MyEnum::SecondValue);
362                                      let ref mut data = *did_run_clone.lock().unwrap();
363                                      *data = true;
364                                  }));
365        {
366            let mut x = tracked_data.as_tracked_mut();
367            *x = MyEnum::SecondValue;
368        }
369
370        assert!(*did_run.lock().unwrap() == true);
371    }
372
373    // Test that instances of DataTracker implement Send, at least if
374    // the owned data type T implements Send.
375    #[cfg(not(feature = "no_send"))]
376    #[test]
377    fn track_send_impl() {
378
379        #[derive(Clone, PartialEq)]
380        struct MyStruct {
381            a: i32,
382        }
383
384        let mut tracked_data = DataTracker::new(MyStruct { a: 42 });
385        tracked_data.add_listener(0,
386                                  Box::new(move |_old_value: &MyStruct, _new_value: &MyStruct| {
387                                  }));
388
389        ::std::thread::spawn(move || {
390            let mut x = tracked_data.as_tracked_mut();
391            (*x).a = 123;
392        });
393    }
394
395}