Skip to content

Commit 675d788

Browse files
committed
consolidate arena/picker via pub trait
1 parent b1377d8 commit 675d788

15 files changed

Lines changed: 158 additions & 126 deletions

crates/fff-c/src/ffi_types.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,9 @@ pub struct FffFileItem {
7474

7575
impl FffFileItem {
7676
pub fn from_item(item: &FileItem, picker: &FilePicker) -> Self {
77-
let rel_path = picker.relative_path(item);
7877
FffFileItem {
79-
relative_path: cstring_new(&rel_path),
80-
file_name: cstring_new(&picker.file_name(item)),
78+
relative_path: cstring_new(&item.relative_path(picker)),
79+
file_name: cstring_new(&item.file_name(picker)),
8180
git_status: cstring_new(format_git_status(item.git_status)),
8281
size: item.size,
8382
modified: item.modified,
@@ -309,10 +308,9 @@ impl FffGrepMatch {
309308
None => (false, 0),
310309
};
311310

312-
let rel_path = picker.relative_path(file);
313311
FffGrepMatch {
314-
relative_path: cstring_new(&rel_path),
315-
file_name: cstring_new(&picker.file_name(file)),
312+
relative_path: cstring_new(&file.relative_path(picker)),
313+
file_name: cstring_new(&file.file_name(picker)),
316314
git_status: cstring_new(format_git_status(file.git_status)),
317315
line_content: cstring_new(&m.line_content),
318316
match_ranges,

crates/fff-core/src/file_picker.rs

Lines changed: 45 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
//! The background scanner and watcher acquire write locks only when mutating
3131
//! the file index, so read-heavy search workloads rarely contend.
3232
33+
use crate::FFFStringStorage;
3334
use crate::background_watcher::BackgroundWatcher;
3435
use crate::bigram_filter::{BigramFilter, BigramIndexBuilder, BigramOverlay};
3536
use crate::error::Error;
@@ -226,7 +227,7 @@ impl FileSync {
226227
// Binary search files by (parent_dir, filename) — same order as the sort
227228
self.files[..self.base_count].binary_search_by(|f| {
228229
f.parent_dir_index().cmp(&dir_idx).then_with(|| {
229-
let fname = f.file_name_from_arena(arena);
230+
let fname = f.file_name(arena);
230231
fname.as_str().cmp(filename)
231232
})
232233
})
@@ -247,19 +248,31 @@ impl FileSync {
247248
self.files.insert(position, file);
248249
}
249250

250-
/// Remove files matching predicate from both base and overflow.
251-
/// Returns number of files removed. Adjusts `base_count` accordingly.
252-
fn retain_files<F>(&mut self, mut predicate: F) -> usize
251+
fn retain_files_with_arena<F>(&mut self, mut predicate: F) -> usize
253252
where
254-
F: FnMut(&FileItem) -> bool,
253+
F: FnMut(&FileItem, ArenaPtr) -> bool,
255254
{
255+
let base_arena = self.arena_base_ptr();
256+
let overflow_arena = self.overflow_arena_ptr();
257+
258+
let base_count = self.base_count;
256259
let initial_len = self.files.len();
257-
// Count how many base files survive.
258-
let base_retained = self.files[..self.base_count]
260+
let base_retained = self.files[..base_count]
259261
.iter()
260-
.filter(|f| predicate(f))
262+
.filter(|f| predicate(f, base_arena))
261263
.count();
262-
self.files.retain(predicate);
264+
265+
self.files.retain(|f| {
266+
predicate(
267+
f,
268+
if f.is_overflow() {
269+
overflow_arena
270+
} else {
271+
base_arena
272+
},
273+
)
274+
});
275+
263276
self.base_count = base_retained;
264277
initial_len - self.files.len()
265278
}
@@ -435,50 +448,26 @@ impl std::fmt::Debug for FilePicker {
435448
}
436449
}
437450

438-
impl FilePicker {
439-
pub fn base_path(&self) -> &Path {
440-
&self.base_path
441-
}
442-
443-
#[inline]
444-
pub(crate) fn arena_base_ptr(&self) -> ArenaPtr {
445-
self.sync_data.arena_base_ptr()
446-
}
447-
448-
/// File name (e.g. `Button.tsx`) from the arena.
449-
#[inline]
450-
pub fn file_name(&self, file: &FileItem) -> String {
451-
file.file_name_from_arena(self.sync_data.arena_for_file(file))
452-
}
453-
454-
/// Relative path (e.g. `src/components/Button.tsx`) from the arena.
451+
impl FFFStringStorage for &FilePicker {
455452
#[inline]
456-
pub fn relative_path(&self, file: &FileItem) -> String {
457-
file.relative_path_from_arena(self.sync_data.arena_for_file(file))
453+
fn arena_for(&self, file: &FileItem) -> crate::simd_path::ArenaPtr {
454+
self.sync_data.arena_for_file(file)
458455
}
459456

460-
/// Directory portion of the path (e.g. `src/components/`).
461457
#[inline]
462-
pub fn dir_str(&self, file: &FileItem) -> String {
463-
file.dir_str(self.sync_data.arena_for_file(file))
464-
}
465-
466-
/// Absolute path on disk.
467-
#[inline]
468-
pub fn absolute_path(&self, file: &FileItem) -> PathBuf {
469-
file.absolute_path(self.sync_data.arena_for_file(file), &self.base_path)
458+
fn base_arena(&self) -> crate::simd_path::ArenaPtr {
459+
self.sync_data.arena_base_ptr()
470460
}
471461

472-
/// Relative path of a directory entry.
473462
#[inline]
474-
pub fn dir_relative_path(&self, dir: &DirItem) -> String {
475-
dir.relative_path(self.sync_data.arena_base_ptr())
463+
fn overflow_arena(&self) -> crate::simd_path::ArenaPtr {
464+
self.sync_data.overflow_arena_ptr()
476465
}
466+
}
477467

478-
/// Absolute path of a directory entry.
479-
#[inline]
480-
pub fn dir_absolute_path(&self, dir: &DirItem) -> PathBuf {
481-
dir.absolute_path(self.sync_data.arena_base_ptr(), &self.base_path)
468+
impl FilePicker {
469+
pub fn base_path(&self) -> &Path {
470+
&self.base_path
482471
}
483472

484473
/// Convert an absolute path to a relative path string (relative to base_path).
@@ -1179,17 +1168,16 @@ impl FilePicker {
11791168
// TODO make this O(n)
11801169
pub fn remove_all_files_in_dir(&mut self, dir: impl AsRef<Path>) -> usize {
11811170
let dir_path = dir.as_ref();
1182-
let dir_rel = self.to_relative_path(dir_path).unwrap_or("").to_string();
1183-
let dir_prefix = if dir_rel.is_empty() {
1171+
let relative_dir = self.to_relative_path(dir_path).unwrap_or("").to_string();
1172+
1173+
let dir_prefix = if relative_dir.is_empty() {
11841174
String::new()
11851175
} else {
1186-
format!("{}/", dir_rel)
1176+
format!("{}{}", relative_dir, std::path::MAIN_SEPARATOR)
11871177
};
1188-
// Use the safe retain_files method which maintains both indices
1189-
let arena = self.arena_base_ptr();
1190-
self.sync_data.retain_files(|file| {
1178+
1179+
self.sync_data.retain_files_with_arena(|file, arena| {
11911180
!file.relative_path_starts_with(arena, &dir_prefix)
1192-
&& !file.relative_path_eq(arena, &dir_rel)
11931181
})
11941182
}
11951183

@@ -1204,6 +1192,11 @@ impl FilePicker {
12041192
}
12051193
}
12061194

1195+
#[inline]
1196+
pub(crate) fn arena_base_ptr(&self) -> ArenaPtr {
1197+
self.sync_data.arena_base_ptr()
1198+
}
1199+
12071200
/// Spawn a background thread to rebuild the bigram index after rescan.
12081201
pub(crate) fn spawn_post_rescan_rebuild(&self, shared_picker: SharedPicker) -> bool {
12091202
if !self.warmup_mmap_cache || self.cancelled.load(Ordering::Relaxed) {
@@ -1934,10 +1927,7 @@ fn walk_filesystem(
19341927
files.par_sort_unstable_by(|a, b| {
19351928
a.parent_dir_index()
19361929
.cmp(&b.parent_dir_index())
1937-
.then_with(|| {
1938-
a.file_name_from_arena(arena)
1939-
.cmp(&b.file_name_from_arena(arena))
1940-
})
1930+
.then_with(|| a.file_name(arena).cmp(&b.file_name(arena)))
19411931
});
19421932
});
19431933

crates/fff-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
//! );
8686
//!
8787
//! assert!(results.total_matched > 0);
88-
//! assert!(picker.relative_path(results.items.first().unwrap()).ends_with("lib.rs"));
88+
//! assert!(results.items.first().unwrap().relative_path(picker).ends_with("lib.rs"));
8989
//!
9090
//! let _ = std::fs::remove_dir_all(&tmp);
9191
//! # Ok::<(), Box<dyn std::error::Error>>(())

crates/fff-core/src/score.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -687,9 +687,9 @@ mod tests {
687687
assert_eq!(scores[2].total, 200, "Third should be third highest");
688688

689689
// Verify the files match
690-
assert_eq!(items[0].relative_path_from_arena(arena), "file4.rs");
691-
assert_eq!(items[1].relative_path_from_arena(arena), "file6.rs");
692-
assert_eq!(items[2].relative_path_from_arena(arena), "file2.rs");
690+
assert_eq!(items[0].relative_path(arena), "file4.rs");
691+
assert_eq!(items[1].relative_path(arena), "file6.rs");
692+
assert_eq!(items[2].relative_path(arena), "file2.rs");
693693
}
694694

695695
#[test]
@@ -783,9 +783,9 @@ mod tests {
783783
assert_eq!(scores[0].total, 200);
784784
assert_eq!(scores[1].total, 100);
785785
assert_eq!(scores[2].total, 50);
786-
assert_eq!(items[0].relative_path_from_arena(arena), "file2.rs");
787-
assert_eq!(items[1].relative_path_from_arena(arena), "file1.rs");
788-
assert_eq!(items[2].relative_path_from_arena(arena), "file3.rs");
786+
assert_eq!(items[0].relative_path(arena), "file2.rs");
787+
assert_eq!(items[1].relative_path(arena), "file1.rs");
788+
assert_eq!(items[2].relative_path(arena), "file3.rs");
789789
}
790790
}
791791

@@ -867,7 +867,7 @@ mod filename_bonus_tests {
867867
items
868868
.iter()
869869
.zip(scores.iter())
870-
.map(|(f, s)| (f.relative_path_from_arena(arena).to_string(), s.clone()))
870+
.map(|(f, s)| (f.relative_path(arena).to_string(), s.clone()))
871871
.collect()
872872
}
873873

@@ -1084,10 +1084,7 @@ mod typo_resistance_tests {
10841084
},
10851085
};
10861086
let (items, _, _) = fuzzy_match_and_score_files(files, &ctx, files.len(), arena, arena);
1087-
items
1088-
.iter()
1089-
.map(|f| f.relative_path_from_arena(arena))
1090-
.collect()
1087+
items.iter().map(|f| f.relative_path(arena)).collect()
10911088
}
10921089

10931090
#[test]

crates/fff-core/src/types.rs

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,36 @@ use crate::query_tracker::QueryMatchEntry;
88
use crate::simd_path::{ArenaPtr, PATH_BUF_SIZE};
99
use fff_query_parser::{FFFQuery, FuzzyQuery, Location};
1010

11+
/// Different sources of the string storage used by FFF
12+
/// implements as a deduplicated 16-bytes alined heap
13+
/// can be stored in RAM or on disk
14+
pub trait FFFStringStorage {
15+
/// Resolve the arena for a [`FileItem`] (handles base vs overflow split).
16+
fn arena_for(&self, file: &FileItem) -> ArenaPtr;
17+
18+
/// The base arena (scan-time paths).
19+
fn base_arena(&self) -> ArenaPtr;
20+
/// The overflow arena (paths added after the last full scan).
21+
fn overflow_arena(&self) -> ArenaPtr;
22+
}
23+
24+
impl FFFStringStorage for ArenaPtr {
25+
#[inline]
26+
fn arena_for(&self, _file: &FileItem) -> ArenaPtr {
27+
*self
28+
}
29+
30+
#[inline]
31+
fn base_arena(&self) -> ArenaPtr {
32+
*self
33+
}
34+
35+
#[inline]
36+
fn overflow_arena(&self) -> ArenaPtr {
37+
*self
38+
}
39+
}
40+
1141
/// Cached file contents — mmap on Unix, heap buffer on Windows.
1242
///
1343
/// On Windows, memory-mapped files hold the file handle open and prevent
@@ -47,30 +77,48 @@ impl FileItemFlags {
4777
pub const OVERFLOW: u8 = 1 << 2;
4878
}
4979

80+
pub struct DirFlags;
81+
82+
impl DirFlags {
83+
pub const OVERFLOW: u8 = 1 << 0;
84+
}
85+
5086
/// A directory in the file index. Shares chunk arena with file paths.
5187
#[derive(Debug, Clone)]
5288
pub struct DirItem {
89+
flags: u8,
5390
pub(crate) path: crate::simd_path::ChunkedString,
5491
}
5592

5693
impl DirItem {
94+
#[inline(always)]
95+
pub fn is_overflow(&self) -> bool {
96+
self.flags & DirFlags::OVERFLOW == 0
97+
}
98+
5799
pub(crate) fn new(path: crate::simd_path::ChunkedString) -> Self {
58-
Self { path }
100+
Self { path, flags: 0 }
59101
}
60102

61103
pub(crate) fn read_relative_path<'a>(&self, arena: ArenaPtr, buf: &'a mut [u8]) -> &'a str {
62104
self.path.read_to_buf(arena, buf)
63105
}
64106

65107
/// Relative dir path as owned String (cold path).
66-
pub fn relative_path(&self, arena: ArenaPtr) -> String {
108+
pub fn relative_path(&self, arena: impl FFFStringStorage) -> String {
67109
let mut out = String::new();
68-
self.path.write_to_string(arena, &mut out);
110+
let ptr = if self.is_overflow() {
111+
arena.overflow_arena()
112+
} else {
113+
arena.base_arena()
114+
};
115+
116+
self.path.write_to_string(ptr, &mut out);
69117
out
70118
}
71119

72120
/// A path = base_path + "/" + relative. Cold path, allocates.
73-
pub fn absolute_path(&self, arena: ArenaPtr, base_path: &Path) -> PathBuf {
121+
pub fn absolute_path(&self, arena: impl FFFStringStorage, base_path: &Path) -> PathBuf {
74122
let rel = self.relative_path(arena);
75123
if rel.is_empty() {
76124
base_path.to_path_buf()
@@ -140,9 +188,9 @@ impl FileItem {
140188
}
141189

142190
/// Returns an absolute path of the file
143-
pub fn absolute_path(&self, arena: ArenaPtr, base_path: &Path) -> PathBuf {
191+
pub fn absolute_path(&self, arena: impl FFFStringStorage, base_path: &Path) -> PathBuf {
144192
let mut buf = [0u8; PATH_BUF_SIZE];
145-
let rel = self.path.read_to_buf(arena, &mut buf);
193+
let rel = self.path.read_to_buf(arena.arena_for(self), &mut buf);
146194
base_path.join(rel)
147195
}
148196

@@ -158,29 +206,29 @@ impl FileItem {
158206
self.parent_dir = idx;
159207
}
160208

161-
pub(crate) fn dir_str(&self, arena: ArenaPtr) -> String {
209+
pub fn dir_str(&self, arena: impl FFFStringStorage) -> String {
162210
let mut s = String::with_capacity(64);
163-
self.path.write_dir_to(arena, &mut s);
211+
self.path.write_dir_to(arena.arena_for(self), &mut s);
164212
s
165213
}
166214

167215
pub(crate) fn write_dir_str(&self, arena: ArenaPtr, out: &mut String) {
168216
self.path.write_dir_to(arena, out);
169217
}
170218

171-
pub(crate) fn file_name_from_arena(&self, arena: ArenaPtr) -> String {
219+
pub fn file_name(&self, arena: impl FFFStringStorage) -> String {
172220
let mut s = String::with_capacity(32);
173-
self.path.write_filename_to(arena, &mut s);
221+
self.path.write_filename_to(arena.arena_for(self), &mut s);
174222
s
175223
}
176224

177225
pub(crate) fn write_file_name_from_arena(&self, arena: ArenaPtr, out: &mut String) {
178226
self.path.write_filename_to(arena, out);
179227
}
180228

181-
pub(crate) fn relative_path_from_arena(&self, arena: ArenaPtr) -> String {
229+
pub fn relative_path(&self, arena: impl FFFStringStorage) -> String {
182230
let mut s = String::with_capacity(64);
183-
self.path.write_to_string(arena, &mut s);
231+
self.path.write_to_string(arena.arena_for(self), &mut s);
184232
s
185233
}
186234

crates/fff-core/tests/bigram_overlay_coherence_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,7 @@ fn fuzzy_search_paths(picker: &FilePicker, query: &str) -> Vec<String> {
10551055
result
10561056
.items
10571057
.iter()
1058-
.map(|f| picker.relative_path(f))
1058+
.map(|f| f.relative_path(picker))
10591059
.collect()
10601060
}
10611061

0 commit comments

Comments
 (0)