diff --git a/crates/reflexo-vfs/src/dummy.rs b/crates/reflexo-vfs/src/dummy.rs index e9825ccf..00b31244 100644 --- a/crates/reflexo-vfs/src/dummy.rs +++ b/crates/reflexo-vfs/src/dummy.rs @@ -1,5 +1,6 @@ use std::path::Path; +use reflexo::ImmutPath; use typst::diag::{FileError, FileResult}; use crate::{AccessModel, Bytes, PathAccessModel, TypstFileId}; @@ -13,8 +14,8 @@ use crate::{AccessModel, Bytes, PathAccessModel, TypstFileId}; pub struct DummyAccessModel; impl AccessModel for DummyAccessModel { - fn content(&self, _src: TypstFileId) -> FileResult { - Err(FileError::AccessDenied) + fn content(&self, _src: TypstFileId) -> (Option, FileResult) { + (None, Err(FileError::AccessDenied)) } } diff --git a/crates/reflexo-vfs/src/lib.rs b/crates/reflexo-vfs/src/lib.rs index 7da1cf6d..74bceda6 100644 --- a/crates/reflexo-vfs/src/lib.rs +++ b/crates/reflexo-vfs/src/lib.rs @@ -20,6 +20,7 @@ pub mod dummy; /// Provides snapshot models pub mod snapshot; +use reflexo::hash::FxHashMap; pub use snapshot::*; /// Provides notify access model which retrieves file system events and changes @@ -50,7 +51,7 @@ use core::fmt; use std::sync::OnceLock; use std::{path::Path, sync::Arc}; -use parking_lot::{Mutex, RwLock}; +use parking_lot::{Mutex, MutexGuard, RwLock}; use typst::diag::{FileError, FileResult}; use crate::notify::NotifyAccessModel; @@ -87,7 +88,7 @@ pub trait AccessModel { fn reset(&mut self) {} /// Return the content of a file entry. - fn content(&self, src: TypstFileId) -> FileResult; + fn content(&self, src: TypstFileId) -> (Option, FileResult); } #[derive(Clone)] @@ -129,18 +130,47 @@ pub trait FsProvider { fn read(&self, id: TypstFileId) -> FileResult; } +type BytesQuery = Arc, FileResult)>>; + #[derive(Debug, Clone, Default)] struct VfsEntry { - bytes: Arc>>, + touched_by_compile: bool, + last_accessed_rev: u64, + bytes: BytesQuery, source: Arc>>, } +#[derive(Default)] +struct VfsMap { + touched_by_compile: bool, + entries: RedBlackTreeMapSync, +} + +impl VfsMap { + /// Read a slot. + #[inline(always)] + fn slot(&mut self, path: TypstFileId, f: impl FnOnce(&mut VfsEntry) -> T) -> T { + if let Some(entry) = self.entries.get_mut(&path) { + f(entry) + } else { + let mut entry = VfsEntry { + touched_by_compile: self.touched_by_compile, + last_accessed_rev: 0, + ..Default::default() + }; + let res = f(&mut entry); + self.entries.insert_mut(path, entry); + res + } + } +} + /// Create a new `Vfs` harnessing over the given `access_model` specific for /// `reflexo_world::CompilerWorld`. With vfs, we can minimize the /// implementation overhead for [`AccessModel`] trait. pub struct Vfs { - touched_by_compile: bool, - managed: Arc>>, + managed: Arc>, + paths: Arc>>>, // access_model: TraceAccessModel>, /// The wrapped access model. access_model: VfsAccessModel, @@ -155,8 +185,8 @@ impl fmt::Debug for Vfs { impl Vfs { pub fn snapshot(&self) -> Self { Self { - touched_by_compile: self.touched_by_compile, managed: self.managed.clone(), + paths: self.paths.clone(), access_model: self.access_model.clone(), } } @@ -189,8 +219,8 @@ impl Vfs { // let access_model = TraceAccessModel::new(access_model); Self { - touched_by_compile: false, managed: Arc::default(), + paths: Arc::default(), access_model, } } @@ -255,45 +285,50 @@ impl Vfs { } /// Read a file. - pub fn read(&self, path: TypstFileId) -> FileResult { - let entry = self.slot(path, |entry| entry.bytes.clone()); + pub fn read_content<'a>( + &self, + bytes: &'a BytesQuery, + fid: TypstFileId, + ) -> &'a FileResult { + &bytes + .get_or_init(|| { + let (path, content) = self.access_model.content(fid); + if let Some(path) = path.as_ref() { + self.paths.lock().entry(path.clone()).or_default().push(fid); + } + + (path, content) + }) + .1 + } - let content = entry.get_or_init(|| self.access_model.content(path)); - content.clone() + /// Read a file. + pub fn read(&self, fid: TypstFileId) -> FileResult { + let bytes = self.managed.lock().slot(fid, |entry| entry.bytes.clone()); + + self.read_content(&bytes, fid).clone() } /// Read a source. - pub fn source(&self, path: TypstFileId) -> FileResult { - let (bytes, source) = self.slot(path, |entry| (entry.bytes.clone(), entry.source.clone())); + pub fn source(&self, fid: TypstFileId) -> FileResult { + let (bytes, source) = self + .managed + .lock() + .slot(fid, |entry| (entry.bytes.clone(), entry.source.clone())); let source = source.get_or_init(|| { - let content = bytes - .get_or_init(|| self.access_model.content(path)) + let content = self + .read_content(&bytes, fid) .as_ref() .map_err(Clone::clone)?; let content = std::str::from_utf8(content).map_err(|_| FileError::InvalidUtf8)?; - Ok(Source::new(path, content.into())) + Ok(Source::new(fid, content.into())) }); source.clone() } - - /// Read a slot. - #[inline(always)] - fn slot(&self, path: TypstFileId, f: impl FnOnce(&mut VfsEntry) -> T) -> T { - let mut m = self.managed.lock(); - - if let Some(entry) = m.get_mut(&path) { - f(entry) - } else { - let mut entry = VfsEntry::default(); - let res = f(&mut entry); - m.insert_mut(path, entry); - res - } - } } pub struct RevisingVfs<'a, M: PathAccessModel + Sized> { @@ -305,52 +340,95 @@ impl RevisingVfs<'_, M> { self.inner } - fn access_model(&mut self) -> &mut VfsAccessModel { - &mut self.inner.access_model + fn locked(&mut self) -> VfsLocked<'_, M> { + VfsLocked { + managed: self.inner.managed.lock(), + paths: self.inner.paths.lock(), + am: &mut self.inner.access_model, + } } /// Reset the shadowing files in [`OverlayAccessModel`]. /// /// Note: This function is independent from [`Vfs::reset`]. pub fn reset_shadow(&mut self) { - self.access_model().clear_shadow(); - self.access_model().inner.inner.clear_shadow(); + let mut vfs = self.locked(); + + for path in vfs.am.inner.inner.file_paths() { + vfs.invalidate_path(&path); + } + for fid in vfs.am.file_paths() { + vfs.invalidate_file_id(fid); + } + + vfs.am.clear_shadow(); + vfs.am.inner.inner.clear_shadow(); } /// Add a shadowing file to the [`OverlayAccessModel`]. pub fn map_shadow(&mut self, path: &Path, snap: FileSnapshot) -> FileResult<()> { - self.access_model() - .inner - .inner - .add_file(path, snap, |c| c.into()); + let mut vfs = self.locked(); + vfs.invalidate_path(path); + + vfs.am.inner.inner.add_file(path, snap, |c| c.into()); Ok(()) } /// Remove a shadowing file from the [`OverlayAccessModel`]. pub fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> { - self.access_model().inner.inner.remove_file(path); + let mut vfs = self.locked(); + vfs.invalidate_path(path); + vfs.am.inner.inner.remove_file(path); Ok(()) } /// Add a shadowing file to the [`OverlayAccessModel`] by file id. pub fn map_shadow_by_id(&mut self, file_id: TypstFileId, snap: FileSnapshot) -> FileResult<()> { - self.access_model().add_file(&file_id, snap, |c| *c); + let mut vfs = self.locked(); + vfs.invalidate_file_id(file_id); + vfs.am.add_file(&file_id, snap, |c| *c); Ok(()) } /// Remove a shadowing file from the [`OverlayAccessModel`] by file id. pub fn remove_shadow_by_id(&mut self, file_id: TypstFileId) { - self.access_model().remove_file(&file_id); + let mut vfs = self.locked(); + vfs.invalidate_file_id(file_id); + vfs.am.remove_file(&file_id); } /// Let the vfs notify the access model with a filesystem event. /// /// See [`NotifyAccessModel`] for more information. pub fn notify_fs_event(&mut self, event: FilesystemEvent) { - self.access_model().inner.inner.inner.notify(event); + let mut vfs = self.locked(); + vfs.am.inner.inner.inner.notify(event); + } +} + +struct VfsLocked<'a, M: PathAccessModel + Sized> { + managed: MutexGuard<'a, VfsMap>, + paths: MutexGuard<'a, FxHashMap>>, + am: &'a mut VfsAccessModel, +} + +impl VfsLocked<'_, M> { + fn invalidate_path(&mut self, path: &Path) { + if let Some(fids) = self.paths.remove(path) { + for fid in fids { + self.invalidate_file_id(fid); + } + } + } + + fn invalidate_file_id(&mut self, file_id: TypstFileId) { + self.managed.slot(file_id, |e| { + e.bytes = Arc::default(); + e.source = Arc::default(); + }); } } diff --git a/crates/reflexo-vfs/src/overlay.rs b/crates/reflexo-vfs/src/overlay.rs index 8c6b1747..1f412855 100644 --- a/crates/reflexo-vfs/src/overlay.rs +++ b/crates/reflexo-vfs/src/overlay.rs @@ -87,9 +87,9 @@ impl AccessModel for OverlayAccessModel { self.inner.reset(); } - fn content(&self, src: TypstFileId) -> FileResult { + fn content(&self, src: TypstFileId) -> (Option, FileResult) { if let Some(content) = self.files.get(&src) { - return content.content().cloned(); + return (None, content.content().cloned()); } self.inner.content(src) diff --git a/crates/reflexo-vfs/src/resolve.rs b/crates/reflexo-vfs/src/resolve.rs index 2ed70b1a..576d286d 100644 --- a/crates/reflexo-vfs/src/resolve.rs +++ b/crates/reflexo-vfs/src/resolve.rs @@ -1,5 +1,6 @@ use std::{fmt::Debug, sync::Arc}; +use reflexo::ImmutPath; use typst::diag::FileResult; use crate::{path_mapper::RootResolver, AccessModel, Bytes, PathAccessModel, TypstFileId}; @@ -23,8 +24,12 @@ impl AccessModel for ResolveAccessModel { self.inner.reset(); } - fn content(&self, fid: TypstFileId) -> FileResult { - self.inner - .content(&self.resolver.path_for_id(fid)?.to_err()?) + fn content(&self, fid: TypstFileId) -> (Option, FileResult) { + let resolved = Ok(()).and_then(|_| self.resolver.path_for_id(fid)?.to_err()); + + match resolved { + Ok(path) => (Some(path.as_path().into()), self.inner.content(&path)), + Err(e) => (None, Err(e)), + } } } diff --git a/crates/reflexo-vfs/src/trace.rs b/crates/reflexo-vfs/src/trace.rs index 9ae013c8..c6fb6ef3 100644 --- a/crates/reflexo-vfs/src/trace.rs +++ b/crates/reflexo-vfs/src/trace.rs @@ -1,5 +1,6 @@ use std::sync::atomic::AtomicU64; +use reflexo::ImmutPath; use typst::diag::FileResult; use crate::{AccessModel, Bytes, TypstFileId}; @@ -30,7 +31,7 @@ impl AccessModel for TraceAccessModel { self.inner.reset(); } - fn content(&self, src: TypstFileId) -> FileResult { + fn content(&self, src: TypstFileId) -> (Option, FileResult) { let instant = reflexo::time::Instant::now(); let res = self.inner.content(src); let elapsed = instant.elapsed();