Skip to main content

alpha2_extensions/
registry.rs

1//! Which provider reads what: chosen by probing bytes, in the order the host
2//! registered providers, and never by retrying another after a failure.
3//!
4//! Every provider call goes through [`contained`]: a provider parses
5//! untrusted files with third-party code (velato, resvg, the image crates),
6//! and a panic in it is that file's failure, never its caller's. Where panics
7//! unwind (native hosts) that is the whole defense; in the browser module a
8//! panic aborts, and `alpha2/extensions` restarts the module instead.
9
10use std::panic::{AssertUnwindSafe, catch_unwind};
11use std::sync::Arc;
12
13use alpha2_model::ProviderSource;
14use alpha2_time::MediaTime;
15
16use crate::api::{
17    ExtensionError, ProbeReport, SharedResolver, VisualFrame, VisualProvider, VisualSource,
18};
19
20/// The providers a host's build includes, highest priority first.
21#[derive(Default)]
22pub struct Registry {
23    providers: Vec<Box<dyn VisualProvider>>,
24}
25
26impl Registry {
27    /// A registry of every provider this build includes, in the default
28    /// priority: SVG, animated images, Lottie, then OpenEXR.
29    #[must_use]
30    pub fn with_defaults() -> Self {
31        // Without any provider feature nothing is registered.
32        #[allow(unused_mut)]
33        let mut registry = Self::default();
34        #[cfg(feature = "svg")]
35        registry.register(Box::new(crate::SvgProvider));
36        #[cfg(feature = "animated-image")]
37        registry.register(Box::new(crate::AnimatedImageProvider));
38        #[cfg(feature = "lottie")]
39        registry.register(Box::new(crate::LottieProvider));
40        #[cfg(feature = "exr")]
41        registry.register(Box::new(crate::ExrProvider));
42        registry
43    }
44
45    /// Adds `provider` after those already registered.
46    pub fn register(&mut self, provider: Box<dyn VisualProvider>) {
47        self.providers.push(provider);
48    }
49
50    /// The registered providers' names, in priority order.
51    #[must_use]
52    pub fn names(&self) -> Vec<&'static str> {
53        self.providers
54            .iter()
55            .map(|provider| provider.name())
56            .collect()
57    }
58
59    /// What the first provider that claims `bytes` finds, or `None` when none
60    /// does. A provider that claims them and fails answers with its error:
61    /// another provider is not tried in its place.
62    #[must_use]
63    pub fn probe(
64        &self,
65        bytes: &[u8],
66    ) -> Option<Result<(&'static str, ProbeReport), ExtensionError>> {
67        self.providers.iter().find_map(|provider| {
68            let name = provider.name();
69            contained(name, || Ok(provider.probe(bytes)))
70                .unwrap_or_else(|error| Some(Err(error)))
71                .map(|report| report.map(|report| (name, report)))
72        })
73    }
74
75    /// Whether this build opens `source`.
76    #[must_use]
77    pub fn supports(&self, source: &ProviderSource) -> bool {
78        match source {
79            ProviderSource::ImageSequence { .. } => Self::reads_image_sequences(),
80            other => self.named(provider_name(other)).is_some(),
81        }
82    }
83
84    /// Whether this build reads image sequences. They have no provider to
85    /// register (each frame is a PNG or an OpenEXR image), so
86    /// [`Self::names`] cannot say.
87    #[must_use]
88    pub const fn reads_image_sequences() -> bool {
89        cfg!(feature = "image-sequence")
90    }
91
92    /// The resources opening `source` reads, by locator: an image
93    /// sequence's first frame. A host that registers resources only while
94    /// they are used registers these before [`Self::open`].
95    #[must_use]
96    pub fn open_needs(&self, source: &ProviderSource) -> Vec<String> {
97        match source {
98            ProviderSource::ImageSequence { frames, .. } => {
99                frames.first().cloned().flatten().into_iter().collect()
100            }
101            _ => Vec::new(),
102        }
103    }
104
105    /// Opens a project's provider source: the bytes at its locator (none for
106    /// an image sequence, whose frames `resolver` supplies). The bytes must
107    /// be what the project says they are. An image sequence keeps
108    /// `resolver` and reads each frame from it as it shows it; other
109    /// providers read what they need while they open.
110    ///
111    /// The source renders through the same panic boundary: a render that
112    /// panics fails, and so does every later one, without running the
113    /// provider's code again on state the panic left behind.
114    ///
115    /// # Errors
116    /// [`ExtensionError::Unsupported`] when this build has no provider for
117    /// it, [`ExtensionError::Mismatch`] when the bytes are another format,
118    /// or why the provider cannot open them ([`ExtensionError::Malformed`]
119    /// when it panicked).
120    // An image sequence keeps the resolver; without that provider it is only lent.
121    #[cfg_attr(not(feature = "image-sequence"), allow(clippy::needless_pass_by_value))]
122    pub fn open(
123        &self,
124        source: &ProviderSource,
125        bytes: Option<Arc<[u8]>>,
126        resolver: SharedResolver,
127    ) -> Result<Box<dyn VisualSource>, ExtensionError> {
128        let name = provider_name(source);
129        let opened = match source {
130            #[cfg(feature = "image-sequence")]
131            ProviderSource::ImageSequence {
132                frames,
133                frame_rate,
134                missing,
135                primaries,
136                ..
137            } => contained(name, || {
138                crate::open_sequence(frames, *frame_rate, *missing, *primaries, resolver)
139            })?,
140            other => {
141                let provider = self.named(name).ok_or_else(|| {
142                    ExtensionError::Unsupported(format!("this build has no {name} provider"))
143                })?;
144                let bytes = bytes.ok_or_else(|| {
145                    ExtensionError::MissingResource("the source's own bytes".into())
146                })?;
147                // The provider's open tells another format apart itself (see
148                // `VisualProvider::open`): a probe here would read the whole file once more.
149                contained(name, || provider.open(bytes, other, &*resolver))?
150            }
151        };
152        Ok(Box::new(Contained {
153            name,
154            source: opened,
155            failed: None,
156        }))
157    }
158
159    fn named(&self, name: &str) -> Option<&dyn VisualProvider> {
160        self.providers
161            .iter()
162            .find(|provider| provider.name() == name)
163            .map(AsRef::as_ref)
164    }
165}
166
167/// A source whose renders go through the panic boundary.
168struct Contained {
169    name: &'static str,
170    source: Box<dyn VisualSource>,
171    /// Why a render panicked: every later render answers it.
172    failed: Option<ExtensionError>,
173}
174
175impl VisualSource for Contained {
176    fn report(&self) -> &ProbeReport {
177        self.source.report()
178    }
179
180    fn render(&mut self, time: MediaTime, fit: (u32, u32)) -> Result<VisualFrame, ExtensionError> {
181        if let Some(error) = &self.failed {
182            return Err(error.clone());
183        }
184        let source = &mut self.source;
185        catch_unwind(AssertUnwindSafe(|| source.render(time, fit))).unwrap_or_else(|payload| {
186            let error = panicked(self.name, payload.as_ref());
187            self.failed = Some(error.clone());
188            Err(error)
189        })
190    }
191
192    fn needs(&self, time: MediaTime) -> Vec<String> {
193        if self.failed.is_some() {
194            return Vec::new();
195        }
196        catch_unwind(AssertUnwindSafe(|| self.source.needs(time))).unwrap_or_default()
197    }
198
199    fn resident_bytes(&self) -> usize {
200        catch_unwind(AssertUnwindSafe(|| self.source.resident_bytes())).unwrap_or_default()
201    }
202
203    /// A reader behind the same panic boundary. A source whose render
204    /// panicked makes none: what it shares may be what the panic left.
205    fn reader(&self) -> Option<Box<dyn VisualSource>> {
206        if self.failed.is_some() {
207            return None;
208        }
209        let source = catch_unwind(AssertUnwindSafe(|| self.source.reader())).ok()??;
210        Some(Box::new(Self {
211            name: self.name,
212            source,
213            failed: None,
214        }))
215    }
216
217    fn shared_bytes(&self) -> usize {
218        catch_unwind(AssertUnwindSafe(|| self.source.shared_bytes())).unwrap_or_default()
219    }
220}
221
222/// `call`, with a panic in it answered as the file's failure.
223fn contained<T>(
224    provider: &str,
225    call: impl FnOnce() -> Result<T, ExtensionError>,
226) -> Result<T, ExtensionError> {
227    catch_unwind(AssertUnwindSafe(call))
228        .unwrap_or_else(|payload| Err(panicked(provider, payload.as_ref())))
229}
230
231fn panicked(provider: &str, payload: &(dyn std::any::Any + Send)) -> ExtensionError {
232    let message = payload
233        .downcast_ref::<&str>()
234        .copied()
235        .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
236        .unwrap_or("no message");
237    ExtensionError::Malformed(format!(
238        "the {provider} provider failed on this file (it panicked: {message})"
239    ))
240}
241
242/// The provider a project's source names.
243fn provider_name(source: &ProviderSource) -> &'static str {
244    match source {
245        ProviderSource::Svg => "svg",
246        ProviderSource::AnimatedImage => "animated-image",
247        ProviderSource::Lottie { .. } => "lottie",
248        ProviderSource::Exr { .. } => "exr",
249        _ => "image-sequence",
250    }
251}