Skip to main content

kui_core/
slot.rs

1//! Slots: the place a host declares in its own view for an extension to
2//! fill, with parameters in and replies out
3//! (`docs/adr/0014-slots-an-extension-fills-in-place.md`).
4//!
5//! A slot is a *position*, not a node. `Ui::slot` (or `slot_with`) is a
6//! call the host makes anywhere among its children, and whatever fills it
7//! draws then and there, as children of the node the host is inside — so
8//! the tree stays preorder by construction and nothing is appended to a
9//! node that has closed. The filling is done by whatever implements
10//! [`Fill`], which the frame was begun with (`Core::frame_with`); the
11//! runner hands in its [`Extensions`], and that type's `Fill` is the loop.
12//!
13//! **Slot names are namespaced, and the host decides the namespace.** An
14//! extension names the slots it fills in its own vocabulary — `"panel"`,
15//! `"status"` — with no `/` in them. The host gives each extension it
16//! loads a namespace, the way an importer picks an alias (`Extensions::
17//! push_as`; `push` uses the extension's own name), and declares slots by
18//! their full name: `ui.slot("fs/panel")` is the `"panel"` of the extension
19//! the host calls `fs`. Exactly one extension can fill a slot, the same
20//! plugin loaded twice is two namespaces with two sets of slots and two
21//! sets of params, and nothing an extension names can collide with
22//! anything another names.
23//!
24//! The reserved slot name `"root"` is what an extension listing no slots
25//! fills — `ns/root`, once after the host's view, which is where every
26//! extension drew before slots existed — and a host that declares
27//! `ui.slot("ns/root")` itself moves that fill to the position it chose.
28//! The core owns the two things the ADR makes it own: the key namespace
29//! of a fill (decision 4; see `Core::fill`) and the bound on it (decision
30//! 5).
31//!
32//! ## An extension may host extensions of its own
33//!
34//! The ADR left one question open — "whether one may offer slots is a
35//! decision for the day one asks" — and the day was a Lua view wanting a
36//! native panel inside it. The answer is yes, and it is the same
37//! mechanism one level down rather than a second one:
38//!
39//! * [`Fill::add`] loads an extension while a frame is being built, which
40//!   is when a guest knows it wants one. It lands in the *same* list under
41//!   a namespace of its own, so the frame has one namespace map and one
42//!   origin per extension however deep the loading went. `todos/panel`
43//!   means one thing to everybody.
44//! * A guest's `ui.slot(…)` declares a slot like anyone's, filled from
45//!   that one list. What it may *not* do is fill itself: the extension
46//!   doing the filling is out of the list while it fills, so a cycle is
47//!   the `recursive-slot` warning and an empty position rather than a
48//!   hang.
49//! * Replies go to whoever declared the slot ([`Extensions::route`]) —
50//!   decision 6 read as it is written, since the thing an extension is
51//!   answering is the slot it was put in. For every extension a host
52//!   declared itself, that is the host, exactly as before.
53
54use crate::input::UiEvent;
55use crate::key::Key;
56use crate::runtime::Extension;
57use crate::tree::OriginId;
58use crate::ui::Ui;
59use crate::value::Value;
60
61/// Which slot an extension is filling, handed to `Extension::view`.
62#[derive(Clone, Copy, Debug)]
63pub struct Slot<'a> {
64    /// The name in the extension's own vocabulary — what it listed in
65    /// `slots`, or `"root"` for the fill after the host's view.
66    pub name: &'a str,
67    /// The namespace the host gave this extension: what makes `name` a
68    /// full slot name (`namespace/name`), and what tells one instance of
69    /// a plugin loaded twice from the other.
70    pub namespace: &'a str,
71    /// What the host passed with `slot_with`; `Value::Null` for `slot`.
72    /// Declared every frame and never retained — read it, do not keep it.
73    pub params: &'a Value,
74    /// The slot's own key, `enclosing.str("namespace/name")`: what
75    /// `key_of` answers for the full name, and the namespace the
76    /// extension's nodes are keyed under.
77    pub key: Key,
78}
79
80/// The reserved slot name an extension listing none fills.
81pub const ROOT_SLOT: &str = "root";
82
83/// What separates a namespace from a slot name in a full name. A
84/// namespace may contain it (the host chooses namespaces, and `"left/fs"`
85/// is a fine one); a slot name an extension lists may not, so a full name
86/// splits at its last one.
87pub const NAMESPACE_SEPARATOR: char = '/';
88
89/// `Value::Null` with a `'static` address, for a slot with no params.
90pub static NULL_PARAMS: Value = Value::Null;
91
92/// How many times a reply may be answered by another reply before the
93/// rest go to the host instead ([`Extensions::route`]). Nesting is a few
94/// levels deep in anything sane; this is the bound that keeps two
95/// extensions answering each other from being an infinite loop.
96pub const MAX_REPLY_HOPS: usize = 16;
97
98impl Slot<'_> {
99    /// The `"root"` slot as a test drives an extension without a runner:
100    /// no namespace, no params, keyed under the root. The runner's own
101    /// root fill is `Slot { name: "root", namespace: <the host's> }`, keyed
102    /// under `"ns/root"`.
103    pub fn root() -> Slot<'static> {
104        Slot {
105            name: ROOT_SLOT,
106            namespace: "",
107            params: &NULL_PARAMS,
108            key: Key::ROOT.str(ROOT_SLOT),
109        }
110    }
111
112    /// The full name the host declares: `namespace/name`, or just `name`
113    /// for a slot with no namespace (`Slot::root`).
114    pub fn full_name(&self) -> String {
115        full_name(self.namespace, self.name)
116    }
117}
118
119/// `namespace/name`, or `name` alone when the namespace is empty.
120pub fn full_name(namespace: &str, name: &str) -> String {
121    if namespace.is_empty() {
122        name.to_owned()
123    } else {
124        format!("{namespace}{NAMESPACE_SEPARATOR}{name}")
125    }
126}
127
128/// Splits a full slot name at its last separator into (namespace, name);
129/// a name with none has the empty namespace.
130/// The one entry in `Extension::slots` that means "every name the host
131/// declares under my namespace": for an extension that learns its slots
132/// after it loads. `fill` matches any declared name against it and
133/// `finish` has nothing to warn about for it (backlog K1).
134pub const ANY_SLOT: &str = "*";
135
136pub fn split_name(full: &str) -> (&str, &str) {
137    match full.rfind(NAMESPACE_SEPARATOR) {
138        Some(i) => (&full[..i], &full[i + 1..]),
139        None => ("", full),
140    }
141}
142
143/// What fills slots: the runner's extension list, or a test's stand-in.
144/// A frame begun with `Core::frame_with` carries one; `Ui::slot` calls
145/// [`Fill::fill`] at the position the host declared, and `Ui::finish`
146/// calls [`Fill::finish`] once the host's view is done.
147pub trait Fill {
148    /// Fill the slot declared as `full_name` now, at the cursor, with
149    /// `key` its key: for the extension it names,
150    /// `ui.fill(origin, slot, |ui| ext.view(slot, ui))`, which is where
151    /// the namespace and the bound come from.
152    fn fill(&mut self, full_name: &str, key: Key, params: &Value, ui: &mut Ui<'_>);
153    /// After the host's view: fill every `ns/root` the host did not
154    /// declare, and warn about every slot an extension names that
155    /// nothing declared this frame.
156    fn finish(&mut self, ui: &mut Ui<'_>);
157    /// Loads `ext` under `namespace`, mid-frame, and answers with the
158    /// origin it got — what a guest that hosts guests of its own calls
159    /// (`env.add_extension` in Lua). It joins the same list as everyone
160    /// else, so `namespace` has to be free of the host's names too.
161    ///
162    /// The default refuses: a `Fill` that is not a list of extensions has
163    /// nowhere to put one.
164    fn add(&mut self, namespace: &str, ext: Box<dyn Extension>) -> Result<OriginId, String> {
165        let _ = ext;
166        Err(format!(
167            "cannot load `{namespace}`: this frame was begun with something other than an \
168             extension list to fill its slots"
169        ))
170    }
171}
172
173/// One loaded extension: its namespace, itself, and the answers of its
174/// that are read while it is busy.
175struct Entry {
176    namespace: String,
177    /// `None` for exactly as long as this extension is filling a slot:
178    /// `fill_one` takes it out so the list is free to fill the slots the
179    /// extension itself declares, and puts it back after. A slot of its
180    /// own that it declares while filling therefore finds nobody, which
181    /// is the cycle warning rather than a borrow panic.
182    ext: Option<Box<dyn Extension>>,
183    /// `Extension::name` and `Extension::slots` as they answered at load.
184    /// Copied because both are read while `ext` is taken — and because
185    /// every binding already reads `slots` once, at load, so there was
186    /// never a second answer to miss.
187    name: String,
188    slots: Vec<String>,
189    /// The origin that declared the slot this extension last filled:
190    /// `OriginId::HOST` for one in the host's own view, another
191    /// extension's when that extension declared it. Where its replies go
192    /// (ADR 0014 decision 6; see `route`).
193    asked_by: OriginId,
194}
195
196/// The runner's extensions, each under the namespace the host gave it.
197/// Origins are positions here: the host is `OriginId::HOST` and the
198/// extension at index `i` is `OriginId(i + 1)`, which is what an event's
199/// origin indexes back into.
200#[derive(Default)]
201pub struct Extensions {
202    list: Vec<Entry>,
203}
204
205impl Extensions {
206    pub fn new() -> Self {
207        Self::default()
208    }
209
210    /// Adds `ext` under its own name as the namespace — `import fs` binds
211    /// `fs`. `push_as` with an empty namespace, which means the same.
212    pub fn push(&mut self, ext: Box<dyn Extension>) -> Result<(), String> {
213        self.push_as(String::new(), ext)
214    }
215
216    /// Adds `ext` under `namespace` — `import fs as left`. An empty
217    /// namespace means the extension's own name: the rule is here, once,
218    /// so that a host taking the namespace from outside the program (a C
219    /// argument, a Node option, a Lua call) passes it through rather than
220    /// substituting `name()` first — three of them did, and a fourth did
221    /// not. Fails when that name is empty too, when the namespace is
222    /// already taken (two extensions cannot share one, since slot names
223    /// would collide), or when a slot the extension lists contains the
224    /// separator (a slot name is the extension's own word; the host adds
225    /// the namespace).
226    pub fn push_as(
227        &mut self,
228        namespace: impl Into<String>,
229        ext: Box<dyn Extension>,
230    ) -> Result<(), String> {
231        self.insert(namespace.into(), ext).map(|_| ())
232    }
233
234    /// `push_as` answering with the origin the extension got, which is
235    /// what [`Fill::add`] hands back to a guest that loaded one.
236    fn insert(&mut self, namespace: String, ext: Box<dyn Extension>) -> Result<OriginId, String> {
237        let namespace = if namespace.is_empty() {
238            ext.name().to_owned()
239        } else {
240            namespace
241        };
242        if namespace.is_empty() {
243            return Err(
244                "extension needs a namespace: it names itself nothing and none was given"
245                    .to_owned(),
246            );
247        }
248        if let Some(taken) = self.list.iter().find(|e| e.namespace == namespace) {
249            return Err(format!(
250                "namespace `{namespace}` is already `{}`'s; give `{}` another with `push_as`",
251                taken.name,
252                ext.name()
253            ));
254        }
255        if let Some(bad) = ext
256            .slots()
257            .iter()
258            .find(|s| s.is_empty() || s.contains(NAMESPACE_SEPARATOR))
259        {
260            return Err(format!(
261                "extension `{}` lists slot {bad:?}: a slot name is one word without `{}` — the \
262                 host adds the namespace",
263                ext.name(),
264                NAMESPACE_SEPARATOR
265            ));
266        }
267        let origin = OriginId(self.list.len() as u16 + 1);
268        self.list.push(Entry {
269            namespace,
270            name: ext.name().to_owned(),
271            slots: ext.slots().to_vec(),
272            ext: Some(ext),
273            asked_by: OriginId::HOST,
274        });
275        Ok(origin)
276    }
277
278    pub fn len(&self) -> usize {
279        self.list.len()
280    }
281
282    pub fn is_empty(&self) -> bool {
283        self.list.is_empty()
284    }
285
286    /// The extension an event's origin names, if any. `None` while that
287    /// extension is drawing — nothing routes events mid-frame — and for
288    /// an origin no extension has.
289    pub fn by_origin(&mut self, origin: OriginId) -> Option<&mut (dyn Extension + 'static)> {
290        let i = (origin.0 as usize).checked_sub(1)?;
291        self.list.get_mut(i)?.ext.as_deref_mut()
292    }
293
294    /// The namespace the host gave the extension at `origin`.
295    pub fn namespace_of(&self, origin: OriginId) -> Option<&str> {
296        let i = (origin.0 as usize).checked_sub(1)?;
297        self.list.get(i).map(|e| e.namespace.as_str())
298    }
299
300    /// Who declared the slot the extension at `origin` last filled, and
301    /// so where its replies go: `OriginId::HOST` unless another extension
302    /// declared it.
303    pub fn asked_by(&self, origin: OriginId) -> OriginId {
304        let Some(i) = (origin.0 as usize).checked_sub(1) else {
305            return OriginId::HOST;
306        };
307        self.list.get(i).map_or(OriginId::HOST, |e| e.asked_by)
308    }
309
310    /// Every (namespace, extension), in origin order.
311    pub fn iter(&self) -> impl Iterator<Item = (&str, &dyn Extension)> {
312        self.list
313            .iter()
314            .filter_map(|e| Some((e.namespace.as_str(), &**e.ext.as_ref()?)))
315    }
316
317    /// Delivers `events` to the extensions they came from and hands
318    /// `to_host` everything addressed to the host: the host's own events,
319    /// and the replies of every extension whose slot the host declared.
320    ///
321    /// A reply from an extension a *guest* placed goes to that guest
322    /// instead, as one more event — its `origin` still the replier's, so
323    /// the receiver knows who spoke — and whatever the guest answers
324    /// travels the same way, up to [`MAX_REPLY_HOPS`] levels. This is the
325    /// loop all four hosts route with; one that grew its own would
326    /// disagree with the others about who hears a nested plugin.
327    pub fn route(
328        &mut self,
329        events: impl IntoIterator<Item = UiEvent>,
330        mut to_host: impl FnMut(UiEvent),
331    ) {
332        // Nobody to deliver to: every event is the host's, and the walk
333        // below would only queue them up to say so. This is every host
334        // that loaded nothing, so it is the common case.
335        if self.list.is_empty() {
336            events.into_iter().for_each(to_host);
337            return;
338        }
339        let mut queue: std::collections::VecDeque<(OriginId, usize, UiEvent)> =
340            events.into_iter().map(|ev| (ev.origin, 0, ev)).collect();
341        while let Some((to, depth, ev)) = queue.pop_front() {
342            if to == OriginId::HOST {
343                to_host(ev);
344                continue;
345            }
346            let Some(ext) = self.by_origin(to) else {
347                // An origin nothing answers to: the host asked for the
348                // frame that made it, so it still hears about it.
349                to_host(ev);
350                continue;
351            };
352            let replies = ext.on_event(&ev);
353            if replies.is_empty() {
354                continue;
355            }
356            let up = self.asked_by(to);
357            for payload in replies {
358                let reply = UiEvent {
359                    origin: to,
360                    window: ev.window,
361                    key: ev.key,
362                    payload,
363                    // About the same node, so from the same slot.
364                    slot: ev.slot,
365                };
366                if up == OriginId::HOST || depth + 1 >= MAX_REPLY_HOPS {
367                    to_host(reply);
368                } else {
369                    queue.push_back((up, depth + 1, reply));
370                }
371            }
372        }
373    }
374
375    /// Fills `name` of the extension at `i` as its origin, at the cursor.
376    ///
377    /// The extension comes *out* of the list for the duration, so the
378    /// list itself stays free to fill the slots this extension declares
379    /// while it draws — and so the one slot it cannot fill is its own,
380    /// which would be the cycle.
381    fn fill_one(&mut self, i: usize, name: &str, key: Key, params: &Value, ui: &mut Ui<'_>) {
382        let ns = self.list[i].namespace.clone();
383        let ext_name = self.list[i].name.clone();
384        let Some(mut ext) = self.list[i].ext.take() else {
385            let full = full_name(&ns, name);
386            ui.core()
387                .warn(crate::diag::recursive_slot(&ext_name, &full, key));
388            return;
389        };
390        // Who put it here, and so where its replies go.
391        self.list[i].asked_by = ui.origin();
392        let slot = Slot {
393            name,
394            namespace: &ns,
395            params,
396            key,
397        };
398        let origin = OriginId(i as u16 + 1);
399        ui.fill_within(origin, &slot, self, |ui| {
400            if let Err(err) = ext.view(&slot, ui) {
401                ui.core().warn(crate::diag::extension_view_error(
402                    &ext_name,
403                    &slot.full_name(),
404                    slot.key,
405                    &err,
406                ));
407                ui.text(
408                    &format!("[{ext_name}] {err}"),
409                    crate::spec::TextStyle::new(13.0)
410                        .color(crate::color::Color::rgb8(0xe8, 0x5d, 0x5d)),
411                );
412            }
413        });
414        self.list[i].ext = Some(ext);
415    }
416}
417
418impl TryFrom<Vec<Box<dyn Extension>>> for Extensions {
419    type Error = String;
420
421    /// Each under its own name; fails as `push` does — two extensions of
422    /// one name need `push_as`.
423    fn try_from(exts: Vec<Box<dyn Extension>>) -> Result<Self, String> {
424        let mut out = Self::new();
425        for ext in exts {
426            out.push(ext)?;
427        }
428        Ok(out)
429    }
430}
431
432/// The runner's loop, as a `Fill`. A declared name is split at its last
433/// `/` into the namespace and the extension's own slot name; the
434/// extension under that namespace fills it if it lists the name (or the
435/// name is `"root"` and it lists none). A view that errors leaves its
436/// message in the tree (red, where the fill would have been) and raises
437/// `extension-view-error`, once per extension and slot.
438impl Fill for Extensions {
439    fn fill(&mut self, full_name: &str, key: Key, params: &Value, ui: &mut Ui<'_>) {
440        let (ns, name) = split_name(full_name);
441        let Some(i) = self.list.iter().position(|e| e.namespace == ns) else {
442            return;
443        };
444        let slots = &self.list[i].slots;
445        // A wildcard takes every declared name, `root` included: under it
446        // `root` is one more name the host chose, not the auto-fill.
447        let wants = slots.iter().any(|s| s == ANY_SLOT)
448            || if name == ROOT_SLOT {
449                slots.is_empty()
450            } else {
451                slots.iter().any(|s| s == name)
452            };
453        if wants {
454            self.fill_one(i, name, key, params, ui);
455        }
456    }
457
458    fn finish(&mut self, ui: &mut Ui<'_>) {
459        // By index rather than over a snapshot: a root fill here may load
460        // an extension of its own, which joins the end of the list, and
461        // the frame it arrived on is the frame it should draw on.
462        let mut i = 0;
463        while i < self.list.len() {
464            let ns = self.list[i].namespace.clone();
465            if self.list[i].slots.is_empty() {
466                // The fill every extension got before slots existed: after
467                // the host's view, in list order.
468                let full = full_name(&ns, ROOT_SLOT);
469                if !ui.slot_declared(&full)
470                    && let Some(key) = ui.core().begin_slot(&full)
471                {
472                    self.fill_one(i, ROOT_SLOT, key, &NULL_PARAMS, ui);
473                }
474            } else {
475                // A wildcard lists nothing to check: whatever the host
476                // declared under the namespace was filled above.
477                for name in self.list[i].slots.clone() {
478                    if name == ANY_SLOT {
479                        continue;
480                    }
481                    let full = full_name(&ns, &name);
482                    if ui.slot_declared(&full) {
483                        continue;
484                    }
485                    let ext_name = self.list[i].name.clone();
486                    ui.core()
487                        .warn(crate::diag::unknown_slot(&ext_name, &ns, &name));
488                }
489            }
490            i += 1;
491        }
492    }
493
494    fn add(&mut self, namespace: &str, ext: Box<dyn Extension>) -> Result<OriginId, String> {
495        self.insert(namespace.to_owned(), ext)
496    }
497}
498
499impl<F: Fill + ?Sized> Fill for &mut F {
500    fn fill(&mut self, full_name: &str, key: Key, params: &Value, ui: &mut Ui<'_>) {
501        F::fill(&mut **self, full_name, key, params, ui);
502    }
503    fn finish(&mut self, ui: &mut Ui<'_>) {
504        F::finish(&mut **self, ui);
505    }
506    fn add(&mut self, namespace: &str, ext: Box<dyn Extension>) -> Result<OriginId, String> {
507        F::add(&mut **self, namespace, ext)
508    }
509}
510
511impl<F: Fill + ?Sized> Fill for Box<F> {
512    fn fill(&mut self, full_name: &str, key: Key, params: &Value, ui: &mut Ui<'_>) {
513        F::fill(&mut **self, full_name, key, params, ui);
514    }
515    fn finish(&mut self, ui: &mut Ui<'_>) {
516        F::finish(&mut **self, ui);
517    }
518    fn add(&mut self, namespace: &str, ext: Box<dyn Extension>) -> Result<OriginId, String> {
519        F::add(&mut **self, namespace, ext)
520    }
521}