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::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#[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
29pub struct WorldComputeGraph<F: CompilerFeat> {
31 pub snap: CompileSnapshot<F>,
33 entries: Mutex<rpds::RedBlackTreeMapSync<TypeId, WorldComputeEntry>>,
35}
36
37pub trait WorldComputable<F: CompilerFeat>: std::any::Any + Send + Sync + Sized {
39 type Output: Send + Sync + 'static;
41
42 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output>;
80}
81
82impl<F: CompilerFeat> WorldComputeGraph<F> {
83 pub fn new(snap: CompileSnapshot<F>) -> Arc<Self> {
85 Arc::new(Self {
86 snap,
87 entries: Default::default(),
88 })
89 }
90
91 pub fn from_world(world: CompilerWorld<F>) -> Arc<Self> {
93 Self::new(CompileSnapshot::from_world(world))
94 }
95
96 pub fn snapshot(&self) -> Arc<Self> {
98 self.snapshot_unsafe(self.snap.clone())
99 }
100
101 pub fn snapshot_unsafe(&self, snap: CompileSnapshot<F>) -> Arc<Self> {
104 Arc::new(Self {
105 snap,
106 entries: Mutex::new(self.entries.lock().clone()),
107 })
108 }
109
110 pub fn task(&self, inputs: TaskInputs) -> Arc<Self> {
113 let mut snap = self.snap.clone();
114 snap = snap.task(inputs);
115 Self::new(snap)
116 }
117
118 pub fn must_get<T: WorldComputable<F>>(&self) -> Result<Arc<T::Output>> {
120 let res = self.get::<T>().transpose()?;
121 res.with_context("computation not found", || {
122 Some(Box::new([("type", std::any::type_name::<T>().to_owned())]))
123 })
124 }
125
126 pub fn get<T: WorldComputable<F>>(&self) -> Option<Result<Arc<T::Output>>> {
128 let computed = self.computed(TypeId::of::<T>()).computed;
129 computed.get().cloned().map(WorldComputeEntry::cast)
130 }
131
132 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 #[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 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> {
173 &self.snap.world
174 }
175
176 pub fn registry(&self) -> &Arc<F::Registry> {
178 &self.snap.world.registry
179 }
180
181 pub fn library(&self) -> &typst::Library {
183 &self.snap.world.library
184 }
185}
186
187pub trait ExportDetection<F: CompilerFeat, D> {
189 type Config: Send + Sync + 'static;
191
192 fn needs_run(graph: &Arc<WorldComputeGraph<F>>, config: &Self::Config) -> bool;
194}
195
196pub trait ExportComputation<F: CompilerFeat, D> {
198 type Output;
200 type Config: Send + Sync + 'static;
202
203 fn run_with<C: WorldComputable<F, Output = Option<Arc<D>>>>(
205 g: &Arc<WorldComputeGraph<F>>,
206 config: &Self::Config,
207 ) -> Result<Self::Output> {
208 let doc = g.compute::<C>()?;
209 let doc = doc.as_ref().as_ref().context("document not found")?;
210 Self::run(g, doc, config)
211 }
212
213 fn cast_run<'a>(
215 g: &Arc<WorldComputeGraph<F>>,
216 doc: impl TryInto<&'a Arc<D>, Error = tinymist_std::Error>,
217 config: &Self::Config,
218 ) -> Result<Self::Output>
219 where
220 D: 'a,
221 {
222 Self::run(g, doc.try_into()?, config)
223 }
224
225 fn run(
227 g: &Arc<WorldComputeGraph<F>>,
228 doc: &Arc<D>,
229 config: &Self::Config,
230 ) -> Result<Self::Output>;
231}
232
233pub struct ConfigTask<T>(pub T);
235
236impl<F: CompilerFeat, T: Send + Sync + 'static> WorldComputable<F> for ConfigTask<T> {
237 type Output = T;
238
239 fn compute(_graph: &Arc<WorldComputeGraph<F>>) -> Result<T> {
240 let id = std::any::type_name::<T>();
241 panic!("{id:?} must be provided before computation");
242 }
243}
244
245pub type FlagTask<T> = ConfigTask<TaskFlagBase<T>>;
247
248pub struct TaskFlagBase<T> {
250 pub enabled: bool,
252 _phantom: std::marker::PhantomData<T>,
254}
255
256impl<T> FlagTask<T> {
257 pub fn flag(flag: bool) -> Arc<TaskFlagBase<T>> {
259 Arc::new(TaskFlagBase {
260 enabled: flag,
261 _phantom: Default::default(),
262 })
263 }
264}
265
266pub type PagedCompilationTask = CompilationTask<TypstPagedDocument>;
268
269pub type HtmlCompilationTask = CompilationTask<TypstHtmlDocument>;
271
272pub struct CompilationTask<D>(std::marker::PhantomData<D>);
274
275impl<D: typst::Document + Send + Sync + 'static> CompilationTask<D> {
276 pub fn ensure_main<F: CompilerFeat>(world: &CompilerWorld<F>) -> SourceResult<()> {
278 let main_id = world.main_id();
279 let checked = main_id.ok_or_else(|| typst::diag::eco_format!("entry file is not set"));
280 checked.at(Span::detached()).map(|_| ())
281 }
282
283 pub fn execute<F: CompilerFeat>(world: &CompilerWorld<F>) -> Warned<SourceResult<Arc<D>>> {
285 let res = Self::ensure_main(world);
286 if let Err(err) = res {
287 return Warned {
288 output: Err(err),
289 warnings: EcoVec::new(),
290 };
291 }
292
293 let is_paged_compilation = TypeId::of::<D>() == TypeId::of::<TypstPagedDocument>();
294 let is_html_compilation = TypeId::of::<D>() == TypeId::of::<TypstHtmlDocument>();
295
296 let mut world = if is_paged_compilation {
297 world.paged_task()
298 } else if is_html_compilation {
299 world.html_task()
301 } else {
302 Cow::Borrowed(world)
303 };
304
305 world.to_mut().set_is_compiling(true);
306 let compiled = ::typst::compile::<D>(world.as_ref());
307 world.to_mut().set_is_compiling(false);
308
309 let exclude_html_warnings = if !is_html_compilation {
310 compiled.warnings
311 } else if compiled.warnings.len() == 1
312 && compiled.warnings[0]
313 .message
314 .starts_with("html export is under active development")
315 {
316 EcoVec::new()
317 } else {
318 compiled.warnings
319 };
320
321 Warned {
322 output: compiled.output.map(Arc::new),
323 warnings: exclude_html_warnings,
324 }
325 }
326}
327
328impl<F: CompilerFeat, D> WorldComputable<F> for CompilationTask<D>
329where
330 D: typst::Document + Send + Sync + 'static,
331{
332 type Output = Option<Warned<SourceResult<Arc<D>>>>;
333
334 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
335 let enabled = graph.must_get::<FlagTask<CompilationTask<D>>>()?.enabled;
336
337 Ok(enabled.then(|| CompilationTask::<D>::execute(&graph.snap.world)))
338 }
339}
340
341pub struct OptionDocumentTask<D>(std::marker::PhantomData<D>);
343
344impl<F: CompilerFeat, D> WorldComputable<F> for OptionDocumentTask<D>
345where
346 D: typst::Document + Send + Sync + 'static,
347{
348 type Output = Option<Arc<D>>;
349
350 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
351 let doc = graph.compute::<CompilationTask<D>>()?;
352 let compiled = doc
353 .as_ref()
354 .as_ref()
355 .and_then(|warned| warned.output.clone().ok());
356
357 Ok(compiled)
358 }
359}
360
361impl<D> OptionDocumentTask<D> where D: typst::Document + Send + Sync + 'static {}
362
363struct CompilationDiagnostics {
365 errors: Option<EcoVec<typst::diag::SourceDiagnostic>>,
366 warnings: Option<EcoVec<typst::diag::SourceDiagnostic>>,
367}
368
369impl CompilationDiagnostics {
370 fn from_result<T>(result: &Option<Warned<SourceResult<T>>>) -> Self {
372 let errors = result
373 .as_ref()
374 .and_then(|r| r.output.as_ref().map_err(|e| e.clone()).err());
375 let warnings = result.as_ref().map(|r| r.warnings.clone());
376
377 Self { errors, warnings }
378 }
379}
380
381pub struct DiagnosticsTask {
383 paged: CompilationDiagnostics,
384 html: CompilationDiagnostics,
385}
386
387impl<F: CompilerFeat> WorldComputable<F> for DiagnosticsTask {
388 type Output = Self;
389
390 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self> {
391 let paged = graph.compute::<PagedCompilationTask>()?.clone();
392 let html = graph.compute::<HtmlCompilationTask>()?.clone();
393
394 Ok(Self {
395 paged: CompilationDiagnostics::from_result(&paged),
396 html: CompilationDiagnostics::from_result(&html),
397 })
398 }
399}
400
401impl DiagnosticsTask {
402 pub fn error_cnt(&self) -> usize {
404 self.paged.errors.as_ref().map_or(0, |e| e.len())
405 + self.html.errors.as_ref().map_or(0, |e| e.len())
406 }
407
408 pub fn warning_cnt(&self) -> usize {
410 self.paged.warnings.as_ref().map_or(0, |e| e.len())
411 + self.html.warnings.as_ref().map_or(0, |e| e.len())
412 }
413
414 pub fn diagnostics(&self) -> impl Iterator<Item = &typst::diag::SourceDiagnostic> {
416 self.paged
417 .errors
418 .iter()
419 .chain(self.paged.warnings.iter())
420 .chain(self.html.errors.iter())
421 .chain(self.html.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::Document + 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 self.compute::<DiagnosticsTask>()
465 }
466}