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;
40
41 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output>;
79}
80
81impl<F: CompilerFeat> WorldComputeGraph<F> {
82 pub fn new(snap: CompileSnapshot<F>) -> Arc<Self> {
84 Arc::new(Self {
85 snap,
86 entries: Default::default(),
87 })
88 }
89
90 pub fn from_world(world: CompilerWorld<F>) -> Arc<Self> {
92 Self::new(CompileSnapshot::from_world(world))
93 }
94
95 pub fn snapshot(&self) -> Arc<Self> {
97 self.snapshot_unsafe(self.snap.clone())
98 }
99
100 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 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 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 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 #[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 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 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 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 pub fn compile(&self) -> Warned<SourceResult<Arc<TypstPagedDocument>>> {
406 self.pure_compile()
407 }
408
409 pub fn compile_html(&self) -> Warned<SourceResult<Arc<TypstHtmlDocument>>> {
411 self.pure_compile()
412 }
413
414 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 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 pub fn shared_diagnostics(self: &Arc<Self>) -> Result<Arc<DiagnosticsTask>> {
428 self.compute::<DiagnosticsTask>()
429 }
430}