1use core::fmt;
4use std::collections::HashSet;
5use std::path::Path;
6use std::sync::{Arc, OnceLock};
7
8use ecow::{EcoString, EcoVec, eco_vec};
9use tinymist_std::error::prelude::Result;
10use tinymist_std::{ImmutPath, typst::TypstDocument};
11use tinymist_task::ExportTarget;
12use tinymist_world::vfs::notify::{
13 FilesystemEvent, MemoryEvent, NotifyDeps, NotifyMessage, UpstreamUpdateEvent,
14};
15use tinymist_world::vfs::{FileId, FsProvider, RevisingVfs, WorkspaceResolver};
16use tinymist_world::{
17 CompileSignal, CompileSnapshot, CompilerFeat, CompilerUniverse, DiagnosticsTask, EntryReader,
18 EntryState, FlagTask, HtmlCompilationTask, PagedCompilationTask, ProjectInsId, TaskInputs,
19 WorldComputeGraph, WorldDeps,
20};
21use tokio::sync::mpsc;
22use typst::diag::FileError;
23
24pub struct CompiledArtifact<F: CompilerFeat> {
26 pub graph: Arc<WorldComputeGraph<F>>,
28 pub diag: Arc<DiagnosticsTask>,
30 pub doc: Option<TypstDocument>,
32 pub deps: OnceLock<EcoVec<FileId>>,
34}
35
36impl<F: CompilerFeat> fmt::Display for CompiledArtifact<F> {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 let rev = self.graph.snap.world.revision();
39 write!(f, "CompiledArtifact({:?}, rev={rev:?})", self.graph.snap.id)
40 }
41}
42
43impl<F: CompilerFeat> std::ops::Deref for CompiledArtifact<F> {
44 type Target = Arc<WorldComputeGraph<F>>;
45
46 fn deref(&self) -> &Self::Target {
47 &self.graph
48 }
49}
50
51impl<F: CompilerFeat> Clone for CompiledArtifact<F> {
52 fn clone(&self) -> Self {
53 Self {
54 graph: self.graph.clone(),
55 doc: self.doc.clone(),
56 diag: self.diag.clone(),
57 deps: self.deps.clone(),
58 }
59 }
60}
61
62impl<F: CompilerFeat> CompiledArtifact<F> {
63 pub fn id(&self) -> &ProjectInsId {
65 &self.graph.snap.id
66 }
67
68 pub fn success_doc(&self) -> Option<TypstDocument> {
70 self.doc
71 .as_ref()
72 .cloned()
73 .or_else(|| self.snap.success_doc.clone())
74 }
75
76 pub fn depended_files(&self) -> &EcoVec<FileId> {
78 self.deps.get_or_init(|| {
79 let mut deps = EcoVec::default();
80 self.graph.snap.world.iter_dependencies(&mut |f| {
81 deps.push(f);
82 });
83
84 deps
85 })
86 }
87
88 pub fn from_graph(graph: Arc<WorldComputeGraph<F>>, is_html: bool) -> CompiledArtifact<F> {
90 let _ = graph.provide::<FlagTask<HtmlCompilationTask>>(Ok(FlagTask::flag(is_html)));
91 let _ = graph.provide::<FlagTask<PagedCompilationTask>>(Ok(FlagTask::flag(!is_html)));
92 let doc = if is_html {
93 graph.shared_compile_html().expect("html").map(From::from)
94 } else {
95 graph.shared_compile().expect("paged").map(From::from)
96 };
97
98 CompiledArtifact {
99 diag: graph.shared_diagnostics().expect("diag"),
100 graph,
101 doc,
102 deps: OnceLock::default(),
103 }
104 }
105
106 pub fn error_cnt(&self) -> usize {
108 self.diag.error_cnt()
109 }
110
111 pub fn warning_cnt(&self) -> usize {
113 self.diag.warning_cnt()
114 }
115
116 pub fn diagnostics(&self) -> impl Iterator<Item = &typst::diag::SourceDiagnostic> {
118 self.diag.diagnostics()
119 }
120
121 pub fn has_errors(&self) -> bool {
123 self.error_cnt() > 0
124 }
125
126 pub fn with_signal(mut self, signal: CompileSignal) -> Self {
128 let mut snap = self.snap.clone();
129 snap.signal = signal;
130
131 self.graph = self.graph.snapshot_unsafe(snap);
132 self
133 }
134}
135
136#[derive(Debug, Clone)]
138pub struct CompileReport {
139 pub id: ProjectInsId,
141 pub compiling_id: Option<FileId>,
143 pub page_count: u32,
145 pub status: CompileStatusEnum,
147}
148
149#[derive(Debug, Clone)]
151pub enum CompileStatusEnum {
152 Suspend,
154 Compiling,
156 CompileSuccess(CompileStatusResult),
158 CompileError(CompileStatusResult),
160 ExportError(CompileStatusResult),
162}
163
164#[derive(Debug, Clone)]
166pub struct CompileStatusResult {
167 diag: u32,
169 elapsed: tinymist_std::time::Duration,
171}
172
173impl CompileReport {
174 pub fn message(&self) -> CompileReportMsg<'_> {
176 CompileReportMsg(self)
177 }
178}
179
180pub struct CompileReportMsg<'a>(&'a CompileReport);
182
183impl fmt::Display for CompileReportMsg<'_> {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 use CompileStatusEnum::*;
186 use CompileStatusResult as Res;
187
188 let input = WorkspaceResolver::display(self.0.compiling_id);
189 let (stage, Res { diag, elapsed }) = match &self.0.status {
190 Suspend => return f.write_str("suspended"),
191 Compiling => return f.write_str("compiling"),
192 CompileSuccess(Res { diag: 0, elapsed }) => {
193 return write!(f, "{input:?}: compilation succeeded in {elapsed:?}");
194 }
195 CompileSuccess(res) => ("compilation succeeded", res),
196 CompileError(res) => ("compilation failed", res),
197 ExportError(res) => ("export failed", res),
198 };
199 write!(f, "{input:?}: {stage} with {diag} warnings in {elapsed:?}")
200 }
201}
202
203pub trait CompileHandler<F: CompilerFeat, Ext>: Send + Sync + 'static {
205 fn on_any_compile_reason(&self, state: &mut ProjectCompiler<F, Ext>);
208 fn notify_compile(&self, res: &CompiledArtifact<F>);
211 fn notify_removed(&self, _id: &ProjectInsId) {}
213 fn status(&self, revision: usize, rep: CompileReport);
215}
216
217impl<F: CompilerFeat + Send + Sync + 'static, Ext: 'static> CompileHandler<F, Ext>
219 for std::marker::PhantomData<fn(F, Ext)>
220{
221 fn on_any_compile_reason(&self, _state: &mut ProjectCompiler<F, Ext>) {
222 log::info!("ProjectHandle: no need to compile");
223 }
224 fn notify_compile(&self, _res: &CompiledArtifact<F>) {}
225 fn status(&self, _revision: usize, _rep: CompileReport) {}
226}
227
228pub enum Interrupt<F: CompilerFeat> {
230 Compile(ProjectInsId),
232 Settle(ProjectInsId),
234 Compiled(CompiledArtifact<F>),
236 ChangeTask(ProjectInsId, TaskInputs),
238 Font(Arc<F::FontResolver>),
240 CreationTimestamp(Option<i64>),
242 Memory(MemoryEvent),
244 Fs(FilesystemEvent),
246 Save(ImmutPath),
248}
249
250impl<F: CompilerFeat> fmt::Debug for Interrupt<F> {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 match self {
253 Interrupt::Compile(id) => write!(f, "Compile({id:?})"),
254 Interrupt::Settle(id) => write!(f, "Settle({id:?})"),
255 Interrupt::Compiled(artifact) => write!(f, "Compiled({:?})", artifact.id()),
256 Interrupt::ChangeTask(id, change) => {
257 write!(f, "ChangeTask({id:?}, entry={:?})", change.entry.is_some())
258 }
259 Interrupt::Font(..) => write!(f, "Font(..)"),
260 Interrupt::CreationTimestamp(ts) => write!(f, "CreationTimestamp({ts:?})"),
261 Interrupt::Memory(..) => write!(f, "Memory(..)"),
262 Interrupt::Fs(..) => write!(f, "Fs(..)"),
263 Interrupt::Save(path) => write!(f, "Save({path:?})"),
264 }
265 }
266}
267
268fn no_reason() -> CompileSignal {
269 CompileSignal::default()
270}
271
272fn reason_by_mem() -> CompileSignal {
273 CompileSignal {
274 by_mem_events: true,
275 ..CompileSignal::default()
276 }
277}
278
279fn reason_by_fs() -> CompileSignal {
280 CompileSignal {
281 by_fs_events: true,
282 ..CompileSignal::default()
283 }
284}
285
286fn reason_by_entry_change() -> CompileSignal {
287 CompileSignal {
288 by_entry_update: true,
289 ..CompileSignal::default()
290 }
291}
292
293struct TaggedMemoryEvent {
295 logical_tick: usize,
297 event: MemoryEvent,
299}
300
301pub struct CompileServerOpts<F: CompilerFeat, Ext> {
303 pub handler: Arc<dyn CompileHandler<F, Ext>>,
305 pub ignore_first_sync: bool,
307 pub export_target: ExportTarget,
309}
310
311impl<F: CompilerFeat + Send + Sync + 'static, Ext: 'static> Default for CompileServerOpts<F, Ext> {
312 fn default() -> Self {
313 Self {
314 handler: Arc::new(std::marker::PhantomData),
315 ignore_first_sync: false,
316 export_target: ExportTarget::Paged,
317 }
318 }
319}
320
321const FILE_MISSING_ERROR_MSG: EcoString = EcoString::inline("t-file-missing");
322pub const FILE_MISSING_ERROR: FileError = FileError::Other(Some(FILE_MISSING_ERROR_MSG));
324
325pub struct ProjectCompiler<F: CompilerFeat, Ext> {
327 pub handler: Arc<dyn CompileHandler<F, Ext>>,
329 export_target: ExportTarget,
331 dep_tx: mpsc::UnboundedSender<NotifyMessage>,
333 pub ignore_first_sync: bool,
335
336 logical_tick: usize,
338 dirty_shadow_logical_tick: usize,
340 estimated_shadow_files: HashSet<Arc<Path>>,
342
343 pub primary: ProjectInsState<F, Ext>,
345 pub dedicates: Vec<ProjectInsState<F, Ext>>,
347 deps: ProjectDeps,
349}
350
351impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCompiler<F, Ext> {
352 pub fn new(
354 verse: CompilerUniverse<F>,
355 dep_tx: mpsc::UnboundedSender<NotifyMessage>,
356 CompileServerOpts {
357 handler,
358 ignore_first_sync,
359 export_target,
360 }: CompileServerOpts<F, Ext>,
361 ) -> Self {
362 let primary = Self::create_project(
363 ProjectInsId("primary".into()),
364 verse,
365 export_target,
366 handler.clone(),
367 );
368 Self {
369 handler,
370 dep_tx,
371 export_target,
372
373 logical_tick: 1,
374 dirty_shadow_logical_tick: 0,
375
376 estimated_shadow_files: Default::default(),
377 ignore_first_sync,
378
379 primary,
380 deps: Default::default(),
381 dedicates: vec![],
382 }
383 }
384
385 pub fn snapshot(&mut self) -> Arc<WorldComputeGraph<F>> {
387 self.primary.snapshot()
388 }
389
390 pub fn compile_once(&mut self) -> CompiledArtifact<F> {
392 let snap = self.primary.make_snapshot();
393 ProjectInsState::run_compile(self.handler.clone(), snap, self.export_target)()
394 }
395
396 pub fn projects(&mut self) -> impl Iterator<Item = &mut ProjectInsState<F, Ext>> {
398 std::iter::once(&mut self.primary).chain(self.dedicates.iter_mut())
399 }
400
401 fn create_project(
402 id: ProjectInsId,
403 verse: CompilerUniverse<F>,
404 export_target: ExportTarget,
405 handler: Arc<dyn CompileHandler<F, Ext>>,
406 ) -> ProjectInsState<F, Ext> {
407 ProjectInsState {
408 id,
409 ext: Default::default(),
410 verse,
411 reason: no_reason(),
412 handler,
413 export_target,
414 deps: Default::default(),
415 latest_compilation: None,
416 cached_snapshot: None,
417 }
418 }
419
420 pub fn find_project<'a>(
422 primary: &'a mut ProjectInsState<F, Ext>,
423 dedicates: &'a mut [ProjectInsState<F, Ext>],
424 id: &ProjectInsId,
425 ) -> &'a mut ProjectInsState<F, Ext> {
426 if id == &primary.id {
427 return primary;
428 }
429
430 dedicates.iter_mut().find(|e| e.id == *id).unwrap()
431 }
432
433 pub fn clear_dedicates(&mut self) {
435 self.dedicates.clear();
436 }
437
438 pub fn restart_dedicate(&mut self, group: &str, entry: EntryState) -> Result<ProjectInsId> {
440 let id = ProjectInsId(group.into());
441
442 let verse = CompilerUniverse::<F>::new_raw(
443 entry,
444 self.primary.verse.features.clone(),
445 Some(self.primary.verse.inputs().clone()),
446 self.primary.verse.vfs().fork(),
447 self.primary.verse.registry.clone(),
448 self.primary.verse.font_resolver.clone(),
449 self.primary.verse.creation_timestamp,
450 );
451
452 let mut proj =
453 Self::create_project(id.clone(), verse, self.export_target, self.handler.clone());
454 proj.reason.merge(reason_by_entry_change());
455
456 self.remove_dedicates(&id);
457 self.dedicates.push(proj);
458
459 Ok(id)
460 }
461
462 fn remove_dedicates(&mut self, id: &ProjectInsId) {
463 let proj = self.dedicates.iter().position(|e| e.id == *id);
464 if let Some(idx) = proj {
465 self.handler.notify_removed(id);
467 self.deps.project_deps.remove_mut(id);
468
469 let _proj = self.dedicates.remove(idx);
470 let res = self
473 .dep_tx
474 .send(NotifyMessage::SyncDependency(Box::new(self.deps.clone())));
475 log_send_error("dep_tx", res);
476 } else {
477 log::warn!("ProjectCompiler: settle project not found {id:?}");
478 }
479 }
480
481 pub fn process(&mut self, intr: Interrupt<F>) {
483 self.process_inner(intr);
485 self.handler.clone().on_any_compile_reason(self);
487 }
488
489 fn process_inner(&mut self, intr: Interrupt<F>) {
490 match intr {
491 Interrupt::Compile(id) => {
492 let proj = Self::find_project(&mut self.primary, &mut self.dedicates, &id);
493 proj.verse.increment_revision(|verse| {
495 verse.flush();
496 });
497
498 proj.reason.merge(reason_by_entry_change());
499 }
500 Interrupt::Compiled(artifact) => {
501 let proj =
502 Self::find_project(&mut self.primary, &mut self.dedicates, artifact.id());
503
504 let processed = proj.process_compile(artifact);
505
506 if processed {
507 self.deps
508 .project_deps
509 .insert_mut(proj.id.clone(), proj.deps.clone());
510
511 let event = NotifyMessage::SyncDependency(Box::new(self.deps.clone()));
512 let err = self.dep_tx.send(event);
513 log_send_error("dep_tx", err);
514 }
515 }
516 Interrupt::Settle(id) => {
517 self.remove_dedicates(&id);
518 }
519 Interrupt::ChangeTask(id, change) => {
520 let proj = Self::find_project(&mut self.primary, &mut self.dedicates, &id);
521 proj.verse.increment_revision(|verse| {
522 if let Some(inputs) = change.inputs.clone() {
523 verse.set_inputs(inputs);
524 }
525
526 if let Some(entry) = change.entry.clone() {
527 let res = verse.mutate_entry(entry);
528 if let Err(err) = res {
529 log::error!("ProjectCompiler: change entry error: {err:?}");
530 }
531 }
532 });
533
534 if let Some(entry) = change.entry {
536 if entry.is_inactive() {
538 log::info!("ProjectCompiler: removing diag");
539 self.handler.status(proj.verse.revision.get(), {
540 CompileReport {
541 id: proj.id.clone(),
542 compiling_id: None,
543 page_count: 0,
544 status: CompileStatusEnum::Suspend,
545 }
546 });
547 }
548
549 proj.latest_compilation = None;
551 }
552
553 proj.reason.merge(reason_by_entry_change());
554 }
555
556 Interrupt::Font(fonts) => {
557 self.projects().for_each(|proj| {
558 let font_changed = proj.verse.increment_revision(|verse| {
559 verse.set_fonts(fonts.clone());
560 verse.font_changed()
561 });
562 if font_changed {
563 proj.reason.merge(reason_by_entry_change());
565 }
566 });
567 }
568 Interrupt::CreationTimestamp(creation_timestamp) => {
569 self.projects().for_each(|proj| {
570 let timestamp_changed = proj.verse.increment_revision(|verse| {
571 verse.set_creation_timestamp(creation_timestamp);
572 verse.creation_timestamp_changed()
574 });
575 if timestamp_changed {
576 proj.reason.merge(reason_by_entry_change());
577 }
578 });
579 }
580 Interrupt::Memory(event) => {
581 log::debug!("ProjectCompiler: memory event incoming");
582
583 let mut files = HashSet::new();
585 if matches!(event, MemoryEvent::Sync(..)) {
586 std::mem::swap(&mut files, &mut self.estimated_shadow_files);
587 }
588
589 let (MemoryEvent::Sync(e) | MemoryEvent::Update(e)) = &event;
590 for path in &e.removes {
591 self.estimated_shadow_files.remove(path);
592 files.insert(Arc::clone(path));
593 }
594 for (path, _) in &e.inserts {
595 self.estimated_shadow_files.insert(Arc::clone(path));
596 files.remove(path);
597 }
598
599 if files.is_empty() && self.dirty_shadow_logical_tick == 0 {
601 let changes = std::iter::repeat_n(event, 1 + self.dedicates.len());
602 let proj = std::iter::once(&mut self.primary).chain(self.dedicates.iter_mut());
603 for (proj, event) in proj.zip(changes) {
604 log::debug!("memory update: vfs {:#?}", proj.verse.vfs().display());
605 let vfs_changed = proj.verse.increment_revision(|verse| {
606 log::debug!("memory update: {:?}", proj.id);
607 Self::apply_memory_changes(&mut verse.vfs(), event.clone());
608 log::debug!("memory update: changed {}", verse.vfs_changed());
609 verse.vfs_changed()
610 });
611 if vfs_changed {
612 proj.reason.merge(reason_by_mem());
613 }
614 log::debug!("memory update: vfs after {:#?}", proj.verse.vfs().display());
615 }
616 return;
617 }
618
619 self.dirty_shadow_logical_tick = self.logical_tick;
622 let event = NotifyMessage::UpstreamUpdate(UpstreamUpdateEvent {
623 invalidates: files.into_iter().collect(),
624 opaque: Box::new(TaggedMemoryEvent {
625 logical_tick: self.logical_tick,
626 event,
627 }),
628 });
629 let err = self.dep_tx.send(event);
630 log_send_error("dep_tx", err);
631 }
632 Interrupt::Save(event) => {
633 let changes = std::iter::repeat_n(&event, 1 + self.dedicates.len());
634 let proj = std::iter::once(&mut self.primary).chain(self.dedicates.iter_mut());
635
636 for (proj, saved_path) in proj.zip(changes) {
637 log::debug!(
638 "ProjectCompiler({}, rev={}): save changes",
639 proj.verse.revision.get(),
640 proj.id
641 );
642
643 let _ = saved_path;
645
646 proj.reason.merge(reason_by_fs());
647 }
648 }
649 Interrupt::Fs(event) => {
650 log::debug!("ProjectCompiler: fs event incoming {event:?}");
651
652 let dirty_tick = &mut self.dirty_shadow_logical_tick;
654 let (changes, is_sync, event) = event.split_with_is_sync();
655 let changes = std::iter::repeat_n(changes, 1 + self.dedicates.len());
656 let proj = std::iter::once(&mut self.primary).chain(self.dedicates.iter_mut());
657
658 for (proj, changes) in proj.zip(changes) {
659 log::debug!(
660 "ProjectCompiler({}, rev={}): fs changes applying",
661 proj.verse.revision.get(),
662 proj.id
663 );
664
665 proj.verse.increment_revision(|verse| {
666 let mut vfs = verse.vfs();
667
668 if Self::apply_delayed_memory_changes(&mut vfs, dirty_tick, &event)
671 .is_none()
672 {
673 log::warn!("ProjectCompiler: unknown upstream update event");
674
675 proj.reason.merge(reason_by_mem());
677 }
678 vfs.notify_fs_changes(changes);
679 });
680
681 log::debug!(
682 "ProjectCompiler({},rev={}): fs changes applied, {is_sync}",
683 proj.id,
684 proj.verse.revision.get(),
685 );
686
687 if !self.ignore_first_sync || !is_sync {
688 proj.reason.merge(reason_by_fs());
689 }
690 }
691 }
692 }
693 }
694
695 fn apply_delayed_memory_changes(
697 verse: &mut RevisingVfs<'_, F::AccessModel>,
698 dirty_shadow_logical_tick: &mut usize,
699 event: &Option<UpstreamUpdateEvent>,
700 ) -> Option<()> {
701 if let Some(event) = event {
703 let TaggedMemoryEvent {
704 logical_tick,
705 event,
706 } = event.opaque.as_ref().downcast_ref()?;
707
708 if logical_tick == dirty_shadow_logical_tick {
710 *dirty_shadow_logical_tick = 0;
711 }
712
713 Self::apply_memory_changes(verse, event.clone());
714 }
715
716 Some(())
717 }
718
719 fn apply_memory_changes(vfs: &mut RevisingVfs<'_, F::AccessModel>, event: MemoryEvent) {
721 if matches!(event, MemoryEvent::Sync(..)) {
722 vfs.reset_shadow();
723 }
724 match event {
725 MemoryEvent::Update(event) | MemoryEvent::Sync(event) => {
726 for path in event.removes {
727 let _ = vfs.unmap_shadow(&path);
728 }
729 for (path, snap) in event.inserts {
730 let _ = vfs.map_shadow(&path, snap);
731 }
732 }
733 }
734 }
735}
736
737pub struct ProjectInsState<F: CompilerFeat, Ext> {
739 pub id: ProjectInsId,
741 pub ext: Ext,
743 pub verse: CompilerUniverse<F>,
745 pub export_target: ExportTarget,
747 pub reason: CompileSignal,
749 pub handler: Arc<dyn CompileHandler<F, Ext>>,
751 deps: EcoVec<ImmutPath>,
753
754 latest_compilation: Option<CompilationState>,
755 cached_snapshot: Option<Arc<WorldComputeGraph<F>>>,
758}
759
760struct CompilationState {
762 revision: usize,
763 doc: Option<TypstDocument>,
765}
766
767impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
768 pub fn snapshot(&mut self) -> Arc<WorldComputeGraph<F>> {
770 match self.cached_snapshot.as_ref() {
772 Some(cached) if cached.world().revision() == self.verse.revision => cached.clone(),
773 _ => {
774 let snap = self.make_snapshot();
775 self.cached_snapshot = Some(snap.clone());
776 snap
777 }
778 }
779 }
780
781 fn make_snapshot(&self) -> Arc<WorldComputeGraph<F>> {
783 let world = self.verse.snapshot();
784 let snap = CompileSnapshot {
785 id: self.id.clone(),
786 world,
787 signal: self.reason,
788 success_doc: self.latest_compilation.as_ref().and_then(|c| c.doc.clone()),
789 };
790 WorldComputeGraph::new(snap)
791 }
792
793 #[must_use]
796 pub fn may_compile2<'a>(
797 &mut self,
798 compute: impl FnOnce(&Arc<WorldComputeGraph<F>>) + 'a,
799 ) -> Option<impl FnOnce() -> Arc<WorldComputeGraph<F>> + 'a> {
800 if !self.reason.any() || self.verse.entry_state().is_inactive() {
801 return None;
802 }
803
804 let snap = self.snapshot();
805 self.reason = Default::default();
806 Some(move || {
807 compute(&snap);
808 snap
809 })
810 }
811
812 #[must_use]
815 pub fn may_compile(
816 &mut self,
817 handler: &Arc<dyn CompileHandler<F, Ext>>,
818 ) -> Option<impl FnOnce() -> CompiledArtifact<F> + 'static> {
819 if !self.reason.any() || self.verse.entry_state().is_inactive() {
820 return None;
821 }
822
823 let snap = self.snapshot();
824 self.reason = Default::default();
825
826 Some(Self::run_compile(handler.clone(), snap, self.export_target))
827 }
828
829 fn run_compile(
831 h: Arc<dyn CompileHandler<F, Ext>>,
832 graph: Arc<WorldComputeGraph<F>>,
833 export_target: ExportTarget,
834 ) -> impl FnOnce() -> CompiledArtifact<F> {
835 let start = tinymist_std::time::Instant::now();
836
837 let id = graph.world().main_id().unwrap();
839 let revision = graph.world().revision().get();
840
841 h.status(revision, {
842 CompileReport {
843 id: graph.snap.id.clone(),
844 compiling_id: Some(id),
845 page_count: 0,
846 status: CompileStatusEnum::Compiling,
847 }
848 });
849
850 move || {
851 let compiled =
852 CompiledArtifact::from_graph(graph, matches!(export_target, ExportTarget::Html));
853
854 let res = CompileStatusResult {
855 diag: (compiled.warning_cnt() + compiled.error_cnt()) as u32,
856 elapsed: start.elapsed(),
857 };
858 let rep = CompileReport {
859 id: compiled.id().clone(),
860 compiling_id: Some(id),
861 page_count: compiled.doc.as_ref().map_or(0, |doc| doc.num_of_pages()),
862 status: match &compiled.doc {
863 Some(..) => CompileStatusEnum::CompileSuccess(res),
864 None => CompileStatusEnum::CompileError(res),
865 },
866 };
867
868 log_compile_report(&rep);
870
871 if compiled
872 .diagnostics()
873 .any(|d| d.message == FILE_MISSING_ERROR_MSG)
874 {
875 return compiled;
876 }
877
878 h.status(revision, rep);
879 h.notify_compile(&compiled);
880 compiled
881 }
882 }
883
884 fn process_compile(&mut self, artifact: CompiledArtifact<F>) -> bool {
885 let world = &artifact.snap.world;
886 let compiled_revision = world.revision().get();
887 if let Some(cur) = &self.latest_compilation
888 && cur.revision >= compiled_revision
889 {
890 return false;
891 }
892
893 let doc = artifact.doc.clone();
895 self.latest_compilation = Some(CompilationState {
896 revision: compiled_revision,
897 doc,
898 });
899 self.cached_snapshot = None;
901
902 let mut deps = eco_vec![];
904 world.iter_dependencies(&mut |dep| {
905 if let Ok(x) = world.file_path(dep).and_then(|e| e.to_err()) {
906 deps.push(x.into())
907 }
908 });
909
910 self.deps = deps.clone();
911
912 let mut world = world.clone();
913
914 let is_primary = self.id == ProjectInsId("primary".into());
915
916 spawn_cpu(move || {
918 let evict_start = tinymist_std::time::Instant::now();
919 if is_primary {
920 comemo::evict(10);
921
922 world.evict_source_cache(30);
925 }
926 world.evict_vfs(60);
927 let elapsed = evict_start.elapsed();
928 log::debug!("ProjectCompiler: evict cache in {elapsed:?}");
929 });
930
931 true
932 }
933}
934
935fn log_compile_report(rep: &CompileReport) {
936 log::info!("{}", rep.message());
937}
938
939#[inline]
940fn log_send_error<T>(chan: &'static str, res: Result<(), mpsc::error::SendError<T>>) -> bool {
941 res.map_err(|err| log::warn!("ProjectCompiler: send to {chan} error: {err}"))
942 .is_ok()
943}
944
945#[derive(Debug, Clone, Default)]
946struct ProjectDeps {
947 project_deps: rpds::RedBlackTreeMapSync<ProjectInsId, EcoVec<ImmutPath>>,
948}
949
950impl NotifyDeps for ProjectDeps {
951 fn dependencies(&self, f: &mut dyn FnMut(&ImmutPath)) {
952 for deps in self.project_deps.values().flat_map(|e| e.iter()) {
953 f(deps);
954 }
955 }
956}
957
958#[cfg(not(target_arch = "wasm32"))]
960pub fn spawn_cpu<F>(func: F)
962where
963 F: FnOnce() + Send + 'static,
964{
965 rayon::spawn(func);
966}
967
968#[cfg(target_arch = "wasm32")]
969pub fn spawn_cpu<F>(func: F)
971where
972 F: FnOnce() + Send + 'static,
973{
974 func();
975}