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 PagedCompilationTask = CompilationTask<TypstPagedDocument>;
249
250pub type HtmlCompilationTask = CompilationTask<TypstHtmlDocument>;
252
253pub type BundleCompilationTask = CompilationTask<Bundle>;
255
256pub struct CompilationTask<D>(std::marker::PhantomData<D>);
258
259impl<D: Output + Send + Sync + 'static> CompilationTask<D> {
260 pub fn ensure_main<F: CompilerFeat>(world: &CompilerWorld<F>) -> SourceResult<()> {
262 let main_id = world.main_id();
263 let checked = main_id.ok_or_else(|| typst::diag::eco_format!("entry file is not set"));
264 checked.at(Span::detached()).map(|_| ())
265 }
266
267 pub fn execute<F: CompilerFeat>(world: &CompilerWorld<F>) -> Warned<SourceResult<Arc<D>>> {
269 let res = Self::ensure_main(world);
270 if let Err(err) = res {
271 return Warned {
272 output: Err(err),
273 warnings: EcoVec::new(),
274 };
275 }
276
277 let is_paged_compilation = TypeId::of::<D>() == TypeId::of::<TypstPagedDocument>();
278 let is_html_compilation = TypeId::of::<D>() == TypeId::of::<TypstHtmlDocument>();
279
280 let mut world = if is_paged_compilation {
281 world.paged_task()
282 } else if is_html_compilation {
283 world.html_task()
285 } else {
286 Cow::Borrowed(world)
287 };
288
289 world.to_mut().set_is_compiling(true);
290 let compiled = ::typst_shim::compile_opt::<D>(world.as_ref());
291 world.to_mut().set_is_compiling(false);
292
293 let exclude_html_warnings = if !is_html_compilation {
294 compiled.warnings
295 } else if compiled.warnings.len() == 1
296 && compiled.warnings[0]
297 .message
298 .starts_with("html export is under active development")
299 {
300 EcoVec::new()
301 } else {
302 compiled.warnings
303 };
304
305 Warned {
306 output: compiled.output.map(Arc::new),
307 warnings: exclude_html_warnings,
308 }
309 }
310}
311
312impl<F: CompilerFeat, D> WorldComputable<F> for CompilationTask<D>
313where
314 D: Output + Send + Sync + 'static,
315{
316 type Output = Warned<SourceResult<Arc<D>>>;
317
318 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
319 Ok(CompilationTask::<D>::execute(&graph.snap.world))
320 }
321}
322
323pub struct OptionDocumentTask<D>(std::marker::PhantomData<D>);
325
326impl<F: CompilerFeat, D> WorldComputable<F> for OptionDocumentTask<D>
327where
328 D: Output + Send + Sync + 'static,
329{
330 type Output = Option<Arc<D>>;
331
332 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
333 let compiled = graph.compute::<CompilationTask<D>>()?;
334 let compiled = compiled.output.clone().ok();
335
336 Ok(compiled)
337 }
338}
339
340impl<D> OptionDocumentTask<D> where D: Output + Send + Sync + 'static {}
341
342struct CompilationDiagnostics {
344 errors: Option<EcoVec<typst::diag::SourceDiagnostic>>,
345 warnings: Option<EcoVec<typst::diag::SourceDiagnostic>>,
346}
347
348impl CompilationDiagnostics {
349 fn from_result<T>(result: Option<&Warned<SourceResult<T>>>) -> Self {
351 let errors = result.and_then(|r| r.output.as_ref().map_err(|e| e.clone()).err());
352 let warnings = result.map(|r| r.warnings.clone());
353
354 Self { errors, warnings }
355 }
356}
357
358pub struct DiagnosticsTask {
360 paged: CompilationDiagnostics,
361 html: CompilationDiagnostics,
362 bundle: CompilationDiagnostics,
363}
364
365impl DiagnosticsTask {
366 fn collect<F: CompilerFeat>(graph: &WorldComputeGraph<F>) -> Result<Self> {
369 let paged = graph.get::<PagedCompilationTask>().transpose()?;
370 let html = graph.get::<HtmlCompilationTask>().transpose()?;
371 let bundle = graph.get::<BundleCompilationTask>().transpose()?;
372
373 Ok(Self {
374 paged: CompilationDiagnostics::from_result(paged.as_deref()),
375 html: CompilationDiagnostics::from_result(html.as_deref()),
376 bundle: CompilationDiagnostics::from_result(bundle.as_deref()),
377 })
378 }
379
380 pub fn from_errors(paged_errors: Option<EcoVec<typst::diag::SourceDiagnostic>>) -> Self {
382 Self {
383 paged: CompilationDiagnostics {
384 errors: paged_errors,
385 warnings: None,
386 },
387 html: CompilationDiagnostics {
388 errors: None,
389 warnings: None,
390 },
391 bundle: CompilationDiagnostics {
392 errors: None,
393 warnings: None,
394 },
395 }
396 }
397
398 pub fn error_cnt(&self) -> usize {
400 self.paged.errors.as_ref().map_or(0, |e| e.len())
401 + self.html.errors.as_ref().map_or(0, |e| e.len())
402 + self.bundle.errors.as_ref().map_or(0, |e| e.len())
403 }
404
405 pub fn warning_cnt(&self) -> usize {
407 self.paged.warnings.as_ref().map_or(0, |e| e.len())
408 + self.html.warnings.as_ref().map_or(0, |e| e.len())
409 + self.bundle.warnings.as_ref().map_or(0, |e| e.len())
410 }
411
412 pub fn diagnostics(&self) -> impl Iterator<Item = &typst::diag::SourceDiagnostic> + Clone {
414 self.paged
415 .errors
416 .iter()
417 .chain(self.paged.warnings.iter())
418 .chain(self.html.errors.iter())
419 .chain(self.html.warnings.iter())
420 .chain(self.bundle.errors.iter())
421 .chain(self.bundle.warnings.iter())
422 .flatten()
423 }
424}
425
426impl<F: CompilerFeat> WorldComputeGraph<F> {
427 pub fn ensure_main(&self) -> SourceResult<()> {
429 CompilationTask::<TypstPagedDocument>::ensure_main(&self.snap.world)
430 }
431
432 pub fn pure_compile<D: ::typst::foundations::Output + Send + Sync + 'static>(
434 &self,
435 ) -> Warned<SourceResult<Arc<D>>> {
436 CompilationTask::<D>::execute(&self.snap.world)
437 }
438
439 pub fn compile(&self) -> Warned<SourceResult<Arc<TypstPagedDocument>>> {
441 self.pure_compile()
442 }
443
444 pub fn compile_html(&self) -> Warned<SourceResult<Arc<TypstHtmlDocument>>> {
446 self.pure_compile()
447 }
448
449 pub fn shared_compile(self: &Arc<Self>) -> Result<Option<Arc<TypstPagedDocument>>> {
451 let doc = self.compute::<OptionDocumentTask<TypstPagedDocument>>()?;
452 Ok(doc.as_ref().clone())
453 }
454
455 pub fn shared_compile_html(self: &Arc<Self>) -> Result<Option<Arc<TypstHtmlDocument>>> {
457 let doc = self.compute::<OptionDocumentTask<TypstHtmlDocument>>()?;
458 Ok(doc.as_ref().clone())
459 }
460
461 #[must_use = "the result must be checked"]
463 pub fn shared_diagnostics(self: &Arc<Self>) -> Result<Arc<DiagnosticsTask>> {
464 Ok(Arc::new(DiagnosticsTask::collect(self)?))
465 }
466}