Skip to content

Commit f7517ff

Browse files
authored
Store VFS path file_name and parent indices instead of separate strings (#655)
1 parent 1a0e43f commit f7517ff

File tree

8 files changed

+150
-34
lines changed

8 files changed

+150
-34
lines changed

Cargo.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/astr/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ rust-version.workspace = true
66

77
[dependencies]
88
diesel.workspace = true
9+
stable_deref_trait = "1.2.1"
910
triomphe.workspace = true
1011

1112
[lints]

crates/astr/src/cow.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use std::ops::Deref;
2+
3+
use super::AStr;
4+
5+
pub enum CowAStr<'a> {
6+
Borrowed(&'a AStr),
7+
Owned(AStr),
8+
}
9+
10+
impl Deref for CowAStr<'_> {
11+
type Target = AStr;
12+
13+
fn deref(&self) -> &Self::Target {
14+
match self {
15+
CowAStr::Borrowed(astr) => astr,
16+
CowAStr::Owned(astr) => astr,
17+
}
18+
}
19+
}

crates/astr/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ use std::{
66
path::Path,
77
};
88

9+
use stable_deref_trait::StableDeref;
910
use triomphe::{Arc, HeaderWithLength};
1011

12+
mod cow;
1113
mod diesel;
1214

15+
pub use self::cow::CowAStr;
16+
1317
/// String 'atom'.
1418
///
1519
/// Cloning doesn't allocate. As of the time of writing, uses reference
@@ -41,6 +45,8 @@ impl Deref for AStr {
4145
}
4246
}
4347

48+
unsafe impl StableDeref for AStr {}
49+
4450
impl Borrow<str> for AStr {
4551
#[inline]
4652
fn borrow(&self) -> &str {

crates/vfs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition.workspace = true
77

88
[dependencies]
99
astr.workspace = true
10+
derive_more.workspace = true
1011
indextree.workspace = true
1112
snafu.workspace = true
1213

crates/vfs/src/path.rs

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use std::ops::Deref;
2+
13
use astr::AStr;
4+
use derive_more::Debug;
25

36
pub fn join(a: &str, b: impl AsRef<str> + Into<AStr>) -> AStr {
47
let b_ = b.as_ref();
@@ -11,15 +14,53 @@ pub fn join(a: &str, b: impl AsRef<str> + Into<AStr>) -> AStr {
1114
}
1215
}
1316

14-
pub fn file_name(path: &str) -> Option<&str> {
15-
path.trim_end_matches('/').rsplit('/').next()
17+
#[derive(Clone, Debug)]
18+
#[debug("{path:?}")]
19+
pub struct VfsPath {
20+
path: AStr,
21+
file_name_start_idx: u32,
22+
parent_end_idx: u32,
1623
}
1724

18-
pub fn parent(path: &str) -> Option<&str> {
19-
path.trim_end_matches('/').rsplit_once('/').map(|(parent, _)| {
20-
// We had to have split on a direct descendent of `/`
21-
if parent.is_empty() { "/" } else { parent }
22-
})
25+
impl VfsPath {
26+
pub fn new(path: AStr) -> Self {
27+
assert!(path.starts_with('/'));
28+
if path.len() > 1 {
29+
assert!(!path.ends_with('/'));
30+
}
31+
32+
let file_name_start_idx = (path.rfind('/').unwrap() + 1).try_into().unwrap();
33+
let parent_end_idx = if file_name_start_idx == 1 {
34+
1
35+
} else {
36+
file_name_start_idx - 1
37+
};
38+
Self {
39+
path,
40+
file_name_start_idx,
41+
parent_end_idx,
42+
}
43+
}
44+
45+
pub fn astr(&self) -> AStr {
46+
self.path.clone()
47+
}
48+
49+
pub fn file_name(&self) -> &str {
50+
&self.path[self.file_name_start_idx as usize..]
51+
}
52+
53+
pub fn parent(&self) -> Option<&str> {
54+
(self.path.len() > 1).then(|| &self.path[..self.parent_end_idx as usize])
55+
}
56+
}
57+
58+
impl Deref for VfsPath {
59+
type Target = str;
60+
61+
fn deref(&self) -> &Self::Target {
62+
&self.path
63+
}
2364
}
2465

2566
pub fn components(path: &str) -> impl Iterator<Item = &str> {
@@ -29,3 +70,44 @@ pub fn components(path: &str) -> impl Iterator<Item = &str> {
2970
.chain(path.split('/'))
3071
.filter(|s| !s.is_empty())
3172
}
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use super::VfsPath;
77+
78+
#[test]
79+
fn vfs_path_root_behavior() {
80+
let p = VfsPath::new("/".into());
81+
assert_eq!(p.parent(), None);
82+
assert_eq!(p.file_name(), "");
83+
}
84+
85+
#[test]
86+
fn vfs_path_non_root_behavior() {
87+
let p = VfsPath::new("/a".into());
88+
assert_eq!(p.parent(), Some("/"));
89+
assert_eq!(p.file_name(), "a");
90+
91+
let p = VfsPath::new("/abc/def/xyz".into());
92+
assert_eq!(p.parent(), Some("/abc/def"));
93+
assert_eq!(p.file_name(), "xyz");
94+
}
95+
96+
#[test]
97+
#[should_panic]
98+
fn vfs_path_trailing_slash_invalid() {
99+
VfsPath::new("/xyz/".into());
100+
}
101+
102+
#[test]
103+
#[should_panic]
104+
fn vfs_path_no_leading_slash_invalid() {
105+
VfsPath::new("xyz/abc".into());
106+
}
107+
108+
#[test]
109+
#[should_panic]
110+
fn vfs_path_no_leading_slash_invalid_2() {
111+
VfsPath::new("abcxyz".into());
112+
}
113+
}

crates/vfs/src/tree/builder.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! Build a vfs tree incrementally
66
use std::collections::BTreeMap;
77

8-
use astr::AStr;
8+
use astr::{AStr, CowAStr};
99

1010
use crate::path;
1111
use crate::tree::{Kind, Tree};
@@ -51,7 +51,7 @@ impl<T: BlitFile> TreeBuilder<T> {
5151
let file = File::new(item);
5252

5353
// Find all parent paths
54-
if let Some(parent) = &file.parent {
54+
if let Some(parent) = file.parent() {
5555
let mut leading_path: Option<AStr> = None;
5656
// Build a set of parent paths skipping `/`, yielding `usr`, `usr/bin`, etc.
5757
for component in path::components(parent) {
@@ -64,6 +64,7 @@ impl<T: BlitFile> TreeBuilder<T> {
6464
.insert(full_path.clone(), File::new(full_path.into()));
6565
}
6666
}
67+
6768
self.explicit.push(file);
6869
}
6970

@@ -73,7 +74,7 @@ impl<T: BlitFile> TreeBuilder<T> {
7374

7475
// Walk again to remove accidental dupes
7576
for i in self.explicit.iter() {
76-
self.implicit_dirs.remove(&i.path);
77+
self.implicit_dirs.remove(&*i.path);
7778
}
7879
}
7980

@@ -85,7 +86,7 @@ impl<T: BlitFile> TreeBuilder<T> {
8586
.iter()
8687
.filter(|f| f.kind.is_directory())
8788
.chain(self.implicit_dirs.values())
88-
.map(|d| (&d.path, d))
89+
.map(|d| (&*d.path, d))
8990
.collect::<BTreeMap<_, _>>();
9091

9192
// build a set of redirects
@@ -96,14 +97,14 @@ impl<T: BlitFile> TreeBuilder<T> {
9697
if let Kind::Symlink(target) = &link.kind {
9798
// Resolve the link.
9899
let target = if target.starts_with('/') {
99-
target.clone()
100-
} else if let Some(parent) = &link.parent {
101-
path::join(parent, target)
100+
CowAStr::Borrowed(target)
101+
} else if let Some(parent) = link.parent() {
102+
CowAStr::Owned(path::join(parent, target))
102103
} else {
103-
target.clone()
104+
CowAStr::Borrowed(target)
104105
};
105-
if all_dirs.contains_key(&target) {
106-
redirects.insert(&link.path, target);
106+
if all_dirs.contains_key(&**target) {
107+
redirects.insert(&*link.path, target);
107108
}
108109
}
109110
}
@@ -123,7 +124,7 @@ impl<T: BlitFile> TreeBuilder<T> {
123124
// New node for this guy
124125
let node = tree.new_node(entry.clone());
125126

126-
if let Some(parent) = &entry.parent {
127+
if let Some(parent) = entry.parent() {
127128
tree.add_child_to_node(node, parent)?;
128129
}
129130
}

crates/vfs/src/tree/mod.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use astr::AStr;
1212
use indextree::{Arena, Descendants, NodeId};
1313
use snafu::Snafu;
1414

15-
use crate::path;
15+
use crate::path::{self, VfsPath};
1616

1717
pub mod builder;
1818

@@ -51,28 +51,32 @@ pub trait BlitFile: Clone + Sized + Debug + From<AStr> {
5151
#[derive(Debug, Clone)]
5252
struct File<T> {
5353
id: AStr,
54-
path: AStr,
55-
file_name: Option<AStr>,
56-
parent: Option<AStr>,
54+
path: VfsPath,
5755
kind: Kind,
5856
inner: T,
5957
}
6058

6159
impl<T: BlitFile> File<T> {
6260
pub fn new(inner: T) -> Self {
63-
let path = inner.path();
64-
let file_name = path::file_name(&path).map(AStr::from);
65-
let parent = path::parent(&path).map(AStr::from);
61+
let path = VfsPath::new(inner.path());
6662

6763
Self {
6864
id: inner.id(),
6965
path,
70-
file_name,
71-
parent,
7266
kind: inner.kind(),
7367
inner,
7468
}
7569
}
70+
71+
#[inline]
72+
fn file_name(&self) -> &str {
73+
self.path.file_name()
74+
}
75+
76+
#[inline]
77+
fn parent(&self) -> Option<&str> {
78+
self.path.parent()
79+
}
7680
}
7781

7882
/// Actual tree implementation, encapsulating indextree
@@ -105,7 +109,7 @@ impl<T: BlitFile> Tree<T> {
105109

106110
/// Generate a new node, store the path mapping for it
107111
fn new_node(&mut self, data: File<T>) -> NodeId {
108-
let path = data.path.clone();
112+
let path = data.path.astr();
109113
let node = self.arena.new_node(data);
110114
self.map.insert(path, node);
111115
self.length += 1;
@@ -130,7 +134,7 @@ impl<T: BlitFile> Tree<T> {
130134
.children(&self.arena)
131135
.filter_map(|n| {
132136
let n = self.arena.get(n)?.get();
133-
if n.file_name == node.get().file_name {
137+
if n.file_name() == node.get().file_name() {
134138
Some(n)
135139
} else {
136140
None
@@ -139,7 +143,7 @@ impl<T: BlitFile> Tree<T> {
139143
.collect::<Vec<_>>();
140144
if !others.is_empty() {
141145
let e = Error::Duplicate {
142-
node_path: node.get().path.clone(),
146+
node_path: node.get().path.astr(),
143147
node_id: node.get().id.clone(),
144148
other_id: others.first().unwrap().id.clone(),
145149
};
@@ -193,7 +197,7 @@ impl<T: BlitFile> Tree<T> {
193197
Some(n) => *n,
194198
None => self.new_node(orphan.clone()),
195199
};
196-
if let Some(parent) = &orphan.parent {
200+
if let Some(parent) = orphan.parent() {
197201
self.add_child_to_node(node, parent)?;
198202
}
199203
}
@@ -218,7 +222,7 @@ impl<T: BlitFile> Tree<T> {
218222
fn structured_children(&self, start: &NodeId) -> Element<'_, T> {
219223
let node = &self.arena[*start];
220224
let item = node.get();
221-
let partial = item.file_name.as_deref().unwrap_or_default();
225+
let partial = item.file_name();
222226

223227
if item.kind.is_directory() {
224228
let children = start

0 commit comments

Comments
 (0)