1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use super::traits::*;
use super::notify_fn::*;

use std::rc::*;
use std::sync::*;
use std::cell::*;

thread_local! {
    static CURRENT_CONTEXT: RefCell<Option<BindingContext>> = RefCell::new(None);
}

///
/// Represents the dependencies of a binding context
///
#[derive(Clone)]
pub struct BindingDependencies {
    /// Set to true if the binding dependencies have been changed since they were registered in the dependencies
    recently_changed: Arc<Mutex<bool>>,

    /// The when_changed monitors for the recently_changed flag
    recent_change_monitors: Rc<RefCell<Vec<Box<dyn Releasable>>>>,

    /// The list of changables that are dependent on this context
    dependencies: Rc<RefCell<Vec<Box<dyn Changeable>>>>
}

impl BindingDependencies {
    ///
    /// Creates a new binding dependencies object
    ///
    pub fn new() -> BindingDependencies {
        BindingDependencies {
            recently_changed:       Arc::new(Mutex::new(false)),
            recent_change_monitors: Rc::new(RefCell::new(vec![])),
            dependencies:           Rc::new(RefCell::new(vec![]))
        }
    }

    ///
    /// Adds a new dependency to this object
    ///
    pub fn add_dependency<TChangeable: Changeable+'static>(&mut self, dependency: TChangeable) {
        // Set the recently changed flag so that we can tell if the dependencies are already out of date before when_changed is called
        let recently_changed            = Arc::clone(&self.recently_changed);
        let mut recent_change_monitors  = self.recent_change_monitors.borrow_mut();
        recent_change_monitors.push(dependency.when_changed(notify(move || { *recently_changed.lock().unwrap() = true; })));

        // Add this dependency to the list
        self.dependencies.borrow_mut().push(Box::new(dependency))
    }

    ///
    /// If the dependencies have not changed since they were registered, registers for changes
    /// and returns a `Releasable`. If the dependencies are already different, returns `None`.
    /// 
    pub fn when_changed_if_unchanged(&self, what: Arc<dyn Notifiable>) -> Option<Box<dyn Releasable>> {
        let mut to_release = vec![];

        // Register with all of the dependencies
        for dep in self.dependencies.borrow_mut().iter_mut() {
            to_release.push(dep.when_changed(Arc::clone(&what)));
        }

        if *self.recently_changed.lock().unwrap() {
            // If a value changed while we were building these dependencies, then immediately generate the notification
            to_release.into_iter().for_each(|mut releasable| releasable.done());

            // Nothing to release
            None
        } else {
            // Otherwise, return the set of releasable values
            Some(Box::new(to_release))
        }
    }
}

impl Changeable for BindingDependencies {
    fn when_changed(&self, what: Arc<dyn Notifiable>) -> Box<dyn Releasable> {
        let when_changed_or_not = self.when_changed_if_unchanged(Arc::clone(&what));

        match when_changed_or_not {
            Some(releasable)    => releasable,
            None                => {
                what.mark_as_changed();
                Box::new(vec![])
            }
        }
    }
}

///
/// Represents a binding context. Binding contexts are
/// per-thread structures, used to track 
///
#[derive(Clone)]
pub struct BindingContext {
    /// The dependencies for this context
    dependencies: BindingDependencies,

    /// None, or the binding context that this context was created within
    nested: Option<Box<BindingContext>>
}

impl BindingContext {
    ///
    /// Gets the active binding context
    ///
    pub fn current() -> Option<BindingContext> {
        CURRENT_CONTEXT.with(|current_context| {
            current_context
                .borrow()
                .as_ref()
                .cloned()
        })
    }

    ///
    /// Panics if we're trying to create a binding, with a particular message
    /// 
    pub fn panic_if_in_binding_context(msg: &str) {
        if CURRENT_CONTEXT.with(|context| context.borrow().is_some()) {
            panic!("Not possible when binding: {}", msg);
        }
    }

    ///
    /// Executes a function in a new binding context
    ///
    pub fn bind<TResult, TFn>(to_do: TFn) -> (TResult, BindingDependencies) 
    where TFn: FnOnce() -> TResult {
        // Remember the previous context
        let previous_context = Self::current();

        // Create a new context
        let dependencies    = BindingDependencies::new();
        let new_context     = BindingContext {
            dependencies:   dependencies.clone(),
            nested:         previous_context.clone().map(Box::new)
        };

        // Make the current context the same as the new context
        CURRENT_CONTEXT.with(|current_context| *current_context.borrow_mut() = Some(new_context));

        // Perform the requested action with this context
        let result = to_do();

        // Reset to the previous context
        CURRENT_CONTEXT.with(|current_context| *current_context.borrow_mut() = previous_context);

        (result, dependencies)
    }

    ///
    /// Performs an action outside of the binding context (dependencies 
    /// will not be tracked for anything the supplied function does)
    /// 
    pub fn out_of_context<TResult, TFn>(to_do: TFn) -> TResult
    where TFn: FnOnce() -> TResult {
        // Remember the previous context
        let previous_context = Self::current();

        // Unset the context
        CURRENT_CONTEXT.with(|current_context| *current_context.borrow_mut() = None);

        // Perform the operations without a binding context
        let result = to_do();

        // Reset to the previous context
        CURRENT_CONTEXT.with(|current_context| *current_context.borrow_mut() = previous_context);

        result
    }

    ///
    /// Adds a dependency to the current context (if one is found)
    /// 
    pub fn add_dependency<TChangeable: Changeable+'static>(dependency: TChangeable) {
        Self::current().map(|mut ctx| ctx.dependencies.add_dependency(dependency));
    }
}