tinymist_world/
compute.rs

1#![allow(missing_docs)]
2
3use std::any::TypeId;
4use std::borrow::Cow;
5use std::sync::{Arc, OnceLock};
6
7use parking_lot::Mutex;
8use tinymist_std::error::prelude::*;
9use tinymist_std::typst::{TypstHtmlDocument, TypstPagedDocument};
10use typst::diag::{At, SourceResult, Warned};
11use typst::ecow::EcoVec;
12use typst::syntax::Span;
13
14use crate::snapshot::CompileSnapshot;
15use crate::{CompilerFeat, EntryReader};
16
17type AnyArc = Arc<dyn std::any::Any + Send + Sync>;
18
19/// A world compute entry.
20#[derive(Debug, Clone, Default)]
21struct WorldComputeEntry {
22    computed: Arc<OnceLock<Result<AnyArc>>>,
23}
24
25impl WorldComputeEntry {
26    fn cast<T: std::any::Any + Send + Sync>(e: Result<AnyArc>) -> Result<Arc<T>> {
27        e.map(|e| e.downcast().expect("T is T"))
28    }
29}
30
31/// A world compute graph.
32pub struct WorldComputeGraph<F: CompilerFeat> {
33    /// The used snapshot.
34    pub snap: CompileSnapshot<F>,
35    /// The computed entries.
36    entries: Mutex<rpds::RedBlackTreeMapSync<TypeId, WorldComputeEntry>>,
37}
38
39/// A world computable trait.
40pub trait WorldComputable<F: CompilerFeat>: std::any::Any + Send + Sync + Sized {
41    type Output: Send + Sync + 'static;
42
43    /// The computation implementation.
44    ///
45    /// ## Example
46    ///
47    /// The example shows that a computation can depend on specific world
48    /// implementation. It computes the system font that only works on the
49    /// system world.
50    ///
51    /// ```rust
52    /// use std::sync::Arc;
53    ///
54    /// use tinymist_std::error::prelude::*;
55    /// use tinymist_world::{WorldComputeGraph, WorldComputable};
56    /// use tinymist_world::font::FontResolverImpl;
57    /// use tinymist_world::system::SystemCompilerFeat;
58    ///
59    ///
60    /// pub struct SystemFontsOnce {
61    ///     fonts: Arc<FontResolverImpl>,
62    /// }
63    ///
64    /// impl WorldComputable<SystemCompilerFeat> for SystemFontsOnce {
65    ///     type Output = Self;
66    ///
67    ///     fn compute(graph: &Arc<WorldComputeGraph<SystemCompilerFeat>>) -> Result<Self> {
68    ///
69    ///         Ok(Self {
70    ///             fonts: graph.snap.world.font_resolver.clone(),
71    ///         })
72    ///     }
73    /// }
74    ///
75    /// /// Computes the system fonts.
76    /// fn compute_system_fonts(graph: &Arc<WorldComputeGraph<SystemCompilerFeat>>) {
77    ///    let _fonts = graph.compute::<SystemFontsOnce>().expect("font").fonts.clone();
78    /// }
79    /// ```
80    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output>;
81}
82
83impl<F: CompilerFeat> WorldComputeGraph<F> {
84    /// Creates a new world compute graph.
85    pub fn new(snap: CompileSnapshot<F>) -> Arc<Self> {
86        Arc::new(Self {
87            snap,
88            entries: Default::default(),
89        })
90    }
91
92    /// Clones the graph with the same snapshot.
93    pub fn snapshot(&self) -> Arc<Self> {
94        Arc::new(Self {
95            snap: self.snap.clone(),
96            entries: Mutex::new(self.entries.lock().clone()),
97        })
98    }
99
100    /// Gets a world computed.
101    pub fn must_get<T: WorldComputable<F>>(&self) -> Result<Arc<T::Output>> {
102        let res = self.get::<T>().transpose()?;
103        res.with_context("computation not found", || {
104            Some(Box::new([("type", std::any::type_name::<T>().to_owned())]))
105        })
106    }
107
108    /// Gets a world computed.
109    pub fn get<T: WorldComputable<F>>(&self) -> Option<Result<Arc<T::Output>>> {
110        let computed = self.computed(TypeId::of::<T>()).computed;
111        computed.get().cloned().map(WorldComputeEntry::cast)
112    }
113
114    pub fn exact_provide<T: WorldComputable<F>>(&self, ins: Result<Arc<T::Output>>) {
115        if self.provide::<T>(ins).is_err() {
116            panic!(
117                "failed to provide computed instance: {:?}",
118                std::any::type_name::<T>()
119            );
120        }
121    }
122
123    /// Provides some precomputed instance.
124    #[must_use = "the result must be checked"]
125    pub fn provide<T: WorldComputable<F>>(
126        &self,
127        ins: Result<Arc<T::Output>>,
128    ) -> Result<(), Result<Arc<T::Output>>> {
129        let entry = self.computed(TypeId::of::<T>()).computed;
130        let initialized = entry.set(ins.map(|e| e as AnyArc));
131        initialized.map_err(WorldComputeEntry::cast)
132    }
133
134    /// Gets or computes a world computable.
135    pub fn compute<T: WorldComputable<F>>(self: &Arc<Self>) -> Result<Arc<T::Output>> {
136        let entry = self.computed(TypeId::of::<T>()).computed;
137        let computed = entry.get_or_init(|| Ok(Arc::new(T::compute(self)?)));
138        WorldComputeEntry::cast(computed.clone())
139    }
140
141    fn computed(&self, id: TypeId) -> WorldComputeEntry {
142        let mut entries = self.entries.lock();
143        if let Some(entry) = entries.get(&id) {
144            entry.clone()
145        } else {
146            let entry = WorldComputeEntry::default();
147            entries.insert_mut(id, entry.clone());
148            entry
149        }
150    }
151}
152
153pub trait ExportDetection<F: CompilerFeat, D> {
154    type Config: Send + Sync + 'static;
155
156    fn needs_run(graph: &Arc<WorldComputeGraph<F>>, config: &Self::Config) -> bool;
157}
158
159pub trait ExportComputation<F: CompilerFeat, D> {
160    type Output;
161    type Config: Send + Sync + 'static;
162
163    fn run_with<C: WorldComputable<F, Output = Option<Arc<D>>>>(
164        g: &Arc<WorldComputeGraph<F>>,
165        config: &Self::Config,
166    ) -> Result<Self::Output> {
167        let doc = g.compute::<C>()?;
168        let doc = doc.as_ref().as_ref().context("document not found")?;
169        Self::run(g, doc, config)
170    }
171
172    fn cast_run<'a>(
173        g: &Arc<WorldComputeGraph<F>>,
174        doc: impl TryInto<&'a Arc<D>, Error = tinymist_std::Error>,
175        config: &Self::Config,
176    ) -> Result<Self::Output>
177    where
178        D: 'a,
179    {
180        Self::run(g, doc.try_into()?, config)
181    }
182
183    fn run(
184        g: &Arc<WorldComputeGraph<F>>,
185        doc: &Arc<D>,
186        config: &Self::Config,
187    ) -> Result<Self::Output>;
188}
189
190pub struct ConfigTask<T>(pub T);
191
192impl<F: CompilerFeat, T: Send + Sync + 'static> WorldComputable<F> for ConfigTask<T> {
193    type Output = T;
194
195    fn compute(_graph: &Arc<WorldComputeGraph<F>>) -> Result<T> {
196        let id = std::any::type_name::<T>();
197        panic!("{id:?} must be provided before computation");
198    }
199}
200
201pub type FlagTask<T> = ConfigTask<TaskFlagBase<T>>;
202pub struct TaskFlagBase<T> {
203    pub enabled: bool,
204    _phantom: std::marker::PhantomData<T>,
205}
206
207impl<T> FlagTask<T> {
208    pub fn flag(flag: bool) -> Arc<TaskFlagBase<T>> {
209        Arc::new(TaskFlagBase {
210            enabled: flag,
211            _phantom: Default::default(),
212        })
213    }
214}
215
216pub type PagedCompilationTask = CompilationTask<TypstPagedDocument>;
217pub type HtmlCompilationTask = CompilationTask<TypstHtmlDocument>;
218
219pub struct CompilationTask<D>(std::marker::PhantomData<D>);
220
221impl<F: CompilerFeat, D> WorldComputable<F> for CompilationTask<D>
222where
223    D: typst::Document + Send + Sync + 'static,
224{
225    type Output = Option<Warned<SourceResult<Arc<D>>>>;
226
227    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
228        let enabled = graph.must_get::<FlagTask<CompilationTask<D>>>()?.enabled;
229
230        Ok(enabled.then(|| {
231            // todo: create html world once
232            let is_paged_compilation = TypeId::of::<D>() == TypeId::of::<TypstPagedDocument>();
233            let is_html_compilation = TypeId::of::<D>() == TypeId::of::<TypstHtmlDocument>();
234
235            let mut world = if is_paged_compilation {
236                graph.snap.world.paged_task()
237            } else if is_html_compilation {
238                graph.snap.world.html_task()
239            } else {
240                Cow::Borrowed(&graph.snap.world)
241            };
242
243            world.to_mut().set_is_compiling(true);
244            let compiled = typst::compile::<D>(world.as_ref());
245            world.to_mut().set_is_compiling(false);
246
247            let exclude_html_warnings = if !is_html_compilation {
248                compiled.warnings
249            } else if compiled.warnings.len() == 1
250                && compiled.warnings[0]
251                    .message
252                    .starts_with("html export is under active development")
253            {
254                EcoVec::new()
255            } else {
256                compiled.warnings
257            };
258
259            Warned {
260                output: compiled.output.map(Arc::new),
261                warnings: exclude_html_warnings,
262            }
263        }))
264    }
265}
266
267pub struct OptionDocumentTask<D>(std::marker::PhantomData<D>);
268
269impl<F: CompilerFeat, D> WorldComputable<F> for OptionDocumentTask<D>
270where
271    D: typst::Document + Send + Sync + 'static,
272{
273    type Output = Option<Arc<D>>;
274
275    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
276        let doc = graph.compute::<CompilationTask<D>>()?;
277        let compiled = doc
278            .as_ref()
279            .as_ref()
280            .and_then(|warned| warned.output.clone().ok());
281
282        Ok(compiled)
283    }
284}
285
286impl<D> OptionDocumentTask<D> where D: typst::Document + Send + Sync + 'static {}
287
288struct CompilationDiagnostics {
289    errors: Option<EcoVec<typst::diag::SourceDiagnostic>>,
290    warnings: Option<EcoVec<typst::diag::SourceDiagnostic>>,
291}
292
293impl CompilationDiagnostics {
294    fn from_result<T>(result: &Option<Warned<SourceResult<T>>>) -> Self {
295        let errors = result
296            .as_ref()
297            .and_then(|r| r.output.as_ref().map_err(|e| e.clone()).err());
298        let warnings = result.as_ref().map(|r| r.warnings.clone());
299
300        Self { errors, warnings }
301    }
302}
303
304pub struct DiagnosticsTask {
305    paged: CompilationDiagnostics,
306    html: CompilationDiagnostics,
307}
308
309impl<F: CompilerFeat> WorldComputable<F> for DiagnosticsTask {
310    type Output = Self;
311
312    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self> {
313        let paged = graph.compute::<PagedCompilationTask>()?.clone();
314        let html = graph.compute::<HtmlCompilationTask>()?.clone();
315
316        Ok(Self {
317            paged: CompilationDiagnostics::from_result(&paged),
318            html: CompilationDiagnostics::from_result(&html),
319        })
320    }
321}
322
323impl DiagnosticsTask {
324    pub fn error_cnt(&self) -> usize {
325        self.paged.errors.as_ref().map_or(0, |e| e.len())
326            + self.html.errors.as_ref().map_or(0, |e| e.len())
327    }
328
329    pub fn warning_cnt(&self) -> usize {
330        self.paged.warnings.as_ref().map_or(0, |e| e.len())
331            + self.html.warnings.as_ref().map_or(0, |e| e.len())
332    }
333
334    pub fn diagnostics(&self) -> impl Iterator<Item = &typst::diag::SourceDiagnostic> {
335        self.paged
336            .errors
337            .iter()
338            .chain(self.paged.warnings.iter())
339            .chain(self.html.errors.iter())
340            .chain(self.html.warnings.iter())
341            .flatten()
342    }
343}
344
345// pub type ErasedVecExportTask<E> = ErasedExportTask<SourceResult<Bytes>, E>;
346// pub type ErasedStrExportTask<E> = ErasedExportTask<SourceResult<String>, E>;
347
348// pub struct ErasedExportTask<T, E> {
349//     _phantom: std::marker::PhantomData<(T, E)>,
350// }
351
352// #[allow(clippy::type_complexity)]
353// struct ErasedExportImpl<F: CompilerFeat, T, E> {
354//     f: Arc<dyn Fn(&Arc<WorldComputeGraph<F>>) -> Result<Option<T>> + Send +
355// Sync>,     _phantom: std::marker::PhantomData<E>,
356// }
357
358// impl<T: Send + Sync + 'static, E: Send + Sync + 'static> ErasedExportTask<T,
359// E> {     #[must_use = "the result must be checked"]
360//     pub fn provide_raw<F: CompilerFeat>(
361//         graph: &Arc<WorldComputeGraph<F>>,
362//         f: impl Fn(&Arc<WorldComputeGraph<F>>) -> Result<Option<T>> + Send +
363// Sync + 'static,     ) -> Result<()> {
364//         let provided = graph.provide::<ConfigTask<ErasedExportImpl<F, T,
365// E>>>(Ok(Arc::new({             ErasedExportImpl {
366//                 f: Arc::new(f),
367//                 _phantom: std::marker::PhantomData,
368//             }
369//         })));
370
371//         if provided.is_err() {
372//             tinymist_std::bail!("already provided")
373//         }
374
375//         Ok(())
376//     }
377
378//     #[must_use = "the result must be checked"]
379//     pub fn provide<F: CompilerFeat, D, C>(graph: &Arc<WorldComputeGraph<F>>)
380// -> Result<()>     where
381//         D: typst::Document + Send + Sync + 'static,
382//         C: WorldComputable<F> + ExportComputation<F, D, Output = T>,
383//     {
384//         Self::provide_raw(graph, OptionDocumentTask::run_export::<F, C>)
385//     }
386// }
387
388// impl<F: CompilerFeat, T: Send + Sync + 'static, E: Send + Sync + 'static>
389// WorldComputable<F>     for ErasedExportTask<T, E>
390// {
391//     type Output = Option<T>;
392
393//     fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
394//         let conf = graph.must_get::<ConfigTask<ErasedExportImpl<F, T,
395// E>>>()?;         (conf.f)(graph)
396//     }
397// }
398
399impl<F: CompilerFeat> WorldComputeGraph<F> {
400    pub fn ensure_main(&self) -> SourceResult<()> {
401        let main_id = self.snap.world.main_id();
402        let checked = main_id.ok_or_else(|| typst::diag::eco_format!("entry file is not set"));
403        checked.at(Span::detached()).map(|_| ())
404    }
405
406    /// Compile once from scratch.
407    pub fn pure_compile<D: ::typst::Document>(&self) -> Warned<SourceResult<Arc<D>>> {
408        let res = self.ensure_main();
409        if let Err(err) = res {
410            return Warned {
411                output: Err(err),
412                warnings: EcoVec::new(),
413            };
414        }
415
416        let res = ::typst::compile::<D>(&self.snap.world);
417        // compile document
418        Warned {
419            output: res.output.map(Arc::new),
420            warnings: res.warnings,
421        }
422    }
423
424    /// Compile once from scratch.
425    pub fn compile(&self) -> Warned<SourceResult<Arc<TypstPagedDocument>>> {
426        self.pure_compile()
427    }
428
429    /// Compile to html once from scratch.
430    pub fn compile_html(&self) -> Warned<SourceResult<Arc<::typst::html::HtmlDocument>>> {
431        self.pure_compile()
432    }
433
434    // With **the compilation state**, query the matches for the selector.
435    // fn query(&mut self, selector: String, document: &TypstDocument) ->
436    // SourceResult<Vec<Content>> {     self.pure_query(world, selector,
437    // document) }
438}