tinymist_world/
compute.rs

1use std::any::TypeId;
2use std::borrow::Cow;
3use std::sync::{Arc, OnceLock};
4
5use parking_lot::Mutex;
6use tinymist_std::error::prelude::*;
7use tinymist_std::typst::{TypstHtmlDocument, TypstPagedDocument};
8use typst::diag::{At, SourceResult, Warned};
9use typst::ecow::EcoVec;
10use typst::foundations::Output;
11use typst::syntax::Span;
12use typst_bundle::Bundle;
13
14use crate::snapshot::CompileSnapshot;
15use crate::{CompilerFeat, CompilerWorld, EntryReader, TaskInputs};
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    /// The output type.
42    type Output: Send + Sync + 'static;
43
44    /// The computation implementation.
45    ///
46    /// ## Example
47    ///
48    /// The example shows that a computation can depend on specific world
49    /// implementation. It computes the system font that only works on the
50    /// system world.
51    ///
52    /// ```rust
53    /// use std::sync::Arc;
54    ///
55    /// use tinymist_std::error::prelude::*;
56    /// use tinymist_world::{WorldComputeGraph, WorldComputable};
57    /// use tinymist_world::font::FontResolverImpl;
58    /// use tinymist_world::system::SystemCompilerFeat;
59    ///
60    ///
61    /// pub struct SystemFontsOnce {
62    ///     fonts: Arc<FontResolverImpl>,
63    /// }
64    ///
65    /// impl WorldComputable<SystemCompilerFeat> for SystemFontsOnce {
66    ///     type Output = Self;
67    ///
68    ///     fn compute(graph: &Arc<WorldComputeGraph<SystemCompilerFeat>>) -> Result<Self> {
69    ///
70    ///         Ok(Self {
71    ///             fonts: graph.snap.world.font_resolver.clone(),
72    ///         })
73    ///     }
74    /// }
75    ///
76    /// /// Computes the system fonts.
77    /// fn compute_system_fonts(graph: &Arc<WorldComputeGraph<SystemCompilerFeat>>) {
78    ///    let _fonts = graph.compute::<SystemFontsOnce>().expect("font").fonts.clone();
79    /// }
80    /// ```
81    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output>;
82}
83
84impl<F: CompilerFeat> WorldComputeGraph<F> {
85    /// Creates a new world compute graph.
86    pub fn new(snap: CompileSnapshot<F>) -> Arc<Self> {
87        Arc::new(Self {
88            snap,
89            entries: Default::default(),
90        })
91    }
92
93    /// Creates a graph from the world.
94    pub fn from_world(world: CompilerWorld<F>) -> Arc<Self> {
95        Self::new(CompileSnapshot::from_world(world))
96    }
97
98    /// Clones the graph with the same snapshot.
99    pub fn snapshot(&self) -> Arc<Self> {
100        self.snapshot_unsafe(self.snap.clone())
101    }
102
103    /// Clones the graph with the same snapshot. Take care of the consistency by
104    /// your self.
105    pub fn snapshot_unsafe(&self, snap: CompileSnapshot<F>) -> Arc<Self> {
106        Arc::new(Self {
107            snap,
108            entries: Mutex::new(self.entries.lock().clone()),
109        })
110    }
111
112    /// Forks a new snapshot that compiles a different document.
113    // todo: share cache if task doesn't change.
114    pub fn task(&self, inputs: TaskInputs) -> Arc<Self> {
115        let mut snap = self.snap.clone();
116        snap = snap.task(inputs);
117        Self::new(snap)
118    }
119
120    /// Gets a world computed.
121    pub fn must_get<T: WorldComputable<F>>(&self) -> Result<Arc<T::Output>> {
122        let res = self.get::<T>().transpose()?;
123        res.with_context("computation not found", || {
124            Some(Box::new([("type", std::any::type_name::<T>().to_owned())]))
125        })
126    }
127
128    /// Gets a world computed.
129    pub fn get<T: WorldComputable<F>>(&self) -> Option<Result<Arc<T::Output>>> {
130        let computed = self.computed(TypeId::of::<T>()).computed;
131        computed.get().cloned().map(WorldComputeEntry::cast)
132    }
133
134    /// Provides an exact instance.
135    pub fn exact_provide<T: WorldComputable<F>>(&self, ins: Result<Arc<T::Output>>) {
136        if self.provide::<T>(ins).is_err() {
137            panic!(
138                "failed to provide computed instance: {:?}",
139                std::any::type_name::<T>()
140            );
141        }
142    }
143
144    /// Provides some precomputed instance.
145    #[must_use = "the result must be checked"]
146    pub fn provide<T: WorldComputable<F>>(
147        &self,
148        ins: Result<Arc<T::Output>>,
149    ) -> Result<(), Result<Arc<T::Output>>> {
150        let entry = self.computed(TypeId::of::<T>()).computed;
151        let initialized = entry.set(ins.map(|e| e as AnyArc));
152        initialized.map_err(WorldComputeEntry::cast)
153    }
154
155    /// Gets or computes a world computable.
156    pub fn compute<T: WorldComputable<F>>(self: &Arc<Self>) -> Result<Arc<T::Output>> {
157        let entry = self.computed(TypeId::of::<T>()).computed;
158        let computed = entry.get_or_init(|| Ok(Arc::new(T::compute(self)?)));
159        WorldComputeEntry::cast(computed.clone())
160    }
161
162    fn computed(&self, id: TypeId) -> WorldComputeEntry {
163        let mut entries = self.entries.lock();
164        if let Some(entry) = entries.get(&id) {
165            entry.clone()
166        } else {
167            let entry = WorldComputeEntry::default();
168            entries.insert_mut(id, entry.clone());
169            entry
170        }
171    }
172
173    /// Gets the world.
174    pub fn world(&self) -> &CompilerWorld<F> {
175        &self.snap.world
176    }
177
178    /// Gets the registry.
179    pub fn registry(&self) -> &Arc<F::Registry> {
180        &self.snap.world.registry
181    }
182
183    /// Gets the library.
184    pub fn library(&self) -> &typst::Library {
185        &self.snap.world.library
186    }
187}
188
189/// A trait to detect the export of a document.
190pub trait ExportDetection<F: CompilerFeat, D> {
191    /// The configuration type.
192    type Config: Send + Sync + 'static;
193
194    /// Determines whether the export needs to be computed.
195    fn needs_run(graph: &Arc<WorldComputeGraph<F>>, config: &Self::Config) -> bool;
196}
197
198/// A trait to compute the export of a document.
199pub trait ExportComputation<F: CompilerFeat, D> {
200    /// The output type.
201    type Output;
202    /// The configuration type.
203    type Config: Send + Sync + 'static;
204
205    /// Runs the export computation.
206    fn run_with<C: WorldComputable<F, Output = Option<Arc<D>>>>(
207        g: &Arc<WorldComputeGraph<F>>,
208        config: &Self::Config,
209    ) -> Result<Self::Output> {
210        let doc = g.compute::<C>()?;
211        let doc = doc.as_ref().as_ref().context("document not found")?;
212        Self::run(g, doc, config)
213    }
214
215    /// Runs the export computation with a caster.
216    fn cast_run<'a>(
217        g: &Arc<WorldComputeGraph<F>>,
218        doc: impl TryInto<&'a Arc<D>, Error = tinymist_std::Error>,
219        config: &Self::Config,
220    ) -> Result<Self::Output>
221    where
222        D: 'a,
223    {
224        Self::run(g, doc.try_into()?, config)
225    }
226
227    /// Runs the export computation.
228    fn run(
229        g: &Arc<WorldComputeGraph<F>>,
230        doc: &Arc<D>,
231        config: &Self::Config,
232    ) -> Result<Self::Output>;
233}
234
235/// A task that computes a configuration.
236pub struct ConfigTask<T>(pub T);
237
238impl<F: CompilerFeat, T: Send + Sync + 'static> WorldComputable<F> for ConfigTask<T> {
239    type Output = T;
240
241    fn compute(_graph: &Arc<WorldComputeGraph<F>>) -> Result<T> {
242        let id = std::any::type_name::<T>();
243        panic!("{id:?} must be provided before computation");
244    }
245}
246
247/// A task that computes a flag.
248pub type FlagTask<T> = ConfigTask<TaskFlagBase<T>>;
249
250/// A base task flag.
251pub struct TaskFlagBase<T> {
252    /// Whether the task is enabled.
253    pub enabled: bool,
254    /// The phantom data.
255    _phantom: std::marker::PhantomData<T>,
256}
257
258impl<T> FlagTask<T> {
259    /// Creates a new flag task.
260    pub fn flag(flag: bool) -> Arc<TaskFlagBase<T>> {
261        Arc::new(TaskFlagBase {
262            enabled: flag,
263            _phantom: Default::default(),
264        })
265    }
266}
267
268/// A task that compiles a paged document.
269pub type PagedCompilationTask = CompilationTask<TypstPagedDocument>;
270
271/// A task that compiles an HTML document.
272pub type HtmlCompilationTask = CompilationTask<TypstHtmlDocument>;
273
274/// A task that compiles a bundle.
275pub type BundleCompilationTask = CompilationTask<Bundle>;
276
277/// A task that compiles a document.
278pub struct CompilationTask<D>(std::marker::PhantomData<D>);
279
280impl<D: Output + Send + Sync + 'static> CompilationTask<D> {
281    /// Ensures the main document.
282    pub fn ensure_main<F: CompilerFeat>(world: &CompilerWorld<F>) -> SourceResult<()> {
283        let main_id = world.main_id();
284        let checked = main_id.ok_or_else(|| typst::diag::eco_format!("entry file is not set"));
285        checked.at(Span::detached()).map(|_| ())
286    }
287
288    /// Executes the compilation.
289    pub fn execute<F: CompilerFeat>(world: &CompilerWorld<F>) -> Warned<SourceResult<Arc<D>>> {
290        let res = Self::ensure_main(world);
291        if let Err(err) = res {
292            return Warned {
293                output: Err(err),
294                warnings: EcoVec::new(),
295            };
296        }
297
298        let is_paged_compilation = TypeId::of::<D>() == TypeId::of::<TypstPagedDocument>();
299        let is_html_compilation = TypeId::of::<D>() == TypeId::of::<TypstHtmlDocument>();
300
301        let mut world = if is_paged_compilation {
302            world.paged_task()
303        } else if is_html_compilation {
304            // todo: create html world once
305            world.html_task()
306        } else {
307            Cow::Borrowed(world)
308        };
309
310        world.to_mut().set_is_compiling(true);
311        let compiled = ::typst_shim::compile_opt::<D>(world.as_ref());
312        world.to_mut().set_is_compiling(false);
313
314        let exclude_html_warnings = if !is_html_compilation {
315            compiled.warnings
316        } else if compiled.warnings.len() == 1
317            && compiled.warnings[0]
318                .message
319                .starts_with("html export is under active development")
320        {
321            EcoVec::new()
322        } else {
323            compiled.warnings
324        };
325
326        Warned {
327            output: compiled.output.map(Arc::new),
328            warnings: exclude_html_warnings,
329        }
330    }
331}
332
333impl<F: CompilerFeat, D> WorldComputable<F> for CompilationTask<D>
334where
335    D: Output + Send + Sync + 'static,
336{
337    type Output = Option<Warned<SourceResult<Arc<D>>>>;
338
339    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
340        let enabled = graph.must_get::<FlagTask<CompilationTask<D>>>()?.enabled;
341
342        Ok(enabled.then(|| CompilationTask::<D>::execute(&graph.snap.world)))
343    }
344}
345
346/// A task that computes an optional document.
347pub struct OptionDocumentTask<D>(std::marker::PhantomData<D>);
348
349impl<F: CompilerFeat, D> WorldComputable<F> for OptionDocumentTask<D>
350where
351    D: Output + Send + Sync + 'static,
352{
353    type Output = Option<Arc<D>>;
354
355    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
356        let doc = graph.compute::<CompilationTask<D>>()?;
357        let compiled = doc
358            .as_ref()
359            .as_ref()
360            .and_then(|warned| warned.output.clone().ok());
361
362        Ok(compiled)
363    }
364}
365
366impl<D> OptionDocumentTask<D> where D: Output + Send + Sync + 'static {}
367
368/// A task that computes the diagnostics of a document.
369struct CompilationDiagnostics {
370    errors: Option<EcoVec<typst::diag::SourceDiagnostic>>,
371    warnings: Option<EcoVec<typst::diag::SourceDiagnostic>>,
372}
373
374impl CompilationDiagnostics {
375    /// Creates a new diagnostics from a result.
376    fn from_result<T>(result: &Option<Warned<SourceResult<T>>>) -> Self {
377        let errors = result
378            .as_ref()
379            .and_then(|r| r.output.as_ref().map_err(|e| e.clone()).err());
380        let warnings = result.as_ref().map(|r| r.warnings.clone());
381
382        Self { errors, warnings }
383    }
384}
385
386/// A task that computes the diagnostics of a document.
387pub struct DiagnosticsTask {
388    paged: CompilationDiagnostics,
389    html: CompilationDiagnostics,
390    bundle: CompilationDiagnostics,
391}
392
393impl<F: CompilerFeat> WorldComputable<F> for DiagnosticsTask {
394    type Output = Self;
395
396    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self> {
397        let paged = graph.compute::<PagedCompilationTask>()?.clone();
398        let html = graph.compute::<HtmlCompilationTask>()?.clone();
399        let bundle = graph.compute::<BundleCompilationTask>()?.clone();
400
401        Ok(Self {
402            paged: CompilationDiagnostics::from_result(&paged),
403            html: CompilationDiagnostics::from_result(&html),
404            bundle: CompilationDiagnostics::from_result(&bundle),
405        })
406    }
407}
408
409impl DiagnosticsTask {
410    /// Creates diagnostics from errors.
411    pub fn from_errors(paged_errors: Option<EcoVec<typst::diag::SourceDiagnostic>>) -> Self {
412        Self {
413            paged: CompilationDiagnostics {
414                errors: paged_errors,
415                warnings: None,
416            },
417            html: CompilationDiagnostics {
418                errors: None,
419                warnings: None,
420            },
421            bundle: CompilationDiagnostics {
422                errors: None,
423                warnings: None,
424            },
425        }
426    }
427
428    /// Gets the number of errors.
429    pub fn error_cnt(&self) -> usize {
430        self.paged.errors.as_ref().map_or(0, |e| e.len())
431            + self.html.errors.as_ref().map_or(0, |e| e.len())
432            + self.bundle.errors.as_ref().map_or(0, |e| e.len())
433    }
434
435    /// Gets the number of warnings.
436    pub fn warning_cnt(&self) -> usize {
437        self.paged.warnings.as_ref().map_or(0, |e| e.len())
438            + self.html.warnings.as_ref().map_or(0, |e| e.len())
439            + self.bundle.warnings.as_ref().map_or(0, |e| e.len())
440    }
441
442    /// Gets the diagnostics.
443    pub fn diagnostics(&self) -> impl Iterator<Item = &typst::diag::SourceDiagnostic> + Clone {
444        self.paged
445            .errors
446            .iter()
447            .chain(self.paged.warnings.iter())
448            .chain(self.html.errors.iter())
449            .chain(self.html.warnings.iter())
450            .chain(self.bundle.errors.iter())
451            .chain(self.bundle.warnings.iter())
452            .flatten()
453    }
454}
455
456impl<F: CompilerFeat> WorldComputeGraph<F> {
457    /// Ensures the main document.
458    pub fn ensure_main(&self) -> SourceResult<()> {
459        CompilationTask::<TypstPagedDocument>::ensure_main(&self.snap.world)
460    }
461
462    /// Compiles once from scratch.
463    pub fn pure_compile<D: ::typst::foundations::Output + Send + Sync + 'static>(
464        &self,
465    ) -> Warned<SourceResult<Arc<D>>> {
466        CompilationTask::<D>::execute(&self.snap.world)
467    }
468
469    /// Compiles once from scratch.
470    pub fn compile(&self) -> Warned<SourceResult<Arc<TypstPagedDocument>>> {
471        self.pure_compile()
472    }
473
474    /// Compiles to html once from scratch.
475    pub fn compile_html(&self) -> Warned<SourceResult<Arc<TypstHtmlDocument>>> {
476        self.pure_compile()
477    }
478
479    /// Compiles paged document with cache
480    pub fn shared_compile(self: &Arc<Self>) -> Result<Option<Arc<TypstPagedDocument>>> {
481        let doc = self.compute::<OptionDocumentTask<TypstPagedDocument>>()?;
482        Ok(doc.as_ref().clone())
483    }
484
485    /// Compiles HTML document with cache
486    pub fn shared_compile_html(self: &Arc<Self>) -> Result<Option<Arc<TypstHtmlDocument>>> {
487        let doc = self.compute::<OptionDocumentTask<TypstHtmlDocument>>()?;
488        Ok(doc.as_ref().clone())
489    }
490
491    /// Gets the diagnostics from shared compilation.
492    #[must_use = "the result must be checked"]
493    pub fn shared_diagnostics(self: &Arc<Self>) -> Result<Arc<DiagnosticsTask>> {
494        self.compute::<DiagnosticsTask>()
495    }
496}