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        // TODO how can the var API be changed to support this? Something like a read-write merge_var!?
108
109        let fallback = self.fallback.get(key.clone(), default, false);
110        let source = self.source.get(key.clone(), fallback.get(), insert);
111        let source_contains = self.source.contains_key(key.clone());
112        let fallback_tag = fallback.var_instance_tag();
113
114        let output = var(if source_contains.get() { source.get() } else { fallback.get() });
115        let weak_output = output.downgrade();
116
117        // update output when not source_contains
118        let fallback_hook = fallback.hook(clmv!(weak_output, source_contains, |args| {
119            if let Some(output) = weak_output.upgrade() {
120                if !source_contains.get() {
121                    let value = args.value().clone();
122                    output.modify(move |o| {
123                        o.set(value);
124                        o.push_tag(fallback_tag);
125                    });
126                }
127                true // retain hook
128            } else {
129                false // output dropped
130            }
131        }));
132
133        // update output
134        let source_hook = source.hook(clmv!(weak_output, |args| {
135            if let Some(output) = weak_output.upgrade() {
136                let output_tag = output.var_instance_tag();
137                if !args.contains_tag(&output_tag) {
138                    output.set(args.value().clone());
139                }
140                true
141            } else {
142                false // output dropped
143            }
144        }));
145
146        // reset output to fallback when contains changes to false
147        // or set output to source in case the entry is back with the same value (no source update)
148        let weak_fallback = fallback.downgrade(); // fallback_hook holds source_contains
149        let source_contains_hook = source_contains.hook(clmv!(weak_output, source, |args| {
150            if let Some(output) = weak_output.upgrade() {
151                if *args.value() {
152                    output.set(source.get());
153                } else {
154                    let fallback = weak_fallback.upgrade().unwrap();
155                    let fallback_value = fallback.get();
156                    let fallback_tag = fallback.var_instance_tag();
157                    output.modify(move |o| {
158                        o.set(fallback_value);
159                        o.push_tag(fallback_tag);
160                    });
161                }
162
163                true
164            } else {
165                false // output dropped
166            }
167        }));
168
169        // update source
170        let output_tag = output.var_instance_tag();
171        output
172            .hook(move |args| {
173                let _hold = (&fallback, &fallback_hook, &source_hook, &source_contains_hook);
174
175                if !args.contains_tag(&fallback_tag) {
176                    let value = args.value().clone();
177                    source.modify(move |s| {
178                        s.set(value);
179                        s.update(); // in case of reset the source var can retain the old value
180                        s.push_tag(output_tag);
181                    });
182                }
183                true
184            })
185            .perm();
186
187        self.output.insert(
188            key,
189            OutputEntry {
190                output_weak: output.downgrade(),
191            },
192        );
193
194        output
195    }
196}
197struct OutputEntry {
198    output_weak: WeakVar<RawConfigValue>,
199}