tinymist_world/
compute.rs1#![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#[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;
42
43 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output>;
81}
82
83impl<F: CompilerFeat> WorldComputeGraph<F> {
84 pub fn new(snap: CompileSnapshot<F>) -> Arc<Self> {
86 Arc::new(Self {
87 snap,
88 entries: Default::default(),
89 })
90 }
91
92 pub fn from_world(world: CompilerWorld<F>) -> Arc<Self> {
94 Self::new(CompileSnapshot::from_world(world))
95 }
96
97 pub fn snapshot(&self) -> Arc<Self> {
99 self.snapshot_unsafe(self.snap.clone())
100 }
101
102 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 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 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 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 #[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> {
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 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 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 pub fn compile(&self) -> Warned<SourceResult<Arc<TypstPagedDocument>>> {
408 self.pure_compile()
409 }
410
411 pub fn compile_html(&self) -> Warned<SourceResult<Arc<TypstHtmlDocument>>> {
413 self.pure_compile()
414 }
415
416 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 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 pub fn shared_diagnostics(self: &Arc<Self>) -> Result<Arc<DiagnosticsTask>> {
430 self.compute::<DiagnosticsTask>()
431 }
432}