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 cached_snapshot: None,
413 handler,
414 export_target,
415 latest_compilation: OnceLock::default(),
416 latest_success_doc: None,
417 deps: Default::default(),
418 committed_revision: 0,
419 }
420 }
421
422 pub fn find_project<'a>(
424 primary: &'a mut ProjectInsState<F, Ext>,
425 dedicates: &'a mut [ProjectInsState<F, Ext>],
426 id: &ProjectInsId,
427 ) -> &'a mut ProjectInsState<F, Ext> {
428 if id == &primary.id {
429 return primary;
430 }
431
432 dedicates.iter_mut().find(|e| e.id == *id).unwrap()
433 }
434
435 pub fn clear_dedicates(&mut self) {
437 self.dedicates.clear();
438 }
439
440 pub fn restart_dedicate(&mut self, group: &str, entry: EntryState) -> Result<ProjectInsId> {
442 let id = ProjectInsId(group.into());
443
444 let verse = CompilerUniverse::<F>::new_raw(
445 entry,
446 self.primary.verse.features.clone(),
447 Some(self.primary.verse.inputs().clone()),
448 self.primary.verse.vfs().fork(),
449 self.primary.verse.registry.clone(),
450 self.primary.verse.font_resolver.clone(),
451 self.primary.verse.creation_timestamp,
452 );
453
454 let mut proj =
455 Self::create_project(id.clone(), verse, self.export_target, self.handler.clone());
456 proj.reason.merge(reason_by_entry_change());
457
458 self.remove_dedicates(&id);
459 self.dedicates.push(proj);
460
461 Ok(id)
462 }
463
464 fn remove_dedicates(&mut self, id: &ProjectInsId) {
465 let proj = self.dedicates.iter().position(|e| e.id == *id);
466 if let Some(idx) = proj {
467 self.handler.notify_removed(id);
469 self.deps.project_deps.remove_mut(id);
470
471 let _proj = self.dedicates.remove(idx);
472 let res = self
475 .dep_tx
476 .send(NotifyMessage::SyncDependency(Box::new(self.deps.clone())));
477 log_send_error("dep_tx", res);
478 } else {
479 log::warn!("ProjectCompiler: settle project not found {id:?}");
480 }
481 }
482
483 pub fn process(&mut self, intr: Interrupt<F>) {
485 self.process_inner(intr);
487 self.handler.clone().on_any_compile_reason(self);
489 }
490
491 fn process_inner(&mut self, intr: Interrupt<F>) {
492 match intr {
493 Interrupt::Compile(id) => {
494 let proj = Self::find_project(&mut self.primary, &mut self.dedicates, &id);
495 proj.verse.increment_revision(|verse| {
497 verse.flush();
498 });
499
500 proj.reason.merge(reason_by_entry_change());
501 }
502 Interrupt::Compiled(artifact) => {
503 let proj =
504 Self::find_project(&mut self.primary, &mut self.dedicates, artifact.id());
505
506 let processed = proj.process_compile(artifact);
507
508 if processed {
509 self.deps
510 .project_deps
511 .insert_mut(proj.id.clone(), proj.deps.clone());
512
513 let event = NotifyMessage::SyncDependency(Box::new(self.deps.clone()));
514 let err = self.dep_tx.send(event);
515 log_send_error("dep_tx", err);
516 }
517 }
518 Interrupt::Settle(id) => {
519 self.remove_dedicates(&id);
520 }
521 Interrupt::ChangeTask(id, change) => {
522 let proj = Self::find_project(&mut self.primary, &mut self.dedicates, &id);
523 proj.verse.increment_revision(|verse| {
524 if let Some(inputs) = change.inputs.clone() {
525 verse.set_inputs(inputs);
526 }
527
528 if let Some(entry) = change.entry.clone() {
529 let res = verse.mutate_entry(entry);
530 if let Err(err) = res {
531 log::error!("ProjectCompiler: change entry error: {err:?}");
532 }
533 }
534 });
535
536 if let Some(entry) = change.entry {
538 if entry.is_inactive() {
540 log::info!("ProjectCompiler: removing diag");
541 self.handler.status(proj.verse.revision.get(), {
542 CompileReport {
543 id: proj.id.clone(),
544 compiling_id: None,
545 page_count: 0,
546 status: CompileStatusEnum::Suspend,
547 }
548 });
549 }
550
551 proj.latest_success_doc = None;
553 }
554
555 proj.reason.merge(reason_by_entry_change());
556 }
557
558 Interrupt::Font(fonts) => {
559 self.projects().for_each(|proj| {
560 let font_changed = proj.verse.increment_revision(|verse| {
561 verse.set_fonts(fonts.clone());
562 verse.font_changed()
563 });
564 if font_changed {
565 proj.reason.merge(reason_by_entry_change());
567 }
568 });
569 }
570 Interrupt::CreationTimestamp(creation_timestamp) => {
571 self.projects().for_each(|proj| {
572 let timestamp_changed = proj.verse.increment_revision(|verse| {
573 verse.set_creation_timestamp(creation_timestamp);
574 verse.creation_timestamp_changed()
576 });
577 if timestamp_changed {
578 proj.reason.merge(reason_by_entry_change());
579 }
580 });
581 }
582 Interrupt::Memory(event) => {
583 log::debug!("ProjectCompiler: memory event incoming");
584
585 let mut files = HashSet::new();
587 if matches!(event, MemoryEvent::Sync(..)) {
588 std::mem::swap(&mut files, &mut self.estimated_shadow_files);
589 }
590
591 let (MemoryEvent::Sync(e) | MemoryEvent::Update(e)) = &event;
592 for path in &e.removes {
593 self.estimated_shadow_files.remove(path);
594 files.insert(Arc::clone(path));
595 }
596 for (path, _) in &e.inserts {
597 self.estimated_shadow_files.insert(Arc::clone(path));
598 files.remove(path);
599 }
600
601 if files.is_empty() && self.dirty_shadow_logical_tick == 0 {
603 let changes = std::iter::repeat_n(event, 1 + self.dedicates.len());
604 let proj = std::iter::once(&mut self.primary).chain(self.dedicates.iter_mut());
605 for (proj, event) in proj.zip(changes) {
606 log::debug!("memory update: vfs {:#?}", proj.verse.vfs().display());
607 let vfs_changed = proj.verse.increment_revision(|verse| {
608 log::debug!("memory update: {:?}", proj.id);
609 Self::apply_memory_changes(&mut verse.vfs(), event.clone());
610 log::debug!("memory update: changed {}", verse.vfs_changed());
611 verse.vfs_changed()
612 });
613 if vfs_changed {
614 proj.reason.merge(reason_by_mem());
615 }
616 log::debug!("memory update: vfs after {:#?}", proj.verse.vfs().display());
617 }
618 return;
619 }
620
621 self.dirty_shadow_logical_tick = self.logical_tick;
624 let event = NotifyMessage::UpstreamUpdate(UpstreamUpdateEvent {
625 invalidates: files.into_iter().collect(),
626 opaque: Box::new(TaggedMemoryEvent {
627 logical_tick: self.logical_tick,
628 event,
629 }),
630 });
631 let err = self.dep_tx.send(event);
632 log_send_error("dep_tx", err);
633 }
634 Interrupt::Save(event) => {
635 let changes = std::iter::repeat_n(&event, 1 + self.dedicates.len());
636 let proj = std::iter::once(&mut self.primary).chain(self.dedicates.iter_mut());
637
638 for (proj, saved_path) in proj.zip(changes) {
639 log::debug!(
640 "ProjectCompiler({}, rev={}): save changes",
641 proj.verse.revision.get(),
642 proj.id
643 );
644
645 let _ = saved_path;
647
648 proj.reason.merge(reason_by_fs());
649 }
650 }
651 Interrupt::Fs(event) => {
652 log::debug!("ProjectCompiler: fs event incoming {event:?}");
653
654 let dirty_tick = &mut self.dirty_shadow_logical_tick;
656 let (changes, is_sync, event) = event.split_with_is_sync();
657 let changes = std::iter::repeat_n(changes, 1 + self.dedicates.len());
658 let proj = std::iter::once(&mut self.primary).chain(self.dedicates.iter_mut());
659
660 for (proj, changes) in proj.zip(changes) {
661 log::debug!(
662 "ProjectCompiler({}, rev={}): fs changes applying",
663 proj.verse.revision.get(),
664 proj.id
665 );
666
667 proj.verse.increment_revision(|verse| {
668 let mut vfs = verse.vfs();
669
670 if Self::apply_delayed_memory_changes(&mut vfs, dirty_tick, &event)
673 .is_none()
674 {
675 log::warn!("ProjectCompiler: unknown upstream update event");
676
677 proj.reason.merge(reason_by_mem());
679 }
680 vfs.notify_fs_changes(changes);
681 });
682
683 log::debug!(
684 "ProjectCompiler({},rev={}): fs changes applied, {is_sync}",
685 proj.id,
686 proj.verse.revision.get(),
687 );
688
689 if !self.ignore_first_sync || !is_sync {
690 proj.reason.merge(reason_by_fs());
691 }
692 }
693 }
694 }
695 }
696
697 fn apply_delayed_memory_changes(
699 verse: &mut RevisingVfs<'_, F::AccessModel>,
700 dirty_shadow_logical_tick: &mut usize,
701 event: &Option<UpstreamUpdateEvent>,
702 ) -> Option<()> {
703 if let Some(event) = event {
705 let TaggedMemoryEvent {
706 logical_tick,
707 event,
708 } = event.opaque.as_ref().downcast_ref()?;
709
710 if logical_tick == dirty_shadow_logical_tick {
712 *dirty_shadow_logical_tick = 0;
713 }
714
715 Self::apply_memory_changes(verse, event.clone());
716 }
717
718 Some(())
719 }
720
721 fn apply_memory_changes(vfs: &mut RevisingVfs<'_, F::AccessModel>, event: MemoryEvent) {
723 if matches!(event, MemoryEvent::Sync(..)) {
724 vfs.reset_shadow();
725 }
726 match event {
727 MemoryEvent::Update(event) | MemoryEvent::Sync(event) => {
728 for path in event.removes {
729 let _ = vfs.unmap_shadow(&path);
730 }
731 for (path, snap) in event.inserts {
732 let _ = vfs.map_shadow(&path, snap);
733 }
734 }
735 }
736 }
737}
738
739pub struct ProjectInsState<F: CompilerFeat, Ext> {
741 pub id: ProjectInsId,
743 pub ext: Ext,
745 pub verse: CompilerUniverse<F>,
747 pub export_target: ExportTarget,
749 pub reason: CompileSignal,
751 pub handler: Arc<dyn CompileHandler<F, Ext>>,
753 deps: EcoVec<ImmutPath>,
755
756 pub cached_snapshot: Option<Arc<WorldComputeGraph<F>>>,
759 pub latest_compilation: OnceLock<CompiledArtifact<F>>,
761 pub latest_success_doc: Option<TypstDocument>,
763
764 committed_revision: usize,
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() {
771 Some(snap) if snap.world().revision() == self.verse.revision => snap.clone(),
772 _ => {
773 let snap = self.make_snapshot();
774 self.cached_snapshot = Some(snap.clone());
775 snap
776 }
777 }
778 }
779
780 fn make_snapshot(&self) -> Arc<WorldComputeGraph<F>> {
782 let world = self.verse.snapshot();
783 let snap = CompileSnapshot {
784 id: self.id.clone(),
785 world,
786 signal: self.reason,
787 success_doc: self.latest_success_doc.clone(),
788 };
789 WorldComputeGraph::new(snap)
790 }
791
792 #[must_use]
795 pub fn may_compile2<'a>(
796 &mut self,
797 compute: impl FnOnce(&Arc<WorldComputeGraph<F>>) + 'a,
798 ) -> Option<impl FnOnce() -> Arc<WorldComputeGraph<F>> + 'a> {
799 if !self.reason.any() || self.verse.entry_state().is_inactive() {
800 return None;
801 }
802
803 let snap = self.snapshot();
804 self.reason = Default::default();
805 Some(move || {
806 compute(&snap);
807 snap
808 })
809 }
810
811 #[must_use]
814 pub fn may_compile(
815 &mut self,
816 handler: &Arc<dyn CompileHandler<F, Ext>>,
817 ) -> Option<impl FnOnce() -> CompiledArtifact<F> + 'static> {
818 if !self.reason.any() || self.verse.entry_state().is_inactive() {
819 return None;
820 }
821
822 let snap = self.snapshot();
823 self.reason = Default::default();
824
825 Some(Self::run_compile(handler.clone(), snap, self.export_target))
826 }
827
828 fn run_compile(
830 h: Arc<dyn CompileHandler<F, Ext>>,
831 graph: Arc<WorldComputeGraph<F>>,
832 export_target: ExportTarget,
833 ) -> impl FnOnce() -> CompiledArtifact<F> {
834 let start = tinymist_std::time::Instant::now();
835
836 let id = graph.world().main_id().unwrap();
838 let revision = graph.world().revision().get();
839
840 h.status(revision, {
841 CompileReport {
842 id: graph.snap.id.clone(),
843 compiling_id: Some(id),
844 page_count: 0,
845 status: CompileStatusEnum::Compiling,
846 }
847 });
848
849 move || {
850 let compiled =
851 CompiledArtifact::from_graph(graph, matches!(export_target, ExportTarget::Html));
852
853 let res = CompileStatusResult {
854 diag: (compiled.warning_cnt() + compiled.error_cnt()) as u32,
855 elapsed: start.elapsed(),
856 };
857 let rep = CompileReport {
858 id: compiled.id().clone(),
859 compiling_id: Some(id),
860 page_count: compiled.doc.as_ref().map_or(0, |doc| doc.num_of_pages()),
861 status: match &compiled.doc {
862 Some(..) => CompileStatusEnum::CompileSuccess(res),
863 None => CompileStatusEnum::CompileError(res),
864 },
865 };
866
867 log_compile_report(&rep);
869
870 if compiled
871 .diagnostics()
872 .any(|d| d.message == FILE_MISSING_ERROR_MSG)
873 {
874 return compiled;
875 }
876
877 h.status(revision, rep);
878 h.notify_compile(&compiled);
879 compiled
880 }
881 }
882
883 fn process_compile(&mut self, artifact: CompiledArtifact<F>) -> bool {
884 let world = &artifact.snap.world;
885 let compiled_revision = world.revision().get();
886 if self.committed_revision >= compiled_revision {
887 return false;
888 }
889
890 let doc = artifact.doc.clone();
892 self.committed_revision = compiled_revision;
893 if doc.is_some() {
894 self.latest_success_doc = doc;
895 }
896 self.cached_snapshot = None; let mut deps = eco_vec![];
900 world.iter_dependencies(&mut |dep| {
901 if let Ok(x) = world.file_path(dep).and_then(|e| e.to_err()) {
902 deps.push(x.into())
903 }
904 });
905
906 self.deps = deps.clone();
907
908 let mut world = world.clone();
909
910 let is_primary = self.id == ProjectInsId("primary".into());
911
912 spawn_cpu(move || {
914 let evict_start = tinymist_std::time::Instant::now();
915 if is_primary {
916 comemo::evict(10);
917
918 world.evict_source_cache(30);
921 }
922 world.evict_vfs(60);
923 let elapsed = evict_start.elapsed();
924 log::debug!("ProjectCompiler: evict cache in {elapsed:?}");
925 });
926
927 true
928 }
929}
930
931fn log_compile_report(rep: &CompileReport) {
932 log::info!("{}", rep.message());
933}
934
935#[inline]
936fn log_send_error<T>(chan: &'static str, res: Result<(), mpsc::error::SendError<T>>) -> bool {
937 res.map_err(|err| log::warn!("ProjectCompiler: send to {chan} error: {err}"))
938 .is_ok()
939}
940
941#[derive(Debug, Clone, Default)]
942struct ProjectDeps {
943 project_deps: rpds::RedBlackTreeMapSync<ProjectInsId, EcoVec<ImmutPath>>,
944}
945
946impl NotifyDeps for ProjectDeps {
947 fn dependencies(&self, f: &mut dyn FnMut(&ImmutPath)) {
948 for deps in self.project_deps.values().flat_map(|e| e.iter()) {
949 f(deps);
950 }
951 }
952}
953
954#[cfg(not(target_arch = "wasm32"))]
956pub fn spawn_cpu<F>(func: F)
958where
959 F: FnOnce() + Send + 'static,
960{
961 rayon::spawn(func);
962}
963
964#[cfg(target_arch = "wasm32")]
965pub fn spawn_cpu<F>(func: F)
967where
968 F: FnOnce() + Send + 'static,
969{
970 func();
971}