Enhance matching module with valid children tracking and code readability improvements

- Added a `validChildren` field to the `VCache` structure to track valid child instances during the matching process.
- Refactored the `cacheVirtual` function to improve readability by formatting parameters across multiple lines.
- Updated the `computePairCost` function to utilize the new `validChildren` field for more accurate cost calculations.
- Simplified the `matchChildren` function by removing redundant code and improving the handling of matched instances.
- Enhanced test cases for the `matchChildren` function to ensure consistent behavior and validate the new changes.

These updates improve the efficiency and clarity of the matching process, ensuring better performance and maintainability.
This commit is contained in:
Joe 2026-02-18 06:40:28 -06:00
parent b687c8862d
commit ddd47b9afa
9 changed files with 287 additions and 363 deletions

View file

@ -937,14 +937,14 @@ impl JobThreadContext {
let start = Instant::now();
let instance_context = InstanceContext::new();
let snapshot = match snapshot_from_vfs(&instance_context, &self.vfs, &self.project_file_path)
{
Ok(s) => s,
Err(e) => {
log::error!("Tree reconciliation snapshot error: {:?}", e);
return;
}
};
let snapshot =
match snapshot_from_vfs(&instance_context, &self.vfs, &self.project_file_path) {
Ok(s) => s,
Err(e) => {
log::error!("Tree reconciliation snapshot error: {:?}", e);
return;
}
};
let mut tree = self.tree.lock().unwrap();
let root_id = tree.get_root_id();

View file

@ -4,7 +4,6 @@ use std::{
mem::forget,
net::{IpAddr, Ipv4Addr, SocketAddr},
path::{Path, PathBuf},
process::{Command, Stdio},
sync::Arc,
};
@ -131,7 +130,7 @@ fn run_live_syncback(project_path: &Path, payload: SyncbackPayload) -> anyhow::R
result.fs_snapshot.removed_paths().len()
);
refresh_git_index(base_path);
crate::git::refresh_git_index(base_path);
forget(session_old);
@ -231,30 +230,3 @@ fn fixup_ref_properties(dom: &mut WeakDom, ref_map: &HashMap<Ref, Ref>) {
}
}
}
fn refresh_git_index(project_dir: &Path) {
let mut check_dir = Some(project_dir);
let mut is_git_repo = false;
while let Some(dir) = check_dir {
if dir.join(".git").exists() {
is_git_repo = true;
break;
}
check_dir = dir.parent();
}
if is_git_repo {
log::info!("Refreshing git index...");
match Command::new("git")
.args(["update-index", "--refresh", "-q"])
.current_dir(project_dir)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
{
Ok(_) => log::info!("Git index refreshed."),
Err(e) => log::warn!("Failed to run git update-index --refresh: {}", e),
}
}
}

View file

@ -2,7 +2,6 @@ use std::{
io::{self, BufReader, Write as _},
mem::forget,
path::{Path, PathBuf},
process::{Command, Stdio},
time::Instant,
};
@ -280,8 +279,7 @@ impl SyncbackCommand {
result.fs_snapshot.removed_paths().len()
);
// Refresh git index if in a git repository
refresh_git_index(base_path);
crate::git::refresh_git_index(base_path);
// Delete input file if using default Project.rbxl location
if let Some(input_path) = &delete_input_after_syncback {
@ -386,42 +384,6 @@ fn download_place(place_id: u64) -> anyhow::Result<NamedTempFile> {
Ok(temp_file)
}
/// Refreshes the git index if the project is in a git repository.
///
/// This is useful because syncback may rewrite files with identical content,
/// which can cause git to report them as modified due to timestamp changes.
fn refresh_git_index(project_dir: &Path) {
// Check if .git exists in project dir or any parent
let mut check_dir = Some(project_dir);
let mut is_git_repo = false;
while let Some(dir) = check_dir {
if dir.join(".git").exists() {
is_git_repo = true;
break;
}
check_dir = dir.parent();
}
if is_git_repo {
log::info!("Refreshing git index...");
// Exit code 1 is normal - it just means files were refreshed.
// We only warn if git itself fails to run.
match Command::new("git")
.args(["update-index", "--refresh", "-q"])
.current_dir(project_dir)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
{
Ok(_) => log::info!("Git index refreshed."),
Err(e) => log::warn!("Failed to run git update-index --refresh: {}", e),
}
} else {
log::debug!("Not a git repository, skipping index refresh.");
}
}
fn read_dom(path: &Path, file_kind: FileKind) -> anyhow::Result<WeakDom> {
let content = BufReader::new(File::open(path)?);
match file_kind {

View file

@ -1,7 +1,7 @@
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
process::Command,
process::{Command, Stdio},
sync::{Arc, Mutex},
};
@ -244,6 +244,39 @@ pub fn compute_git_metadata(tree_handle: &Arc<Mutex<RojoTree>>, repo_root: &Path
}
}
/// Refreshes the git index if the project is in a git repository.
///
/// This is useful because syncback may rewrite files with identical content,
/// which can cause git to report them as modified due to timestamp changes.
pub fn refresh_git_index(project_dir: &Path) {
let mut check_dir = Some(project_dir);
let mut is_git_repo = false;
while let Some(dir) = check_dir {
if dir.join(".git").exists() {
is_git_repo = true;
break;
}
check_dir = dir.parent();
}
if is_git_repo {
log::info!("Refreshing git index...");
match Command::new("git")
.args(["update-index", "--refresh", "-q"])
.current_dir(project_dir)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
{
Ok(_) => log::info!("Git index refreshed."),
Err(e) => log::warn!("Failed to run git update-index --refresh: {}", e),
}
} else {
log::debug!("Not a git repository, skipping index refresh.");
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -1318,7 +1318,10 @@ mod tests {
let (tree, children) = make_tree_with_children(&[("Alpha", "Folder"), ("Beta", "Folder")]);
let result = match_forward(snaps, &children, &tree, &MatchingSession::new());
assert_eq!(result.matched.len(), 2);
assert_eq!(result.total_cost, 0, "Identical instances should have zero cost");
assert_eq!(
result.total_cost, 0,
"Identical instances should have zero cost"
);
}
#[test]
@ -1390,14 +1393,8 @@ mod tests {
#[test]
fn session_cache_consistent() {
let snaps1 = vec![
make_snapshot("X", "Folder"),
make_snapshot("Y", "Folder"),
];
let snaps2 = vec![
make_snapshot("X", "Folder"),
make_snapshot("Y", "Folder"),
];
let snaps1 = vec![make_snapshot("X", "Folder"), make_snapshot("Y", "Folder")];
let snaps2 = vec![make_snapshot("X", "Folder"), make_snapshot("Y", "Folder")];
let (tree, children) = make_tree_with_children(&[("X", "Folder"), ("Y", "Folder")]);
let session = MatchingSession::new();
@ -1406,10 +1403,7 @@ mod tests {
assert_eq!(r1.matched.len(), r2.matched.len());
assert_eq!(r1.total_cost, r2.total_cost);
assert_eq!(
r1.unmatched_snapshot.len(),
r2.unmatched_snapshot.len()
);
assert_eq!(r1.unmatched_snapshot.len(), r2.unmatched_snapshot.len());
}
#[test]

View file

@ -829,29 +829,14 @@ mod tests {
fn total_cost_includes_unmatched() {
let mut new_dom = WeakDom::new(InstanceBuilder::new("DataModel"));
let new_root = new_dom.root_ref();
new_dom.insert(
new_root,
InstanceBuilder::new("Folder").with_name("A"),
);
new_dom.insert(
new_root,
InstanceBuilder::new("Folder").with_name("B"),
);
new_dom.insert(
new_root,
InstanceBuilder::new("Folder").with_name("C"),
);
new_dom.insert(new_root, InstanceBuilder::new("Folder").with_name("A"));
new_dom.insert(new_root, InstanceBuilder::new("Folder").with_name("B"));
new_dom.insert(new_root, InstanceBuilder::new("Folder").with_name("C"));
let mut old_dom = WeakDom::new(InstanceBuilder::new("DataModel"));
let old_root = old_dom.root_ref();
old_dom.insert(
old_root,
InstanceBuilder::new("Folder").with_name("A"),
);
old_dom.insert(
old_root,
InstanceBuilder::new("Folder").with_name("B"),
);
old_dom.insert(old_root, InstanceBuilder::new("Folder").with_name("A"));
old_dom.insert(old_root, InstanceBuilder::new("Folder").with_name("B"));
let new_children: Vec<Ref> = new_dom.get_by_ref(new_root).unwrap().children().to_vec();
let old_children: Vec<Ref> = old_dom.get_by_ref(old_root).unwrap().children().to_vec();

View file

@ -61,12 +61,22 @@ pub fn filter_properties_with_stats<'inst>(
return true;
}
if !sync_unscriptable {
if let Some(data) = class_data {
let mut current = class_data;
loop {
let data = match current {
Some(d) => d,
None => break,
};
if let Some(prop_data) = data.properties.get(prop_name.as_str()) {
if matches!(prop_data.scriptability, Scriptability::None) {
return true;
}
break;
}
current = data
.superclass
.as_ref()
.and_then(|s| database.classes.get(&**s));
}
}
false