tinymist_world/
compute.rs1use 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#[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
31pub struct WorldComputeGraph<F: CompilerFeat> {
33 pub snap: CompileSnapshot<F>,
35 entries: Mutex<rpds::RedBlackTreeMapSync<TypeId, WorldComputeEntry>>,
37}
38
39pub trait WorldComputable<F: CompilerFeat>: std::any::Any + Send + Sync + Sized {
41 type Output: Send + Sync + 'static;
43
44 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output>;
82}
83
84impl<F: CompilerFeat> WorldComputeGraph<F> {
85 pub fn new(snap: CompileSnapshot<F>) -> Arc<Self> {
87 Arc::new(Self {
88 snap,
89 entries: Default::default(),
90 })
91 }
92
93 pub fn from_world(world: CompilerWorld<F>) -> Arc<Self> {
95 Self::new(CompileSnapshot::from_world(world))
96 }
97
98 pub fn snapshot(&self) -> Arc<Self> {
100 self.snapshot_unsafe(self.snap.clone())
101 }
102
103 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 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 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 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 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 #[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 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 pub fn world(&self) -> &CompilerWorld<F> {
175 &self.snap.world
176 }
177
178 pub fn registry(&self) -> &Arc<F::Registry> {
180 &self.snap.world.registry
181 }
182
183 pub fn library(&self) -> &typst::Library {
185 &self.snap.world.library
186 }
187}
188
189pub trait ExportDetection<F: CompilerFeat, D> {
191 type Config: Send + Sync + 'static;
193
194 fn needs_run(graph: &Arc<WorldComputeGraph<F>>, config: &Self::Config) -> bool;
196}
197
198pub trait ExportComputation<F: CompilerFeat, D> {
200 type Output;
202 type Config: Send + Sync + 'static;
204
205 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 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 fn run(
229 g: &Arc<WorldComputeGraph<F>>,
230 doc: &Arc<D>,
231 config: &Self::Config,
232 ) -> Result<Self::Output>;
233}
234
235pub 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
247pub type FlagTask<T> = ConfigTask<TaskFlagBase<T>>;
249
250pub struct TaskFlagBase<T> {
252 pub enabled: bool,
254 _phantom: std::marker::PhantomData<T>,
256}
257
258impl<T> FlagTask<T> {
259 pub fn flag(flag: bool) -> Arc<TaskFlagBase<T>> {
261 Arc::new(TaskFlagBase {
262 enabled: flag,
263 _phantom: Default::default(),
264 })
265 }
266}
267
268pub type PagedCompilationTask = CompilationTask<TypstPagedDocument>;
270
271pub type HtmlCompilationTask = CompilationTask<TypstHtmlDocument>;
273
274pub type BundleCompilationTask = CompilationTask<Bundle>;
276
277pub struct CompilationTask<D>(std::marker::PhantomData<D>);
279
280impl<D: Output + Send + Sync + 'static> CompilationTask<D> {
281 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 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 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
346pub 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
368struct CompilationDiagnostics {
370 errors: Option<EcoVec<typst::diag::SourceDiagnostic>>,
371 warnings: Option<EcoVec<typst::diag::SourceDiagnostic>>,
372}
373
374impl CompilationDiagnostics {
375 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
386pub 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 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 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 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 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 pub fn ensure_main(&self) -> SourceResult<()> {
459 CompilationTask::<TypstPagedDocument>::ensure_main(&self.snap.world)
460 }
461
462 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 pub fn compile(&self) -> Warned<SourceResult<Arc<TypstPagedDocument>>> {
471 self.pure_compile()
472 }
473
474 pub fn compile_html(&self) -> Warned<SourceResult<Arc<TypstHtmlDocument>>> {
476 self.pure_compile()
477 }
478
479 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 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 #[must_use = "the result must be checked"]
493 pub fn shared_diagnostics(self: &Arc<Self>) -> Result<Arc<DiagnosticsTask>> {
494 self.compute::<DiagnosticsTask>()
495 }
496}