Skip to main content

lumen_core/
backends.rs

1//! The registry every backend kind shares.
2//!
3//! A backend kind (renderers, layout engines, window systems) is a trait in
4//! [`crate::traits`] plus an entry type that names one implementation, ranks
5//! it, and says how to build it. Each implementation registers its entry
6//! into the app's [`Backends`] registry from its own crate while the app is
7//! built, and a launch asks the registry for one by name or for every entry
8//! in priority order. The core names no implementation, so a binary that
9//! does not link a backend has no entry for it and pays nothing for it.
10
11use crate::app::App;
12use bevy_ecs::prelude::Resource;
13
14/// What a registry entry tells the registry about the backend it names.
15///
16/// Entries are small `Copy` values (a name, a rank, a constructor function
17/// pointer), so a launch takes them out of the registry and builds the
18/// backend without holding the world.
19pub trait BackendEntry: Copy + std::fmt::Debug + Send + Sync + 'static {
20    /// The backend kind, for messages: `"render"`, `"layout"`, `"window"`.
21    const KIND: &'static str;
22
23    /// The name the backend is registered and selected under.
24    fn name(&self) -> &'static str;
25
26    /// Trial order when nothing names a backend: higher first.
27    fn priority(&self) -> i32;
28}
29
30/// Main-world registry of the backends of one kind this binary carries.
31#[derive(Resource, Clone, Debug)]
32pub struct Backends<B: BackendEntry> {
33    entries: Vec<B>,
34}
35
36impl<B: BackendEntry> Default for Backends<B> {
37    fn default() -> Self {
38        Self {
39            entries: Vec::new(),
40        }
41    }
42}
43
44impl<B: BackendEntry> Backends<B> {
45    /// Add `backend`, replacing one registered under the same name.
46    pub fn register(&mut self, backend: B) {
47        self.entries.retain(|b| b.name() != backend.name());
48        self.entries.push(backend);
49    }
50
51    /// The backend registered as `name`.
52    pub fn get(&self, name: &str) -> Option<&B> {
53        self.entries.iter().find(|b| b.name() == name)
54    }
55
56    /// Every backend, highest priority first; equal priorities in name order,
57    /// so the order is the same in every process.
58    pub fn by_priority(&self) -> Vec<B> {
59        let mut list = self.entries.clone();
60        list.sort_by(|a, b| b.priority().cmp(&a.priority()).then(a.name().cmp(b.name())));
61        list
62    }
63
64    /// The highest-priority backend, for a kind a launch uses exactly one of
65    /// and no configuration names.
66    pub fn preferred(&self) -> Option<B> {
67        self.by_priority().into_iter().next()
68    }
69
70    /// The registered names, sorted, for a message naming what is available.
71    pub fn names(&self) -> Vec<&'static str> {
72        let mut names: Vec<&'static str> = self.entries.iter().map(|b| b.name()).collect();
73        names.sort_unstable();
74        names
75    }
76
77    /// The backends a launch should try, in order: the one `choice` names,
78    /// or every backend by priority when `choice` is `None`. An error names
79    /// what was asked for and what this binary carries.
80    pub fn select(&self, choice: Option<&str>) -> Result<Vec<B>, String> {
81        match choice {
82            Some(name) => self.get(name).map(|b| vec![*b]).ok_or_else(|| {
83                format!(
84                    "the app asks for the '{name}' {} backend, and this build carries {}",
85                    B::KIND,
86                    describe(&self.names())
87                )
88            }),
89            None if self.entries.is_empty() => Err(missing::<B>()),
90            None => Ok(self.by_priority()),
91        }
92    }
93}
94
95/// The message for a build that carries no backend of kind `B`.
96pub fn missing<B: BackendEntry>() -> String {
97    format!("this build carries no {} backend", B::KIND)
98}
99
100/// `"none"`, `"only 'a'"`, or `"'a' and 'b'"`, for an error message.
101fn describe(names: &[&str]) -> String {
102    match names {
103        [] => "none".to_string(),
104        [one] => format!("only '{one}'"),
105        many => {
106            let quoted: Vec<String> = many.iter().map(|n| format!("'{n}'")).collect();
107            let (last, rest) = quoted.split_last().expect("at least two");
108            format!("{} and {last}", rest.join(", "))
109        }
110    }
111}
112
113/// Register `backend` into `app`'s registry of its kind, creating the
114/// registry on first use. What a backend's capability install calls.
115pub fn register_backend<B: BackendEntry>(app: &mut App, backend: B) {
116    app.world
117        .get_resource_or_insert_with(Backends::<B>::default)
118        .register(backend);
119}
120
121/// The entries of kind `B` registered into `app`, empty when none are.
122pub fn registered<B: BackendEntry>(app: &App) -> Backends<B> {
123    app.world
124        .get_resource::<Backends<B>>()
125        .cloned()
126        .unwrap_or_default()
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[derive(Clone, Copy, Debug)]
134    struct Entry {
135        name: &'static str,
136        priority: i32,
137    }
138
139    impl BackendEntry for Entry {
140        const KIND: &'static str = "test";
141        fn name(&self) -> &'static str {
142            self.name
143        }
144        fn priority(&self) -> i32 {
145            self.priority
146        }
147    }
148
149    fn entry(name: &'static str, priority: i32) -> Entry {
150        Entry { name, priority }
151    }
152
153    #[test]
154    fn a_name_selects_one_backend_and_none_selects_all_by_priority() {
155        let mut backends = Backends::default();
156        backends.register(entry("slow", 0));
157        backends.register(entry("fast", 10));
158        backends.register(entry("also-slow", 0));
159
160        let named = backends.select(Some("slow")).expect("registered");
161        assert_eq!(named.len(), 1);
162        assert_eq!(named[0].name, "slow");
163
164        let order: Vec<&str> = backends
165            .select(None)
166            .expect("some")
167            .iter()
168            .map(|b| b.name)
169            .collect();
170        assert_eq!(order, ["fast", "also-slow", "slow"]);
171        assert_eq!(backends.preferred().map(|b| b.name), Some("fast"));
172    }
173
174    #[test]
175    fn asking_for_a_backend_the_build_lacks_says_what_it_has() {
176        let mut backends = Backends::default();
177        let none = backends.select(Some("cpu")).expect_err("empty");
178        assert!(
179            none.contains("'cpu' test backend") && none.contains("none"),
180            "{none}"
181        );
182        assert_eq!(
183            backends.select(None).expect_err("empty"),
184            "this build carries no test backend"
185        );
186        assert!(backends.preferred().is_none());
187
188        backends.register(entry("gpu", 10));
189        let one = backends.select(Some("cpu")).expect_err("missing");
190        assert!(one.contains("only 'gpu'"), "{one}");
191
192        backends.register(entry("other", 0));
193        backends.register(entry("third", 0));
194        let many = backends.select(Some("cpu")).expect_err("missing");
195        assert!(many.contains("'gpu', 'other' and 'third'"), "{many}");
196    }
197
198    #[test]
199    fn registering_a_name_again_replaces_it() {
200        let mut app = App::new();
201        register_backend(&mut app, entry("gpu", 1));
202        register_backend(&mut app, entry("gpu", 5));
203        let backends = registered::<Entry>(&app);
204        assert_eq!(backends.names(), ["gpu"]);
205        assert_eq!(backends.get("gpu").map(|b| b.priority), Some(5));
206    }
207}