zng_ext_config/
fallback.rs

1use crate::task::parking_lot::Mutex;
2use zng_var::expr_var;
3
4use super::*;
5
6/// Reset controls of a [`FallbackConfig`].
7pub trait FallbackConfigReset: AnyConfig + Sync {
8    /// Removes the `key` from the config source and reverts all active variables to the fallback source.
9    fn reset(&self, key: &ConfigKey);
10
11    /// Gets if the config source contains the `key`.
12    fn can_reset(&self, key: ConfigKey) -> Var<bool>;
13
14    /// Clone a reference to the config.
15    fn clone_boxed(&self) -> Box<dyn FallbackConfigReset>;
16}
17impl Clone for Box<dyn FallbackConfigReset> {
18    fn clone(&self) -> Self {
19        self.clone_boxed()
20    }
21}
22
23/// Represents a copy-on-write config source that wraps two other sources, a read-write config and a read-only fallback config.
24///
25/// The config variables are connected to both sources, if the read-write config is not set the var will update with the
26/// fallback config, if it is set it will sync with the read-write config.
27///
28/// The `FallbackConfig` type is an `Arc` internally, so you can keep a cloned reference to it after moving it into
29/// [`CONFIG`] or another combinator config.
30pub struct FallbackConfig<S: Config, F: Config>(Arc<Mutex<FallbackConfigData<S, F>>>);
31
32impl<S: Config, F: Config> FallbackConfig<S, F> {
33    /// New from write source and fallback source.
34    pub fn new(source: S, fallback: F) -> Self {
35        Self(Arc::new(Mutex::new(FallbackConfigData {
36            source,
37            fallback,
38            output: Default::default(),
39        })))
40    }
41}
42
43impl<S: AnyConfig, F: AnyConfig> AnyConfig for FallbackConfig<S, F> {
44    fn status(&self) -> Var<ConfigStatus> {
45        let self_ = self.0.lock();
46        expr_var! {
47            ConfigStatus::merge_status([#{self_.fallback.status()}.clone(), #{self_.source.status()}.clone()].into_iter())
48        }
49    }
50
51    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool) -> Var<RawConfigValue> {
52        self.0.lock().bind_raw(key, default, insert)
53    }
54
55    fn contains_key(&mut self, key: ConfigKey) -> Var<bool> {
56        let mut self_ = self.0.lock();
57        expr_var! {
58            *#{self_.source.contains_key(key.clone())} || *#{self_.fallback.contains_key(key)}
59        }
60    }
61
62    fn remove(&mut self, key: &ConfigKey) -> bool {
63        let mut self_ = self.0.lock();
64        let a = self_.source.remove(key);
65        let b = self_.fallback.remove(key);
66        a || b
67    }
68
69    fn low_memory(&mut self) {
70        let mut self_ = self.0.lock();
71        self_.source.low_memory();
72        self_.fallback.low_memory();
73        self_.output.retain(|_, v| v.output_weak.strong_count() > 0);
74    }
75}
76impl<S: AnyConfig, F: AnyConfig> FallbackConfigReset for FallbackConfig<S, F> {
77    fn reset(&self, key: &ConfigKey) {
78        let mut self_ = self.0.lock();
79        // remove from source
80        self_.source.remove(key);
81    }
82
83    fn can_reset(&self, key: ConfigKey) -> Var<bool> {
84        let mut self_ = self.0.lock();
85        self_.source.contains_key(key)
86    }
87
88    fn clone_boxed(&self) -> Box<dyn FallbackConfigReset> {
89        Box::new(Self(self.0.clone()))
90    }
91}
92
93struct FallbackConfigData<S, F> {
94    source: S,
95    fallback: F,
96    output: HashMap<ConfigKey, OutputEntry>,
97}
98
99impl<S: AnyConfig, F: AnyConfig> FallbackConfigData<S, F> {
100    fn bind_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool) -> Var<RawConfigValue> {
101        if let Some(entry) = self.output.get(&key)
102            && let Some(output) = entry.output_weak.upgrade()
103        {
104            return output;
105        }
106
107        let fallback = self.fallback.get(key.clone(), default, false);
108        let source = self.source.get(key.clone(), fallback.get(), insert);
109        let source_contains = self.source.contains_key(key.clone());
110        let fallback_tag = fallback.var_instance_tag();
111
112        let output = var(if source_contains.get() { source.get() } else { fallback.get() });
113        let weak_output = output.downgrade();
114
115        // update output when not source_contains
116        let fallback_hook = fallback.hook(clmv!(weak_output, source_contains, |args| {
117            if let Some(output) = weak_output.upgrade() {
118                if !source_contains.get() {
119                    let value = args.value().clone();
120                    output.modify(move |o| {
121                        o.set(value);
122                        o.push_tag(fallback_tag);
123                    });
124                }
125                true // retain hook
126            } else {
127                false // output dropped
128            }
129        }));
130
131        // update output
132        let source_hook = source.hook(clmv!(weak_output, |args| {
133            if let Some(output) = weak_output.upgrade() {
134                let output_tag = output.var_instance_tag();
135                if !args.contains_tag(&output_tag) {
136                    output.set(args.value().clone());
137                }
138                true
139            } else {
140                false // output dropped
141            }
142        }));
143
144        // reset output to fallback when contains changes to false
145        // or set output to source in case the entry is back with the same value (no source update)
146        let weak_fallback = fallback.downgrade(); // fallback_hook holds source_contains
147        let source_contains_hook = source_contains.hook(clmv!(weak_output, source, |args| {
148            if let Some(output) = weak_output.upgrade() {
149                if *args.value() {
150                    output.set(source.get());
151                } else {
152                    let fallback = weak_fallback.upgrade().unwrap();
153                    let fallback_value = fallback.get();
154                    let fallback_tag = fallback.var_instance_tag();
155                    output.modify(move |o| {
156                        o.set(fallback_value);
157                        o.push_tag(fallback_tag);
158                    });
159                }
160
161                true
162            } else {
163                false // output dropped
164            }
165        }));
166
167        // update source
168        let output_tag = output.var_instance_tag();
169        output
170            .hook(move |args| {
171                let _hold = (&fallback, &fallback_hook, &source_hook, &source_contains_hook);
172
173                if !args.contains_tag(&fallback_tag) {
174                    let value = args.value().clone();
175                    source.modify(move |s| {
176                        s.set(value);
177                        s.update(); // in case of reset the source var can retain the old value
178                        s.push_tag(output_tag);
179                    });
180                }
181                true
182            })
183            .perm();
184
185        self.output.insert(
186            key,
187            OutputEntry {
188                output_weak: output.downgrade(),
189            },
190        );
191
192        output
193    }
194}
195struct OutputEntry {
196    output_weak: WeakVar<RawConfigValue>,
197}