Skip to main content

guinea_core/scope/
features.rs

1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use crate::actor::shape::Declared;
6
7use super::{Scope, ScopeData};
8
9/// A subscription a feature made, as devtools see it.
10#[derive(Clone, Debug, PartialEq)]
11pub struct Listener {
12    pub event: &'static str,
13    /// The actor that listens, or `None` for a feature's own callback.
14    pub actor: Option<&'static str>,
15    pub bus: crate::trace::Bus,
16    pub feature: Option<&'static str>,
17}
18
19/// A feature installed in a scope, as devtools see it.
20#[derive(Clone, Copy, Debug, PartialEq)]
21pub struct Installed {
22    pub name: &'static str,
23    /// Where `impl Feature` was written, when `#[installs]` wrote it.
24    pub declared: Option<Declared>,
25}
26
27impl Scope {
28    /// Marks feature `F` (its `install` function, used purely as a type
29    /// identity) as owned by this scope. `F` must not already be marked
30    /// here - two different call sites both claiming ownership of the same
31    /// feature in the same scope is a setup bug, not something to merge
32    /// silently.
33    pub fn mark_feature_installed<F: 'static>(&self) {
34        let data = self.installing("installing a feature");
35        let newly_inserted = data.installed_features.borrow_mut().insert(TypeId::of::<F>());
36        assert!(
37            newly_inserted,
38            "feature already installed in this scope - install() called twice for the same feature"
39        );
40    }
41
42    /// Whether anything in this scope has claimed `R`.
43    ///
44    /// What tells an export that was earned from one that was only declared.
45    pub fn claims<R: 'static>(&self) -> bool {
46        self.data()
47            .is_some_and(|data| data.owners.borrow().contains_key(&TypeId::of::<R>()))
48    }
49
50    pub fn note_reducer_owner<R: 'static>(&self) {
51        let data = self.installing("claiming a reducer");
52        data.installed_features.borrow_mut().insert(TypeId::of::<R>());
53        let section = data.current_section();
54        data.owners.borrow_mut().entry(TypeId::of::<R>()).or_insert(section);
55    }
56
57    /// Notes where reducer `R` was claimed, the first claim winning.
58    pub fn note_reducer_declared<R: 'static>(&self, declared: Declared) {
59        let data = self.installing("claiming a reducer");
60        data.declarations.borrow_mut().entry(TypeId::of::<R>()).or_insert(declared);
61    }
62
63    /// The manifest directory of the feature installing now, for a claim that
64    /// only knows the file the compiler gave it.
65    pub fn current_crate_dir(&self) -> Option<&'static str> {
66        let data = self.data()?;
67        let section = data.current_section();
68        let declarations = data.section_declarations.borrow();
69        declarations.get(section).copied().flatten().map(|declared| declared.crate_dir)
70    }
71
72    /// Opens a section for what is about to install - the feature `name`, or
73    /// something that is not a feature, such as a plugin. Returns its index.
74    pub fn open_section(&self, name: Option<&'static str>, declared: Option<Declared>) -> usize {
75        let data = self.installing("installing a feature");
76        let mut sections = data.sections.borrow_mut();
77        if sections.is_empty() {
78            sections.push(HashMap::new());
79        }
80        sections.push(HashMap::new());
81        let index = sections.len() - 1;
82        let mut names = data.section_names.borrow_mut();
83        names.resize(index + 1, None);
84        names[index] = name;
85
86        let mut declarations = data.section_declarations.borrow_mut();
87        declarations.resize(index + 1, None);
88        declarations[index] = declared;
89
90        data.installing.borrow_mut().push(index);
91        index
92    }
93
94    /// The feature a section belongs to; `None` for the segment's own.
95    pub fn section_name(&self, section: usize) -> Option<&'static str> {
96        self.data()?.section_name(section)
97    }
98
99    /// Every feature installed here, in the order they were.
100    pub fn features(&self) -> Vec<Installed> {
101        let Some(data) = self.data() else {
102            return Vec::new();
103        };
104        let declarations = data.section_declarations.borrow();
105
106        data.section_names
107            .borrow()
108            .iter()
109            .enumerate()
110            .filter_map(|(section, name)| {
111                Some(Installed {
112                    name: (*name)?,
113                    declared: declarations.get(section).copied().flatten(),
114                })
115            })
116            .collect()
117    }
118
119    /// The feature being installed right now, if any.
120    pub fn current_feature(&self) -> Option<&'static str> {
121        let data = self.data()?;
122        data.section_name(data.current_section())
123    }
124
125    /// Notes that whatever is installing listens to `event` on `bus` - through
126    /// `actor` when an actor does the listening.
127    pub fn note_listener(
128        &self,
129        event: &'static str,
130        actor: Option<&'static str>,
131        bus: crate::trace::Bus,
132    ) {
133        let Some(data) = self.data() else { return };
134        let feature = data.section_name(data.current_section());
135        data.listeners.borrow_mut().push(Listener {
136            event,
137            actor,
138            bus,
139            feature,
140        });
141    }
142
143    pub fn listeners(&self) -> Vec<Listener> {
144        self.data()
145            .map(|data| data.listeners.borrow().clone())
146            .unwrap_or_default()
147    }
148
149    pub fn close_section(&self) {
150        if let Some(data) = self.data() {
151            data.installing.borrow_mut().pop();
152        }
153    }
154
155    /// The section being installed, or the segment's own when none is.
156    pub fn current_section(&self) -> usize {
157        self.data().map_or(0, |data| data.current_section())
158    }
159
160    /// Which section owns `R` - the instance whose dispatcher a reader of `R`
161    /// should be handed.
162    pub fn section_of<R: 'static>(&self) -> usize {
163        self.data()
164            .and_then(|data| data.owners.borrow().get(&TypeId::of::<R>()).copied())
165            .unwrap_or(0)
166    }
167
168    /// Whether feature `F` was marked installed in *this exact* scope.
169    pub fn has_feature<F: 'static>(&self) -> bool {
170        self.data()
171            .is_some_and(|data| data.installed_features.borrow().contains(&TypeId::of::<F>()))
172    }
173
174    /// Marks `R` as readable from segments below this one.
175    ///
176    /// Called by `cx.install::<F>()` for everything in `F::Exports`, and by
177    /// an application's `export::<R>()`. A reducer a feature claimed but did not export stays
178    /// visible to the feature itself and invisible from below - which is the
179    /// whole difference between a feature and a folder.
180    pub fn note_export<R: 'static>(&self) {
181        let data = self.installing("exporting a reducer");
182        data.exports.borrow_mut().insert(TypeId::of::<R>());
183    }
184
185    /// Whether `R` is readable from below this scope.
186    pub fn exports<R: 'static>(&self) -> bool {
187        self.data()
188            .is_some_and(|data| data.exports.borrow().contains(&TypeId::of::<R>()))
189    }
190
191    /// Says that this scope answers `M`, and how.
192    ///
193    /// Keyed by the action, not by whoever answers it - which is what keeps
194    /// the answerer out of every signature the UI touches. `actor!` calls this
195    /// for each handler it lists; a domain that runs on tasks, or a channel,
196    /// or a plain closure over a `RefCell`, calls it itself.
197    pub fn answers<M: 'static>(&self, answer: impl Fn(M) + 'static) {
198        let data = self.installing("answering an action");
199        let answer: Rc<dyn Fn(M)> = Rc::new(answer);
200        let section = data.current_section();
201
202        let mut sections = data.sections.borrow_mut();
203        while sections.len() <= section {
204            sections.push(HashMap::new());
205        }
206        sections[section].insert(TypeId::of::<M>(), Rc::new(answer) as Rc<dyn Any>);
207    }
208
209    /// What answers `M` in one section of this scope, if anything does.
210    pub fn answerer<M: 'static>(&self, section: usize) -> Option<Rc<dyn Fn(M)>> {
211        self.data()?.answerer::<M>(section)
212    }
213
214    /// What answers `M` anywhere in this scope - the first feature that does,
215    /// in the order they installed. For a sender that knows the action and
216    /// not which state it was reading.
217    pub fn first_answerer<M: 'static>(&self) -> Option<Rc<dyn Fn(M)>> {
218        let data = self.data()?;
219        let sections = data.sections.borrow().len();
220        (0..sections).find_map(|section| data.answerer::<M>(section))
221    }
222}
223
224impl ScopeData {
225    fn answerer<M: 'static>(&self, section: usize) -> Option<Rc<dyn Fn(M)>> {
226        let sections = self.sections.borrow();
227        let answer = sections.get(section)?.get(&TypeId::of::<M>())?.clone();
228        answer.downcast::<Rc<dyn Fn(M)>>().ok().map(|a| (*a).clone())
229    }
230}