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