fix: emit latest status and artifact with correct signals (#1294) (#1330)

* fix: emit latest compiled artifact with correct signals

* fix: bad guard
This commit is contained in:
Myriad-Dreamin 2025-02-19 22:34:11 +08:00 committed by GitHub
parent 26fd50febf
commit 0c64bea89e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 125 additions and 42 deletions

View file

@ -127,20 +127,12 @@ impl<F: CompilerFeat> CompiledArtifact<F> {
pub enum CompileReport {
Suspend,
Stage(FileId, &'static str, tinymist_std::time::Time),
CompileError(
FileId,
EcoVec<SourceDiagnostic>,
tinymist_std::time::Duration,
),
ExportError(
FileId,
EcoVec<SourceDiagnostic>,
tinymist_std::time::Duration,
),
CompileError(FileId, usize, tinymist_std::time::Duration),
ExportError(FileId, usize, tinymist_std::time::Duration),
CompileSuccess(
FileId,
// warnings, if not empty
EcoVec<SourceDiagnostic>,
usize,
tinymist_std::time::Duration,
),
}
@ -166,7 +158,7 @@ impl CompileReport {
}
}
pub fn diagnostics(self) -> Option<EcoVec<SourceDiagnostic>> {
pub fn diagnostics_size(self) -> Option<usize> {
match self {
Self::Suspend | Self::Stage(..) => None,
Self::CompileError(_, diagnostics, ..)
@ -193,13 +185,12 @@ impl fmt::Display for CompileReportMsg<'_> {
Suspend => write!(f, "suspended"),
Stage(_, stage, ..) => write!(f, "{input:?}: {stage} ..."),
CompileSuccess(_, warnings, duration) => {
if warnings.is_empty() {
if *warnings == 0 {
write!(f, "{input:?}: compilation succeeded in {duration:?}")
} else {
write!(
f,
"{input:?}: compilation succeeded with {} warnings in {duration:?}",
warnings.len()
"{input:?}: compilation succeeded with {warnings} warnings in {duration:?}",
)
}
}
@ -217,7 +208,7 @@ pub trait CompileHandler<F: CompilerFeat, Ext>: Send + Sync + 'static {
fn on_any_compile_reason(&self, state: &mut ProjectCompiler<F, Ext>);
// todo: notify project specific compile
/// Called when a compilation is done.
fn notify_compile(&self, res: &CompiledArtifact<F>, rep: CompileReport);
fn notify_compile(&self, res: &CompiledArtifact<F>);
/// Called when a project is removed.
fn notify_removed(&self, _id: &ProjectInsId) {}
/// Called when the compilation status is changed.
@ -231,7 +222,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: 'static> CompileHandler<F, Ex
fn on_any_compile_reason(&self, _state: &mut ProjectCompiler<F, Ext>) {
log::info!("ProjectHandle: no need to compile");
}
fn notify_compile(&self, _res: &CompiledArtifact<F>, _rep: CompileReport) {}
fn notify_compile(&self, _res: &CompiledArtifact<F>) {}
fn status(&self, _revision: usize, _id: &ProjectInsId, _rep: CompileReport) {}
}
@ -280,6 +271,16 @@ pub struct CompileReasons {
pub by_entry_update: bool,
}
impl From<CompileReasons> for ExportSignal {
fn from(value: CompileReasons) -> Self {
Self {
by_mem_events: value.by_memory_events,
by_fs_events: value.by_fs_events,
by_entry_update: value.by_entry_update,
}
}
}
impl CompileReasons {
/// Merge two reasons.
pub fn see(&mut self, reason: CompileReasons) {
@ -795,13 +796,15 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
let elapsed = start.elapsed().unwrap_or_default();
let rep = match &compiled.doc {
Ok(..) => CompileReport::CompileSuccess(id, compiled.warnings.clone(), elapsed),
Err(err) => CompileReport::CompileError(id, err.clone(), elapsed),
Ok(..) => CompileReport::CompileSuccess(id, compiled.warnings.len(), elapsed),
Err(err) => CompileReport::CompileError(id, err.len(), elapsed),
};
// todo: we need to check revision for really concurrent compilation
log_compile_report(&rep);
h.notify_compile(&compiled, rep);
h.status(revision, &compiled.id, rep);
h.notify_compile(&compiled);
compiled
}

View file

@ -36,6 +36,15 @@ pub struct ExportSignal {
pub by_entry_update: bool,
}
impl ExportSignal {
/// Merge two signals.
pub fn merge(&mut self, other: ExportSignal) {
self.by_mem_events |= other.by_mem_events;
self.by_fs_events |= other.by_fs_events;
self.by_entry_update |= other.by_entry_update;
}
}
/// A snapshot of the project and compilation state.
pub struct CompileSnapshot<F: CompilerFeat> {
/// The project id.

View file

@ -648,7 +648,7 @@ impl<F: CompilerFeat> World for CompilerWorld<F> {
///
/// The returned `Source` file's [id](Source::id) does not have to match the
/// given `id`. Due to symlinks, two different file id's can point to the
/// same on-disk file. Implementors can deduplicate and return the same
/// same on-disk file. Implementers can deduplicate and return the same
/// `Source` if they want to, but do not have to.
fn source(&self, id: FileId) -> FileResult<Source> {
static DETACH_SOURCE: LazyLock<Source> =

View file

@ -21,7 +21,7 @@
pub use tinymist_project::*;
use std::sync::Arc;
use std::{num::NonZeroUsize, sync::Arc};
use parking_lot::Mutex;
use reflexo::{hash::FxHashMap, path::unix_slash};
@ -173,6 +173,7 @@ impl ServerState {
stats: Arc::default(),
}),
status_revision: Mutex::default(),
notified_revision: Mutex::default(),
});
@ -229,10 +230,64 @@ impl ServerState {
#[derive(Default)]
pub struct ProjectInsStateExt {
pub notified_revision: usize,
pub pending_reasons: CompileReasons,
pub emitted_reasons: CompileReasons,
pub is_compiling: bool,
pub last_compilation: Option<LspCompiledArtifact>,
}
impl ProjectInsStateExt {
/// Remembers the last compilation. Emits the pending reasons during
/// compilation if any.
pub fn compiled(
&mut self,
revision: &NonZeroUsize,
handler: &dyn CompileHandler<LspCompilerFeat, ProjectInsStateExt>,
compilation: &LspCompiledArtifact,
) {
let rev = compilation.world.revision().get();
if self.notified_revision >= rev {
return;
}
self.notified_revision = rev;
self.is_compiling = false;
self.last_compilation = Some(compilation.clone());
self.emit_pending_reasons(revision, handler);
}
/// Emits the pending reasons if the latest compiled revision matches.
pub fn emit_pending_reasons(
&mut self,
revision: &NonZeroUsize,
handler: &dyn CompileHandler<LspCompilerFeat, ProjectInsStateExt>,
) -> bool {
let Some(last_compilation) = self.last_compilation.as_ref() else {
return false;
};
let last_rev = last_compilation.world.revision();
if last_rev != *revision {
return false;
}
let pending_reasons = self.pending_reasons.exclude(self.emitted_reasons);
if !pending_reasons.any() {
return false;
}
self.emitted_reasons.see(self.pending_reasons);
let mut last_compilation = last_compilation.clone();
last_compilation.snap.signal = pending_reasons.into();
handler.notify_compile(&last_compilation);
self.pending_reasons = CompileReasons::default();
true
}
}
pub struct ProjectState {
pub compiler: LspProjectCompiler,
pub preview: ProjectPreviewState,
@ -269,8 +324,8 @@ impl ProjectState {
if let Interrupt::Compiled(compiled) = &intr {
let proj = self.compiler.projects().find(|p| p.id == compiled.id);
if let Some(proj) = proj {
proj.ext.is_compiling = false;
proj.ext.last_compilation = Some(compiled.clone());
proj.ext
.compiled(&proj.verse.revision, proj.handler.as_ref(), compiled);
}
}
@ -346,6 +401,7 @@ pub struct CompileHandlerImpl {
pub(crate) editor_tx: EditorSender,
pub(crate) client: Box<dyn ProjectClient>,
pub(crate) status_revision: Mutex<FxHashMap<ProjectInsId, usize>>,
pub(crate) notified_revision: Mutex<FxHashMap<ProjectInsId, usize>>,
}
@ -438,11 +494,27 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
s.verse.vfs().is_clean_compile(last_rev.get(), &deps)
}
{
log::info!("Project: skip compilation for {id:?} due to harmless vfs changes");
s.ext.pending_reasons.see(reason);
s.reason = CompileReasons::default();
let pending_reasons = s.ext.pending_reasons.exclude(s.ext.emitted_reasons);
let emitted = s
.ext
.emit_pending_reasons(&s.verse.revision, s.handler.as_ref());
if !emitted {
log::info!("Project: skip compilation for {id:?} due to harmless vfs changes");
} else {
log::info!(
"Project: emit compilation again for {id:?}, reason: {pending_reasons:?}"
);
}
continue;
}
s.ext.pending_reasons = CompileReasons::default();
s.ext.emitted_reasons = reason;
let Some(compile_fn) = s.may_compile(&c.handler) else {
continue;
};
@ -454,6 +526,20 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
}
fn status(&self, revision: usize, id: &ProjectInsId, rep: CompileReport) {
// todo: we need to manage the revision for fn status() as well
{
let mut n_revs = self.status_revision.lock();
let n_rev = n_revs.entry(id.clone()).or_default();
if *n_rev > revision {
log::info!(
"Project: outdated status for revision {} <= {n_rev}",
revision,
);
return;
}
*n_rev = revision;
}
// todo: seems to duplicate with CompileStatus
let status = match rep {
CompileReport::Suspend => {
@ -505,8 +591,7 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
n_revs.remove(id);
}
fn notify_compile(&self, snap: &LspCompiledArtifact, rep: CompileReport) {
// todo: we need to manage the revision for fn status() as well
fn notify_compile(&self, snap: &LspCompiledArtifact) {
{
let mut n_revs = self.notified_revision.lock();
let n_rev = n_revs.entry(snap.id.clone()).or_default();
@ -525,21 +610,6 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
self.client.interrupt(LspInterrupt::Compiled(snap.clone()));
self.export.signal(snap);
self.editor_tx
.send(EditorRequest::Status(CompileStatus {
id: snap.id.clone(),
path: rep
.compiling_id()
.map(|s| unix_slash(s.vpath().as_rooted_path()))
.unwrap_or_default(),
status: if snap.doc.is_ok() {
CompileStatusEnum::CompileSuccess
} else {
CompileStatusEnum::CompileError
},
}))
.unwrap();
#[cfg(feature = "preview")]
if let Some(inner) = self.preview.get(&snap.id) {
let snap = snap.clone();

View file

@ -651,6 +651,7 @@ pub async fn preview_main(args: PreviewCliArgs) -> Result<()> {
client: Box::new(intr_tx.clone()),
analysis: Arc::default(),
status_revision: Mutex::default(),
notified_revision: Mutex::default(),
});