Skip to main content

kui_core/
slot.rs

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