Skip to content

Commit

Permalink
g1
Browse files Browse the repository at this point in the history
  • Loading branch information
Myriad-Dreamin committed Jan 18, 2025
1 parent bf149fc commit 3f674f0
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 51 deletions.
5 changes: 3 additions & 2 deletions crates/reflexo-vfs/src/dummy.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::path::Path;

use reflexo::ImmutPath;
use typst::diag::{FileError, FileResult};

use crate::{AccessModel, Bytes, PathAccessModel, TypstFileId};
Expand All @@ -13,8 +14,8 @@ use crate::{AccessModel, Bytes, PathAccessModel, TypstFileId};
pub struct DummyAccessModel;

impl AccessModel for DummyAccessModel {
fn content(&self, _src: TypstFileId) -> FileResult<Bytes> {
Err(FileError::AccessDenied)
fn content(&self, _src: TypstFileId) -> (Option<ImmutPath>, FileResult<Bytes>) {
(None, Err(FileError::AccessDenied))
}
}

Expand Down
164 changes: 121 additions & 43 deletions crates/reflexo-vfs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -87,7 +88,7 @@ pub trait AccessModel {
fn reset(&mut self) {}

/// Return the content of a file entry.
fn content(&self, src: TypstFileId) -> FileResult<Bytes>;
fn content(&self, src: TypstFileId) -> (Option<ImmutPath>, FileResult<Bytes>);
}

#[derive(Clone)]
Expand Down Expand Up @@ -129,18 +130,47 @@ pub trait FsProvider {
fn read(&self, id: TypstFileId) -> FileResult<Bytes>;
}

type BytesQuery = Arc<OnceLock<(Option<ImmutPath>, FileResult<Bytes>)>>;

#[derive(Debug, Clone, Default)]
struct VfsEntry {
bytes: Arc<OnceLock<FileResult<Bytes>>>,
touched_by_compile: bool,
last_accessed_rev: u64,
bytes: BytesQuery,
source: Arc<OnceLock<FileResult<Source>>>,
}

#[derive(Default)]
struct VfsMap {
touched_by_compile: bool,
entries: RedBlackTreeMapSync<TypstFileId, VfsEntry>,
}

impl VfsMap {
/// Read a slot.
#[inline(always)]
fn slot<T>(&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<M: PathAccessModel + Sized> {
touched_by_compile: bool,
managed: Arc<Mutex<RedBlackTreeMapSync<TypstFileId, VfsEntry>>>,
managed: Arc<Mutex<VfsMap>>,
paths: Arc<Mutex<FxHashMap<ImmutPath, Vec<TypstFileId>>>>,
// access_model: TraceAccessModel<VfsAccessModel<M>>,
/// The wrapped access model.
access_model: VfsAccessModel<M>,
Expand All @@ -155,8 +185,8 @@ impl<M: PathAccessModel + Sized> fmt::Debug for Vfs<M> {
impl<M: PathAccessModel + Clone + Sized> Vfs<M> {
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(),
}
}
Expand Down Expand Up @@ -189,8 +219,8 @@ impl<M: PathAccessModel + Sized> Vfs<M> {
// let access_model = TraceAccessModel::new(access_model);

Self {
touched_by_compile: false,
managed: Arc::default(),
paths: Arc::default(),
access_model,
}
}
Expand Down Expand Up @@ -255,45 +285,50 @@ impl<M: PathAccessModel + Sized> Vfs<M> {
}

/// Read a file.
pub fn read(&self, path: TypstFileId) -> FileResult<Bytes> {
let entry = self.slot(path, |entry| entry.bytes.clone());
pub fn read_content<'a>(
&self,
bytes: &'a BytesQuery,
fid: TypstFileId,
) -> &'a FileResult<Bytes> {
&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<Bytes> {
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<Source> {
let (bytes, source) = self.slot(path, |entry| (entry.bytes.clone(), entry.source.clone()));
pub fn source(&self, fid: TypstFileId) -> FileResult<Source> {
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<T>(&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> {
Expand All @@ -305,52 +340,95 @@ impl<M: PathAccessModel + Sized> RevisingVfs<'_, M> {
self.inner
}

fn access_model(&mut self) -> &mut VfsAccessModel<M> {
&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<ImmutPath, Vec<TypstFileId>>>,
am: &'a mut VfsAccessModel<M>,
}

impl<M: PathAccessModel + Sized> 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();
});
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/reflexo-vfs/src/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ impl<M: AccessModel> AccessModel for OverlayAccessModel<TypstFileId, M> {
self.inner.reset();
}

fn content(&self, src: TypstFileId) -> FileResult<Bytes> {
fn content(&self, src: TypstFileId) -> (Option<ImmutPath>, FileResult<Bytes>) {
if let Some(content) = self.files.get(&src) {
return content.content().cloned();
return (None, content.content().cloned());
}

self.inner.content(src)
Expand Down
11 changes: 8 additions & 3 deletions crates/reflexo-vfs/src/resolve.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -23,8 +24,12 @@ impl<M: PathAccessModel> AccessModel for ResolveAccessModel<M> {
self.inner.reset();
}

fn content(&self, fid: TypstFileId) -> FileResult<Bytes> {
self.inner
.content(&self.resolver.path_for_id(fid)?.to_err()?)
fn content(&self, fid: TypstFileId) -> (Option<ImmutPath>, FileResult<Bytes>) {
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)),
}
}
}
3 changes: 2 additions & 1 deletion crates/reflexo-vfs/src/trace.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::sync::atomic::AtomicU64;

use reflexo::ImmutPath;
use typst::diag::FileResult;

use crate::{AccessModel, Bytes, TypstFileId};
Expand Down Expand Up @@ -30,7 +31,7 @@ impl<M: AccessModel + Sized> AccessModel for TraceAccessModel<M> {
self.inner.reset();
}

fn content(&self, src: TypstFileId) -> FileResult<Bytes> {
fn content(&self, src: TypstFileId) -> (Option<ImmutPath>, FileResult<Bytes>) {
let instant = reflexo::time::Instant::now();
let res = self.inner.content(src);
let elapsed = instant.elapsed();
Expand Down

0 comments on commit 3f674f0

Please sign in to comment.