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, 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    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    /// Creates a graph from the world.
93    pub fn from_world(world: CompilerWorld<F>) -> Arc<Self> {
94        Self::new(CompileSnapshot::from_world(world))
95    }
96
97    /// Clones the graph with the same snapshot.
98    pub fn snapshot(&self) -> Arc<Self> {
99        self.snapshot_unsafe(self.snap.clone())
100    }
101
102    /// Clones the graph with the same snapshot. Take care of the consistency by
103    /// your self.
104    pub fn snapshot_unsafe(&self, snap: CompileSnapshot<F>) -> Arc<Self> {
105        Arc::new(Self {
106            snap,
107            entries: Mutex::new(self.entries.lock().clone()),
108        })
109    }
110
111    /// Forks a new snapshot that compiles a different document.
112    // todo: share cache if task doesn't change.
113    pub fn task(&self, inputs: TaskInputs) -> Arc<Self> {
114        let mut snap = self.snap.clone();
115        snap = snap.task(inputs);
116        Self::new(snap)
117    }
118
119    /// Gets a world computed.
120    pub fn must_get<T: WorldComputable<F>>(&self) -> Result<Arc<T::Output>> {
121        let res = self.get::<T>().transpose()?;
122        res.with_context("computation not found", || {
123            Some(Box::new([("type", std::any::type_name::<T>().to_owned())]))
124        })
125    }
126
127    /// Gets a world computed.
128    pub fn get<T: WorldComputable<F>>(&self) -> Option<Result<Arc<T::Output>>> {
129        let computed = self.computed(TypeId::of::<T>()).computed;
130        computed.get().cloned().map(WorldComputeEntry::cast)
131    }
132
133    pub fn exact_provide<T: WorldComputable<F>>(&self, ins: Result<Arc<T::Output>>) {
134        if self.provide::<T>(ins).is_err() {
135            panic!(
136                "failed to provide computed instance: {:?}",
137                std::any::type_name::<T>()
138            );
139        }
140    }
141
142    /// Provides some precomputed instance.
143    #[must_use = "the result must be checked"]
144    pub fn provide<T: WorldComputable<F>>(
145        &self,
146        ins: Result<Arc<T::Output>>,
147    ) -> Result<(), Result<Arc<T::Output>>> {
148        let entry = self.computed(TypeId::of::<T>()).computed;
149        let initialized = entry.set(ins.map(|e| e as AnyArc));
150        initialized.map_err(WorldComputeEntry::cast)
151    }
152
153    /// Gets or computes a world computable.
154    pub fn compute<T: WorldComputable<F>>(self: &Arc<Self>) -> Result<Arc<T::Output>> {
155        let entry = self.computed(TypeId::of::<T>()).computed;
156        let computed = entry.get_or_init(|| Ok(Arc::new(T::compute(self)?)));
157        WorldComputeEntry::cast(computed.clone())
158    }
159
160    fn computed(&self, id: TypeId) -> WorldComputeEntry {
161        let mut entries = self.entries.lock();
162        if let Some(entry) = entries.get(&id) {
163            entry.clone()
164        } else {
165            let entry = WorldComputeEntry::default();
166            entries.insert_mut(id, entry.clone());
167            entry
168        }
169    }
170
171    pub fn world(&self) -> &CompilerWorld<F> {
172        &self.snap.world
173    }
174
175    pub fn registry(&self) -> &Arc<F::Registry> {
176        &self.snap.world.registry
177    }
178
179    pub fn library(&self) -> &typst::Library {
180        &self.snap.world.library
181    }
182}
183
184pub trait ExportDetection<F: CompilerFeat, D> {
185    type Config: Send + Sync + 'static;
186
187    fn needs_run(graph: &Arc<WorldComputeGraph<F>>, config: &Self::Config) -> bool;
188}
189
190pub trait ExportComputation<F: CompilerFeat, D> {
191    type Output;
192    type Config: Send + Sync + 'static;
193
194    fn run_with<C: WorldComputable<F, Output = Option<Arc<D>>>>(
195        g: &Arc<WorldComputeGraph<F>>,
196        config: &Self::Config,
197    ) -> Result<Self::Output> {
198        let doc = g.compute::<C>()?;
199        let doc = doc.as_ref().as_ref().context("document not found")?;
200        Self::run(g, doc, config)
201    }
202
203    fn cast_run<'a>(
204        g: &Arc<WorldComputeGraph<F>>,
205        doc: impl TryInto<&'a Arc<D>, Error = tinymist_std::Error>,
206        config: &Self::Config,
207    ) -> Result<Self::Output>
208    where
209        D: 'a,
210    {
211        Self::run(g, doc.try_into()?, config)
212    }
213
214    fn run(
215        g: &Arc<WorldComputeGraph<F>>,
216        doc: &Arc<D>,
217        config: &Self::Config,
218    ) -> Result<Self::Output>;
219}
220
221pub struct ConfigTask<T>(pub T);
222
223impl<F: CompilerFeat, T: Send + Sync + 'static> WorldComputable<F> for ConfigTask<T> {
224    type Output = T;
225
226    fn compute(_graph: &Arc<WorldComputeGraph<F>>) -> Result<T> {
227        let id = std::any::type_name::<T>();
228        panic!("{id:?} must be provided before computation");
229    }
230}
231
232pub type FlagTask<T> = ConfigTask<TaskFlagBase<T>>;
233pub struct TaskFlagBase<T> {
234    pub enabled: bool,
235    _phantom: std::marker::PhantomData<T>,
236}
237
238impl<T> FlagTask<T> {
239    pub fn flag(flag: bool) -> Arc<TaskFlagBase<T>> {
240        Arc::new(TaskFlagBase {
241            enabled: flag,
242            _phantom: Default::default(),
243        })
244    }
245}
246
247pub type PagedCompilationTask = CompilationTask<TypstPagedDocument>;
248pub type HtmlCompilationTask = CompilationTask<TypstHtmlDocument>;
249
250pub struct CompilationTask<D>(std::marker::PhantomData<D>);
251
252impl<D: typst::Document + Send + Sync + 'static> CompilationTask<D> {
253    pub fn ensure_main<F: CompilerFeat>(world: &CompilerWorld<F>) -> SourceResult<()> {
254        let main_id = world.main_id();
255        let checked = main_id.ok_or_else(|| typst::diag::eco_format!("entry file is not set"));
256        checked.at(Span::detached()).map(|_| ())
257    }
258
259    pub fn execute<F: CompilerFeat>(world: &CompilerWorld<F>) -> Warned<SourceResult<Arc<D>>> {
260        let res = Self::ensure_main(world);
261        if let Err(err) = res {
262            return Warned {
263                output: Err(err),
264                warnings: EcoVec::new(),
265            };
266        }
267
268        let is_paged_compilation = TypeId::of::<D>() == TypeId::of::<TypstPagedDocument>();
269        let is_html_compilation = TypeId::of::<D>() == TypeId::of::<TypstHtmlDocument>();
270
271        let mut world = if is_paged_compilation {
272            world.paged_task()
273        } else if is_html_compilation {
274            // todo: create html world once
275            world.html_task()
276        } else {
277            Cow::Borrowed(world)
278        };
279
280        world.to_mut().set_is_compiling(true);
281        let compiled = ::typst::compile::<D>(world.as_ref());
282        world.to_mut().set_is_compiling(false);
283
284        let exclude_html_warnings = if !is_html_compilation {
285            compiled.warnings
286        } else if compiled.warnings.len() == 1
287            && compiled.warnings[0]
288                .message
289                .starts_with("html export is under active development")
290        {
291            EcoVec::new()
292        } else {
293            compiled.warnings
294        };
295
296        Warned {
297            output: compiled.output.map(Arc::new),
298            warnings: exclude_html_warnings,
299        }
300    }
301}
302
303impl<F: CompilerFeat, D> WorldComputable<F> for CompilationTask<D>
304where
305    D: typst::Document + Send + Sync + 'static,
306{
307    type Output = Option<Warned<SourceResult<Arc<D>>>>;
308
309    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
310        let enabled = graph.must_get::<FlagTask<CompilationTask<D>>>()?.enabled;
311
312        Ok(enabled.then(|| CompilationTask::<D>::execute(&graph.snap.world)))
313    }
314}
315
316pub struct OptionDocumentTask<D>(std::marker::PhantomData<D>);
317
318impl<F: CompilerFeat, D> WorldComputable<F> for OptionDocumentTask<D>
319where
320    D: typst::Document + Send + Sync + 'static,
321{
322    type Output = Option<Arc<D>>;
323
324    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
325        let doc = graph.compute::<CompilationTask<D>>()?;
326        let compiled = doc
327            .as_ref()
328            .as_ref()
329            .and_then(|warned| warned.output.clone().ok());
330
331        Ok(compiled)
332    }
333}
334
335impl<D> OptionDocumentTask<D> where D: typst::Document + Send + Sync + 'static {}
336
337struct CompilationDiagnostics {
338    errors: Option<EcoVec<typst::diag::SourceDiagnostic>>,
339    warnings: Option<EcoVec<typst::diag::SourceDiagnostic>>,
340}
341
342impl CompilationDiagnostics {
343    fn from_result<T>(result: &Option<Warned<SourceResult<T>>>) -> Self {
344        let errors = result
345            .as_ref()
346            .and_then(|r| r.output.as_ref().map_err(|e| e.clone()).err());
347        let warnings = result.as_ref().map(|r| r.warnings.clone());
348
349        Self { errors, warnings }
350    }
351}
352
353pub struct DiagnosticsTask {
354    paged: CompilationDiagnostics,
355    html: CompilationDiagnostics,
356}
357
358impl<F: CompilerFeat> WorldComputable<F> for DiagnosticsTask {
359    type Output = Self;
360
361    fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self> {
362        let paged = graph.compute::<PagedCompilationTask>()?.clone();
363        let html = graph.compute::<HtmlCompilationTask>()?.clone();
364
365        Ok(Self {
366            paged: CompilationDiagnostics::from_result(&paged),
367            html: CompilationDiagnostics::from_result(&html),
368        })
369    }
370}
371
372impl DiagnosticsTask {
373    pub fn error_cnt(&self) -> usize {
374        self.paged.errors.as_ref().map_or(0, |e| e.len())
375            + self.html.errors.as_ref().map_or(0, |e| e.len())
376    }
377
378    pub fn warning_cnt(&self) -> usize {
379        self.paged.warnings.as_ref().map_or(0, |e| e.len())
380            + self.html.warnings.as_ref().map_or(0, |e| e.len())
381    }
382
383    pub fn diagnostics(&self) -> impl Iterator<Item = &typst::diag::SourceDiagnostic> {
384        self.paged
385            .errors
386            .iter()
387            .chain(self.paged.warnings.iter())
388            .chain(self.html.errors.iter())
389            .chain(self.html.warnings.iter())
390            .flatten()
391    }
392}
393
394impl<F: CompilerFeat> WorldComputeGraph<F> {
395    pub fn ensure_main(&self) -> SourceResult<()> {
396        CompilationTask::<TypstPagedDocument>::ensure_main(&self.snap.world)
397    }
398
399    /// Compile once from scratch.
400    pub fn pure_compile<D: ::typst::Document + Send + Sync + 'static>(
401        &self,
402    ) -> Warned<SourceResult<Arc<D>>> {
403        CompilationTask::<D>::execute(&self.snap.world)
404    }
405
406    /// Compile once from scratch.
407    pub fn compile(&self) -> Warned<SourceResult<Arc<TypstPagedDocument>>> {
408        self.pure_compile()
409    }
410
411    /// Compile to html once from scratch.
412    pub fn compile_html(&self) -> Warned<SourceResult<Arc<TypstHtmlDocument>>> {
413        self.pure_compile()
414    }
415
416    /// Compile paged document with cache
417    pub fn shared_compile(self: &Arc<Self>) -> Result<Option<Arc<TypstPagedDocument>>> {
418        let doc = self.compute::<OptionDocumentTask<TypstPagedDocument>>()?;
419        Ok(doc.as_ref().clone())
420    }
421
422    /// Compile HTML document with cache
423    pub fn shared_compile_html(self: &Arc<Self>) -> Result<Option<Arc<TypstHtmlDocument>>> {
424        let doc = self.compute::<OptionDocumentTask<TypstHtmlDocument>>()?;
425        Ok(doc.as_ref().clone())
426    }
427
428    /// Gets the diagnostics from shared compilation.
429    pub fn shared_diagnostics(self: &Arc<Self>) -> Result<Arc<DiagnosticsTask>> {
430        self.compute::<DiagnosticsTask>()
431    }
432}