mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 14:01:37 +00:00
Enhance PrefetchCache with directory init-file resolution
- Added a new field `dir_init` to `PrefetchCache` to store pre-resolved init-file information for directories, allowing for efficient retrieval during prefetch operations. - Introduced the `prefetch_dir_init` method in the `Vfs` struct to look up init-file info from the prefetch cache. - Updated the `prefetch_project_files` function to populate the `dir_init` map based on the defined `INIT_FILE_PRIORITY`. - Adjusted related tests to ensure proper initialization of the `dir_init` field in various scenarios. These changes improve the performance of directory handling in the VFS by reducing the need for repeated metadata probes.
This commit is contained in:
parent
f1992a61b1
commit
6168597acb
5 changed files with 159 additions and 66 deletions
|
|
@ -19,7 +19,7 @@ use crate::{
|
|||
apply_patch_set, compute_patch_set, AppliedPatchSet, InstanceContext, InstanceSnapshot,
|
||||
PatchSet, RojoTree,
|
||||
},
|
||||
snapshot_middleware::{is_script_relevant_path, snapshot_from_vfs},
|
||||
snapshot_middleware::{is_script_relevant_path, snapshot_from_vfs, INIT_FILE_PRIORITY},
|
||||
};
|
||||
|
||||
/// Set to `true` to validate on plugin connect (useful for testing, do not enable on production).
|
||||
|
|
@ -155,31 +155,35 @@ fn prefetch_project_files(project: &Project, sync_scripts_only: bool) -> io::Res
|
|||
files: HashMap::new(),
|
||||
is_file: HashMap::new(),
|
||||
children: HashMap::new(),
|
||||
dir_init: HashMap::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let walk_start = Instant::now();
|
||||
|
||||
let mut entries: Vec<walkdir::DirEntry> = Vec::new();
|
||||
for root in &roots {
|
||||
if !root.exists() {
|
||||
continue;
|
||||
}
|
||||
for e in WalkDir::new(root).follow_links(true).into_iter().flatten() {
|
||||
|
||||
// Walk the entire project folder so that every file under it
|
||||
// (nested project files, etc.) is covered by the is_file map.
|
||||
// P1 returns NotFound for cache misses when prefetch is active.
|
||||
if folder.exists() {
|
||||
for e in WalkDir::new(folder)
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
entries.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Also include the project file itself (read by project middleware).
|
||||
if let Ok(meta) = std::fs::metadata(&project.file_location) {
|
||||
if meta.is_file() {
|
||||
entries.push(
|
||||
WalkDir::new(&project.file_location)
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
);
|
||||
// Also walk $path roots that may be OUTSIDE the project folder
|
||||
// (e.g. $path: "../module"). These wouldn't be covered above.
|
||||
for root in &roots {
|
||||
if !root.exists() || root.starts_with(folder) {
|
||||
continue;
|
||||
}
|
||||
for e in WalkDir::new(root).follow_links(true).into_iter().flatten() {
|
||||
entries.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -215,8 +219,9 @@ fn prefetch_project_files(project: &Project, sync_scripts_only: bool) -> io::Res
|
|||
read_elapsed,
|
||||
);
|
||||
|
||||
let mut is_file_map: HashMap<std::path::PathBuf, bool> = HashMap::new();
|
||||
let mut children_map: HashMap<std::path::PathBuf, Vec<std::path::PathBuf>> = HashMap::new();
|
||||
let mut is_file_map: HashMap<std::path::PathBuf, bool> = HashMap::with_capacity(entries.len());
|
||||
let mut children_map: HashMap<std::path::PathBuf, Vec<std::path::PathBuf>> =
|
||||
HashMap::with_capacity(dir_count);
|
||||
|
||||
for entry in &entries {
|
||||
let path = entry.path().to_path_buf();
|
||||
|
|
@ -242,10 +247,28 @@ fn prefetch_project_files(project: &Project, sync_scripts_only: bool) -> io::Res
|
|||
children_map.len(),
|
||||
);
|
||||
|
||||
let mut dir_init_map: HashMap<std::path::PathBuf, Option<(String, std::path::PathBuf)>> =
|
||||
HashMap::with_capacity(children_map.len());
|
||||
for (dir_path, child_paths) in &children_map {
|
||||
let child_names: std::collections::HashSet<&str> = child_paths
|
||||
.iter()
|
||||
.filter_map(|p| p.file_name().and_then(|n| n.to_str()))
|
||||
.collect();
|
||||
let mut found = None;
|
||||
for &(_, init_name) in INIT_FILE_PRIORITY {
|
||||
if child_names.contains(init_name) {
|
||||
found = Some((init_name.to_string(), dir_path.join(init_name)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
dir_init_map.insert(dir_path.clone(), found);
|
||||
}
|
||||
|
||||
Ok(PrefetchCache {
|
||||
files: file_data.into_iter().collect::<HashMap<_, _>>(),
|
||||
is_file: is_file_map,
|
||||
children: children_map,
|
||||
dir_init: dir_init_map,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -273,12 +296,15 @@ impl ServeSession {
|
|||
let prefetch_start = Instant::now();
|
||||
match prefetch_project_files(&root_project, sync_scripts_only) {
|
||||
Ok(cache) => {
|
||||
let count = cache.files.len();
|
||||
log::debug!(
|
||||
"Prefetch total: {} files in {:.1?}",
|
||||
cache.files.len(),
|
||||
count,
|
||||
prefetch_start.elapsed()
|
||||
);
|
||||
vfs.set_prefetch_cache(cache);
|
||||
if !cache.is_file.is_empty() {
|
||||
vfs.set_prefetch_cache(cache);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("Prefetch failed, falling back to sequential reads: {err}");
|
||||
|
|
@ -291,37 +317,45 @@ impl ServeSession {
|
|||
let mut instance_context = InstanceContext::new();
|
||||
instance_context.sync_scripts_only = sync_scripts_only;
|
||||
|
||||
let watch_start = Instant::now();
|
||||
let mut path_roots = Vec::new();
|
||||
collect_path_roots(
|
||||
&root_project.tree,
|
||||
root_project.folder_location(),
|
||||
&mut path_roots,
|
||||
);
|
||||
let _ = vfs.watch(root_project.folder_location());
|
||||
for root in &path_roots {
|
||||
let _ = vfs.watch(root);
|
||||
}
|
||||
log::debug!(
|
||||
"Set up {} recursive watches in {:.1?}",
|
||||
path_roots.len() + 1,
|
||||
watch_start.elapsed(),
|
||||
);
|
||||
|
||||
let snap_start = Instant::now();
|
||||
log::trace!("Generating snapshot of instances from VFS");
|
||||
vfs.set_watch_enabled(false);
|
||||
let snapshot = snapshot_from_vfs(&instance_context, vfs, start_path)?;
|
||||
vfs.set_watch_enabled(true);
|
||||
log::debug!("Snapshot built in {:.1?}", snap_start.elapsed());
|
||||
|
||||
vfs.clear_prefetch_cache();
|
||||
|
||||
let patch_start = Instant::now();
|
||||
log::trace!("Computing initial patch set");
|
||||
let patch_set = compute_patch_set(snapshot, &tree, root_id);
|
||||
let project_folder = root_project.folder_location().to_path_buf();
|
||||
std::thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
let watch_start = Instant::now();
|
||||
let _ = vfs.watch(&project_folder);
|
||||
for root in &path_roots {
|
||||
let _ = vfs.watch(root);
|
||||
}
|
||||
log::debug!(
|
||||
"Set up {} recursive watches in {:.1?}",
|
||||
path_roots.len() + 1,
|
||||
watch_start.elapsed(),
|
||||
);
|
||||
});
|
||||
|
||||
log::trace!("Applying initial patch set");
|
||||
apply_patch_set(&mut tree, patch_set);
|
||||
log::debug!("Patch computed + applied in {:.1?}", patch_start.elapsed());
|
||||
log::trace!("Computing initial patch set");
|
||||
let patch_set = compute_patch_set(snapshot, &tree, root_id);
|
||||
|
||||
log::trace!("Applying initial patch set");
|
||||
apply_patch_set(&mut tree, patch_set);
|
||||
});
|
||||
log::debug!("Watch + patch in {:.1?}", patch_start.elapsed());
|
||||
|
||||
Ok((root_project, tree))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,9 +274,20 @@ fn compute_children_patches(
|
|||
.get_instance(id)
|
||||
.expect("Instance did not exist in tree");
|
||||
|
||||
let instance_children = instance.children().to_vec();
|
||||
let instance_children = instance.children();
|
||||
let snapshot_children = take(&mut snapshot.children);
|
||||
|
||||
if instance_children.is_empty() {
|
||||
for snapshot_child in snapshot_children {
|
||||
patch_set.added_instances.push(PatchAdd {
|
||||
parent_id: id,
|
||||
instance: snapshot_child,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let instance_children = instance_children.to_vec();
|
||||
let match_result = match_forward(snapshot_children, &instance_children, tree, session);
|
||||
|
||||
// Matched pairs: recursively compute patches.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::{
|
||||
glob::Glob,
|
||||
project::DEFAULT_PROJECT_NAMES,
|
||||
syncback::{dedup_suffix::strip_dedup_suffix, SyncbackReturn, SyncbackSnapshot},
|
||||
};
|
||||
use crate::{
|
||||
|
|
@ -110,6 +109,27 @@ pub fn snapshot_from_vfs(
|
|||
}
|
||||
}
|
||||
|
||||
/// Single source of truth for init-file resolution priority.
|
||||
/// Project files are checked first, then init files in this order.
|
||||
/// Used by both `get_dir_middleware` (live) and `prefetch_project_files` (cache).
|
||||
pub static INIT_FILE_PRIORITY: &[(Middleware, &str)] = &[
|
||||
(Middleware::Project, "default.project.json5"),
|
||||
(Middleware::Project, "default.project.json"),
|
||||
(Middleware::ModuleScriptDir, "init.luau"),
|
||||
(Middleware::ServerScriptDir, "init.server.luau"),
|
||||
(Middleware::ClientScriptDir, "init.client.luau"),
|
||||
(Middleware::PluginScriptDir, "init.plugin.luau"),
|
||||
(Middleware::LocalScriptDir, "init.local.luau"),
|
||||
(Middleware::LegacyScriptDir, "init.legacy.luau"),
|
||||
(Middleware::CsvDir, "init.csv"),
|
||||
// Legacy extensions (for backwards compatibility)
|
||||
// init.server.lua → Script with RunContext.Legacy (old emitLegacyScripts behavior)
|
||||
// init.client.lua → LocalScript (old emitLegacyScripts behavior)
|
||||
(Middleware::ModuleScriptDir, "init.lua"),
|
||||
(Middleware::LegacyScriptDir, "init.server.lua"),
|
||||
(Middleware::LocalScriptDir, "init.client.lua"),
|
||||
];
|
||||
|
||||
/// Gets the appropriate middleware for a directory by checking for `init`
|
||||
/// files. This uses an intrinsic priority list and for compatibility,
|
||||
/// that order should be left unchanged.
|
||||
|
|
@ -127,37 +147,24 @@ fn get_dir_middleware<'path>(
|
|||
.ok_or_else(|| anyhow::anyhow!("File name was not valid UTF-8: {}", dir_path.display()))?;
|
||||
let dir_name = strip_dedup_suffix(dir_name);
|
||||
|
||||
static INIT_PATHS: OnceLock<Vec<(Middleware, &str)>> = OnceLock::new();
|
||||
let order = INIT_PATHS.get_or_init(|| {
|
||||
vec![
|
||||
// Modern extensions (preferred)
|
||||
(Middleware::ModuleScriptDir, "init.luau"),
|
||||
(Middleware::ServerScriptDir, "init.server.luau"),
|
||||
(Middleware::ClientScriptDir, "init.client.luau"),
|
||||
(Middleware::PluginScriptDir, "init.plugin.luau"),
|
||||
(Middleware::LocalScriptDir, "init.local.luau"),
|
||||
(Middleware::LegacyScriptDir, "init.legacy.luau"),
|
||||
(Middleware::CsvDir, "init.csv"),
|
||||
// Legacy extensions (for backwards compatibility)
|
||||
// init.server.lua → Script with RunContext.Legacy (old emitLegacyScripts behavior)
|
||||
// init.client.lua → LocalScript (old emitLegacyScripts behavior)
|
||||
(Middleware::ModuleScriptDir, "init.lua"),
|
||||
(Middleware::LegacyScriptDir, "init.server.lua"),
|
||||
(Middleware::LocalScriptDir, "init.client.lua"),
|
||||
]
|
||||
});
|
||||
|
||||
for default_project_name in DEFAULT_PROJECT_NAMES {
|
||||
let project_path = dir_path.join(default_project_name);
|
||||
if vfs.metadata(&project_path).with_not_found()?.is_some() {
|
||||
return Ok((Middleware::Project, dir_name, project_path));
|
||||
}
|
||||
if let Some(cached) = vfs.prefetch_dir_init(dir_path) {
|
||||
return match cached {
|
||||
Some((init_name, init_path)) => {
|
||||
let middleware = INIT_FILE_PRIORITY
|
||||
.iter()
|
||||
.find(|(_, name)| *name == init_name)
|
||||
.map(|(m, _)| *m)
|
||||
.unwrap_or(Middleware::Dir);
|
||||
Ok((middleware, dir_name, init_path))
|
||||
}
|
||||
None => Ok((Middleware::Dir, dir_name, dir_path.to_path_buf())),
|
||||
};
|
||||
}
|
||||
|
||||
for (middleware, name) in order {
|
||||
for &(middleware, name) in INIT_FILE_PRIORITY {
|
||||
let test_path = dir_path.join(name);
|
||||
if vfs.metadata(&test_path).with_not_found()?.is_some() {
|
||||
return Ok((*middleware, dir_name, test_path));
|
||||
return Ok((middleware, dir_name, test_path));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue