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, EntryReader};
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 snapshot(&self) -> Arc<Self> {
94 Arc::new(Self {
95 snap: self.snap.clone(),
96 entries: Mutex::new(self.entries.lock().clone()),
97 })
98 }
99
100 pub fn must_get<T: WorldComputable<F>>(&self) -> Result<Arc<T::Output>> {
102 let res = self.get::<T>().transpose()?;
103 res.with_context("computation not found", || {
104 Some(Box::new([("type", std::any::type_name::<T>().to_owned())]))
105 })
106 }
107
108 pub fn get<T: WorldComputable<F>>(&self) -> Option<Result<Arc<T::Output>>> {
110 let computed = self.computed(TypeId::of::<T>()).computed;
111 computed.get().cloned().map(WorldComputeEntry::cast)
112 }
113
114 pub fn exact_provide<T: WorldComputable<F>>(&self, ins: Result<Arc<T::Output>>) {
115 if self.provide::<T>(ins).is_err() {
116 panic!(
117 "failed to provide computed instance: {:?}",
118 std::any::type_name::<T>()
119 );
120 }
121 }
122
123 #[must_use = "the result must be checked"]
125 pub fn provide<T: WorldComputable<F>>(
126 &self,
127 ins: Result<Arc<T::Output>>,
128 ) -> Result<(), Result<Arc<T::Output>>> {
129 let entry = self.computed(TypeId::of::<T>()).computed;
130 let initialized = entry.set(ins.map(|e| e as AnyArc));
131 initialized.map_err(WorldComputeEntry::cast)
132 }
133
134 pub fn compute<T: WorldComputable<F>>(self: &Arc<Self>) -> Result<Arc<T::Output>> {
136 let entry = self.computed(TypeId::of::<T>()).computed;
137 let computed = entry.get_or_init(|| Ok(Arc::new(T::compute(self)?)));
138 WorldComputeEntry::cast(computed.clone())
139 }
140
141 fn computed(&self, id: TypeId) -> WorldComputeEntry {
142 let mut entries = self.entries.lock();
143 if let Some(entry) = entries.get(&id) {
144 entry.clone()
145 } else {
146 let entry = WorldComputeEntry::default();
147 entries.insert_mut(id, entry.clone());
148 entry
149 }
150 }
151}
152
153pub trait ExportDetection<F: CompilerFeat, D> {
154 type Config: Send + Sync + 'static;
155
156 fn needs_run(graph: &Arc<WorldComputeGraph<F>>, config: &Self::Config) -> bool;
157}
158
159pub trait ExportComputation<F: CompilerFeat, D> {
160 type Output;
161 type Config: Send + Sync + 'static;
162
163 fn run_with<C: WorldComputable<F, Output = Option<Arc<D>>>>(
164 g: &Arc<WorldComputeGraph<F>>,
165 config: &Self::Config,
166 ) -> Result<Self::Output> {
167 let doc = g.compute::<C>()?;
168 let doc = doc.as_ref().as_ref().context("document not found")?;
169 Self::run(g, doc, config)
170 }
171
172 fn cast_run<'a>(
173 g: &Arc<WorldComputeGraph<F>>,
174 doc: impl TryInto<&'a Arc<D>, Error = tinymist_std::Error>,
175 config: &Self::Config,
176 ) -> Result<Self::Output>
177 where
178 D: 'a,
179 {
180 Self::run(g, doc.try_into()?, config)
181 }
182
183 fn run(
184 g: &Arc<WorldComputeGraph<F>>,
185 doc: &Arc<D>,
186 config: &Self::Config,
187 ) -> Result<Self::Output>;
188}
189
190pub struct ConfigTask<T>(pub T);
191
192impl<F: CompilerFeat, T: Send + Sync + 'static> WorldComputable<F> for ConfigTask<T> {
193 type Output = T;
194
195 fn compute(_graph: &Arc<WorldComputeGraph<F>>) -> Result<T> {
196 let id = std::any::type_name::<T>();
197 panic!("{id:?} must be provided before computation");
198 }
199}
200
201pub type FlagTask<T> = ConfigTask<TaskFlagBase<T>>;
202pub struct TaskFlagBase<T> {
203 pub enabled: bool,
204 _phantom: std::marker::PhantomData<T>,
205}
206
207impl<T> FlagTask<T> {
208 pub fn flag(flag: bool) -> Arc<TaskFlagBase<T>> {
209 Arc::new(TaskFlagBase {
210 enabled: flag,
211 _phantom: Default::default(),
212 })
213 }
214}
215
216pub type PagedCompilationTask = CompilationTask<TypstPagedDocument>;
217pub type HtmlCompilationTask = CompilationTask<TypstHtmlDocument>;
218
219pub struct CompilationTask<D>(std::marker::PhantomData<D>);
220
221impl<F: CompilerFeat, D> WorldComputable<F> for CompilationTask<D>
222where
223 D: typst::Document + Send + Sync + 'static,
224{
225 type Output = Option<Warned<SourceResult<Arc<D>>>>;
226
227 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
228 let enabled = graph.must_get::<FlagTask<CompilationTask<D>>>()?.enabled;
229
230 Ok(enabled.then(|| {
231 let is_paged_compilation = TypeId::of::<D>() == TypeId::of::<TypstPagedDocument>();
233 let is_html_compilation = TypeId::of::<D>() == TypeId::of::<TypstHtmlDocument>();
234
235 let mut world = if is_paged_compilation {
236 graph.snap.world.paged_task()
237 } else if is_html_compilation {
238 graph.snap.world.html_task()
239 } else {
240 Cow::Borrowed(&graph.snap.world)
241 };
242
243 world.to_mut().set_is_compiling(true);
244 let compiled = typst::compile::<D>(world.as_ref());
245 world.to_mut().set_is_compiling(false);
246
247 let exclude_html_warnings = if !is_html_compilation {
248 compiled.warnings
249 } else if compiled.warnings.len() == 1
250 && compiled.warnings[0]
251 .message
252 .starts_with("html export is under active development")
253 {
254 EcoVec::new()
255 } else {
256 compiled.warnings
257 };
258
259 Warned {
260 output: compiled.output.map(Arc::new),
261 warnings: exclude_html_warnings,
262 }
263 }))
264 }
265}
266
267pub struct OptionDocumentTask<D>(std::marker::PhantomData<D>);
268
269impl<F: CompilerFeat, D> WorldComputable<F> for OptionDocumentTask<D>
270where
271 D: typst::Document + Send + Sync + 'static,
272{
273 type Output = Option<Arc<D>>;
274
275 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
276 let doc = graph.compute::<CompilationTask<D>>()?;
277 let compiled = doc
278 .as_ref()
279 .as_ref()
280 .and_then(|warned| warned.output.clone().ok());
281
282 Ok(compiled)
283 }
284}
285
286impl<D> OptionDocumentTask<D> where D: typst::Document + Send + Sync + 'static {}
287
288struct CompilationDiagnostics {
289 errors: Option<EcoVec<typst::diag::SourceDiagnostic>>,
290 warnings: Option<EcoVec<typst::diag::SourceDiagnostic>>,
291}
292
293impl CompilationDiagnostics {
294 fn from_result<T>(result: &Option<Warned<SourceResult<T>>>) -> Self {
295 let errors = result
296 .as_ref()
297 .and_then(|r| r.output.as_ref().map_err(|e| e.clone()).err());
298 let warnings = result.as_ref().map(|r| r.warnings.clone());
299
300 Self { errors, warnings }
301 }
302}
303
304pub struct DiagnosticsTask {
305 paged: CompilationDiagnostics,
306 html: CompilationDiagnostics,
307}
308
309impl<F: CompilerFeat> WorldComputable<F> for DiagnosticsTask {
310 type Output = Self;
311
312 fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self> {
313 let paged = graph.compute::<PagedCompilationTask>()?.clone();
314 let html = graph.compute::<HtmlCompilationTask>()?.clone();
315
316 Ok(Self {
317 paged: CompilationDiagnostics::from_result(&paged),
318 html: CompilationDiagnostics::from_result(&html),
319 })
320 }
321}
322
323impl DiagnosticsTask {
324 pub fn error_cnt(&self) -> usize {
325 self.paged.errors.as_ref().map_or(0, |e| e.len())
326 + self.html.errors.as_ref().map_or(0, |e| e.len())
327 }
328
329 pub fn warning_cnt(&self) -> usize {
330 self.paged.warnings.as_ref().map_or(0, |e| e.len())
331 + self.html.warnings.as_ref().map_or(0, |e| e.len())
332 }
333
334 pub fn diagnostics(&self) -> impl Iterator<Item = &typst::diag::SourceDiagnostic> {
335 self.paged
336 .errors
337 .iter()
338 .chain(self.paged.warnings.iter())
339 .chain(self.html.errors.iter())
340 .chain(self.html.warnings.iter())
341 .flatten()
342 }
343}
344
345impl<F: CompilerFeat> WorldComputeGraph<F> {
400 pub fn ensure_main(&self) -> SourceResult<()> {
401 let main_id = self.snap.world.main_id();
402 let checked = main_id.ok_or_else(|| typst::diag::eco_format!("entry file is not set"));
403 checked.at(Span::detached()).map(|_| ())
404 }
405
406 pub fn pure_compile<D: ::typst::Document>(&self) -> Warned<SourceResult<Arc<D>>> {
408 let res = self.ensure_main();
409 if let Err(err) = res {
410 return Warned {
411 output: Err(err),
412 warnings: EcoVec::new(),
413 };
414 }
415
416 let res = ::typst::compile::<D>(&self.snap.world);
417 Warned {
419 output: res.output.map(Arc::new),
420 warnings: res.warnings,
421 }
422 }
423
424 pub fn compile(&self) -> Warned<SourceResult<Arc<TypstPagedDocument>>> {
426 self.pure_compile()
427 }
428
429 pub fn compile_html(&self) -> Warned<SourceResult<Arc<::typst::html::HtmlDocument>>> {
431 self.pure_compile()
432 }
433
434 }