feat: watch dependencies of multiple projects (#1231)

This commit is contained in:
Myriad-Dreamin 2025-01-30 01:44:57 +08:00 committed by GitHub
parent 8d588c1057
commit 2b56e71435
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 74 additions and 23 deletions

View file

@ -24,6 +24,7 @@ pathdiff.workspace = true
tokio.workspace = true
rayon.workspace = true
reflexo-typst.workspace = true
rpds.workspace = true
semver.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -5,12 +5,13 @@ use std::collections::HashSet;
use std::path::Path;
use std::sync::{Arc, OnceLock};
use ecow::{EcoString, EcoVec};
use ecow::{eco_vec, EcoString, EcoVec};
use reflexo_typst::features::{CompileFeature, FeatureSet, WITH_COMPILING_STATUS_FEATURE};
use reflexo_typst::{CompileEnv, CompileReport, Compiler, TypstDocument};
use tinymist_std::error::prelude::Result;
use tinymist_std::ImmutPath;
use tinymist_world::vfs::notify::{
FilesystemEvent, MemoryEvent, NotifyMessage, UpstreamUpdateEvent,
FilesystemEvent, MemoryEvent, NotifyDeps, NotifyMessage, UpstreamUpdateEvent,
};
use tinymist_world::vfs::{FileId, FsProvider, RevisingVfs};
use tinymist_world::{
@ -30,7 +31,7 @@ pub type LspInterrupt = Interrupt<LspCompilerFeat>;
/// Project instance id. This is slightly different from the project ids that
/// persist in disk.
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProjectInsId(EcoString);
impl fmt::Display for ProjectInsId {
@ -368,6 +369,8 @@ pub struct ProjectCompiler<F: CompilerFeat, Ext> {
pub primary: ProjectInsState<F, Ext>,
/// The states for dedicate tasks
pub dedicates: Vec<ProjectInsState<F, Ext>>,
/// The project file dependencies.
deps: ProjectDeps,
}
impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCompiler<F, Ext> {
@ -385,7 +388,6 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
ProjectInsId("primary".into()),
verse,
handler.clone(),
dep_tx.clone(),
feature_set.clone(),
);
Self {
@ -399,6 +401,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
estimated_shadow_files: Default::default(),
primary,
deps: Default::default(),
dedicates: vec![],
}
}
@ -423,7 +426,6 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
id: ProjectInsId,
verse: CompilerUniverse<F>,
handler: Arc<dyn CompileHandler<F, Ext>>,
dep_tx: mpsc::UnboundedSender<NotifyMessage>,
feature_set: FeatureSet,
) -> ProjectInsState<F, Ext> {
ProjectInsState {
@ -433,7 +435,6 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
reason: no_reason(),
snapshot: None,
handler,
dep_tx,
compilation: OnceLock::default(),
latest_doc: None,
latest_success_doc: None,
@ -443,6 +444,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
.clone()
.configure(&WITH_COMPILING_STATUS_FEATURE, true),
),
deps: Default::default(),
committed_revision: 0,
}
}
@ -476,7 +478,6 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
id.clone(),
verse,
self.handler.clone(),
self.dep_tx.clone(),
self.primary.once_feature_set.as_ref().to_owned(),
);
@ -491,9 +492,15 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
if let Some(idx) = proj {
// Resets the handle state, e.g. notified revision
self.handler.notify_removed(id);
self.deps.project_deps.remove_mut(id);
let _proj = self.dedicates.remove(idx);
// todo: kill compilations
let res = self
.dep_tx
.send(NotifyMessage::SyncDependency(Box::new(self.deps.clone())));
log_send_error("dep_tx", res);
} else {
log::warn!("ProjectCompiler: settle project not found {id:?}");
}
@ -521,7 +528,17 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
Interrupt::Compiled(artifact) => {
let proj = Self::find_project(&mut self.primary, &mut self.dedicates, &artifact.id);
proj.process_compile(artifact);
let processed = proj.process_compile(artifact);
if processed {
self.deps
.project_deps
.insert_mut(proj.id.clone(), proj.deps.clone());
let event = NotifyMessage::SyncDependency(Box::new(self.deps.clone()));
let err = self.dep_tx.send(event);
log_send_error("dep_tx", err);
}
}
Interrupt::Settle(id) => {
self.remove_dedicates(&id);
@ -715,8 +732,8 @@ pub struct ProjectInsState<F: CompilerFeat, Ext> {
pub compilation: OnceLock<CompiledArtifact<F>>,
/// The compilation handle.
pub handler: Arc<dyn CompileHandler<F, Ext>>,
/// Channel for sending interrupts to the compiler actor.
dep_tx: mpsc::UnboundedSender<NotifyMessage>,
/// The file dependencies.
deps: EcoVec<ImmutPath>,
/// The latest compiled document.
pub(crate) latest_doc: Option<Arc<TypstDocument>>,
@ -819,11 +836,11 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
}
}
fn process_compile(&mut self, artifact: CompiledArtifact<F>) {
fn process_compile(&mut self, artifact: CompiledArtifact<F>) -> bool {
let world = &artifact.snap.world;
let compiled_revision = world.revision().get();
if self.committed_revision >= compiled_revision {
return;
return false;
}
// Update state.
@ -835,15 +852,14 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
}
// Notify the new file dependencies.
let mut deps = vec![];
let mut deps = eco_vec![];
world.iter_dependencies(&mut |dep| {
if let Ok(x) = world.file_path(dep).and_then(|e| e.to_err()) {
deps.push(x.into())
}
});
let event = NotifyMessage::SyncDependency(deps);
let err = self.dep_tx.send(event);
log_send_error("dep_tx", err);
self.deps = deps.clone();
let mut world = artifact.snap.world;
@ -863,6 +879,8 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
let elapsed = evict_start.elapsed();
log::info!("ProjectCompiler: evict cache in {elapsed:?}");
});
true
}
}
@ -877,3 +895,16 @@ fn log_send_error<T>(chan: &'static str, res: Result<(), mpsc::error::SendError<
res.map_err(|err| log::warn!("ProjectCompiler: send to {chan} error: {err}"))
.is_ok()
}
#[derive(Debug, Clone, Default)]
struct ProjectDeps {
project_deps: rpds::RedBlackTreeMapSync<ProjectInsId, EcoVec<ImmutPath>>,
}
impl NotifyDeps for ProjectDeps {
fn dependencies(&self, f: &mut dyn FnMut(&ImmutPath)) {
for deps in self.project_deps.values().flat_map(|e| e.iter()) {
f(deps);
}
}
}

View file

@ -13,6 +13,7 @@ use std::collections::HashMap;
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
use tinymist_std::ImmutPath;
use tinymist_world::vfs::notify::NotifyDeps;
use tokio::sync::mpsc;
use typst::diag::FileError;
@ -187,7 +188,7 @@ impl<F: FnMut(FilesystemEvent) + Send + Sync> NotifyActor<F> {
self.invalidate_upstream(event);
}
ActorEvent::Message(Some(SyncDependency(paths))) => {
if let Some(changeset) = self.update_watches(&paths) {
if let Some(changeset) = self.update_watches(paths.as_ref()) {
(self.interrupted_by_events)(FilesystemEvent::Update(changeset));
}
}
@ -219,7 +220,7 @@ impl<F: FnMut(FilesystemEvent) + Send + Sync> NotifyActor<F> {
}
/// Update the watches of corresponding files.
fn update_watches(&mut self, paths: &[ImmutPath]) -> Option<FileChangeSet> {
fn update_watches(&mut self, paths: &dyn NotifyDeps) -> Option<FileChangeSet> {
// Increase the lifetime per external message.
self.lifetime += 1;
@ -234,7 +235,7 @@ impl<F: FnMut(FilesystemEvent) + Send + Sync> NotifyActor<F> {
//
// Also check whether the file is updated since there is a window
// between unwatch the file and watch the file again.
for path in paths.iter() {
paths.dependencies(&mut |path| {
let mut contained = false;
// Update or insert the entry with the new lifetime.
let entry = self
@ -243,16 +244,20 @@ impl<F: FnMut(FilesystemEvent) + Send + Sync> NotifyActor<F> {
.and_modify(|watch_entry| {
contained = true;
watch_entry.lifetime = self.lifetime;
watch_entry.seen = true;
})
.or_insert_with(|| WatchedEntry {
lifetime: self.lifetime,
watching: false,
seen: true,
seen: false,
state: WatchState::Stable,
prev: None,
});
if entry.seen {
return;
}
entry.seen = true;
// Update in-memory metadata for now.
let meta = path.metadata().map_err(|e| FileError::from_io(e, path));
@ -278,7 +283,7 @@ impl<F: FnMut(FilesystemEvent) + Send + Sync> NotifyActor<F> {
let watched = self.inner.content(path);
changeset.inserts.push((path.clone(), watched.into()));
}
}
});
// Remove old entries.
// Note: since we have increased the lifetime, it is safe to remove the